CakePHP 2.0 のリリース(訳)

CakePHPコアチームはメジャーなバージョン番号のジャンプをアナウンスすることに興奮を覚えています。
CakePHP 2.0 安定版が出ました!
私たちはこのリリースに永久の時を注ぎ込み、このバージョンのフレームワークに素晴らしいアイデアの数々を成すことができました。
これは2.0が意味するものの応急な概要です:

  • PHP4のサポートを打ち切り、全てのコードをPHP5.2以上に厳密に尊守するように書き直しました。
  • 例外、PDO、SPL、json_encodeなどなどのネイティブな機能の使用。
  • ファイル命名へPSR-0を取り入れました。あらゆるクラスは同じ名前を用いてファイルにマッピングされます。覚えることが少なくなりました!
  • 新しいエラーと例外のハンドラーは簡易な設定や、ページが見つからないエラー、承認エラーなどのエラーとの楽な協調、など数多くのものを提供します。
  • マルチリンガルな開発を簡単にするI18n関数の向上。
  • コアファイルを書き換えることなしの、コンポーネント、ヘルパー、ビヘイビアなどのCakePHPライブラリとして振る舞う独自のオブジェクトの組み込みのサポート。
  • コンソールライブラリはスクラッチから書きなおされ、ヘルプの自動的な生成、引数チェック、色付けなどがあります!
  • HTTPリクエストを簡単に扱える新しいRequestとResponseオブジェクト。
  • 簡単なカスタムエンジンによるより良いセッションハンドリング。骨を折る事無くMemcacheなどのストレージシステムにセッションを任せます。
  • 完全に書きなおされたAuthシステム。外部のログインサービスやパーミッションシステムのための独自の実装を簡単に設定、差し込めるようになりました。
  • 複数のデータ転送(transports)のサポートがある真新しいEメールライブラリ。これはグローバル設定、ログ、キャラクターセットのサポートを楽に利用できます。
  • PHPUnitを選んだことに依るSimpleUnitの除外。PHPUnitはより多くのオプション、よりよいモックオブジェクト、コンソール出力の向上を含む、PHPのテストフレームワークデファクトスタンダードです。*1
  • SecurityComponentを余計なことをしないように焼き直しをしました。より簡単に、選んでCSRFとフォーム改ざん攻撃からアプリケーションを守ることができるようになりました。
  • PostgreSqlSQLiteSqlServerのサポートが改善されました。これはMysqlデータソースと同じく、第一級市民(first class citizens)になりました。
  • フォームヘルパーでのHTML 5のフォームinputのサポート。
  • セクシーになったデフォルトの外見は新しいCSS 3の機能を活用してます。
  • ほぼ全てが遅延読み込みをするようになり、デバッグモードでもアプリケーションがまるで飛んでるように感じるほど、とても早くなりました。

以上は恐らくCakePHP 2.0がもっている長所すべてについての記述に及びません。
従って、このリリースを最もドキュメント化されたフレームワークのバージョンとするために構築した優れた新しいbook*2を読むことをお勧めします。
2.0の内部のすべての長所をよりよく理解するためのこのビデオ*3を見ることもお勧めします。スライド*4もあります!
また、多少の時間をかけて今日のリリースをもたらしたように私たちが従っているロードマップを詳しく示しているこのビデオ*5を見てください。
更に、全面的にCakePHPの生態システムが改善されました。
私たちは時間をかけてひとつのコマンドで走る完全なコアのテストスイートを作りました。
これは新しい継続的インテグレーションサーバー*6を使ってコアコードの健康状態を計ることを可能にします。
私たちは、これが堅牢なフレームワークであることを、これまで以上に自信を持っています。

CakePHP1.3から移行する方は、どうか非常に詳細な移行ガイド*7をチェックし、このリリースにバンドルされた新しいUpgradeシェルを必ず使うようにしてください。
多くのアプリケーションは既に移行しており、全般的なプロセスはとてもスムーズで簡単と評価されています。
このバージョンには既によく知られたプラグインと新しいプラグインの生態システムの完全な動作がついてきてます。
次はいくつかの注目すべきもののリストです:

  • DebugKit*8 : 開発用のツールバーを提供し、パフォーマンスの計測、リクエストの履歴、ビュー変数やその他のものの解析をするツールが用意されています。
  • AclExtras*9 : ACLパーミッションにアクセスコントロールオブジェクトの生成と同期を簡単にします
  • Localized*10 : アプリケーションで、各々の国で表示される正しいテキストのバリデーションを行う国特有のクラスのセット
  • Datasources*11 : XmlRPC、Ldap、などなどの追加のデータソース
  • Migrations*12 : 簡単なデータベーススキーマの移行と履歴追跡
  • Search*13 : 数分でURLリクエストから複雑な検索クエリの生成
  • Entity*14 : モデルに配列の代わりにオブジェクトを返すようにする
  • MongoDb*15 : MongoDBをデータベースとして使うソリューションの一つで、データソースを変更したのすら気づかないことでしょう!
  • MongoCake*16 : MongoDBを使い、モデルでオブジェクトを返す、Doctrine ODMとの互換性を保ったもの

