Phalconモデルまとめ(3) 計量・ハイドレーションモード・新規作成と更新

「最速」PHPフレームワークPhalconのモデルについて、基本事項をまとめます(公式ドキュメントの翻訳+αです)。記事執筆時のPhalconのバージョンは1.3.1です。

モデルの計量

計量(集約)はデータベースの操作を助ける機能で、COUNT/SUM/MAX/MIN/AVGなどがあります。Phalcon\Mvc\Modelでもこれらの機能を利用できます。

COUNTの例:

SUMの例:

AVERAGEの例:

MAX/MINの例:

ハイドレーションモード

Phalconのモデルの結果セットは、全てがオブジェクトです。DBの各行が単一のオブジェクトになっています。これらのオブジェクトに変更を加え、保存して永続化することができます。

結果セットの形式を変更するモードのことを、「ハイドレーションモード」といいます。

ハイドレーションモードは、find()のパラメーターとして渡すこともできます。

レコードの作成・更新

Phalcon\Mvc\Model::save() メソッドによって、レコードの作成・更新ができます。save()メソッドは内部でPhalcon\Mvc\Modelのcreate()又はupdate()を呼びます。いずれのメソッドを呼ぶかは、エンティティの主キーが定義済みか否かによって決まります。

また、このメソッドは、同時にバリデーションも実行します。

全てのカラムに手動で代入する代わりに、save()に配列を渡すことができます。Phalcon\Mvc\Model は各カラムに定義済みのsetterが無いか確認します。

確実に作成・更新する

アプリケーションが同時に多くの利用者に利用されている時、レコードを新規作成すると予想していたのに実際には更新がされてしまうことがあります。Phalcon\Mvc\Model::save()を使用してDBへの永続化を行うと、このような現象が発生する可能性があります。もし、絶対にレコードが新規作成又は更新されるようにしたい場合、save()の代わりにcreate()又はupdate()を呼びます。

これらのメソッドには、save()と同様、配列をパラメータとして渡すことができます。

自動採番されるid

モデルがidを示すカラムをもつことがあります。これらのカラムはふつう、テーブルの主キーとして使用されます。Phalcon\Mvc\Modelはidのカラムを認識することができます。そのため、Phalcon\Mvc\Modelが生成するSQLのINSERT文には、idが含まれません(DBMSが自動採番できるようにするため)。レコードを新規作成した際には、DBMSによって採番されたidがモデルに登録されます。

Phalcon\Mvc\Modelはidのカラムを認識することができます。DBMSの種類によりますが、PostgreSQLのようなSERIAL型のカラムであることもあれば、MySQLのようにauto_incrementが設定されたカラムである場合もあります。

関連テーブルの一括保存

マジックプロパティによって、あるレコードとその関連するプロパティを一度に保存することができます。

保存するレコードと、その関連レコードには、has-manyの関係があります。

アルバムとアーティストを同時に保存すると、暗黙的にトランザクションが使用されます。そのため、何らかの原因で関連レコードの保存に失敗した場合、親となるレコードも保存されません(内部的にエラーが発生します)。

注意点:以下のメソッドのオーバーロードによって関連するエンティティを追加しても、効果はありません。

  • Phalcon\Mvc\Model::beforeSave()
  • Phalcon\Mvc\Model::beforeCreate()
  • Phalcon\Mvc\Model::beforeUpdate()

もし、保存時の挙動を変更したいなら、Phalcon\Mvc\Model::save()をオーバーロード(※)する必要があります。

※:原文では「You need to overload PhalconMvcModel::save()」なのでそのまま訳しました。overrideの間違い?

今回はここまで

次回は、Validation Messagesから先、Phalconのモデルによるバリデーションの方法を紹介します。

『GitHub実践入門』を読了



GitHubは、オンラインのGitリポジトリ共有サービス。Gitは、分散型のバージョン管理システムで、近年とても人気がある。

