オレオレ MVC を作ってみる(2)

オレオレ MVC を作ってみる(1) | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/1861

前回のコードを少し変更して wordpress のカテゴリ一覧とカテゴリ内の記事を表示させます。

■最初の index.php

まずは、クラス名とメソッドを取り出す index.php から。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 
</head>
<body>
<?php
$url = $_GET['url'];
$url = strtolower($url);

$vars =  preg_split('|/|',$url);
$params = array();
if ( count($vars) == 1 ) {
	$base = $url;
	$method = "index";
} else if ( count($vars) == 2 ) {
	$base = $vars[0];
	$method = $vars[1];
} else {
	$base = $vars[0];
	$method = $vars[1];
	for($i=2; $i<count($vars); $i++ ) {
		$params[] = $vars[$i];
	}
}
$classfile = $base."_controller.php";
$classname = strtoupper(substr($base,0,1)).substr($base,1).'Controller';

echo "classfile: $classfile<br/>";
echo "classname: $classname<br/>";
echo "method: $method<br/>";
echo "param: ";print_r($params); echo "<br/>";

include('controllers/'.$classfile);
$c = new ReflectionClass($classname);
$obj = $c->newInstance();
$method = $c->getMethod($method);
$method->invokeArgs($obj,$params);
?>
</body>

GET で送られる URL から「/」でパースするところは同じで、メソッドのパラメータを受けれるようにします。これの難点は常に body タグ内に view のコードが入ってしまうことですよね。一旦他のレンダリングに任せて、その中で view のコードを呼ぶようにしないと、head タグなどが書き出せません。

■ビューを作る

<!-- views/categories/item.php -->
<h1>カテゴリ一内の記事</h1>
<table>
	<tr>
		<th>Post.id</th>
		<th>Post.post_title</th>
		<th>User.display_name</th>
		<th>Post.post_date</th>
		<th>Post.guid</th>
	</tr>
	<?php foreach($Categories as $item) : ?>
	<tr>
		<td><?php echo $item['Post']['id']; ?></td>
		<td><?php echo $item['Post']['post_title']; ?></td>
		<td><?php echo $item['User']['display_name']; ?></td>
		<td><?php echo $item['Post']['post_date']; ?></td>
		<td><?php echo $item['Post']['guid']; ?></td>
	</tr>		
	<?php endforeach; ?>
</table>

ビューの作りは CakePHP の *.ctp と同じ。このあたりは互換が保てて楽ちん。

■コントローラーを作る

<?php
include 'models/category.php';
class CategoriesController {
	var $name = 'CategoriesController';
	function index() {
		// view に渡す変数
		global $Categories;
		// モデルを作成して
		$category = new Category();
		// 検索
		$Categories = $category->find('all');
		include('views/categories/index.php');
	}
	function item($slug) {
		// view に渡す変数
		global $Categories;
		// モデルを作成して
		$category = new Category();
		// 検索
		$Categories = $category->findBySlug($slug,10);
		include('views/categories/item.php');
	}
}
?>

コントローラーは、モデルを new してからメソッドを呼ぶだけ。最後に view をインクルードして表示ってな具合です。この include 部分を変えて index.php との連携を取らないといけませんね。

■モデルの基底クラスを作る

モデルの基底クラスを作りました。

<?php
class Model {
	var $cn ;
	function open() {
//		$this->cn = new PDO("mysql:host=127.0.0.1;dbname=moonmile_cake",
//			"moonmile_cake","cakephp");
		$this->cn = new PDO("mysql:host=127.0.0.1;dbname=wordpress",
			"wordpress","wordpress");
	}
	function close() {
		$this->cn = null;
	}
	function query($sql) {
		$this->open();
		$results = $this->cn->query($sql);
		$this->close();
		return $this->toArray($results);
	}
	function toArray($stmt) {
		$items = array();
		foreach($stmt as $item) {
			foreach($item as $key => $value) {
				if (strpos($key,'.') == true ) {
					list($table,$column) = split('\.',$key);
					$tables[$table][$column] = $value;
				}
			}
			$items[] = $tables;
		}
		return $items ;
	}
}
?>

ひとまず、データベースの open と close は、ここから継承することにすると。後、クエリを実行した後で配列に直すのが前回はださかったので、toArray メソッドを作って自動化します。こうすることで、Term.term_id のような名前をクエリで付けておけば $item[‘Term’][‘term_id’]のように参照ができます。

■モデルを作る

この基底クラスを継承して Category モデルクラスを作ります。

<?php
require('lib/model.php');

class Category extends Model {
	var $name = 'Category';
	function find($type) {
		$sql = <<< HERE
select
	Term.term_id	`Category.term_id`,
	Term.name		`Category.name`,
	Term.slug		`Category.slug`
from
	wp_terms as Term
  	inner join wp_term_taxonomy as TermTaxonomy
  	on ( Term.term_id = TermTaxonomy.term_id )
WHERE
	TermTaxonomy.taxonomy = 'category'
	order by Term.term_id
;
HERE;
		$items = $this->query($sql);
		return $items ;
	}