2.0.0-RC3から79のコミットがあり20のチケットが解決されました。変更は以下のものを含みます:

  • CakeEmailでの本文とヘッダの文字セット(charset)の設定のサポート。これはユーザが日本語の文字セットでEメールを送ることを可能にします。
  • 内部のドキュメントのリンクを更新しました。
  • ビヘイビアとヘルパーの設定に文字列を使用する文法を除外しました。これはarray('MyBehavior' => 'config string')とすることができなくなりました。代わりに配列の文法を使用してください。
  • 独自のアダプタを用いたセッションの使用でエラーを投げることがなくなりました。
  • 使用されない例外クラスが削除されました。

全ての関連するコミット、チケット、ドキュメントの編集、その他フレームワークへの助力を通しての貢献に関して、多大な感謝を捧げます。あなたがいなければ、CakePHPはなかったことでしょう。

後書き

Bakeryのリリース記事の訳です。
http://bakery.cakephp.org/articles/lorenzo/2011/10/16/cakephp_2_0_released

予想外に早いリリースとなって、困惑してる一方、ようやく2.0が安定版となったことに非常にほっとしています。
更に特筆すべきは、CakeEntityがここで紹介されたことです。
MongoDbは既にCakePHPコアの一部として認識していいほど定着していますが、日本人開発者のプラグインが大々的に紹介されるのはやはり非常に喜ばしいですね。
ドキュメントですが、移行ガイド、新機能の章は翻訳済み*17ですので、それを見て新しいCakePHP2.0アプリケーションを作成する、もしくは移行するといいと思います。
さあ、これからナウな2.0を使ってブイブイ言わせましょう!:P

新鮮な CakePHP 2.0.0-RC3 をお切り分けしました(訳)

CakePHPコアチームはCakePHP 2.0.0-RC3*1をこんなにも早くお見せできることを誇りに思います。2.0.0-RC3は2.0.0の最終RC(リリース候補)となります。もしRCの予定期間の間に致命的な問題が無ければ、実に早いstableリリースを期待できるはずです。例の通り、新機能の追加、非互換な変更はCakePHP2.0に無いことでしょう。変更ログ*2、またはリリースをダウンロードしてください。

2.0.0-RC2から、82のコミットがあり、24のチケットが解決されました。ベータリリースからの主要な機能追加はありませんでした。いくつかの変更点を挙げます:

  • CakePHPコアのテストケースの安定性が強化されました。
  • Model::find()のjoinsキーの処理が書き直されました。これは連結するテーブルにテーブル接頭辞を自動的につける機能と、コードが綺麗になったことを意味します。
  • ファイルキャッシュのロックをデフォルトにしました。ファイルをロックすることで書きこみ途中のファイルによって引き起こされるunserialize()の警告(warnings)を伴う多くの問題を防止します。
  • カスタム承認(Authorization)クラスは独自のパスワードハッシュ化を実装できるようになりました。明快なサンプルはこの記事*3を見てください。

ドキュメントの邁進は良い軌道を描きつつあり、コミュニティからの非常に価値の高い貢献のおかげでとても幸福を感じています。もし新しいbookの何かが、何かを見失っていたり、誤っていたりするように感じたら、どうか続けてgithubでパッチを送ってください!APIページ*4は既に全てのCakePHP 2のクラスの、最新となるメソッドのドキュメントとして仕上がっています。

また、 http://ci.cakephp.orgCakePHPリリースの質を確かにするための鍵を保証していることを見て嬉しく思います。このツールで、チームが全てのサポートされるデータソースに対するコードに成された全ての変更を素早くテストすることができます。私たちはこれがプロジェクトの歴史の中で絶対的に全てのサポートされるデータソースで全てのテストが統一的にパスする最初の瞬間となるであろうことを誇りを持ってアナウンスします。

全ての関連するコミット、チケット、ドキュメントの編集、その他フレームワークへの助力を通しての貢献に関して、多大な感謝を捧げます。あなたがいなければ、CakePHPはなかったことでしょう。

後書き

はやすぎワロタ
ちょっと原文が文章の推敲というかそういうのが適当になってるあたりが筆者(Jose)の興奮を伺えて良いですね。
余談ですがドキュメントの日本語化も適当に進めています。既に他の場所で書き終えてるものを移行するだけも時間がかかっているので、猫の手もなんとやらです。
そのうちプルリクエストまでの流れをだいたい何かで纏めれたらいいなと思っていますがいつになるやら。

Mediaプラグインをアップロード処理の基本から学ぶ(2) - 制限, 検証

検証環境

PHPのアップロード処理