本書の書名を見た時は、GitHubで本1冊書くほどのネタあるの? と思ったど、本書はGitHubの画面の使い方といった表面的な情報だけでなく、Gitの入門からGitHubを利用したワークフローまで、GitHubを取り巻くトピックを的確に網羅している。

主な対象読者は、「Gitって名前は聞いたことがあるけど、使ったことはない」くらいの人から、「Gitは使ってるけどGitHubはあまり使いこなせてない」といった人あたり。GitHubでバリバリに開発している人でも、ショートカット等の細かなtipsなど、何かしら得る部分はあると思う(会社での導入の説得材料としても、本書は使いでがありそう)。

対象読者のレベルをしっかりとおさえた丁寧な解説なので、楽に読める。やや概念的に難しいのはワークフローの部分だが、ここも手を動かしながらのチュートリアルがあるため、非常に理解しやすい。

対象読者に該当する人にはおすすめ。

[Chrome] Conduitを削除する方法

WindowsでChromeを使っていたら、Conduitというブラウザハイジャッカーがインストールされていました。ホームページを改ざんして、ブラウザ起動時にConduit版のbingページを表示するアドウェア(実質的にマルウェアといっても差し支えない)。

まず、Windows機からConduitを削除しました(「コントロールパネル」→「Search Protect」をアンインストール)。

しかし、Chrome起動時のタブが戻らない!

AdwCleaner等のソフトも試しましたが、効果なし。

最終的に解決に至った方法は、「Chromeの設定リセット」でした。(AdwCleaner等の働きのおかげもあるかもしれませんが、決め手になったのは「Chromeの設定リセット」です)

Chromeの設定リセット方法は、こちらのページ(Google公式)を参考に。

設定をリセットすると拡張やブックマークも消去されるのかな、と思ってたのですが、拡張やブックマークの情報はリセット後にも残りました。リセットされるのは、ホームページ・新しいタブ・検索エンジン等、Conduitに改ざんされて困っていた部分なので、ほとんどデメリット無しで、Conduitがホームページから消えました。

今回困ったのは、Conduit(Search Protection)をインストールしたのはWindows機だったんですが、Chromeの設定は別PCにも共有される、という点です。Chromeを利用している別のMac等でもいちいちブラウザ設定をリセットする必要があるのは結構面倒でした。設定の共有は良し悪しですね。

Phalconモデルまとめ(2)リレーション

「最速」PHPフレームワークPhalconのモデルについて、基本事項をまとめます(公式ドキュメントの翻訳+αです)。記事執筆時のPhalconのバージョンは1.3.1です。

リレーションの定義

Phalconでは、リレーションはモデルのinitialize()メソッドの中で定義する必要があります。リレーションを定義するメソッドには4種類あり、いずれも「自分自身のフィールド名(≒カラム名)」「参照するモデル名」「参照するフィールド名」の3つのパラメータをとります。

メソッド 状態
hasMany 1対多
hasOne 1対1
belongsTo 多対1
hasManyToNany 多対多

以下のようなテーブルの関係を考えてみます。

  • 1つのRobotsは、1つ以上のRobotsPartsをもつ
  • 1つのPartsは、1つ以上のRobotsPartsをもつ
  • 1つ以上のRobotsPartsが、Robotsに属する
  • 1つ以上のRobotsPartsが、Partsに属する
  • RobotsとPartsは、RobotsPartsを介して、多対多の関係になっている(RobotsPartsは中間テーブル)

ER:robots-robots_parts-parts

それぞれのモデルは以下のように実装できます(Phalcon DevToolsはv1.3.1現在、リレーションの自動生成には対応していません。リレーションの記述は手動で行う必要があります)。

リレーション定義メソッドの第1引数には、自分自身のフィールド名、第2引数には参照するモデル名、第3引数には参照するモデルのフィールド名を渡します。複数のフィールドのリレーションを指定したい場合、配列を使うこともできます。

3つのモデルからなる多対多の関係を単一のメソッドで記述すると、以下のようになります。

リレーションを活用する