	function findBySlug($slug,$max) {
		$sql = <<< HERE
select
	Post.ID				`Post.id`,
	Post.post_title		`Post.post_title`,
	User.display_name	`User.display_name`,
	Post.post_date		`Post.post_date`,
	Post.guid			`Post.guid`
from
	wp_terms as Term
  	inner join wp_term_taxonomy as TermTaxonomy
  	on ( Term.term_id = TermTaxonomy.term_id )
	inner join wp_term_relationships as TermRelationship
	on ( TermTaxonomy.term_taxonomy_id = TermRelationship.term_taxonomy_id ) 
	inner join wp_posts as Post
	on ( TermRelationship.object_id = Post.ID ) 
  inner join wp_users as User
  on ( Post.post_author = User.ID )
WHERE
	TermTaxonomy.taxonomy = 'category'
AND Post.post_status = 'publish'
AND Term.slug = '$slug'
	order by Post.post_date desc 
	limit $max ;
HERE;
		$items = $this->query($sql);
		return $items ;
	}
}
?>

完全にクエリを書くということで、非自動化 O/R マッピングッ!!! SQL を分かっている場合はこれでも十分かなぁと。下手に Model::$belongsTo 等で悩むよりは、と考えてしまいます。

余談ですが、CakePHP のアソシエーションは、LINQ to Entities のアソシエーションのようにグラフィカルかつダイアログ等で入力してから、コードに直してやれば良いかと思っています。LINQ to Entities の設定って XML ファイルに書き出されるので、同様に CakePHP のアソシエーションを XML に設定しておいて読み込ませるってこともできますね。

このオレオレ MVC の O/R マッピングは完全に SQL 文を書くしかない体制なので、この部分をコピー&ペーストして MySQL Workbench で実行してチェックってことになります。なので、inner join を使ってきっちり結合させます。

■実行してみる

これだけのコードで MVC が実現できます(非常にチープですが)。

▼カテゴリ一覧

▼カテゴリ内の記事

まぁ、これだとリストの表示しかできないので、お次は insert, update, delete ですね。いや、その前にレイアウト読み込みをさせないと。

カテゴリー: 開発, CakePHP | オレオレ MVC を作ってみる(2) はコメントを受け付けていません

LINQ to Entities の実行クエリを表示する(続)

ちょっと前回の続き。

LINQ to Entities の実行クエリを表示する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/1983

マスターページに渡すのに ViewData を使っていたのだが、static なクラスなんだから、そのまま渡してしまってもいい訳です。ViewData を使うと、モデル→コントローラー→ビュー となってモデルで LINQ を定義しているのに、わざわざコントローラーで ViewData に指定しないという手間が発生する訳で。

まずは、LINQ to Entities のクエリを表示するクラスを作ってしまいます。

public static class DebugSQL
{
    private static List<string> sqlList = new List<string>();
    public static string SQL
    {
        get
        {
            string sql = "";
            foreach (string s in sqlList)
            {
                sql += s + "\n";
            }
            return sql;
        }
                
    }
    public static List<string> SQLList
    {
        get {
            return sqlList; 
        }
    }
    public static string ToTraceString(IQueryable query)
    {
        var objectQuery = query as ObjectQuery;
        if (objectQuery != null)
        {
            return objectQuery.ToTraceString();
        }
        else
        {
            return "no sql;";
        }
    }
    public static void Add(IQueryable query)
    {
        var objectQuery = query as ObjectQuery;
        if (objectQuery != null)
        {
            sqlList.Add( objectQuery.ToTraceString());
        }
    }
    public static void Clear()
    {
        sqlList = new List<string>();
    }
}

スタティックなクラスにするので、モデルやコントローラーで LINQ を使うところで、こんな風に DebugSQL::Add メソッドを使って追加していきます。

List<CategoryModel> model = new List<CategoryModel>();
var items = from t in ent.wp_terms
            where t.wp_term_taxonomy.FirstOrDefault().taxonomy == "category"
            select t;
DebugSQL.Add(items);

今回は複数回の呼び出しにも対処しているので、LINQ を使っているところで Add しておけば OK。

そして、マスターページ(Site.Master)

<div id="debug">
    <h2>実行クエリ</h2>
    <% List<string> sqls = MvcWordpress.Models.DebugSQL.SQLList; %>
    <% foreach (var item in sqls) { %>
        <%: item%><br />
    <% }%>
    <% MvcWordpress.Models.DebugSQL.Clear(); %>
</div>

で、こんな風に SQLList プロパティを参照してループで廻して表示します。
こうしておくと、CakePHP のように実行クエリが表示できます。

このすさまじいクエリ(苦笑)を SQL Server Management Studio なんかにコピー&ペーストして使えば、まぁ、なんかが分かるかもってな具合で。

