今度は wordpress のカテゴリ一覧、と指定したカテゴリ内の記事を拾ってみます。
wordpress はカテゴリの扱いがややこしくて、カテゴリの名前自体は wp_terms に入っているのですが…その後の wp_posts テーブルまでの関係がややこしいという。
# 更に云えば、カスタムフィールドと関係を見ていくと、なにやら大変という感じなのです。
- terms: カテゴリ名などの名称
- term_taxonomy: terms の分類(category, link_category など)
- term_relationships: カテゴリとの親子関係
- posts: 記事自体
という 4 つのテーブルが関わってきます。
いきなりはややこしいので、まずはカテゴリ一覧。
■カテゴリ一覧のビュー(views/categories/index.ctp)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <h2>カテゴリ一覧</h2> <table> <tr> <th>term_id</th> <th>term_taxonomy_id</th> <th>name</th> <th>slug</th> </tr> <?php foreach ( $Categories as $item ) : ?> <tr> <td><?php echo $item [ 'Term' ][ 'term_id' ]; ?> <td><?php echo $item [ 'TermTaxonomy' ][ 'term_taxonomy_id' ]; ?> <td><?php echo $item [ 'Term' ][ 'name' ]; ?> <td><?php echo $item [ 'Term' ][ 'slug' ]; ?> </tr> <?php endforeach ; ?> </table> <h2>カテゴリ一覧(リンク版)</h2> <ul> <?php foreach ( $Categories as $item ) : ?> <li> <a href= "http://localhost/cake_wp/categories/item/<?php echo $item['Term']['slug']; ?>" ><?php echo $item [ 'Term' ][ 'name' ]; ?></a> </li> <?php endforeach ; ?> </ul> |
カテゴリだけを表示したいので、Term と TermTaxonomy モデルだけを使います。
■コントローラー(controllers/categories_controller.php)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php class CategoriesController extends AppController { var $name = 'Categories' ; var $uses = array ( 'Category' ); function index() { $this ->set( 'Categories' , $this ->Category->findCategories()); } function item( $slug = '' ) { $this ->set( 'Categories' , $this ->Category->findPosts( $slug )); $this ->set( 'ViewData' , array ( 'slug' => $slug , 'name' => $this ->Category->getCategoryName( $slug ) )); } } ?> |
(追記 2014/06/22 item function を追加)
コントローラーは CategoriesController にしました。4 つのテーブルを使うので、どれの名前も適切ではないんですよね。そして、一覧表を取る時には index メソッドを作っています。
さて、モデルはどうするかというと、Term モデル、TermTaxonomy モデルを直接利用することは避けました。先にも書きましたがデータベース上は正規化されていることが常な訳で、そのまま O/R マッピングをしてしまうと、オブジェクト指向の世界では「現実離れ」なクラスになってしまいます。なので、より自然な形に戻す(本来の形に戻す)ために、Category モデルクラスを作ります。
ここでリストを取得するために、findPosts というメソッドを新たに作っています。
# find(‘all’) とか find(‘first’) とか合わせたほうがよいのですが、ひとまず。
■モデル(models/category.php)
さて、Category モデルは実テーブルとは連結していないので、$useTable = false にします。で、問題なのは、どうやってデータを取ってくるのか?ですね。
先に Term と TermTaxonomy モデルのアソシエーションを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 | <!-- models/term.php --> <?php class Term extends AppModel { var $name = 'Term' ; var $primaryKey = 'term_id' ; var $hasOne = array ( 'TermTaxonomy' => array ( 'className' => 'TermTaxonomy' , 'foreignKey' => 'term_taxonomy_id' )); } ?> |
Term.term_taxonomy_id (1)-(1) TermTaxonomy.term_taxonomy_id の関係になるので Model::$hasOne を指定。
1 2 3 4 5 6 7 8 9 10 11 12 13 | <!-- models/term_taxonomy.php --> <?php class TermTaxonomy extends AppModel { var $name = 'TermTaxonomy' ; var $useTable = 'term_taxonomy' ; var $primaryKey = 'term_taxonomy_id' ; var $hasOne = array ( 'Term' => array ( 'className' => 'Term' , 'foreignKey' => 'term_id' )); } ?> |
逆向きも同じで Model::$hasOne を指定。
こうしておくと、次のように findCategories が書けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!-- models/category.php --> <?php class Category extends AppModel { var $name = 'Category' ; var $useTable = false ; function findCategories() { if (0) { $TermTaxonomy = ClassRegistry::init( 'TermTaxonomy' ); $items = $TermTaxonomy ->find( 'all' , array ( 'conditions' => "taxonomy = 'category'" )); return $items ; } else { $Term = ClassRegistry::init( 'Term' ); $items = $Term ->find( 'all' , array ( 'conditions' => "TermTaxonomy.taxonomy = 'category'" )); return $items ; } } } |
if 文で分けていますが、どちらも同じ記述です。
テーブルに連結していない場合は、ClassRegistry::init メソッドでモデルクラスを読み込みます。この後は、普通に検索をして OK です。
TermTaxonomy モデルを中心にした場合は、’category’ を直接検索できますが、Term モデルを中心にした場合は、TermTaxonomy.taxonomy な形で conditions を設定します。
これはどちらも結果が同じになります。
[img 20110201_07.jpg]
今度は、カテゴリ内の記事一覧を取得します。
■カテゴリ内記事のビュー(views/categories/item.ctp)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <h2>カテゴリ内の記事一覧</h2> <?php echo $ViewData [ 'slug' ]; ?> / <?php echo $ViewData [ 'name' ]; ?> <table> <tr> <th>Post.ID</th> <th>Post.post_title</th> <th>Post.post_date</th> <th>Post.guid</th> </tr> <?php foreach ( $Categories as $item ) : ?> <tr> <td><?php echo $item [ 'Post' ][ 'ID' ]; ?> <td><?php echo $item [ 'Post' ][ 'post_title' ]; ?> <td><?php echo $item [ 'Post' ][ 'post_date' ]; ?> <td><?php echo $item [ 'Post' ][ 'guid' ]; ?> </tr> <?php endforeach ; ?> </table> |
な感じで、Post モデルから情報を取ってきます。カテゴリのスラッグ(名前)と日本語名称は、$ViewData という変数に入れておきます。
■まずは、コントローラーをクエリで書いてしまう。
面倒なので、Category::findPosts メソッドの中身をクエリで書いてしまったのがこれです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | <!-- models/category.php --> <?php class Category extends AppModel { var $name = 'Category' ; var $useTable = false ; function findCategories() { if (0) { $TermTaxonomy = ClassRegistry::init( 'TermTaxonomy' ); $items = $TermTaxonomy ->find( 'all' , array ( 'conditions' => "taxonomy = 'category'" )); return $items ; } else { $Term = ClassRegistry::init( 'Term' ); $items = $Term ->find( 'all' , array ( 'conditions' => "TermTaxonomy.taxonomy = 'category'" )); return $items ; } } // カテゴリ内の記事一覧を取得する function findPosts( $slug , $max =10) { if (0) { $sql = <<< HERE select * from wp_term_taxonomy as TermTaxonomy inner join wp_term_relationships as TermRelationship on TermTaxonomy.term_taxonomy_id = TermRelationship.term_taxonomy_id inner join wp_terms as Term on ( Term.term_id = TermTaxonomy.term_id ) inner join wp_posts as Post on ( TermRelationship.object_id = Post.ID ) where TermTaxonomy.taxonomy = 'category' and Term.slug = '$slug' order by Post.post_date desc limit $max ; HERE; return $this ->query( $sql ); } else { $Post = ClassRegistry::init( 'Post' ); $items = $Post ->find( 'all' , array ( 'conditions' => "TermTaxonomy.taxonomy = 'category'" )); return $items ; } } function getCategoryName( $slug ) { $Term = ClassRegistry::init( 'Term' ); $item = $Term ->find( 'first' , array ( 'conditions' => "slug = '$slug'" )); return $item [ 'Term' ][ 'name' ]; } } ?> |
これでも用は足りるのですが、プレフィクス「wp_」が、そのまま出てきてしまうので難点が多いですよね。ただ、複雑なアソシエーション(外部キー)で悩むよりは、このほうが安全だと思います。
■アソシエーションで設定してみる。
が、CakePHP なのでアソシエーションで頑張ってみようとモデルを作ります。結論から言うと、4 つのテーブルを連携させて動かすことができません…果たして動くのか動かないのか分からないのですが、一応、ソースを晒しておきます。
TermRelationship モデルクラスはプライマリーキーを2つ持つので、$primaryKey に対して array で指定しているのですが、これでいいんでしょうかねぇ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <!-- models/term_relationship.php --> <?php class TermRelationship extends AppModel { var $name = 'TermRelationship' ; var $primaryKey = array ( 'object_id' , 'term_taxonomy_id' ); var $belongsTo = array ( 'TermTaxonomy' => array ( 'className' => 'TermTaxonomy' , 'conditions' => 'TermTaxonomy.term_taxonomy_id = TermRelationship.term_taxonomy_id' )); var $hasOne = array ( 'Post' => array ( 'className' => 'Post' , 'conditions' => 'Post.ID = TermRelationship.object_id' )); } ?> |
Post モデルに対しては、Model::$hasOne を使い、TermTaxonomy モデルに対しては Model::$belongsTo なんですが。
Post モデルに関しては、TermRelationship への連携を Model::$hasOne で指定しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <!-- models/post.php --> <?php class Post extends AppModel { var $name = 'Post' ; var $belongsTo = array ( 'User' => array ( 'className' => 'User' , 'foreignKey' => 'post_author' )); var $hasOne = array ( 'TermRelationship' => array ( 'className' => 'TermRelationship' , 'foreignKey' => 'object_id' )); } |
これを有効にして、Category::findPosts メソッドを書き換えます。
1 2 3 4 5 6 7 8 9 | // カテゴリ内の記事一覧を取得する function findPosts( $slug , $max =10) { $TermTaxonomy = ClassRegistry::init( 'TermTaxonomy' ); $items = $TermTaxonomy ->find( 'all' , array ( 'conditions' => array ( 'TermTaxonomy.taxonomy' => 'category' , 'Post.post_status' => 'publish' ))); return $items ; } |
これを動かしてみるのですが、SQL文でエラーが出ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | SELECT `TermTaxonomy`.`term_taxonomy_id`, `TermTaxonomy`.`term_id`, `TermTaxonomy`.`taxonomy`, `TermTaxonomy`.`description`, `TermTaxonomy`.`parent`, `TermTaxonomy`.` count `, `Post`.`ID`, `Post`.`post_author`, `Post`.`post_date`, `Post`.`post_date_gmt`, `Post`.`post_content`, `Post`.`post_title`, `Post`.`post_excerpt`, `Post`.`post_status`, `Post`.`comment_status`, `Post`.`ping_status`, `Post`.`post_password`, `Post`.`post_name`, `Post`.`to_ping`, `Post`.`pinged`, `Post`.`post_modified`, `Post`.`post_modified_gmt`, `Post`.`post_content_filtered`, `Post`.`post_parent`, `Post`.`guid`, `Post`.`menu_order`, `Post`.`post_type`, `Post`.`post_mime_type`, `Post`.`comment_count`, `Term`.`term_id`, `Term`.` name `, `Term`.`slug`, `Term`.`term_group` FROM `wp_term_taxonomy` AS `TermTaxonomy` LEFT JOIN `wp_posts` AS `Post` ON (`TermRelationship`.`object_id` = `Post`.`ID` AND `TermTaxonomy`.`post_id` = `Post`.`id`) LEFT JOIN `wp_terms` AS `Term` ON (`Term`.`term_id` = `TermTaxonomy`.`term_taxonomy_id`) WHERE `TermTaxonomy`.`taxonomy` = 'category' AND `Post`.`post_status` = 'publish' |
`TermRelationship`.`object_id` が無いとのことなので、TermRelationship の定義が悪いのですが、ちょっとよくわかりません。TermRelationship モデル自体は、プライマリキーが object_id と term_taxonomy_id の 2 つになるので、これが原因でしょう。
後でもう少し調べてみますが、この手の複雑なテーブルの場合はクエリ文が楽かなと思ってしまいます。まあ、作業量を考えると、CakePHP に準拠してないテーブルを扱う時は(wordpress は、準拠していると思うのだけど)クエリのほうが作成が速いってのがありますね。
とても勉強になりました、ありがとうございます。
私も色々試した結果…
$joinsで各テーブルの連結を定義し、beforeFindで$queryDataとマージしました。
こういう使い方は作法が悪いんでしょうか?
すみません、カテゴリ内の記事一覧を取得する場合の「CategoriesController」部分の「function item()」の書き方を教えてもらえませんでしょうか?
勝手なお願いですみません。宜しくお願い致します。
ちょっと前の記事で覚えていないのですが、
function Item($slug) のように スラッグや ID 指定して1つだけ取ってきたい、ことですよね?
CategoriesController クラスに function Item($slug) を作って、
Category クラスの getCategoryName のように $Term->find(‘first’, … ) して、ひとつだけ取ってくる getCategory を自作したうえで、これで呼び出す。
って感じでしょうか。
お返事ありがとうございました。
CakePHP から wordpress のデータを扱う(3)という記事を拝見しまして、その通りに動かしてみましたが、「カテゴリ内の記事一覧」が表示されています画像のように表示できませんでした。
ひょっとしたら「CategoriesController」に「function item()」に関する記載がございませんでしたので、教えていただけたらと思っています。
お忙しいところすみません、宜しければお願い致します。
ソースを確認してみました。
なるほど、カテゴリ内の記事表示 item function が抜けておりました。…ので、追記しましたので参考にしてください。findPosts の解説は書いたのに Controller のコードの解説を直すのをわすれていたようです。
お忙しい中ご連絡いただきましてありがとうございました。
いろいろ勝手なことを言いましたすみませんでした。