モデルの関係が明示的に定義されると、関連するレコードを簡単に一括取得できます。

Phalconは、関連するモデルへのデータの保存・取得にマジックメソッド(__set/__get/__call)を用います。

リレーションと同じ名前のプロパティにアクセスすると、関連するレコードが自動で取得されます。

getterもマジックメソッドで実装されています(明示的な定義が不要なgetter:マジックゲッター)。

Phalcon\Mvc\Modelは、「get」が頭についているメソッドの呼び出しがされると、findFirst()又はfind()の結果を返します。以下の例では、マジックゲッターを使った場合と使わない場合とを比較しています。

関連するレコードを手動で取得する場合:

「get」という接頭辞は、find()/findFirst()する際に使用されます。どのメソッドが呼ばれるかは、リレーションの種類によります。

種類 説明 メソッド
belongsTo 関連するレコードのモデルを返す findFirst
hasOne 関連するレコードのモデルを返す findFirst
hasMany 関連するモデルの検索結果を返す find
hasManyToMany 関連するモデルの検索結果を返す(暗黙的にINNER JOINを行う) (複雑なクエリ)

「count」という接頭辞を使うことで、関連レコードの数を数えることもできます。

リレーションのエイリアス

エイリアスの働きを説明するため、以下の例を使用します。

「robots_similiar」は、あるロボットと別のロボットの類似性を定義しています。

ER:robots-robots_similar

このリレーションは以下のように実装できます。

いずれのリレーションも同じモデルを指しているため、どのレコードを取得するのか判然としません。

エイリアスを使うことで、リレーションの名前を付け直すことができます。

エイリアスを使うことで、レコードの取得は簡単になります。

仮想外部キー制約

デフォルトでは、リレーションはRDBの外部キー制約のようには動作しません。外部キー制約によってエラーとされるような値をDBに入れようとした場合、Phalconは何のバリデーションエラーも発しません。リレーション定義時の第4引数にパラメータを設定することで、この挙動を変更できます。

belongsTo()リレーションが外部キー制約として振る舞うように変更すると、DBへの値の登録時にも、外部キー制約によるバリデーションが行われるようになります。同じように、hasMany()/hasOne()の振る舞いが変更されると、その値が別テーブルから参照されている限り、削除することができなくなります。

CASCADEとRESTRICT

仮想外部キー制約として働くリレーションは、デフォルトでは作成・更新・削除に制限を加え、データの完全性を保ちます。

上記コード例では、マスタとなるロボットのレコードが削除されると、それを参照しているパーツのレコードも全て削除されるように設定しています。

[補足]名前空間とエイリアス

Phalcon公式ドキュメントには無いけれど、知っておいたほうが良いワザとして、「名前空間のエイリアス設定」があります。名前空間を使用したモデルへのリレーション設定は1つ上のサンプルで、エイリアスも別のサンプルにはありますが、両方を一度に扱ったサンプルはありません。モデルに名前空間を使用している場合、エイリアスの使用は必須に近いと思うので、ここで紹介しておきます。

上記のような設定を行ったモデルでは、以下のようにしてStore\Models\Partsにアクセスすることができます。

しかし、いちいち名前空間をフルパスで書くのはいかにも面倒臭い。この場合、エイリアスを利用すると幾分楽ができます。

今回はここまで

ここまでで、リレーションの基本は押さえました。次回は、Generating Calculationsから先をみていきます。

Phalconモデルまとめ(1)データ取得の基本

「最速」PHPフレームワークPhalconのモデルについて、基本事項をまとめます(公式ドキュメントの翻訳+αです)。記事執筆時のPhalconのバージョンは1.3.1です。なお、サンプルコードを実行したい場合、環境構築を参考にしてください。

モデルの基本

Phlaconのモデルは、Phalcon\Mvc\Modelを継承したクラスです。モデルクラスは以下の条件を満たす必要があります。

  • modelsディレクトリに配置する
  • モデルファイルは1つのクラスだけを含む
  • クラス名はキャメルケース