PHPのアップロードサポートでは、基本的にアップロードされたファイルの以下の情報が$_FILES*1連想配列として入ります。*2

  1. name - アップロード元のファイル名
  2. type - MIMEタイプ
  3. size - ファイル容量(バイト)
  4. tmp_name - 一時ファイルとして保存されたアップロードファイルのパス
  5. error - アップロードに成功したか(エラー内容)

これらを元に、ファイルの検証・移動を行います。

1. name - アップロード元のファイル名

送信元から送られてくるファイル名が入りますが、これはあまり信用してはいけません
悪意のあるファイル名の送信により、プログラム上で対策を行わないと上位ディレクトリのファイルにアクセスされる危険性もあります。
また、ファイル名がマルチバイトである場合、ファイルシステムによってはアクセスが不可能であるかまたは困難となる可能性があります。
これを回避するために、Transferビヘイビアのtransfer()メソッドを、モデルが同名のメソッドを実装することによりオーバーライドする必要があります。
具体的な実装に関しては、docs/TUTORIAL at from davidpersson/media - GitHubを参考にしてください。

2. type - MIMEタイプ

MIMEタイプが入りますが、マニュアルにもある通り、送信側から送られてきたヘッダーの内容がそのまま入るので、これは信用できません
Mediaプラグインは、上述のmmライブラリを使ってMIMEタイプを自動解析します。
MIMEタイプの解析の方法は様々ありますが、例えばFileInfoなどを使って解析することができます。
これはmmのMime_Typeの設定で変更することができます。


また、解析済のMIMEタイプを用いて、checkMimeType*3バリデーションが使用できます。

3. size - ファイル容量(バイト)

ファイルのバイト数が入ります。
PHPの設定によりファイルサイズも制御できますが、アップロードされるファイルの種類によってファイル容量を抑制する場合は、アプリケーション側で処理をする必要が出てきます。
これをMediaプラグインcheckSizeバリデーションで行います。

4. tmp_name - 一時ファイルとして保存されたアップロードファイルのパス

アップロードされたファイルは、一時的なファイルとしてファイルシステムに保存されます。(/tmpや、C:\TEMPなど)
これをPHPアプリケーションが取り扱えるディレクトリにコピー、または移動する*4ことによって、アプリケーションがはじめてそれを単純なファイル
として操作
することが可能になります。
なお、移動先はMediaプラグイン設定のtransferになります。(後の記事で説明)

5. error - アップロードに成功したか(エラー内容)

PHPのエラー定数を用いて判別しますが、要は

  • ファイルがアップロードされていないか
  • アップロードされたファイルが壊れていないか
  • PHPなどの設定上の制限を満たしているか
  • その他システムエラーが起きているか

などの場合分けです。
アップロードされていない、もしくは正しくアップロードが完了していない場合、Mediaプラグインはこれを無視します

ファイルの移動

ファイルの移動を行う時に、その移動元、移動先が正しい場所であるか検証する必要があります。

ファイルの場所

もしシステムファイルにアクセスできたり、または特定のディレクトリにアップロードファイルが書き込まれることがあったら、非常に危険な事態に陥る可能性があります。
これを検証するために、MeidaプラグインcheckLocationバリデーションが利用できます。例えば、/tmpAPP/tmpAPP/webroot/mediaMEDIA定数)等を指定します。

