カテゴリー : CakePHP

CakePHP 外部PHPプログラムからライブラリのように使う方法

情報は数年前から散見できたが、使いたくなってきたので試してみた。驚くほど簡単に利用でき、今後のアプリケーション作りに活かせるかな、と云う事で備忘録となります。

先ずはソース。

<?php
	$_GET['url'] = 'favicon.ico';
	require_once = 'webroot/index.php';

	$dispatcher = new Dispatcher();
	$result = $dispatcher->dispatch('/posts/index', array('return'=>false));
	echo $result;

これで posts コントローラの index アクションを実行し、レンダリングされた結果が表示できる。

$_GET['url']に文字列「favicon.ico」をセットすると、require_once で読み込んだ index.php の実行が dispatcher を動作せずに終了するようになっている。

さて、レンダリングは layout を含んでいるので、アクションのレンダリング結果だけを必要とする場合はどうするか?

dispatch()の2番目のパラメータで、「’bare’=>true」を与えればいいようだ(試した結果であって、ソース中身は確認してない)。

CakePHP の利用で大変ありがたいのがデータベースの処理だが、Model も簡単に利用できる。

<?php
	$_GET['url'] = 'favicon.ico';
	require_once = 'webroot/index.php';

	$postModel = ClassRegistry::init('Post');
	$post = $postModel->find('first');

	echo $post['Post']['title'] . '' . $post['Post']['content'];

CakePHP はいよいよ 2.0ベータがリリースされたので、準備を怠らないようにしないと、と思うこの頃です。

CakePHP 今さらですがClassRegistryクラスのメモ

暫くぶりに CakePHP でアプリケーションを作ろうかといろいろ構想練っている。その中でユーザーに提供したシステムを更新する際、、自動アップデート機能を盛り込みたいなぁ、と思って以前ブックマークしたsdozonoさんの記事「CakePHPのワンクリックアップデート用ソースコード」を見ていたら、「ClassRegistry」なるクラスがあった。

そこで早速検索して、忘れても直ぐに参照できるよういつものようにブログにメモ。

コントローラで複数のモデルを参照、利用する際、プロパティ $uses に参照するモデル名の配列を与える。

$uses = array('Post', 'User')

ところでコントローラのアクションは、プログラムの実行に先立って $uses に登録されたモデル全てを読み込むので、モデルのメソッドを利用しないアクションでは効率が悪くなる。そこで、利用するアクションのみモデルをロードする、ClassRegistry を利用する。

$Post = ClassRegistry::init('Post');
$posts = $Post->find('all');

又は

$posts = ClassRegistry::init('Post')->find('all')

既に読み込まれていれば、ビューで利用できる。

$Category = ClassRegistry::getObject('Category');
$categories = $Post->find('list');

App::import() でも処理できるが、データベースの接続先が $default に固定されてしまい、ユニットテストで利用できないので ClassRegistry の利用をするといいらしい。

参考ページ
cakephperさん:色々なモデルからデータを読み込んでViewにセットする
bennyleeさん:ClassRegistryの備忘録
foldrrさん:CakePHP モデルの読み込みは App::import ではなく ClassRegistry::init で
hiromi2424さん:ClassRegistry徹底解剖

(CakePHP Ver.1.3.9)

追記1

ページに表示するメインナビゲーションやサイドバーに表示するコンポーネントなど、ページの主文と関係ないデータを表示したいときどうしたらいいだろうか?

解決方法の1つとして、Elements から ClassRegistry を利用してモデルのメソッドを呼び出すといいだろう。

MVCの構成から、「View から Model を呼び出すべきでない」、という考えもあるのであまり深くは突っ込まない。

追記2

デバッグ時にデータベースのアクセスログを表示する方法、すっかり忘れていた。テンプレートに

<?php echo $this->element('sql_dump') ?>

を追加する。

CakePHP 1.3 でいくつか便利になった事

ビューに埋め込む JavaScript

JavaScript を利用するページが限られている場合、layout に記述すると必要ないページまでロードするので、それは避けたい。そこでコントローラ/アクションに係るビューファイルに記述するのだが、ライブラリの読み込みは head 部に置きたいし、その他のソースもできれば head 部に記述したい。その方法について、「jQueryのAjaxでSelect入替」でやり方を記述していた。

1.2 ではコントローラで予め JavaScript ヘルパーを指定しておく必要があったが、1.3 になって Javascript ヘルパーは非推奨になった。で、代わりに Html ヘルパーで事足りるようになったようだ。

以下、head 部に jQuery の読み込みとイベントプログラムを挿入するための、ビュー上での記述の例です。ただし、layout ファイルでは「echo $scripts_for_layout」を記述しておく必要がある。

<?php
$html->script(array('jquery-1.4.2.min'), false);
$html->scriptBlock(<<<JS
$(document).ready(function() {
	$("button").click(function() {
		return confirm('転送します');
	});
});
JS
, array('inline'=>false));
?>

