写真から固定比で画像を切り取るツールを探す

求ムッ!!! 等倍で切り取るツール…を探していた訳ですが、結構身近にありました。
# Photoshop とかあればいいんですけど、この手のデザインツールは手元にないのです。

無料写真編集ソフト フォトギャラリー – Windows Live on MSN
windowslive.jp.msn.com/photo.htm

去年あたりに新しくなった Windows Live Essential 2011 というのがありまして、この中のフォトギャラリーが要件を満たしてくれます。無料だし、Windows 環境だしという訳で。

指定の画像をダブルクリックした後に、

トリミングをクリック。

縦横比を正方形で選択。他もできそうです。

大きさを決めた後に再びトリミングのボタンを押せば完了。

これをぽちぽちとやって、200枚を2時間というスピードで、まぁなんとかという訳ですね。保存のところでちょっと時間が掛かるから、もう少し効率化したい訳ですが、美女Linux ハートマーク版の場合、顔が入らないといけないから自動って訳にはいきませんがな。

画像は 160×160 に揃えるので、後からツールで揃えます。

IrfanView – Official Homepage – one of the most popular viewers worldwide
www.irfanview.com/
IrfanView 日本語版のページ
www8.plala.or.jp/kusutaku/iview/

で揃えてしまうか、まぁ、自前のツールを使うかといったところですね。irfanView だと複数ファイルもいけるので、それで十分かと。自前のツールを使う場合は perl でバッチファイルを書いて何度も動かすときに便利、って具合です。

実は irfanView にも固定比で取れるのですが、結構面倒なんですよね。さすがに 200 枚という多さの場合には、ちょっと無理ってな感じなのでした。

カテゴリー: ツール | 写真から固定比で画像を切り取るツールを探す はコメントを受け付けていません

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

今度は、wordpress のコメント表示

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

のところで記事の表示ができたので、これを流用します。

アクセスは http://servername/comments/recent のようにして、最近のコメントを取得。

■ビューを作る

ひな型が出来ているので、今度はいきなりビューを作ってしまいます。
モデル名やテーブルのカラム名なんかは想定で作ってしまって、後で合わせるってことができますね。テーブル仕様書ができていれば、これで分業ができるはずです。

<h2>最近投稿されたコメント</h2>
<table>
	<tr>
		<th>comment_ID</th>
		<th>comment_post_ID</th>
		<th>comment_author</th>
	</tr>
	<?php foreach($Comments as $item) : ?>
	<tr>
		<td><?php echo $item['Comment']['comment_ID']; ?>
		<td><?php echo $item['Comment']['comment_post_ID']; ?>
		<td><?php echo $item['Comment']['comment_author']; ?>
		<td><?php echo $item['Comment']['comment_author_url']; ?>
		<td><?php echo $item['Post']['post_title']; ?>
		<td><?php echo $item['Post']['guid']; ?>
	</tr>
	<?php endforeach ; ?>
</table>

<h2>最近投稿されたコメント(リンク版)</h2>
<ul>
	<?php foreach($Comments as $item) : ?>
		<li>
			<?php if ( $item['Comment']['comment_author_url'] == '' ) : ?>
				<?php echo $item['Comment']['comment_author']; ?>
			<?php else : ?>
				<a href="<?php echo $item['Comment']['comment_author_url']; ?>"><?php echo $item['Comment']['comment_author']; ?></a>
			<?php endif; ?>
			on
			<a href="<?php echo $item['Post']['guid']; ?>">
			<?php echo $item['Post']['post_title']; ?>
			</a>
		</li>
	<?php endforeach ; ?>
</ul>

開発時の手順は、

  1. プログラマがテーブルタグで、必要な部品を揃えておく&チェックする。
  2. デザイナが、部品をコピー&ペーストしながらレイアウトを組む。

って感じですかね。<?php echo … ?>なところに値が入るってことだけ説明すれば後は CSS でデザインしてもらうとか。

# 実は Microsoft の Expression Design は、仮データを XML などで作っておいて、デザイン時にもデータが表示される、ってのが売りなんですが、、、これってあまり宣伝されていない気が。って、使っている人ってどのくらい?って感じなのですが。

■コントローラーを作る

コントローラーはあっさりと作りました。
まあ、全体用の index メソッドと最新のコメントを取る recent メソッドです。

<?php
class CommentsController extends AppController {

	var $name = 'Comments';
	function index() {
		$this->set('Comments',$this->Comment->find('all'));
	}
	function recent() {
		$this->set('Comments',$this->Comment->findRecentComments(5));
	}
}
?>

CakePHP の Model::find メソッドを使っていて気づいたのですが、web の場合、mysql の limit(行数制限)は必須かもしれませんね。デスクトップアプリケーションだと、間違って数万行を出してもアプリを止めれば接続が切れるのでサーバーのダウンが避けられますが、web 経由の接続だとあっという間にダウンしてしまいそう。なので、必ず limit を指定しておいて最初の数百行に抑えておくというのが安全だと思います。
# SQL Server や Oracle だと top コマンド

