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

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

モデルの計量

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

COUNTの例:

<?php

// 従業員の数は?
$rowcount = Employees::count();

// 従業員が割り当てられているエリアの数は?
$rowcount = Employees::count(array("distinct" => "area"));

// Testing部門には何人の従業員がいる?
$rowcount = Employees::count("area = 'Testing'");

// 従業員を、部門ごとにグループ分けして数える
$group = Employees::count(array("group" => "area"));
foreach ($group as $row) {
   echo "There are ", $row->rowcount, " in ", $row->area;
}

// 従業員を、部門ごとにグループ分けして数え、数の少ない順に並べる
$group = Employees::count(array(
    "group" => "area",
    "order" => "rowcount"
));

// バインド機構を使ってSQLインジェクションを防ぐ
$group = Employees::count(array(
    "type > ?0",
    "bind" => array($type)
));

SUMの例:

<?php

// 全ての従業員の給料の合計は?
$total = Employees::sum(array("column" => "salary"));

// セールス部門の従業員の給料の合計は?
$total = Employees::sum(array(
    "column"     => "salary",
    "conditions" => "area = 'Sales'"
));

// 部門ごとの従業員の給料の合計は?
$group = Employees::sum(array(
    "column" => "salary",
    "group"  => "area"
));
foreach ($group as $row) {
   echo "The sum of salaries of the ", $row->area, " is ", $row->sumatory;
}

// 部門ごとの従業員の給料の合計を算出し、合計額が多い順に並べる
$group = Employees::sum(array(
    "column" => "salary",
    "group"  => "area",
    "order"  => "sumatory DESC"
));

// バインド機構を使ってSQLインジェクションを防ぐ
$group = Employees::sum(array(
    "conditions" => "area > ?0",
    "bind" => array($area)
));

AVERAGEの例:

<?php

// 全従業員の平均給料は?
$average = Employees::average(array("column" => "salary"));

// セールス部門の従業員の平均給料は?
$average = Employees::average(array(
    "column" => "salary",
    "conditions" => "area = 'Sales'"
));

// バインド機構を使ってSQLインジェクションを防ぐ
$average = Employees::average(array(
    "column" => "age",
    "conditions" => "area > ?0",
    "bind" => array($area)
));

MAX/MINの例:

<?php

// 全従業員のうち、最高齢は?
$age = Employees::maximum(array("column" => "age"));

// セールス部門の最高齢は?
$age = Employees::maximum(array(
    "column" => "age",
    "conditions" => "area = 'Sales'"
));

// 全従業員で最も少ない給料は?
$salary = Employees::minimum(array("column" => "salary"));

ハイドレーションモード

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

<?php

// オブジェクトの結果セットを操作
foreach (Robots::find() as $robot) {
    $robot->year = 2000;
    $robot->save();
}

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

<?php

use Phalcon\Mvc\Model\Resultset;

$robots = Robots::find();

// 全てのロボットを配列として返す
$robots->setHydrateMode(Resultset::HYDRATE_ARRAYS);

foreach ($robots as $robot) {
    echo $robot['year'], PHP_EOL;
}

// 全てのロボットをstdClassのインスタンスとして返す
$robots->setHydrateMode(Resultset::HYDRATE_OBJECTS);

foreach ($robots as $robot) {
    echo $robot->year, PHP_EOL;
}

// 全てのロボットをRobotsモデルのインスタンスとして返す
$robots->setHydrateMode(Resultset::HYDRATE_RECORDS);

foreach ($robots as $robot) {
    echo $robot->year, PHP_EOL;
}

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

<?php

use Phalcon\Mvc\Model\Resultset;

$robots = Robots::find(array(
    'hydration' => Resultset::HYDRATE_ARRAYS
));

foreach ($robots as $robot) {
    echo $robot['year'], PHP_EOL;
}

レコードの作成・更新

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

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

<?php

$robot       = new Robots();
$robot->type = "mechanical";
$robot->name = "Astro Boy";
$robot->year = 1952;
if ($robot->save() == false) {
    echo "今はロボットを保存できないようです: \n";
    foreach ($robot->getMessages() as $message) {
        echo $message, "\n";
    }
} else {
    echo "おめでとうございます、新しいロボットが作成されました!";
}

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

<?php

$robot = new Robots();
$robot->save(array(
    "type" => "mechanical",
    "name" => "Astro Boy",
    "year" => 1952
));

確実に作成・更新する

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

<?php

$robot       = new Robots();
$robot->type = "mechanical";
$robot->name = "Astro Boy";
$robot->year = 1952;

// このレコードは絶対に新規作成されなければならない
if ($robot->create() === false) {
    echo "今はロボットを保存できないようです: \n";
    foreach ($robot->getMessages() as $message) {
        echo $message, "\n";
    }
} else {
    echo "おめでとうございます、新しいロボットが作成されました!";
}

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

自動採番されるid

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

?php

$robot->save();

echo "生成されたid: ", $robot->id;

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

関連テーブルの一括保存

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

<?php

// アーティストを作成
$artist = new Artists();
$artist->name = 'Shinichi Osawa';
$artist->country = 'Japan';

// アルバムを作成
$album = new Albums();
$album->name = 'The One';
$album->artist = $artist; // アーティストを代入
$album->year = 2008;

// アルバムとアーティストの両方を保存
$album->save();

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

<?php

// 既存のレコードの取得
$artist = Artists::findFirst('name = "Shinichi Osawa"');

// アルバムの新規作成
$album = new Albums();
$album->name = 'The One';
$album->artist = $artist;

$songs = array();

// 最初の曲の作成
$songs[0] = new Songs();
$songs[0]->name = 'Star Guitar';
$songs[0]->duration = '5:54';

// 2曲目の作成
$songs[1] = new Songs();
$songs[1]->name = 'Last Days';
$songs[1]->duration = '4:29';

// songs配列を代入
$album->songs = $songs;

// アルバムとアルバムに含まれる曲を一度に保存
$album->save();

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

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

  • 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のモデルによるバリデーションの方法を紹介します。