SELECT            TOP (10) object_id, ID, post_author, post_date, post_date_gmt, post_content, post_title, post_category, post_excerpt, post_status, comment_status, ping_status, post_password, 
                        post_name, to_ping, pinged, post_modified, post_modified_gmt, post_content_filtered, post_parent, guid, menu_order, post_type, post_mime_type, comment_count
FROM              (SELECT            Extent1.object_id, Extent3.ID, Extent3.post_author, Extent3.post_date, Extent3.post_date_gmt, Extent3.post_content, Extent3.post_title, Extent3.post_category, 
                                                 Extent3.post_excerpt, Extent3.post_status, Extent3.comment_status, Extent3.ping_status, Extent3.post_password, Extent3.post_name, Extent3.to_ping, Extent3.pinged, 
                                                 Extent3.post_modified, Extent3.post_modified_gmt, Extent3.post_content_filtered, Extent3.post_parent, Extent3.guid, Extent3.menu_order, Extent3.post_type, 
                                                 Extent3.post_mime_type, Extent3.comment_count
                         FROM              wp_term_relationships AS Extent1 INNER JOIN
                                                 wp_term_taxonomy AS Extent2 ON Extent1.term_taxonomy_id = Extent2.term_taxonomy_id LEFT OUTER JOIN
                                                 wp_posts AS Extent3 ON Extent1.object_id = Extent3.ID
                         WHERE             ('category' = Extent2.taxonomy) AND (Extent2.term_id = @p__linq__0)) AS Project1
ORDER BY       post_date DESC

ええ、こちらのクエリの解析は後程。

カテゴリー: ASP.NET | LINQ to Entities の実行クエリを表示する(続) はコメントを受け付けていません

LINQ to Entities の実行クエリを表示する

LINQ to SQL の場合は Log プロパティを使う