■モデルを作る

Comment.comment_post_ID (*)-(1) Post.ID の連携があるので、Model::$belongTo 変数に記述します。実際は inner join にしたいところですが、Comment の親には必ず Post があるので、結果は同じになります。

<!-- models/comment.php -->
<?php
class Comment extends AppModel
{
	var $name = 'Comment';

	var $belongsTo = array(
	    'Post' => array(
				'conditions' => array(
					array('Post.post_status'=>'publish'),
					array('Comment.comment_approved'=>'1')),
			'className' => 'Post',
			'foreignKey' => 'comment_post_ID' ));

	// 最近投稿した記事を取得する
	function findRecentComments($max = 5) {
		return parent::find('all', 
			array(
				'conditions' => array(
					array('Post.post_status'=>'publish'),
					array('Comment.comment_approved'=>'1')),
				'order'=>'comment_date desc',
                'limit'=>$max));
	}
}
?>

最近投稿されたコメントのほうは、最大値を指定して、日付を逆順にして表示。
あと、記事(Post)の方のステータスが変わった場合にも対処するために、Model::$belongsTo のほうに、conditions を追加しておきます。こうすると、left join の on のところに条件が追加されます。

■結果

これも結果が見れます。

http://wpcake.moonmile.net/comments/recent

と言う訳で、お次はカテゴリ一覧とカテゴリ内の表示、これが wordpress では、ややこしい部類なのです。

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

CakePHP から wordpress のデータを扱う

再び CakePHP 本体に戻って、その実力なぞ…というか自分的に調査。

そういえば、v1.3.x からは Model::findAll メソッドが動かず Model::find(‘all’) に切り替えられたんだそうだ。

find :: データを取得する :: モデル :: CakePHPによる開発 :: マニュアル :: 1.3コレクション :: The Cookbook
http://book.cakephp.org/ja/view/1018/find

この変更は、ちと大きいのかなぁ。古いマニュアルを読んでいると findAll を使っているところが多いので、悩んだりします。因みに、findAllByカラム名 は動くんですよね(何故か、findAll だけが動かないという現象で悩んだ)

wordpress のデータベースの構造は比較的簡単で、11 個しかありません。詳しいところは、