上記例が、Robotsモデルの実装例です。RobotsがPhalcon\Mvc\Modelを継承している点に注目してください。Phalcon\Mvc\Modelを継承することで、データベースにおいて基本的なCRUD処理から、データのバリデーション、複数のモデルの関係に基づいた検索など様々な機能を利用することができます。

デフォルトでは、「Robots」モデルは「robots」テーブルを参照します。参照するテーブルを手動で設定したい場合は、getSource()メソッドを使用します。

上記コード例では、Robotsは「the_robots」テーブルを参照します。

initialize()メソッドは、1リクエストに対して1回だけ呼ばれます。モデルの振る舞いのカスタマイズに適しています。

initialize()は1回のリクエストを通して1回だけ呼ばれ、モデルの全てのインスタンスの初期化に使うことを目的としています。インスタンスが作られる度に呼ばれるメソッドが欲しい場合は、onConstruct()メソッドを使います。

publicプロパティとgetter/setter

モデルのプロパティは、publicに実装し、どこからでも読み取り・変更できるようにすることができます。(Phalcon DevToolsのデフォルトではpublicプロパティでモデルを自動生成します)

getterとsetterを使うことで、プロパティの操作を自由に行えるようにしつつ、モデルにセットされるデータの整形やバリデーションを行うことができます。(Phalcon DevToolsのモデル生成コマンドに–get-setオプションを付けると、getter/setterでモデルを自動生成します)

publicなプロパティは、利用する際にシンプルなコードになるという利点があります。一方、getter/setterは、テストのしやすさや拡張性・メンテナンス性を向上させてくれます。自分の作ろうとしているアプリケーションに適した実装を選んでください。PhalconのORマッパーはいずれの実装にも対応しています。

モデルの名前空間

クラス名の衝突を避けるため、名前空間を使えます。参照するテーブルはクラス名に基づくため、以下の例では「robots」テーブルが参照されます。

レコードからオブジェクトへの変換

モデルの全てのインスタンスは、テーブルの1行を表します。オブジェクトのプロパティを取得することで、DBのレコードにアクセスすることができます。一例として、以下のrobotsテーブルを考えます。

以下のコードで、プライマリーキーによる検索を行い、その結果を表示できます。

レコードを一度取得すれば、そのデータを変更して保存することもできます。

Phalconのモデルを使う場合、生のSQLを書く必要はありません。Phalcon\Mvc\Modelはデータベースの抽象化を行ってくれます。

レコードの検索

Phalcon\Mvc\Modelはデータの検索のためにいくつかのメソッドを提供しています。以下はfind()メソッドの使用例です。

findFirst()メソッドを使うことで、判定基準に合った最初のレコードを取得することができます。

find()/findFirst()には、検索条件となる連想配列を渡すことができます。

利用可能なクエリオプションは以下です。

パラメータ 説明
conditions 検索条件。Phalcon\Mvc\Modelは第1引数を検索条件とみなす。 “conditions” => “name LIKE ‘steve%’”
columns 指定したカラムだけを返すようにする。 “columns” => “id, name”
bind プレースホルダーをbindする値で置き換える。 “bind” => array(“status” => “A”, “type” => “some-time”)
bindTypes バインド時にパラメーターを指定した型に変換する。 “bindTypes” => array(Column::BIND_TYPE_STR, Column::BIND_TYPE_INT)
order 検索結果をソートする。 “order” => “name DESC, status”
limit 検索結果の件数を制限する。 “limit” => 10 / “limit” => array(“number” => 10, “offset” => 5)
group 複数のレコードからデータを集め、結果を1つ以上のグループにまとめる。 “group” => “name, status”
for_update 検索対象データに排他ロックをかける。 “for_update” => true
shared_lock 検索対象データに共用ロックをかける。 “shared_lock” => true
cache 結果をキャッシュし、DBアクセスを減らす “cache” => array(“lifetime” => 3600, “key” => “my-find-key”)
hydration 結果セットの型を指定する(オブジェクト/連想配列)。 “hydration” => Resultset::HYDRATE_OBJECTS