LINQ to SQLのログ出力をVisual Studioの[出力]ウィンドウに出力するには?[3.5、C#、VB] - @IT
http://www.atmarkit.co.jp/fdotnet/dotnettips/801linqoutput/linqoutput.html

ってことで良いのですが、LINQ to Entities の場合は、ObjectQuery クラスにキャストして ToTraceString を使うってのが常套手段らしいです。

[C#]LINQ to Entitiesで発行されているSQL文を見たい
http://blogs.wankuma.com/kazuki/archive/2009/06/01/173926.aspx

上で示されているようなメソッドを作ればよいので、ASP.NET MVC で CakePHP 風な実行クエリを表示するようにしてみます。クエリを表示させると SQL Server Management Studio などで実行できるので確認が楽なんですよね。後、変なクエリをしていないかを確認できます。

using System.Data.Objects;

public static class DebugSQL
{
    private static string _sql;
    public static string SQL
    {
        get
        {
            return _sql;
        }
    }
    public static void ToTraceString<T>(IQueryable<T> query)
    {
        var objectQuery = query as ObjectQuery;
        if (objectQuery != null)
        {
            _sql = objectQuery.ToTraceString();
        }
        else
        {
            _sql = "no sql;";
        }
    }
}

こんな形で、static class を定義しておきます。これだと1回のクエリしか保存できないので、複数回のクエリも保存したいですね。これは後程修正します。

そしてコントローラー中で、

            var items = from t in ent.wp_terms
                        where t.wp_term_taxonomy.FirstOrDefault().taxonomy == "category"
                        select t;
            DebugSQL.ToTraceString(items);

のように LINQ を実行した後に、DebugSQL.ToTraceString で SQL を保存しておきます。

これを画面に表示する時には、CakePHP のようにマスターページを使いたいので、Site.Master を変更します。

        <div id="main">
            <asp:ContentPlaceHolder ID="MainContent" runat="server" />
            <div id="debug">
                <h2>実行クエリ</h2>
                <%: ViewData["SQL"] %>
            </div>
            <div id="footer">
            </div>
        </div>

こんな風に ViewData を使えばいいです。

これを設定するために、コントローラーで ViewData を指定します。

public ActionResult Index()
{
    Models.CategoryModel cate = new Models.CategoryModel();
    var model = cate.GetCategories();
    ViewData["SQL"] = Models.DebugSQL.SQL;
    return View(model);
}

CakePHP ではレイアウトのページで実行SQLを表示しているので、この手のデバッグ情報はコントローラーやモデルには書きたくないところですよね。。。ちょっと工夫したいところです。

まぁ、それでも一応、実行するとうまくクエリが取れます。

このクエリを SQL Serve Management Studio で実行するとこんな感じ。

実は、Visual Studio 2010 上でもクエリが実行できて、サーバーエクスプローラーから「新しいクエリ」でペインを開けます。

余分なウィンドウを消して、クエリを実行すると(余計なことに)クエリ文を整形してくれます。

元のクエリ文が残らないのでちょっと不安なのですが、うまくサブクエリになっている(笑)ことがわかります。

出力された結果が微妙なサブクエリになっているところが不安を醸し出します。アソシエーション経由でクエリを掛けるとこんな感じになるんですねぇ。わざわざ TOP を使ってサブクエリになっているのは FirstOrDefault メソッドを使っているからなので、このあたりは EDM の アソシエーションを 1対1 にすれば直るのかもしれません。

カテゴリー: 開発, ASP.NET | LINQ to Entities の実行クエリを表示する はコメントを受け付けていません

ASP.NET MVC から wordpress のデータを扱う(4)

ASP.NET MVC では LINQ to Entities を使えるので、あらかじめリレーションをしておくと楽…なんですが、逆にリレーションが設定されていない場合は大変ってのがありますね。CakePHP の場合は Model::$belongsTo 等で後からリレーション/アソシエーションを設定するのですが、LINQ to Entities の場合はこれが大変、というか、これはどう設定すればいんでしょう???な状態なのですが、ええ、実は意外と簡単でした。

デザイナの背景をクリックして「追加」→「アソシエーション」ですね。

そして、アソシエーションのプロパティで「参照に関する制約」を忘れずに設定

という訳です。詳しくは後ほどブログに書きますが、ひとまず(個人的に)速報まで。

さて、お次はカテゴリ内の記事の一覧を作ります。関連するテーブルは以下の4つ。

  • terms: カテゴリ名などの名称
  • term_taxonomy: terms の分類(category, link_category など)
  • term_relationships: カテゴリとの親子関係
  • posts: 記事自体

CakePHP から wordpress のデータを扱う(3) | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/1922

と同じように書いていきます。

■ビューを作る

目標となるビューはこんな感じです。

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<MvcWordpress.Models.CategoryModel>>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Index
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>カテゴリ一覧</h2>
    <table>
        <tr>
            <th>term_id</th>
            <th>term_taxonomy_id</th>
            <th>name</th>
            <th>slug</th>
        </tr>

    <% foreach (var item in Model) { %>
        <tr>
            <td><%: item.Term.term_id %></td>
            <td><%: item.TermTaxonomy.term_taxonomy_id %></td>
            <td><%: item.Term.name %></td>
            <td><%: item.Term.slug %></td>
        </tr>
    <% } %>
    </table>
    <h2>カテゴリ一覧(リンク版)</h2>
    <ul>
    <% foreach (var item in Model) { %>
        <li><a href="/Categories/Item/<%: item.Term.slug %>"><%: item.Term.name %></a></li>
    <% } %>
    </ul>
</asp:Content>

CakePHP と同じように CategoryModel というモデルクラスを独自に作ります。理由は同じで、wp_terms も wp_term_taxonomy も名前として適切ではないからです。そうそう、後から書きますが、実は今回の場合はデータベースに View を作ったほうがよいです。と言うのも、wp_term の扱いがメタ情報データ的に扱われているので、wp_posts や wp_comments のような通常のテーブルと扱いが違うためです。このあたりの理由は、時間があれば別途。

カテゴリのそれぞれの参照は、「item.Term.term_id」に、元テーブル名.カラム名 のようにアクセスできる形にしておきます。カテゴリのための別名を作っても良いのですが、プログラミングするときに覚えることが増えてしまう(再マッピングのために)ので、この程度の複雑度ならばこのまま使います。カラム名がちょっと不明な感じ(T200idとか)の場合であれば、別名を定義しますが。

■コントローラーを作る

コントローラーはこんな感じにシンプルになります。

public class CategoriesController : Controller
{
    //
    // GET: /Categories/

    public ActionResult Index()
    {
        Models.CategoryModel cate = new Models.CategoryModel();
        var model = cate.GetCategories();
        return View(model);
    }
}

最初作っていたときは、ここに LINQ が入っていたのですが、やめました。理由としては、

  • データベースで外部キー/リレーションを使っているのに、有効活用できていない。
  • リレーションの複雑さをコントローラーに持ち込むのは適切ではない。モデルとしてデータベースに近いところに置くべきだろう。

としたためです。ちょっとした試行錯誤の結果ですが、簡単なクエリであればコントローラーにおいてもよいのですが、複雑になるとちょっとという感じがしますね。このあたり、ASP.NET MVC の場合は、コントローラーとモデルの役割分担が難しいという感じがします。逆に言えば、CakePHP のほうは、モデルでやるのが適切という強制力が働いています。

■モデルを作る

モデルでは、このように LINQ to Entities のクラスを活用しています。

public class CategoryModel
{
    public wp_terms Term { get; set; }
    public wp_term_taxonomy TermTaxonomy { get; set; }

    /// <summary>
    /// カテゴリ一覧を取得
    /// </summary>
    /// <returns></returns>
    public List<CategoryModel> GetCategories()
    {
        wordpressEntities ent = new wordpressEntities();
        List<CategoryModel> model = new List<CategoryModel>();
        var items = from t in ent.wp_terms
                    where t.wp_term_taxonomy.FirstOrDefault().taxonomy == "category"
                    select t;
        foreach ( var item in items )
        {
            model.Add(new CategoryModel
            {
                Term = item,
                TermTaxonomy = item.wp_term_taxonomy.First()
            });
        }
        return model; 
    }
}

カテゴリ一覧を表示する時に必要なテーブルは2つ(wp_termとwp_term_taxonomy)なので、wp_term を中心にして検索しています。
カテゴリだけを表示するための LINQ に工夫があって、アソシエーションを利用しています。

こんな風に既に EDM(Entity Data Model)のほうには外部キーが設定してあるので、相互にテーブルが参照できます。具体的には、t.wp_term_taxonomy.taxonomy として、wp_term_taxnomy テーブルのカラムを指定できる訳ですね。ここでは、wp_term(1)-(*)wp_term_taxnomy という関係なので(私の外部キーの設定が間違っているのでこんな風になっています。本来は 1対1 です)、FirstOrDefault メソッドを使ってコレクションの最初を拾ってきていますが、アソシエーションを設定しておくとデータベースの外部キーを LINQ で再度 join しなくて済みます。
と言いますか、データベースに指定してあるのに、再び LINQ で join するのは O/R マッピング的に本末転倒ですよね。関係というロジックが2箇所に分散されてしまいます。

これを実行した結果が次です。

綺麗に表示されていますが、実際の作成手順は CakePHP と逆になりますね。ASP.NET MVC だとどうしてもコンパイルエラーに引かれてしまうのと、インテリセンスを使ってのコーディングが普通なので、次の順序で作ります(実際作ったし)。

  1. wordpress のデータベースに外部キーを設定。
  2. データベースから EDM へ取り込む(モデルの作成)
  3. EDM を使って コントローラーを仮に作成。LINQ 版を作る。
  4. ビューを作成して、LINQ のテスト。
  5. EDM を使って CategoryModel を作成。
  6. コントローラーの LINQ を CategoryModel に移してリファクタリング

な手順です。いきなり、CategoryModel を作ろうとすると失敗するので、まずはコントローラーに書いて実験するところから始めました。そして、方針が固まったら CategoryModel に移してという流れですね。

お次は、カテゴリ内の一覧を ASP.NET MVC で作ります。

カテゴリー: 開発, ASP.NET, Wordpress | ASP.NET MVC から wordpress のデータを扱う(4) はコメントを受け付けていません

美女Linux iGoogle ブログパーツを公開

さっくりと、開発バージョンを公開しておきます。本家 bijo-linux.com への公開はもうちょっとお待ちを

美女Linux 0.9 – 自分のホームページに追加
http://www.google.com/ig/directory?url=bijo-linux.com/bp/js/bijo-0.9.xml

http://www.google.com/ig/directory?url=bijo-linux.com/bp/js/bijo-off-0.9.xml

http://www.google.com/ig/directory?url=bijo-linux.com/bp/js/bijo-feb-0.9.xml

美女Linux v0.9
http://www.google.com/ig/adde?moduleurl=http://bijo-linux.com/bp/js/bijo-0.9.xml&source=imag
美女Linux v0.9 オフショット版
http://www.google.com/ig/adde?moduleurl=http://bijo-linux.com/bp/js/bijo-off-0.9.xml&source=imag
美女Linux v0.9 Feb.版
http://www.google.com/ig/adde?moduleurl=http://bijo-linux.com/bp/js/bijo-feb-0.9.xml&source=imag

以前、作ったときの手順を忘れていて…

美女Linux 公開 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/1642

なんだ、xml を作るだけだったか、っていうオチでした。
プレビューのページなどを付けておかないと。

カテゴリー: 開発, ブログパーツ | 美女Linux iGoogle ブログパーツを公開 はコメントを受け付けていません

Visual Studio 2010 で CLI/C++ のインテリセンスが使える拡張アドイン Visual Assist X

以前、

C++/CLIにインテリセンスが必須な理由 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/793

なところで、Visual Studio 2010 では C++/CLI のインテリセンスだけが効かない。という話をしましたが、どうやら、有料の拡張アドインを入れれば C++/CLI のインテリセンスが使えます。

# そうそう、Visual Studio 2008 に戻せば、C++/CLI のインテリセンスが効くんですけどね。何故か、2010 を入れた後に 2008 を入れることができないんですよ。ここはなんとかしなくちゃなあと。やっぱり、2010 をアンインストールしてから、2008 -> 2010 の順に入れ直すしかないんですかね?

Visual Assist X – an add-in for Visual Studio by Whole Tomato Software
http://www.wholetomato.com/default.asp
Visual Assist X
http://visualstudiogallery.msdn.microsoft.com/D6678A1F-C700-4532-8C5E-A799D875063D/?SRC=Home

で、30日試用版がダウンロードできます。

ダウンロードしたら、*.vsix のファイルをダブルクリック。

するとインストーラーが起動します(実は、このあたり悩んだ…アドイン拡張アドインは違うのですね。

ちなみに、Assite X は、C++/CLI だけじゃなく、C# や Visual Basic などのインテリセンスも賢くしてくれます。色々機能があるので先のサイトでデモを見ると良いかと。

Visual Studio 2010 を起動して、「ツール」→「拡張機能マネージャー」を見ると、

のように Assist X が有効になっています。

ちなみに、オンライン ギャラリーを見ると、色々アドインがありますね(知らんかった)。

さて、肝心のインテリセンス機能ですが、こんな風に出ます。

やったッ!!! これで、開発効率が上がるぜッ!!! という訳で、パーソナルライセンスが $99。買うかどうか思案中ですね…思案しているのは、現在 C++/CLI の案件を抱えてないってことで。まぁ、サンプル作りとかにも使えるし、せっかく MVP Visual C++ を受賞したし、という訳なのですが。

カテゴリー: 開発, ツール | Visual Studio 2010 で CLI/C++ のインテリセンスが使える拡張アドイン Visual Assist X はコメントを受け付けていません

ASP.NET MVC から wordpress のデータを扱う(3)

実はEDM(Entity Data Model)では、あらかじめデータベースでリレーションを設定しておくと、モデルのほうでアソシエーションが設定されます…って、なんだか分かりませんね(汗)

要は、こんな感じです。
最初の SQL Server 2008 R2 上で、リレーションを設定します。

ダイアグラムではこんな風。

このあたり、.NET ラボの勉強会で話したのですが、SQL Server Management Studio はそれなりに新しくなっていくのに、このダイアグラムは SQL Server 7.0 の頃と変わり映えしません。時代遅れなのか、誰も使っていないのかというところですが、データベース上の外部キーって大切なんですけどねぇ。

これを Visual Studio の EDM を更新すると、こんな風にアソシエーションが設定されます。

ちなみに、データベース上では「リレーション」とか「外部キー」と言われるんですが、オブジェクト指向の方(CakePHPとかも)の O/R マッピングの世界では「アソシエーション」って言っているようです。どちらでもいいんですが、統一してほしいような気が(機能が違うからっていう意味もあるでしょうが)。

こういう風にしておくと、wp_posts モデルの中に wp_users というプロパティ(ナビゲーション)が出来きます。逆向きの wp_users モデルの中にもできますね。
これのナビゲーションを使うと、wp_posts.wp_users.display_name というように、関係するテーブルの列名を表示させることができるのです。

この機能をうまく使うと、ビューが簡単になります。

■ビューを作る

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<MvcWordpress.wp_posts>>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Recent
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>最近投稿した記事</h2>
    <table>
        <tr>
            <th>ID</th>
            <th>post_date</th>
            <th>post_title</th>
            <th>post_author</th>
            <th>User.display_name</th>
            <th>guid</th>
        </tr>
        <% foreach (var item in Model) { %>
            <tr>
                <td><%: item.ID %></td> 
                <td><%: item.post_title %></td>
                <td><%: item.post_date %></td>
                <td><%: item.post_author %></td>
                <td><%: item.wp_users.display_name %></td>
                <td><%: item.guid %></td>
            </tr>
        <% } %>
    </table>
    <h2>最近投稿した記事(リンク版)</h2>
    <ul>
        <% foreach (var item in Model) { %>
        <li>
            <a href="<%: item.guid %>"><%: item.post_title %></a>
            by <%: item.wp_users.display_name %>
            at <%: item.post_date %>
        </li>
        <% } %>
    </ul>
</asp:Content>

渡されるモデルは、wp_posts のコレクションということで、IEnumerable と指定し直します。こうすると、wp_posts の列名はそのまま使えるし、関連する wp_users のテーブルも「<%: item.wp_users.display_name %>」のように簡単に参照できます。

■コントローラーを直す

コントローラーのほうは、LINQ to Entities に直して、wp_posts テーブルだけを参照するようにします。参照先のテーブルは、自動的に読み込まれるので LINQ 自体も簡単になります。

public ActionResult Recent()
{
    int max = 5;
    wordpressEntities ent = new wordpressEntities();
    var model = (from p in ent.wp_posts
                 where p.post_status == "publish"
                 orderby p.post_date descending
                 select p).Take(max);

    return View(model);
}

このあたり、CakePHP の Model::$belongsTo と同じような使い方ができます。というか、同じようにするために外部キー/アソシエーション/ナビゲーションを利用する訳です。

実行するとすんなり動きます。

こんな訳で、1段階のリレーションは簡単にできるのですが、さて、4つのリレーションの場合はどうするんでしょうねえ?

カテゴリー: ASP.NET, Wordpress | ASP.NET MVC から wordpress のデータを扱う(3) はコメントを受け付けていません

ASP.NET MVC から wordpress のデータを扱う(2)

次に最近投稿した一覧を表示させます。
今回は、コントローラーの PostsController::RecentPosts メソッドから作っていきますね。

■コントローラーを LINQ で作る。

public ActionResult Recent()
{
    int max = 5;
    wordpressEntities ent = new wordpressEntities();
    var model = (from p in ent.wp_posts
                    join u in ent.wp_users on p.post_author equals u.ID
                    where p.post_status == "publish"
                    orderby p.post_date descending
                    select new
                    { Post = p, User = u }
                    ).Take(max);

    return View(model);
}

投稿した人の名前(User.display_name)を表示させたいので、wp_users テーブルとリンクさせます。そていて、最初の5行だけ表示させたいので Take 関数を使っています。
このための専用のモデルを作るのは面倒なので、無名クラスを作って new { Post = p, User = u } として作成しています。

こうすると、ビュー側で

  • item.Post.post_author
  • item.User.display_name

のように参照ができるハズですね(実は、うまくいきません…)。

■ビューを作る

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Recent
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>最近投稿した記事</h2>
    <table>
        <tr>
            <th>ID</th>
            <th>post_date</th>
            <th>post_title</th>
            <th>post_author</th>
            <th>User.display_name</th>
            <th>guid</th>
        </tr>
        <% foreach (var item in Model) { %>
            <tr>
                <td><%: item.Post.ID %></td> 
                <td><%: item.Post.post_title %></td>
                <td><%: item.Post.post_date %></td>
                <td><%: item.Post.post_author %></td>
                <td><%: item.User.display_name %></td>
                <td><%: item.Post.guid %></td>
            </tr>
        <% } %>
    </table>
    <h2>最近投稿した記事(リンク版)</h2>
    <ul>
        <% foreach (var item in Model) { %>
        <li>
            <a href="<%: item.Post.guid %>"><%: item.Post.post_title %></a>
            by <%: item.User.display_name %>
            at <%: item.Post.post_date %>
        </li>
        <% } %>
    </ul>
</asp:Content>

型無しの指定なので、ViewPage のままです。
それぞれの表示は、そんまま aspx に記述するとタイピングミスが起こるので、あらかじめコントローラー内でコーディングしたものをコピー&ペーストしました。

コントローラーで、

foreach (var item in model)
{
    Debug.Print("{0},{1}", 
        item.Post.ID, 
        item.Post.post_date,
        item.Post.post_author,
        item.User.display_name,
        item.Post.guid );
}

のように作っていくと、インテリセンスが使えて便利です。

さて、これを実行するとですね。。。実行エラーが発生するのです。

なんででしょうね?無名クラスで定義したものが、ビューのところではうまく取得できないようです。

■仕方がないのでモデルクラスを作る

という訳で、仕方がないので Posts モデルクラスを作ります。先に作った FindPosts メソッドも踏まえて、こんな感じです。

public class Posts : wp_posts 
{
    public wp_users User { get; set; }
    public wp_posts Post { get; set; }

    public IQueryable<wp_posts> FindPosts()
    {
        wordpressEntities ent = new wordpressEntities();
        var model = from t in ent.wp_posts
                    where t.post_status == "publish"
                    select t;
        return model;
    }
    public List<Posts> RecentPosts(int max)
    {
        wordpressEntities ent = new wordpressEntities();
        var model = (from p in ent.wp_posts
                        join u in ent.wp_users on p.post_author equals u.ID
                        where p.post_status == "publish"
                        orderby p.post_date descending
                        select new { Post = p, User = u }
                        ).Take(max);

        List<Posts> posts = new List<Posts>();
        foreach (var item in model)
        {
            posts.Add( 
                new Posts { Post = item.Post, User = item.User });
        }
        return posts;
    }
}

プロパティで wp_posts と wp_users のテーブルが参照できるように Post, User を定義しておきます。後は、コントローラーで作成していた LINQ の文をコピー&ペーストして、この結果を List に詰めて返します。

対して、コントローラーの RecentPosts メソッドは非常になるのでが、なんだかなぁ、という感じですね。

public ActionResult Recent()
{
    int max = 5;
    Models.Posts post = new Models.Posts();
    var model = post.RecentPosts(max);
    return View(model);
}

これを動かすと、ビューはそのままで綺麗に動きます。

一応、動くには動くのですが、wp_posts と wp_users のテーブルをいちいち join させないといけないのが CakePHP よりも劣っていますよね。

というのは、EDM(Entitiy Data Model)のところでリレーションを設定していないのが原因なのです。CakePHP では Model::$belongsTo などで join を指定しているのですが、wordpress のテーブルから引っ張ってきたテーブルでは、この join が設定されていません。

なので、お次は、リレーションを設定したうえで、最近投稿した一覧を表示させてみます。

カテゴリー: ASP.NET, Wordpress | 1件のコメント

ASP.NET MVC から wordpress のデータを扱う(1)

という訳で、今度は ASP.NET MVC から wordpress のデータを扱っていきます。これで CakePHP と比較すると、ASP.NET MVC が何処まで優れている/劣っているのかが分かるかと。

CakePHP から wordpress のデータを扱う | Moonmile Solutions Blog
www.moonmile.net/blog/archives/1888
CakePHP から wordpress のデータを扱う(2) | Moonmile Solutions Blog
www.moonmile.net/blog/archives/1896
CakePHP から wordpress のデータを扱う(3) | Moonmile Solutions Blog
www.moonmile.net/blog/archives/1922

wordpress のデータベースは SQL Server には対応していないので、テーブルの作成とインポートは別途行っています。実は、このあたり苦労したので…後で情報として残しておきますね。

ASP.NET MVC アプリの場合は LINQ to Entities を使うと楽なので、ズルをしてこれを使います。モデルの作成が楽というのがあるのですが。LINQ が使えるというのがミソでして。

■ビューを作る

ASP.NET MVC のビュー作成は、EDM(Entity Data Model)を使うと自動生成してくれるので、これを利用します。wp_posts クラスを指定します。

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<MvcWordpress.wp_posts>>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Index
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Index</h2>

    <table>
        <tr>
            <th>
                ID
            </th>
            <th>
                post_author
            </th>
            <th>
                post_date
            </th>
            <th>
                post_title
            </th>
            <th>
                post_category
            </th>
            <th>
                guid
            </th>
        </tr>

    <% foreach (var item in Model) { %>
    
        <tr>
            <td>
                <%: item.ID %>
            </td>
            <td>
                <%: item.post_author %>
            </td>
            <td>
                <%: String.Format("{0:g}", item.post_date) %>
            </td>
            <td>
                <%: item.post_title %>
            </td>
            <td>
                <%: item.post_category %>
            </td>
            <td>
                <%: item.guid %>
            </td>
        </tr>
    
    <% } %>

    </table>

</asp:Content>

■コントローラーを作る

EDM の場合、クラス名を変更できない(と思う)ので、「wp_」のようなプレフィクスはそのまま残ってしまいますね。これは、CakePHP のほうが prefix の設定ができる分、有利です。
更に云えば、T100,T200,T300 のような管理しやすいが他人には分かりにくいテーブル名の場合は、そのままクラス名として使われてしまいますね。CakePHP の Model はテーブル名を変更できるので、これも CakePHP のほうがよいかなと。

public class PostsController : Controller
{
    //
    // GET: /Post/

    public ActionResult Index()
    {
        wordpressEntities ent = new wordpressEntities();
        var model = from t in ent.wp_posts
                    where t.post_status == "publish"
                    select t;
        return View(model);
    }
}

ASP.NET MVC では、PostsController クラスで post_status をチェックしています。これは、EDM のクラスにはメソッドが追加できない(と思う)ので、findPosts メソッドを追加できないからなんですよねぇ。多分、拡張メソッドを使ってメソッドを追加するんでしょうが、単純にメソッドを追加、とはいかないようです。

■モデルは EDM そのままで

モデルは CakePHP と同様に EDM そのものを使います。wp_posts だけを返すのであれば、これで十分です。

■モデルのラッパークラスを作ってみる

ASP.NET MVC では、コントローラーでデータ加工をすることが多いので(EDMをモデルとして直接使う場合は特に)、コントローラーの各メソッドでクエリを書きます。
が、post_status = ‘publish’ のような、フラグ(他にも削除フラグとかなんとか)を、いちいちコントローラーでチェックするのは抵抗があるし、間違いも発生しやすいものです。
なので、wp_posts モデルをラップした Post クラスを作って CakePHP の時のように findPosts メソッドを作ってみます。

public class Post : wp_posts
{
    public IQueryable<wp_posts> FindPosts()
    {
        wordpressEntities ent = new wordpressEntities();
        var model = from t in ent.wp_posts
                    where t.post_status == "publish"
                    select t;
        return model;
    }
}

単純ですが、wp_posts クラスを継承したクラスを作ります。この継承が意味があるかどうかは別として、ひとまず wp_posts と同じものを指しているというつもりです。

コントローラーの Index メソッドは Posts::FindPosts メソッドを使って次のように書きます。

public ActionResult Index()
{
    Models.Post post = new Models.Post();
    var model = post.FindPosts();
    return View(model);
}

まあ、こんな風にしたほうが分かりやすいかな、と。
本来は、wp_posts.FindPosts() な形で書きたいですね。

お次は、最近投稿した記事一覧を表示させます。

カテゴリー: ASP.NET, Wordpress | 1件のコメント

CakePHP から wordpress のデータを扱う(3)

今度は 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 は、準拠していると思うのだけど)クエリのほうが作成が速いってのがありますね。

カテゴリー: CakePHP, Wordpress | 6件のコメント