Amazon.co.jp: PHPによるWordPressカスタマイズブック―2.8対応 テンプレートの改造からプラグインの作成まで: 藤本 壱: 本
[amazon http://www.amazon.co.jp/dp/4883376745]

を参照して貰うとして。2.8 ベースなのですが、私的にはこれで十分な状態。

  • wp_posts: 投稿記事/ページ
  • wp_comments: コメント
  • wp_users: ユーザー情報

あと、カテゴリやカスタムフィールド関係が

  • wp_terms
  • wp_term_taxonomy
  • wp_term_relationships

に入っています。

これらの関係を、CakePHP から扱うにはどうするのか?ってのを実践してみれば、実運用で使える技術が鍛えられるはず。

ひとまず、プレフィックスの「wp_」が邪魔なのですが、CakePHP のデータベースの設定で prefix を指定できるので、これを使います。

class DATABASE_CONFIG {
	var $default = array(
		'driver' => 'mysql',
		'persistent' => false,
		'host' => '127.0.0.1',
		'login' => 'manabook',
		'password' => 'manabook',
		'database' => 'manabook',
		'prefix' => 'wp_',	★
	);

こうしておくと、テーブル名が「wp_posts」でも Post モデルとして参照できます。

■最初はURLを入れる

CakePHP はマニュアル無しでサイトを作れるのか? | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/1753 

自分でやったことを、もう一度試してみるるので、最初は url を決めます。
http://servername/posts

と入力します。

■コントローラー作る

当然エラーが出て、コントローラーが無いとのお達しなので、PostsController を作ります。

<?php
class PostsController extends AppController {

	var $name = 'Posts';
	// ひとまず一覧
	function index() {
		$this->set('Posts',$this->Post->findPublish());
	}
	// 最近の一覧
	function recent() {
		$this->set('Posts',$this->Post->findRecentPosts(5));
	}
}
?>

ここで findPublish メソッドと findRecnetPosts メソッドを作っていますが、実は wp_posts は、公開(post_status)の状態があって、一覧の場合はこのカラムを必ずみることになります。
この条件をコントローラーで指定してもよいのですが、ここは Post モデルに任せることにします。

■モデルを作る

色々試行錯誤した結果、こんな感じになります。

<!-- models/post.php -->
<?php
class Post extends AppModel
{
	var $name = 'Post';

	var $belongsTo = array(
	    'User' => array(
			'className' => 'User',
			'foreignKey' => 'post_author' ));

	function findPublish() {
		return parent::find('all',
			array('conditions' => array('Post.post_status'=>'publish'),
				  'order'=>'post_date'));
	}
	// 最近投稿した記事を取得する
	function findRecentPosts($max = 5) {
		return parent::find('all',
			array('conditions' => array('Post.post_status'=>'publish'),
				  'order'=>'post_date desc',
				  'limit'=>$max));
	}
}
?>

Model::find メソッドの記法がややこしいのですが、Post.post_status の $conditions を必ずいれておいて、後は order by するのを忘れないように。

Model::$belongsTo を指定しているのは、投稿したユーザー名(wp_user)にリンクさせるためです。

■ビューを作る

これを踏まえて、ざっくりとビューを作ると、

<table>
	<tr>
		<th>ID</th>
		<th>post_date</th>
		<th>post_title</th>
		<th>guid</th>
	</tr>
	<?php foreach($Posts as $item) : ?>
	<tr>
		<td><?php echo $item['Post']['ID']; ?>
		<td><?php echo $item['Post']['post_date']; ?>
		<td><?php echo $item['Post']['post_title']; ?>
		<td><?php echo $item['Post']['guid']; ?>
	</tr>
	<?php endforeach ; ?>
</table>

まあ、ひとまずはテーブル表示をするだけ。下記で実行できます。

http://wpcake.moonmile.net/posts

■最近投稿した記事も表示させる

wordpress のように最近投稿した記事(recent posts)も表示させてみます。

<h2>最近投稿した記事</h2>
<table>
	<tr>
		<th>ID</th>
		<th>post_date</th>
		<th>post_title</th>
	</tr>
	<?php foreach($Posts as $item) : ?>
	<tr>
		<td><?php echo $item['Post']['ID']; ?>
		<td><?php echo $item['Post']['post_date']; ?>
		<td><?php echo $item['Post']['post_title']; ?>
		<td><?php echo $item['Post']['post_author']; ?>
		<td><?php echo $item['User']['user_nicename']; ?>
		<td><?php echo $item['Post']['guid']; ?>

	</tr>
	<?php endforeach ; ?>
</table>
<h2>最近投稿した記事(リンク版)</h2>
<ul>
<?php foreach($Posts as $item) : ?>
	<li>
		<a href="<?php echo $item['Post']['guid']; ?>"><?php echo $item['Post']['post_title']; ?></a> by <?php echo $item['User']['user_nicename']; ?> at <?php echo $item['Post']['post_date']; ?>
	</li>
<?php endforeach; ?>
</ul>

先にテーブルで値を確認した後に、ペタペタとリンクの表示を作っていきました。

下記で実行できます。

http://wpcake.moonmile.net/posts/recent

お次は、コメント一覧なぞ。

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

写真にマスクをかけて切り抜きをする

久し振りに、美女Linux http://bijo-linux.com のブログパーツ作成です。
今回は、ブログパーツ自体をハートマークにしてしまおうという企画で、出来上がりはこんな感じ。

ここのブログだと、背景が白なので変わり映えしませんが、色がついていたり画像が貼ってあったりすると、透過されているのがよくわかります。

さて、これをどう実現するのかと言うと、って非常に簡単で。

  • ハートマークのフレームを用意します。
  • ハートマークに切り抜いた写真を用意します。
  • そして重ね合わせ

しているだけです。普通、写真のほうは四角形だったりするので、ブログパーツ自身も四角形になってしまうことが多いのですが、元の写真から切り抜いてしまえば、重ね合わせのときに両方とも背景が投下されます。

▼切り抜いた写真

▼フレーム画像

の重ね合わせです。

さて、この切り抜いた画像をどう作るかというと、マスク画像との重ね合わせになるわけですが、

▼元の写真

▼マスク画像

を使います。これを使って、ちまちま画像ツールでやってもいいんですが…ちょっとそれは無理ッ!!! なので、マスクするだけのツールを作りました。

public class ImageOverlay
{
    public bool OverlayMask(string photofile, string maskfile, string outfile)
    {
        try
        {
            Bitmap photo = new Bitmap(photofile);
            Bitmap mask = new Bitmap(maskfile);
            Bitmap output = this.OverlayMask(photo, mask);
            if (output == null)
            {
                return false;
            }
            output.Save(outfile);
            return true;
        }
        catch
        {
            Console.WriteLine("can't make overlay file"); 
            return false;
        }
    }
    public Bitmap OverlayMask(Bitmap bmpPhoto, Bitmap bmpMask)
    {
        int width = bmpPhoto.Width;
        int height = bmpPhoto.Height;
        Bitmap bmpOutput = new Bitmap(width, height, PixelFormat.Format32bppArgb);

        for (int y = 0; y < width; y++)
        {
            for (int x = 0; x < height; x++)
            {
                Color photo = bmpPhoto.GetPixel(x, y);
                byte red = bmpMask.GetPixel(x, y).R;
                byte alpha = (byte)(255 - red);
                Color mask = Color.FromArgb(alpha, photo);
                bmpOutput.SetPixel(x, y, mask);
            }
        }
        return bmpOutput;
    }
}
  • photo: 元画像
  • mask: マスク画像
  • output: 出力画像

ってな訳で、ちまちまと1ドットずつ重ね合わせ…って、なんかいい方法ないですかね?

ImageAttributes クラス (System.Drawing.Imaging)
http://msdn.microsoft.com/ja-jp/library/h4f66d59.aspx

あたりで、alpha チャンネルを変えるだけでいいと思うだけど、まあ、これでもそれなりのスピードが出ているので良しとしますか(画像が大きくなるとちょっとアレなんでしょうが)。

カテゴリー: 開発, ブログパーツ | 写真にマスクをかけて切り抜きをする はコメントを受け付けていません

クローリングされにくいユニークなIDを発行する方法

ちょっと思いつきメモ。

CakePHP を使っていて思うのだけど、id が大抵 auto inrement になっているのが問題になる場合がある。内部的な id ならば int でガンガン連携すればいいのだけど、

  • レガシーデータベースの場合は、id が文字列だったり
  • レガシーデータベースの場合は、プライマリーキーが複数だったり、
  • 連続する id だと、クローリングされやすい。

という問題がある。

先の2つは、後で CakePHP で確認するとして、連続する id で問題なのは、

  • 商品IDのように、サイトでクローリングされるとちょっと困る

場合なのだ。

例えば http://servername/books/100 という id を振ってあったら、次は容易に http://servername/books/101 と想像できる。で、そうなると、books/10000 なんてあったら、すぐにでも perl なりでクローリングができてしまうという…まあ、ブログなんかはいいんだけど(検索されるのが目的だし)、商品サイトなんかは良し悪しといところがある。

で、文字列なり数値で別なユニークなキーが欲しいときは、

m: ある程度大きい素数
n: m よりも大きく、m で割り切れない数

にしておいて、

id = (m * i) mod n
0 <= i <= n

とすると id が取得できる、ってこれであっているのかな?(他に良い方法がありそうだけど)

証明は、

  1. n > m * i の間は、id は任意になる。
  2. n < m * i+1 となる時、id = m*(i+1)-n = k とすると、k は m を割り切れない(mが素数だから)
  3. 初期値の s0 = k – m は、i が n になるまで一意になるので、id は結果的に一意になる。

…って、きちんとした証明になっていないので、こっちは後程。

カテゴリー: 開発, 雑談 | クローリングされにくいユニークなIDを発行する方法 はコメントを受け付けていません

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

CakePHP の構造が分かったので、簡易 MVC を作ってみます。
理由としては、

  • CakePHP を導入するまでもないかなぁ、という時?
  • 導入前に、そもそも MVC が、どういうものなのかを把握する時?
  • PHP で MVC の繋がりってどう作るんでしょうね?

といったところです。

いずれ、CakePHP に移行するという前提にして、命名規約などは CakePHP に準じます。

いわゆる

まずは view です。

<!-- views/magazine/index.php -->
<h1>雑誌一覧 magazine の表示</h1>
<table>
	<tr>
		<th>Magazine.id</th>
		<th>Magazine.name</th>
		<th>Publisher.name</th>
		<th>State.name</th>
	</tr>
	<?php foreach($Magazines as $item) : ?>
	<tr>
		<td><?php echo $item['Magazine']['id']; ?></td>
		<td><?php echo $item['Magazine']['name']; ?></td>
		<td><?php echo $item['Publisher']['name']; ?></td>
		<td><?php echo $item['State']['name']; ?></td>
	</tr>		
	<?php endforeach; ?>
</table>

CakePHP と同じように、$Magazines でモデルを参照できるようにします。

お次は、コントローラー

<?php
include 'models/magazine.php';
class MagazineController {
	var $name = 'MagazineController';
	function index() {
		// view に渡す変数
		global $Magazines;
		// モデルを作成して
		$magazine = new Magazine();
		// 検索
		$Magazines = $magazine->findAll();
		include('views/magazine/index.php');
	}
}
?>

モデルクラスを include して、index メソッド内では最後にビューを include して表示、って感じです。

で、肝心のモデルはどうなっているかというと、

<?php
class Magazine {
	var $name = 'Magazine';
	var $cn ;
	function open() {
		$this->cn = new PDO("mysql:host=127.0.0.1;dbname=moonmile_cake",
			"moonmile_cake","cakephp");
	}
	function close() {
		$this->cn = null;
	}

	function findAll() {
		$this->open();
		$sql = <<< HERE
select
	Magazine.id 	`Magazine.id`,
	Magazine.name 	`Magazine.name`,
	Publisher.id	`Publisher.id`,
	Publisher.name	`Publisher.name`,
	State.id		`State.id`,
	State.name		`State.name`
from 
	magazines as Magazine
	inner join publishers as Publisher
		on ( Magazine.publisher_id = Publisher.id ) 
	inner join states as State
		on ( Publisher.state_id = State.id )
	order by Magazine.id;
HERE;
		$stmt = $this->cn->query($sql);
		foreach($stmt as $item) {
			$row['id'] = $item['Magazine.id'];
			$row['name'] = $item['Magazine.name'];
			$table['Magazine'] = $row;
			$row['id'] = $item['Publisher.id'];
			$row['name'] = $item['Publisher.name'];
			$table['Publisher'] = $row;
			$row['id'] = $item['State.id'];
			$row['name'] = $item['State.name'];
			$table['State'] = $row;
			$items[] = $table; 
		}
		$this->close();
		return $items;
	}
}
?>

データベースからクエリで読み込んで、コントローラーから findAll メソッドの呼び出しでデータを設定しています。
クエリ文は慣れるとすらすら書けるでしょうし、データへの再設定もそれなりに…なのかなぁ。

さて、これらを動かすために、http://servername/magazine で表示させたい訳ですが、mod_rewirte を有効にして URL の書き換えを利用するために、次のように index.php を細工します。

<?php
$url = $_GET['url'];

$url = strtolower($url);
$pos = strpos($url,"/");
if ( $pos === false ) {
	$base = $url;
	$method = "index";
} else {
	$base = substr($url,0,$pos);
	$method = substr($url,$pos+1);
}

$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/>";

include('controllers/'.$classfile);
$c = new ReflectionClass($classname);
$obj = $c->newInstance();
$method = $c->getMethod($method);
$method->invoke($obj);
?>

指定されたコントローラーのメソッドを呼び出すためにリフレクションを使います。多分、匿名関数の実行なんかを使う方がすっきり書けるのかもしれませんが、ひとまず。
ただし、この場合だと、http://servername/magazine/item/2 のように、雑誌の id が 2 の情報を得る、というような引数を指定することができないので、もう少し手を加える必要がありますね。


追記 2011/01/30
URL 書き換えの index.php ですが、../ 等を使って親ディレクトリのファイルを取れる可能性もあるので、実際に使うときはもう少し調節が必要です。このあたりは、パラメータを取得できるときにもで書き換えてみます(できるのか?)。

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

CakePHP を開発する環境を探る

PHP 専用エディタってのを使っていないので、アレなのですが、手元には、

  • newQX エディタ
  • Visual Studio 2010
  • Microsoft Visual Web Developer 2010 Express
  • WebMatrix
  • Microsoft Express Web 4

がインストールされています。なので、どれがいいかというと…は自分で判断するとして、参考までにキャプチャを貼りつけておきます。

■newQX エディタ

キーワードに色を付けるだけですが、まぁ、これでも十分なんですが。

■Visual Studio 2010

Visual Studio 2010 だと、PHP は全然ダメですね。xml として認識されてしまう模様。

■Microsoft Visual Web Developer 2010 Express

ちょっと期待したりするのですが、無料で配布されている Web Developer も同じ状況です。

■WebMatrix

最近リリースされた WebMatrix は結構使えます。PHP のコードもこんな風に。

ですが、CakePHP で使われる *.ctp ファイルがこんな感じに駄目なので…

■Microsoft Express Web 4

という訳で、結局デザイナーツールな Express Web を使う羽目に。

ほら、*.ctp もご覧の通り…って、これって CakePHP の拡張子を認識しているわけですよね。

折角、Express Web で CakePHP の view template が認識されているのだから、WebMatrix でも、と思うのは私だけでしょうか?これって、フィードバックで送ればなんとかるかなぁ。
まぁ、WebMatrix の方はデザイナが表示されないので、タグが認識されたところで使いづらいのですが。

カテゴリー: 開発, ツール | 3件のコメント

独自のモデルを作ってマッピングする(queryを使う)

CakePHPの場合、モデルとテーブルが1対1と想定しているので、これ以外のマッピングをする場合は、独自の Model を作ったほうがいいと思います。

例えば、先の記事のように、出版社(Publisher)から必ず都道府県(State)の名前を参照するのであれば、あらかじめそういう Model を作ってしまったほうが利便性が高いかな、と。

このような関係があるとき、雑誌の一覧を次のように取得したいと思う訳です。

  • 雑誌ID
  • 雑誌名
  • 出版社名
  • 出版社の所在地/都道府県

これを通常ならば、データベースのビューを作って、

  • id
  • magazine_name
  • publisher_name
  • publisher_state

のような形で取得しても良いのですが、環境によってはデータベースを変更できなかったり、データ管理は別の会社が構築したものだったりして、データベースとプログラムの間には大きな溝があるのが常だったりします。

# 小さなプロジェクトならばいいんですけどね。
# あと、もの分かり良い(以下略

という訳で http://servername/magazines/lists でアクセスできるようにします。
まずは、ビューを作成して、普通のモデルを使っているように書きます。というか書きたいわけです。

<!-- views/magazines/lists.ctp -->
<h2>雑誌一覧(独自)</h2>
<table>
	<tr>
		<td>id</td>
		<td>name</td>
		<td>Publisher.name</td>
		<td>State.name</td>
	</tr>
<?php foreach($MagazineLists as $item) : ?>
	<tr>
		<td><?php echo $item['Magazine']['id'] ?></td>
		<td><?php echo $item['Magazine']['name'] ?></td>
		<td><?php echo $item['Publisher']['name'] ?></td>
		<td><?php echo $item['State']['name'] ?></td>
	</tr>
<?php endforeach ; ?>
</table>

これに合わせて、コントローラーに lists メソッドを追加します。

<?php
class MagazinesController extends AppController {
	var $name = 'Magazines';
	var $uses = array('Magazine','Publisher','MagazineList');
	function index() {
		$this->set('Magazines',$this->Magazine->find('all'));
		$this->set('Publishers',$this->Publisher->find('all'));
	}
	// 独自の雑誌リスト ★
	function lists() {
		$this->set('MagazineLists',$this->MagazineList->findAll());
	}
}
?>

findAll メソッドを使っているのは、他のモデルと揃えるためです。いわゆる元のメソッドをオーバーライドするという奴です。
モデルのクラス名は「MagazineList」(ファイル名は命名規則で magazine_list.php)になります。

<!-- models/magazine_list.php -->
<?php
class MagazineList extends AppModel
{
	var $name = 'MagazineList';
	var $useTable = false;	// ★テーブルに連結させない

	function findAll()
	{
		$sql = <<< HERE
SELECT
 Magazine.id,
 Magazine.name,
 Publisher.id,
 Publisher.name,
 State.id,
 State.name
FROM
 magazines as Magazine
 inner join publishers as Publisher 
  on ( Magazine.publisher_id = Publisher.id ) 
 inner join states as State
  on ( Publisher.state_id = State.id )
 order by 
  Magazine.id
HERE;
		return $this->query($sql); // クエリを実行する
	}

}
?>

独自にクエリを実行するので、Mode::query メソッドを使います。極悪ですねw。
クエリ文自体は、ヒアドキュメントにしておいて、MySQL Workbench などでテストできるようにしておくのがミソです。

あとは、結果を返す時に、「Magazine.id」のように返しておくと(多分asで別名を定義してもOKかと) $item[‘Magazine’][‘id’] のようにあたかも CakePHP の findAll メソッドを使ったように結果を取得できます。

これを実行すると、こんな感じ。

当然ですが、クエリ文は query メソッドで実行したものが使われています。left join でないので、データベース屋さんとしてはすっきりという感じですね。

私見ですが、そもそもO/Rマッピング(ORM)ってのは、オブジェクト指向とデータ指向を結びつけるというのが目的で、なにもクラス(オブジェクト)とテーブルを1対1にする、というのが目的ではないのですね(初手としては良いのでしょうが)。

O/R マッピングをするときに、右のデータは既に正規化されている状態なので、そのままクラスにマッピングするのは不便です。と言いますか、非正規化の状態に戻さないと使えません。RDB 自体がリレーショナルであることを基本としているので、ひとつの現実を表すのに publisher_id や state_id などの identify を使う訳で、これを id のままマッピングするよりも、元の非正規化の状態に戻して、オブジェクト指向の世界へと誘うのがよいかと思っています。

ま、機能と使い方は色々なので、利用する時に合わせるのがベターかと。

カテゴリー: CakePHP | 1件のコメント

CakePHPで3つのテーブルを連結させる。

さて、雑誌(magazines)と出版社(publishers)を Model::$belongsTo で連結させることができましたが、今度は都道府県(states)と出版社を連結させて、3つのテーブルを使います。

実用的な問題として、ひとつのテーブルで収まることはまず無い訳で、ログやら単純なマスターテーブル程度ならばいいのですが、【真面目に】テーブルを正規化していれば、3 つ以上のテーブルが連結するのは当たり前ですッ!!! … だといいなぁ、という希望的観測も含めて(苦笑)。

-- 雑誌テーブル
create table magazines (
 id int not null,
 name varchar(50),
 publisher_id int,
 price int
);
-- 出版社テーブル
create table publishers (
 id int not null,
 name varchar(50),
 state_id int
);
-- 都道府県テーブル
create table states (
 id int not null,
 name varchar(20)
);

# 説明が面倒なので、T100 を magazines に、T200 を publishers に変更しましたw

ER 図はこんな感じで、1対多の関係です。

これを出版社を中心にして(cakephp は left join で連結させるので)、

<h2>出版社一覧</h2>
<table>
	<tr>
		<td>id</td>
		<td>name</td>
		<td>Magazine.name</td>
		<td>State.name</td>
	</tr>
<?php foreach($Publishers as $item) : ?>
	<tr>
		<td><?php echo $item['Publisher']['id'] ?></td>
		<td><?php echo $item['Publisher']['name'] ?></td>
		<td><?php echo $item['Magazine']['name'] ?></td>
		<td><?php echo $item['State']['name'] ?></td>
	</tr>
<?php endforeach ; ?>
</table>

のように、都道府県名(State)を表示させようとすると、Publisher モデルクラスに次のように加筆します。

<!-- models/publisher.php -->
<?php
class Publisher extends AppModel
{
	var $name = 'Publisher ';
    var $belongsTo = array(
    	'Magazine' => array(
			'className' => 'Magazine',
			'conditions' => 'Magazine.publisher_id = Publisher.id',
			'order' => 'Publisher.id ASC',
			'foreignKey' => ''),
        'State' => array (				// ここから追加
	    	'className' => 'State',
			'conditions' => '',
			'order' => 'State.id ASC',
			'foreignKey' => 'state_id')
        );
}
?>

関係が

  • Publisher->Magazine
  • Publisher->State

なので、1段階しか探索をしないため結構簡単にできます。

実行すると素直にデータが取得できます。

SELECT 
 `Publisher`.`id`, 
 `Publisher`.`name`, 
 `Publisher`.`state_id`, 
 `Magazine`.`id`, 
 `Magazine`.`name`, 
 `Magazine`.`publisher_id`, 
 `Magazine`.`price`, 
 `State`.`id`, 
 `State`.`name` 
FROM `publishers` AS `Publisher` 
 LEFT JOIN `magazines` AS `Magazine` 
  ON (`Magazine`.`publisher_id` = `Publisher`.`id`) 
 LEFT JOIN `states` AS `State` 
  ON (`Publisher`.`state_id` = `State`.`id`) 
WHERE 1 = 1 
 ORDER BY `Publisher`.`id` ASC, `State`.`id` ASC 

発行されるクエリを見ると、left join でうまく繋がっています。
難点を云えば、

  • left join でつながっているので、雑誌を持たない出版社も表示されている。この場合、inner join にどうすれば切り替えられるのかは不明です。
  • 全てのカラムを取得しているので、カラム内のデータが多い場合(varchar(5000)など)は、データ転送に時間がかかる。できれば、index.ctp で使っているカラムだけのクエリを作って欲しいのですが、これもどうやるのか不明です。

そんな感じで、クエリ文を直接書いてしまえば良いのでは?と思ったりしますが、PHP は分かるけど、SQL は分からない(C# でも LINQ は分かるけど SQL が分からない、サブクエリが書けない、という方もいらっしゃるので)場合もあるので、これはこれで良いのかなと。

# 余談ですが、Model::$hasMany を使うとクエリ文がたくさん発行されてしまうという…これって、サブクエリを使えばいいんじゃないの?と思うのですが、パフォーマンス的にはどうなんでしょう。CakePHP 2.0 だと違うのかな?

次に雑誌(Magazine)を中心にして、連結をさせます。

  • Magazine->Publiser
  • Magazine->Publiers->State

という形で Magazine から State まで 2段階の探索が必要になるわけですが。

<!-- models/magazine.php -->
<?php
class Magazine extends AppModel
{
	var $name = 'Magazine';
	var $belongsTo = array(
    'Publisher' => array(
			'className' => 'Publisher',
			'conditions' => '',
			'order' => 'Magazine.id ASC',
			'foreignKey' => 'publisher_id' ),
    'State' => array(				// ここから追加
			'className' => 'State',
			'conditions' => 'Publisher.state_id = State.id',
			'order' => 'State.id ASC',
			'foreignKey' => '' ),
            );
}
?>

こんな風に conditions に連結の条件を指定してしまいます。
本当は、Model::$recursive を設定するのでしょうが…

recursive :: Model の属性 :: モデル :: CakePHPによる開発 :: マニュアル :: 1.2コレクション :: The Cookbook
http://book.cakephp.org/ja/view/439/recursive
recursiveの正しい理解CakePHP – CPA-LABテクニカル
http://www.cpa-lab.com/tech/081

なんか思ったように動いてくれません。なので、手っ取り早い方法としては、Model::$belongsTo に設定してしまうのがお手軽のなのかなぁと。

このようにモデルで設定しておくと、次のように $item[‘State’][‘name’] で都道府県名が表示できます。

<h2>雑誌一覧</h2>
<table>
	<tr>
		<td>id</td>
		<td>name</td>
		<td>Publisher.name</td>
		<td>State.name</td>
	</tr>
<?php foreach($Magazines as $item) : ?>
	<tr>
		<td><?php echo $item['Magazine']['id'] ?></td>
		<td><?php echo $item['Magazine']['name'] ?></td>
		<td><?php echo $item['Publisher']['name'] ?></td>
		<td><?php echo $item['State']['name'] ?></td>
	</tr>
<?php endforeach ; ?>
</table>

発行されるクエリは以下の通りです。

SELECT 
 `Magazine`.`id`, 
 `Magazine`.`name`, 
 `Magazine`.`publisher_id`, 
 `Magazine`.`price`, 
 `Publisher`.`id`, 
 `Publisher`.`name`, 
 `Publisher`.`state_id`, 
 `State`.`id`, 
 `State`.`name` 
FROM `magazines` AS `Magazine` 
 LEFT JOIN `publishers` AS `Publisher` 
  ON (`Magazine`.`publisher_id` = `Publisher`.`id`) 
 LEFT JOIN `states` AS `State` 
  ON (`Publisher`.`state_id` = `State`.`id`) 
WHERE 1 = 1 
 ORDER BY `Magazine`.`id` ASC, `State`.`id` ASC 

表示が若干違うのは、left join をしているからですね。

カテゴリー: CakePHP | 3件のコメント

CakePHPで2つのテーブルを連結させる(2)

雑誌(magazines)が出版社(publishers)に属する、という形で magazines のほうから Model::$belongsTo を使って連結できたので、今度は出版社が持っている雑誌の一覧を出してみます。

<h2>出版社一覧</h2>
<table>
	<tr>
		<td>id</td>
		<td>name</td>
		<td>Magazine.name</td>
	</tr>
<?php foreach($Publishers as $item) : ?>
	<tr>
		<td><?php echo $item['Publisher']['id'] ?></td>
		<td><?php echo $item['Publisher']['name'] ?></td>
		<td><?php echo $item['Magazine']['name'] ?></td> ★
	</tr>
<?php endforeach ; ?>
</table>

こんな風に、$Publishers のループの中で、$item[‘Magazine’][‘name’] で参照できれば良いなと。

モデルの Magazine で Model::$belongsTo を設定してあるので、逆向きもできているんじゃないかなぁ、と期待してそのまま動かすと。

なエラーが出ます。逆向きは自動で設定してくれないのですね。。。
なので、Publisher モデルに加筆します。

<!-- models/publisher.php -->
<?php
class Publisher extends AppModel
{
	var $name = 'Publisher ';
	var $useTable = 'T200';
	var $hasMany = array('Magazine' =>
		array(
			'className' => 'Magazine',
			'conditions' => '',
			'order' => 'Magazine.T200id ASC',
            'limit' => 10,
			'foreignKey' => 'T200id',
            'dependent' => true,
            'exclusive' => false,
            'finderQuery' => ''));
}
?>

Mode::$hasMany の詳しい使い方は、マニュアルを見るとして、これを実行するとやっぱりエラーがでます。

「Undefined index: name」というエラーが出ているので、どうやら、Magazine は見つかったらしいけど、name が見つからない。で、print_r($Publishers) で見てみると、Magazine 以降はツリー構造になっているのですね。

Array
(
    [0] => Array
        (
            [Publisher] => Array
                (
                    [id] => 1
                    [name] => 平和システム
                )
 
            [Magazine] => Array
                (
                    [0] => Array
                        (
                            [id] => 1
                            [name] => 逆引き大全F#555
                            [T200id] => 1
                            [price] => 1000
                            [publisher_id] => 1
                        )
 
                    [1] => Array
                        (
                            [id] => 2
                            [name] => 逆引き大全CakePHP555
                            [T200id] => 1
                            [price] => 2000
                            [publisher_id] => 1
                        )
 
                )
 
        )
    [1] => Array
        (
            [Publisher] => Array
                (
                    [id] => 2
                    [name] => 日経BB社
                )
 
            [Magazine] => Array
                (
                    [0] => Array
                        (
                            [id] => 3
                            [name] => ひと目で分かるOracleアプリケーション入門
                            [T200id] => 2
                            [price] => 3000
                            [publisher_id] => 2
                        )
 
                )
 
        )
 

なので、アクセスの仕方としては、$item[‘Magazine’][0][‘name’] のように添え字が間に挟まるわけですが、これだと一覧表には不便です(多分、出版社→その配下の雑誌を表示、のように遷移する場合に使うのかと)。

仕方がないので、Model::$belongsTo を使ってみます。意味的には変なのですが。

<!-- models/publisher.php -->
<?php
class Publisher extends AppModel
{
	var $name = 'Publisher ';
	var $useTable = 'T200';
    var $belongsTo = array('Magazine' =>
		array(
			'className' => 'Magazine',
			'conditions' => 'Magazine.T200id = Publisher.id',
			'order' => 'Publisher.id ASC',
			'foreignKey' => ''));
}
?>

外部キー(foreignKey)を指定することはできないので、join の条件(conditions)を直接指定します。
ここでハマるのは、T100, T200 の元のテーブル名を使うことができなくて(それはそれで良いのですが)、Magazine, Publisher というモデル名を使うところですね。SQL 文で別名定義しているところの制限なので、MySQL だけの制限かもしれません。

これを実行すると、めでたく一覧表が出ます。

がッ!!! 一見大丈夫そうにみえて、ああ、left join を使っているので雑誌を出していない出版社の名前も一覧に出てきます。これは、まぁこれで良いのかと思いますが。

でも left join でループしない場合はどうするんでしょうね?という疑問が(これはまだ解決せず)

カテゴリー: CakePHP | CakePHPで2つのテーブルを連結させる(2) はコメントを受け付けていません