お好みであれば、パラメータではなくオブジェクト指向の構文でクエリを組み立てることもできます。

静的メソッドのquery()がPhalcon\Mvc\Model\Criteriaオブジェクトを返します。

全てのクエリは内部的にPHQLとして扱われます。PHQLは、Phalcon独自のクエリ言語です。

最後に、findFirstBy<プロパティ名>メソッドがあります。このメソッドは、先に紹介したfindFirst()の拡張です。

一例として、以下のようなモデルがあるとします。

ここで、nameを元に検索したい場合、findByName()メソッドを使って以下のように検索できます。

モデルの結果セット

findFirst()は、検索結果のモデルのインスタンスを直接返します。

一方、find()はPhalcon\Mvc\Model\Resultset\Simpleオブジェクトを返します。このオブジェクトは、データの順次取得や特定のデータの探索、レコード数のカウント等の機能をカプセル化します。

これらのオブジェクトは、通常の配列よりも機能が豊富です。Phalcon\Mvc\Model\Resultsetで最も素晴らしい機能の1つは、メモリ内に存在するレコードはどんな時でも1件だけである、という点です。このため、大量のデータを扱うときでもメモリの消費は最小限に抑えられます。

Phalconの結果セットはDBMSのカーソルをエミュレートしているため、場所を指定してデータを取得することができます。DBMSによってはカーソルに対応していないものもあるため、注意してください(非対応のDBMSの場合、特定の位置のレコードを取得しようとする度にDBに問い合わせが行われます)。

巨大な問い合わせ結果をメモリに持っておくと、リソースを大きく消費します。そのため、結果セットは32行の固まりとしてDBから取得し、問い合わせ回数の減少とメモリの節約を行っています。

結果セットをキャッシュしておくことができます(Phaclon\Cacheなどが利用できます)。しかし、データのシリアライズを行うと全てのデータを取得して配列にするため、キャッシュの作成処理を行っている間はメモリ消費が多くなります。

結果セットのフィルタング

最も効率的なデータのフィルタリング方法は、検索条件を指定することです。この場合、データベースは(利用可能であれば)インデックスを使用するため、高速にデータを取得できます。加えて、PHPでデータのフィルタリングを行うこともできます。

パラメータのバインド

Phaclon\Mvc\Modelはパラメータのバインド機構をサポートしています。バインド機構を使うことによるパフォーマンスへの影響はわずかですが、この方法を使うことでSQLインジェクション攻撃を受ける可能性を減少させることができます。文字列と数字のプレースホルダーがサポートされています。

数字のプレースホルダーを使う場合、1, 2といったint型の値として定義する必要があります。’1′, ’2′といった定義は文字列とみなされるため、数字のプレースホルダーとしては機能しません。

文字列のプレースホルダーは、PDOによって自動的にエスケープされます。この機能は文字コードを考慮するため、データベース設定で適切な文字コードを指定することを推奨します。

パラメーターの型を指定することもできます。

bindTypesのデフォルトはPhalcon\Db\Column::BIND_PARAM_STR(文字列)です。文字列型のパラメータの型を指定する必要はありません。

バインド機構はfind()/findFirst()/findFirstBy<プロパティ名>のいずれでも利用できます。一方、count()、sum()、average()といったメソッドには利用できません。

取得したレコードの初期化

DBからデータを取得した後に、何かしらの初期化処理が必要な場合があります。このようなメソッドは、「afterFetch」メソッドとして定義できます。このイベントはインスタンスの作成とデータの代入時に実行されます。

もしgetter/setterを使っていれば、アクセス時に初期化することもできます。

今回はここまで

以上で、データ取得の基本は終わりです(公式ドキュメントのInitializing/Preparing fetched recordsまで)。次回は、Relationships between Modelsから先、複数テーブルの扱いをみていきます。