CakePHP paginatorのnumber()でspanタグをカレント以外から外す

CakePHPのページリンク機能が大変便利であることは、使用している多くの人が認めるところだと思う。自分も利用させていただいてる。利用するのに少し困るのは、毎回デザインへの合わせ込みをするのだが、たまに苦労することだ。

さて今回のデザインは、ページリンクをCSSの「border」を利用して枠で囲んでいる。paginatorヘルパーを利用して表示すると

0630paginator

のように、ページ番号のリンク表示が2重の枠で表示された。paginatorヘルパーが出力したhtmlを見ると、

<span class="disabled prev_page"><< Prev</span>
<span class="current">1</span>
<span><a href="/users/index/page:2">2</a></span>
<a href="/users/index/page:2">Next >><a>

となっている。CSSでborderをspanタグとaタグの両方に対して指定しており、aタグでリンクされたhtmlがspanタグで囲まれているため、上のように表示されてしまった。

ところで、デフォルトのdivタグからspanタグへの変更は、「paginatorでページリンクを1行にまとめて表示するには」でメモしてあります。また、ページ番号の区切りの「|」は、

$paginator->numbers(array('separator'=>false));

とすれば出力されなくなる。

さて修正だが、リンクの付いたページ番号のところでspanタグが出力されないようにすれば、どうやら解決しそうだ。

paginatorヘルパーの利用で、出力しないようにするパラメータがないかと探ってみたが、どうも情報は見当たらない。そこで今回は、「cake/libs/view/helpers/paginator.php」にパッチを当てることにした。

当該ソースプログラムの541行目

$out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));

$out .= $this->link($i, array('page' => $i), $options);

とすることで、

0630repaginator

と表示するようになった。なお、同ソースの507行目、517行目、521行目も同様に修正しておけばページ数が増えたとき、幸せになれる(お決まりですが自己責任でお願いします)。

(CakePHP 1.2.3.8166)

追加情報:

上のネタはWebアプリ用のフリーテンプレートを利用させていただきました。
MOONGIFTさん:http://www.moongift.jp/2009/06/web_app_theme/#more-16257
制作者Andreaさん:http://gravityblast.com/

CakePHP Formヘルパーを利用して複数レコードの一括編集と保存

データの編集で、同じモデルの複数レコードを一括で編集表示し保存する、ような処理を行ないたいことがよくある。レコード1件1件をその都度編集、保存する手間を省きたい訳だ。

CakePHPのFormヘルパーは、inputエレメントをlabelエレメントやdivエレメントを付けて出力してくれるので、 fieldsetエレメントやlegendエレメントを利用して編集ブロックとしてまとめるなど、CSSを利用して統一したデザインにすることができる。 デザインについては、sdozonoさんの以下の記事が大変参考になった。

FormHelperとCSS

Formヘルパーでは、createメソッドにモデル名を設定すれば、後はinputメソッドにテーブルのカラム名を指定し、最後にendメソッド を置いて<form>ブロックの出来上がりである。サーバーにデータがsubmitされた際のactionで は、$this->dataにモデル名とカラム名の配列で入力を取得できるため、そのままモデルのvalidateを利用できるし、saveメソッ ドで保存もできる。

であるわけだが、<input>にid属性を与えるので、あくまでもデータレコード1件では使い勝手が非常に良いのだが、さて同じモデルの複数レコードを一覧で編集したいときはどうすればいいのだろうか?

以下のような使い方ができたので、次回参考のため記録する。

ユーザーのIDと名前の一括編集登録ビューの作成、および保存するアクションを例にする。

1.一括表示編集のアクション
先ずはデータの取得部分、保存については以降で検討する。

function multiedit() {
    $data = $this->User->find('all');
    $this->set(compact('data'));
}
2.一括表示編集のビュー
CakePHPで実際に吐き出されたコードを参照すると良い。<input>でname属性を「data[User][0][name]」のように、モデル名の後ろがインクリメントされた番号になるようにするのがキモである。

multiedit.ctp

<?php
    echo $form->create('User', array('action'=>'multiedit'));
    foreach($data['User'] as $key => $user) {
        echo $form->input("{$key}.id", array('type'=>'hidden', 'value'=>$user['id']));
        echo $form->input("{$key}.name", array('value'=>$user['name']));
        echo $form->input("{$key}.shimei", array('value'=>$user['shimei']));
    }
    echo $form->end("保存");
?>
3.アクションでのデータ保存の部分
$this->dataでアクセスできる配列データは、モデルのfindメソッドで複数件数のデータを取得した時と同様な扱い方ができる。アクションに保存する命令を加える。

function multiedit() {
    if (!empty($this->data)) {
        foreach ($this->data['User'] as $data) {
            $this->User->save($data);
        }
        $this->redirect('index');
    }
    $data = $this->User->find('all');
    $this->set(compact('data'));
}

今までは、と言うと、ちょっと強引なやり方をしていたようだ。

(CakePHP 1.2.3.8166)