ファイルのアクセス権限(パーミッション

移動元のファイルは読み込み可能であり、移動先のディレクトリは書き込み可能である必要があります。
そうでない場合、アップロードが必ず失敗することは想像の範疇だと思います。
これは、MediaプラグインcheckAccessバリデーションを使用することで検証できます。


また、移動元のファイルにもし実行可能属性が付いていたらどうでしょうか。
そうなる状況はあり得るかという疑問はありますが、実際共有サーバーなどで/tmpなどに悪意のある操作をされることは想定できます。
これを検証するために、MediaプラグインにはcheckPermissionバリデーションが用意されています。

その他の制限、検証

拡張子

拡張子そのものを制限します。これはMIMEタイプでapplication/octet-streamを許可した場合など、与えられた拡張子がアプリケーションの要件にあったものかを制限します。
これはcheckExtensionバリデーションがMediaプラグインに用意されています。

ピクセル

ピクセル数を制限します。
ピクセル数が大きい物をリサイズする時に、メモリを非常に多く浪費します。
これを制限しないと、PHP、またはサーバーのメモリ限界を超えることになり、最悪ハングアップすることもあります。
また、このチェックではMediaプラグインcheckPixelsバリデーションが利用できます。

次回予告

いかがだったでしょうか。アップロードの検証だけでも多くの面からの処理が必要になることが理解できると思います。
次回はアップロードされたファイルの変換と配置についてになります。
サムネイルの生成など、アプリケーションのロジックにも関わるところで、一番関心を引くところではないでしょうか。

次回: Mediaプラグインをアップロード処理の基本から学ぶ(3) - 変換, 配置 -

*1:CakePHPでは、FormHelper#file()などで出力したinput[type=file]の内容は、$_FILESからディスパッチャを通して$this->dataに格納されます。

*2:PHP: POST メソッドによるアップロード - Manual

*3:この記事で**バリデーションと表記しているものはTransferビヘイビアのものを指す

*4:PHP: move_uploaded_file - Manual

CakePHPの3つの大バージョン全てにリリースがありました

CakePHPコアチームはCakePHP 1.2.11、1.3.12 と 2.0.0-RC2をこんなにも早くお見せできることを誇りに思います。1.2.11と1.3.12はバグ修正・メンテナンスのリリースです。この3つのリリースはバージョン2.0のリリース候補の2度目の試作物を含み、大部分は修正と、新しいテストケースと少しの向上点です。これら3バージョンでは後方非互換な変更はありませんでした。

これらが新鮮なうちにダウンロード*1ページで手に入れましょう!

すべてのブランチで導入された関連する変更で、目立つものは以下の通りです(すべてのバージョンが共通のバグのために関連する修正とコミットを共有していることに注意してください):

1.2

  • Windowsでunlink()が開かれたハンドルがあるファイルを削除できない、ファイルの削除問題の修正
  • Securityコンポーネントの複数の問題の修正
  • TimeHelperで12:00〜12:59の時間がAM(午前)として表示されてしまう問題の修正
  • Xmlライブラリでの複数のバグの修正
  • Set::extract()関数でのバグ修正
  • CacheHelperでの重複したコンテンツ・空のタグの問題ノ修正

このバージョンは、15以上のコミットと10のチケットの解決があった非常に長い時間の後に最終的にタグ付けされています。変更の完全なリストは、変更ログ*2をご覧になって下さい。

1.3

  • Postgresに関連したCakeSchemaでのバグの修正
  • Containableビヘイビアのバグ修正
  • memcacheへのunixソケットのサポートの追加
  • 科学的表記法がMySQLMySQL独自の形式として渡されなかったバグの修正。これは精度の損失を招いていました。
  • Set::extract()関数でのバグ修正
  • TRUNCATEの代わりにDELETE FROMを使うようにPostgreSQLを変更しました。これはフィクスチャで制約(constraints)を使うデータベースの互換性を向上させるでしょう。

20以上のチケットを閉じた計53のコミットはこのバージョンが最も安定しCakePHPのバージョンが既によくテストされたものに強化することになりました。変更の完全なリストは、変更ログ*3をご覧になって下さい。

2.0

  • Configureクラスの「default」設定は、まだ作成されていない場合自動的に作成されるようになりました。
  • フルページのキャッシュとテーマの組み合わせが動作するようになりました。
  • FormヘルパーとRssヘルパーの複数のバグ修正
  • Upgradeシェルの向上
  • PaginatorHelperで%page%スタイルのプレースホルダーが非推奨になりました。ScaffoldとBakeテンプレートが非推奨のプレースホルダーを使わないように更新されました。
  • モデルのバリデーションメッセージはバリデーション配列の「rule」キーからのパラメータで埋められるsprintfのプレースホルダーを受け入れるようになりました。
  • 新しいテストの山がスイートに追加されました。

64コミットと10以上のクローズされたチケットが完全な変更ログ*4で見れます。

2.0の最終的なstableリリースにとても近くなってまいりました。チームは今、残りのバグ修正と新しい2.0のbook*5にあるドキュメントの向上に焦点を当てています。もしあなたがドキュメントを磨き、向上することで私たちに手を貸してくれるなら、どうぞ自由にレポジトリ*6をフォークして変更のプルリクエストを送ってください。また、APIドキュメント化の向上を望んでもいますので、コードでのドキュメントブロック(doc blocks)のプルリクエストもまた非常に歓迎します!

全ての関連するコミット、チケット、ドキュメントの編集、その他フレームワークへの助力を通しての貢献に関して、多大な感謝を捧げます。また、CakeFest 2011に参加した人々にも感謝を。このお祭りは去年より更に良いものとなりました。

 -- 訳ここまで

後書き

Bakeryのリリース記事の訳です。
http://bakery.cakephp.org/articles/lorenzo/2011/09/19/cakephp_triple_layered_edition_released

RC-1の和訳をしなかったのは気づいたのが遅かったのと、なんかめんどくさくなったからです。そのうち暇つぶしに翻訳しましょうかね。
ともかく、非常に早いペースですね。RCは3までなので、年内確定と見ていいでしょう。新しいドキュメントも訳していきたいですね。やることいっぱい!

2chのスレに全レスする(12ホール目 1-100)

2chのスレとは

2chのWebProg板にある、CakePHPスレッドがあります。この記事時点での最新スレッドは
【PHP】フレームワーク CakePHP 12ホール目【笑】
になります。

注意

  • 暇つぶし。基本的に質問形式のレスに回答していきます。*1
  • 回答がついてあるものもお構いなしです。
  • 気分でレスしていくので飛ばすレスもあります。
  • あと偉そうな書き方をすることが多いですが断定的に言ってるだけなのであんま気分を害さないでください。(´・ω・`)

>>5

携帯サイトとPCサイトをCakeで作るとき、

Router::connect('/m/', array('controller' => 'home', 'action' => 'index', 'prefix' => 'mobile'));
Router::connect('/m', array('controller' => 'home', 'action' => 'index', 'prefix' => 'mobile'));
Router::connect('/m/:controller/:action/*', array('prefix' => 'mobile'));

  • app_controller.php

function afterFilter() {
  if (!preg_match("/^m(\/)?/", $this->params['url']['url'])) {
   $this->redirect('/m/' . $this->params['url']['url']);
  }
  if ($this->isMobile) {
    $this->output = mb_convert_kana($this->output, 'k');
    $this->output = mb_convert_encoding($this->output, 'SJIS', 'UTF-8');
  }
}
こんな感じで振り分けたりしてるんだけど、

function show() {
$this->set('abc', $this->Model->find('all'));
}

function mobile_show() {
$this->set('abc', $this->Model->find('all'));
}
上記のようにコントローラにPC用と携帯用で全く同じ処理なのに
別々のアクションを作らないといけない。
これはしょうがないのかな?
もっとナイスな実装方法があれば助言お願いします


prefix => 'mobile'ではなく'mobile' => trueなどとしたほうが良い。
beforeFilter()などでviewを切り替える方法もあるが、同一URLとするとビューキャッシュととことん相性が悪い。
テーマも動揺にビューキャッシュの問題が出てくる。
同一アクション内で携帯、またはPC用の場合分けが必要な時こそKtaiLibraryを使えば良い。

>>15

$this->data の中にあるデータ全てに置換処理をしたい場合どうしたらいいですか

Setクラスにそんな便利なメソッドがありそうなものだけど無いので自作しましょう。
array_walk_recursive()と無名関数の組み合わせが一番楽だけど、無理ならメソッド作って再帰
cake/basic.phpのstripslashes_deep()のコードが参考になります。

>>22

バリデーションで、
同じフォームのある特定のselectのある選択肢が選択された時のみ、
ある input をrequiredにする、
ってできる?
↓これでできそうな気がするけど、同じフォームの値は取ってこれないのかな
http://book.cakephp.org/ja/view/1179/%E7%8B%AC%E8%87%AA%E3%81%AE%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%AB%E3%83%BC%E3%83%AB

独自のバリデーションを作成してその中で分岐する手もあるが、beforeValidate()でModel::$validateの個別ルールのrequiredを書き換えることで解決できるならそれが一番手っ取り早い。

<?php

class Post extends AppModel {

	public $validate = array(
		'hoge' => array
			'required' => array(
				'required' => false,
				'allowEmpty' => true,
			),
		),
	);

	public function beforeValidate() {
		$hogeRequired = !empty($this->data['Post']['piyo']) && $this->data['Post']['piyo'] == 2;
		$this->validate['hoge']['required']['required'] = $hogeRequired;
		$this->validate['hoge']['required']['allowEmpty'] = !$hogeRequired;
		return true;
	}

}

>>26

CakePHPってAppControllerやAppModelにかなり手を入れないと
使い物にならないよね。
あと、標準のコンポーネントやヘルパーはホント使い物にならない。
特にFormヘルパーは心の底から使えない。
こんな屑フレームワークが何故こんなに持てはやされているのか謎。

「かなり手を入れる」という意味は2つ推測できます。
・共通の処理を多く定義すること
・メソッドのオーバライドや便利メソッドを追加して挙動そのものをいっぱい変えること
前者の場合は、よりコンポーネント・ビヘイビア化をするべき時期に来たということです。
後者の場合、その変更が有益ならコアに統合するようにリクエストを送ることです。そうでないならば、その変更自体が良いプラクティスで無い可能性が高いと言えます。
どちらにしろ、どんな手の加え方なのかわからない以上、なんとも言えないところですが。
少なくとも自分はAppController, AppModelがほぼ使うコンポーネント群(コンポーネント、ヘルパー、ビヘイビア)の定義意外あまり書くものが無いようにできています。


標準のコンポーネントやヘルパーが使い物にならないなら、いったい何を使っているのでしょうか。
それはCakePHPという「ガワ」を使ってるだけの独自フレームワークと呼べるのではないでしょうか。


後半に関しては議論が分かれるところですが、2.0で改善されたものも非常に多くあります。
また、「使えない」と思ったらコアにフィードバック等を投げましょう。
それが全体として有用なら、一瞬で採用して取り込んでくれるポテンシャルが開発チームにはあります。
このような「使えない」という議論はかなりループしてるので、次から無視します。(どういう点が使えない、という具体的な話なら価値があります)

休憩

>>28-48ぐらいまで上の話題で流れているのでちょっと休憩。
あんまり議論っぽいことには突っ込みづらいです。
何故かというと話が長くなるからです。
あと相手の誤解と思われる部分や、情報が足りない場合に対して推測で話をするのがめんどいです。

>>49

Jsヘルパーって使ってますか?
複雑になるだけな気がするんですが、メリットがあったら教えてもらいたいです。

Jsヘルパーも、AjaxヘルパーもJavascriptヘルパーも、基本的なJavascriptの操作を提供するだけのもので、フロントエンジニアがいたり、自分でゴリゴリ書ける場合は自分で書くべきです。
ただし、バッファリングを行って所定の位置に吐き出すなどをしたい場合はこれを使ったほうがいいでしょう。
さもないとテンプレートにob_start()からはじまるPHPコードの「沼」が出来上がることでしょう。

>>50

$this->Model->getLastInsertID()
を使って他テーブルの外部IDを設定(ひもづけ)するのは危険かな?

例えばショッピングサイトで注文テーブルの注文IDを、注文内容テーブルの外部IDに設定するような場合。

処理の途中で別のINSERTが入った場合はどうなるんだろう・・・?

Model::save()直後には、Model::$idに新規レコードならばそのlastInsertId、更新ならばそのIDが入ります。
通常はそれを使ってください。また、別のINSERTが〜というのは、設計の問題です。そうならないように実装してください。


ここらへんからしばらくトランザクションの議論が続きますが、省略。
>>74の理解で合ってます。

>>99

パスワードの入力エリア2つにして、一方を確認用にするって
仕様が良くあると思いますが、この場合のバリデーションってどうしてます?

さんざ議論され尽くされてるので、フォーラムで検索とかしてください。
CakePHP2.0では、AuthComponent::password()がstaticになった恩恵で、モデルからも呼び出せます。あと、自動的にハッシュ化はしてくれなくなりました。



とりあえず今回は100まで。もうちょっと誰も答えてない質問とかに答えていきたいですね。

*1:Q. 全レスじゃないじゃん A. こまけぇこたぁいいんだよ!

Mediaプラグインをアップロード処理の基本から学ぶ(1) - 導入

検証環境

イントロダクション

davidpersson/media - GitHub
Mediaプラグインとは、CakePHPプラグインの一つです。
主にアップロードファイルを取り扱うプラグインで、CakePHPでアップロードの処理をするためのデファクトスタンダードとなっています。


このプラグインはファイルのアップロードからアップロードされたファイルの検証、DBへのメタデータの保存、変換、ダウンロード、表示、フォーム連携までを多段階に抽象化し、その恩恵として非常に柔軟な設定・拡張ができるようになっています。

バージョン

バージョンとしては、nextブランチ(1.3系、バージョン1.3.x)とmasterブランチ(1.2系、バージョン0.6.x)がありますが、nextブランチがカレントのブランチとなっており、これを使うのが推奨されています。
また、1.2系の場合は、1.3系で取り入れられた拡張方法が足りていないため、不可避の問題が生じる可能性があります。
更に、1.3系では機能が統合・削除・分割などがされているので、古い情報が役に立たない時があります。
この連載では、1.3系の解説になります。

mmライブラリ

Mediaプラグインは、バージョン1.3.0から、内部のファイル変換処理をmmライブラリhttp://github.com/davidpersson/mm)に分割しています。
このため、CakePHP以外のPHPアプリケーションでもmmライブラリを使用することによって、同機能を使用することが可能になりました。

アップロード処理の基本概念

ファイルをweb上でアップロードさせる場合、アプリケーション側では非常に多面的な処理を行います。

アップロードにかける基本的な制限

容量の制限などをPHPhttpdの設定により行います。
大容量のファイルを無制限にアップロードさせるのは、サーバー残容量に深刻なダメージを与えることがあります。
また、場合によっては違法アップロードの温床にもなりかねません。

検証

アップロードされるファイルは基本的に信用できません
アプリケーション特有の制限、またファイルの種類による制限(画像、音声により異なるなど)をかける必要がある他、悪意のある情報(メタデータを取り除いてやる、または許可しない必要があります。
これを緻密にしない限り、アプリケーションの(アップロードに纏わる)品質は担保れされないことでしょう。

画像などの変換処理

サムネイルなどの画像変換を行います。
サムネイル等が必要なくても、一般的にGIFAR(変換の章で説明)などを回避するために、閲覧者に表示させるものは内部でメタ情報を削ったものを提供するため、ほぼ必須の工程となります。

ファイル情報(メタデータ)の取得

アプリケーションの要件によっては、アスペクト比、縦横の長さ、MIMEタイプなどを取得する必要がでてきます。
これらはフィールドキャッシュか、またはキャッシュ機構を用いて適切なコストに抑える必要があるでしょう。

DBへの登録

アップロードされたファイルの基本的な情報は、DBに登録するのが一般的です。
ファイルだけで管理するとなると、複雑なロジックが必要になったり、また探索のコストがスケールしなくなるなど致命的な欠陥を招きます。

ファイルアクセスへの制限

要件によっては、ファイルへの直接のアクセスを制限する必要がでてきます。
ほとんどの場合、ファイル名(URI)にランダム文字列(またはハッシュ)を割り当てることによってこれを達成できます。
権限通りのアクセス制限を施す必要がある場合は、アプリケーションを通すことによってこれを実現するべきでしょう。

次回予告

次回はアップロードの制限、検証の回になります。
アップロード処理の肝と呼ぶべきところで、ここを押さえれば後はロジックに集中できることでしょう。

次回: Mediaプラグインをアップロード処理の基本から学ぶ(2) - 制限, 検証 -

CakePHPアプリケーションの基本的な設計指針 (3) - カスタムfindタイプ -

イントロダクション

標準のfindの種類(first, all, count, threaded, neighbor, list)だけでは、ビジネスロジックに対応できないことがあります。
これに対するひとつのプラクティスとしては、カスタムfindタイプを定義することです。
この記事では、実際の開発を想定したリファクタリングの過程を通してカスタムfindタイプの定義の仕方と活用方法、その意義をご紹介します。

「次」の記事

例えば、カレントのレコードの「*1」のレコードを取得したい場合、それに纏わる複雑な処理は、単純なqueryの発行だけでは済まないことがあります。
この「次」のレコードを探索するロジックを例に、ボブ*2がこれを実装していくお話をしましょう。

要件の定義

ボブの上司のサム*3は、クライアントの会社のサイトにブログモジュールを追加するプロジェクトの打ち合わせで、次の要件を定義しました。

  • 記事のモデルとして「Post」を使う。
  • IDを比較することによって「次」の記事を決める(ID昇順)
  • 「自分」がID順で「最後」のものであったら、「最初」の記事を「次」の記事とする(A -> B -> C -> A
  • 「次」に「自分」自身は含めない
  • 「自分」以外何もなかったら見つからなかったものとする

クライアントはこれさえあればユーザは次々と記事を見てくれるだろうとご満悦のようです。
この要件を渡されたボブは、こんなの楽勝だなと思い、さっそく実装にとりかかることにしました。

個別メソッド

まずはじめに、ボブはモデルに専用のメソッドを定義し、それをアプリケーション内で利用することにしました。

非常にシンプルに記述でき、ボブはにんまりとしながら泥のようなコーヒーをすすりました。
これで要件を満たすことはできましたが、これでボブの仕事は終わりではありませんでした

カテゴリーの絞り込み

しばらく時が経ち、ボブが休憩室でギターの練習をしていると、サムが来てこう言いました。
「ブログのカテゴリーの絞り込みからも『次』の記事に飛ばして欲しいという要望があったんだ。やってくれるかい」
ボブはようやく新しい曲が覚えられそうなのに横槍を入れられて不機嫌そうですが、渋々これを承諾し、作業に取り掛かることにしました。


ボブはカテゴリーで絞りこまれたレコードを探索するには、条件(スコープ)をつけることが必要と考えました。
そこで、以下のように書き換えることにしました。

これでカテゴリーの絞り込みができるようになりました。ボブがチケット管理システムで該当チケットに完了のステータスをつけると、5分もしないうちにサムがボブのデスクに来て言いました。
「これではカテゴリーを必ず指定しないといけないから、全体記事で使うことができないじゃないか。*4
ボブは慌てて、カテゴリーを渡す引数をオプションにして対応することにしました。

これで全体記事、またはカテゴリー絞り込みから「次」の記事を探すことができるようになりました。

タグでの絞り込みの追加

これで柔軟に対応できたな、と思ってボブがまたギターの練習をしている間、タグ機能の追加の要件が出てきました。
タグ絞り込みから「次」の機能を使おうと思うと、やはりこのままではいけないようです。
そこで、試行錯誤した結果、以下のようなコードでなんとか動くことを確認しました。

最初のシンプルなコードから比べると、かなりごちゃごちゃとしてきました。
更に、引数の取り方も強引で、使う時に都度確認しながらでないとよくわからくなってきました
ボブはすっかり疲れきった顔で、今日は退社することにしました。

翌日

昨日、ようやくタグの絞り込み機能が実装できたと思った束の間、今度はACLによる権限からの絞り込みを追加する必要が出てきました。
また引数を追加するのでしょうか。
ACLを通した絞り込みはどうするのでしょうか。
タグと同じ方法でできればいいですが、そうでなかった場合、いくら時間をかけて調べることになるのでしょうか
「もうたくさんだ!」ボブは叫びます。
そして、CPUロゴマークがはがれかけてる古いノートPCは窓から投げ捨てられプロジェクトは闇へと消えさっていくのでした・・・。

と、ならないように

ボブは新しい仕事をさがすために就職情報サイトを閲覧していると、上司のサムからメールが届きます。
「パン屋のジョナサン*5が言うには、カスタムfindが全てを上手くやってくれる*6というんだ。あのノートPCだが、減価償却として経理に申請が通ったから*7、気にすることはないさ」
ボブはこう返信しました。
「そうか。その助言は調べる価値がありそうだ。しかし、なぜジョナサンにこの仕事を頼まない?」
色々問答があった末、ボブはカスタムfindへのリファクタリングを行った上で、ジョナサンにこの仕事を引き継ぐことになりました。

カスタムfindタイプの定義

ボブが調べた結果、カスタムfindタイプの定義は、特殊な書き方を伴うということがわかりました。
そこで、現状のソースコードを一旦捨て、「次」のレコードを探索する汎用的なカスタムfindタイプを作成することにしました。
まず、カスタムfindタイプを使うには、Model::$_findMethodsプロパティに、使うfindタイプが有効であることを伝えなければいけません。

実は他のfindタイプ(all, first)などが同じようにclass Modelに定義されていますが、モデルのコンストラクタでModel::$_findMethodsPost::$_findMethods自動的にマージしてくれます。賢いですね :D
これで、find()に'next'タイプがあることが伝わりました。
さて、find('next')を呼んだ時に、いったい何が起こるのでしょうか。
ボブは自身の情報網(と、ジョナサン)を駆使した挙句、以下のような実装に成功しました。

呼び出されるモデルのメソッドは、_findNext()です。これは、_find + findタイプの頭文字を大文字にしたもので、_findFirst(), _findAll()などがあります。
第一引数$stateにはbefore、afterしか入らず、これはfind()の低級APIであるデータソースのread()が呼ばれる前(before)か後(after)かが入ります。
beforeの場合、$query(第二引数)を返す必要があります。これは、このメソッドでfindオプション($query)の書き換えができることを意味します。
afterの場合、find()の返り値そのものを返却する必要があります。これは第三引数の$resultsを元に返す必要があります。これは、このメソッドで返り値の整形ができることを意味します。

他の_findMethod()の再利用

さて、ボブがテストしてみたところ、今のところ順調に動いているようです。しかし、調べた過程の中で、一部がModel#_findFirst()と同じことをしていることに気づきました。
そこで、_findFirst()再利用するようにリファクタリングすることにしました。

上手く動いているようです!ボブはカテゴリとタグの絞り込みをどうするか思案した結果、これらはbeforeFind()で記述することにしました。
これにより、'next'以外でもカテゴリやタグの絞り込みが行えます

ビヘイビア化

カスタムfindタイプにリファクタリングをした後、ボブは円満退社し、ジョナサンがボブの仕事を引き継ぎました。
ボブのソースコードをチェックアウトし、中身を見たジョナサンは、find('next')は汎用的に使いまわせると思いました。
また、他のコードもいつか再利用できるかもしれないと考え、最終的にPostモデルのソースコードは次のようになりました。

なんということでしょう。ジョナサンは全てをビヘイビアに置き換えてしまったのです*8

エピローグ

かくてPostモデルは、その複雑なロジックが完全に抽象化され汎用的なロジックの組み合わせとして記述されるようになりました。
これなら、ACLを用いた探索機能の追加も他のことを忘れて行うことができるでしょう。
その後ボブは新しい仕事として、パン屋ジョナサンの店員として働き、ジョナサンはサムと一緒にベンチャーとして独立し一山当てることになりますがこの設定本当に必要だったんでしょうか

後書き

Mode#find()の振る舞い・用法は、まずほとんどの開発者が理解すべきもので、これに追従することによって様々な設計を統一することができます
また、再利用法として場合によってはページネーションにも使えます*9
更に、CakeDC謹製Searchプラグインを使う場合はfindを経由するので、これに応用することもできます。


一見面倒に見えるカスタムfindタイプですが、慣れてしまえば非常にストレスフリーに、柔軟さを伴った実装ができます
複雑なロジックからボブのように逃げるのではなく、ジョナサンのようにスマートに実装するようにしましょう ;)

      • -

この記事で使ったサンプルコードはこちら:
preferable example for getting data method for model ― Gist
最後にビヘイビア化したコードはこちら:
preferable example for getting data method for model(behaviorized) ― Gist

*1:アプリケーションの要件により「次」が何を示すのかは異なる

*2:新入社員24歳PG。CakePHP歴1年。細かいことを言うサムは苦手

*3:入社4年目32歳GM。自由すぎるボブにいつも振り回されがち。ジョナサンとは家が近く親友。

*4:「すべての記事」というカテゴリーは、HABTMにすれば実現できますが、それは別の話とします

*5:28歳パン屋店主。プログラムは趣味。サムのことはただの知り合いだと思っている

*6:注:誇張

*7:ダメ、絶対

*8:カスタムfindモデルのビヘイビア化には、ModelBehavior#mapMethods()を使ってマッピングする必要があります。詳しくはソースコードAPIを参照してください

*9:$this->paginate[] = 'next'など