今度は wordpress のカテゴリ一覧、と指定したカテゴリ内の記事を拾ってみます。
wordpress はカテゴリの扱いがややこしくて、カテゴリの名前自体は wp_terms に入っているのですが…その後の wp_posts テーブルまでの関係がややこしいという。
# 更に云えば、カスタムフィールドと関係を見ていくと、なにやら大変という感じなのです。
- terms: カテゴリ名などの名称
- term_taxonomy: terms の分類(category, link_category など)
- term_relationships: カテゴリとの親子関係
- posts: 記事自体
という 4 つのテーブルが関わってきます。
いきなりはややこしいので、まずはカテゴリ一覧。
■カテゴリ一覧のビュー(views/categories/index.ctp)
<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)
<?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 モデルのアソシエーションを設定します。
<!-- 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 を指定。
<!-- 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 が書けます。
<!-- 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)
<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 メソッドの中身をクエリで書いてしまったのがこれです。
<!-- 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 で指定しているのですが、これでいいんでしょうかねぇ?
<!-- 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 で指定しておきます。
<!-- 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 メソッドを書き換えます。
// カテゴリ内の記事一覧を取得する 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文でエラーが出ます。
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 のコードの解説を直すのをわすれていたようです。
お忙しい中ご連絡いただきましてありがとうございました。
いろいろ勝手なことを言いましたすみませんでした。