Strings of Life

PHP/Phalcon/MySQL/JavaScript/RegExp/Ruby/Perl/ActionScript

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等でもいちいちブラウザ設定をリセットする必要があるのは結構面倒でした。設定の共有は良し悪しですね。

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

リレーションの定義

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

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

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

CREATE TABLE `robots` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(70) NOT NULL,
    `type` varchar(32) NOT NULL,
    `year` int(11) NOT NULL,
    PRIMARY KEY (`id`)
);

CREATE TABLE `robots_parts` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `robots_id` int(10) NOT NULL,
    `parts_id` int(10) NOT NULL,
    `created_at` DATE NOT NULL,
    PRIMARY KEY (`id`),
    KEY `robots_id` (`robots_id`),
    KEY `parts_id` (`parts_id`)
);

CREATE TABLE `parts` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(70) NOT NULL,
    PRIMARY KEY (`id`)
);
  • 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現在、リレーションの自動生成には対応していません。リレーションの記述は手動で行う必要があります)。

<?php

class Robots extends \Phalcon\Mvc\Model
{
    public $id;

    public $name;

    public function initialize()
    {
        $this->hasMany("id", "RobotsParts", "robots_id");
    }

}
<?php

class Parts extends \Phalcon\Mvc\Model
{

    public $id;

    public $name;

    public function initialize()
    {
        $this->hasMany("id", "RobotsParts", "parts_id");
    }

}
<?php

class RobotsParts extends \Phalcon\Mvc\Model
{

    public $id;

    public $robots_id;

    public $parts_id;

    public function initialize()
    {
        $this->belongsTo("robots_id", "Robots", "id");
        $this->belongsTo("parts_id", "Parts", "id");
    }

}

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

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

<?php

class Robots extends \Phalcon\Mvc\Model
{
    public $id;

    public $name;

    public function initialize()
    {
        $this->hasManyToMany(
            "id",
            "RobotsParts",
            "robots_id", "parts_id",
            "Parts",
            "id"
        );
    }
}

リレーションを活用する

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

<?php

$robot = Robots::findFirst(2);
foreach ($robot->robotsParts as $robotPart) {
    echo $robotPart->parts->name, "\n";
}

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

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

<?php

$robot = Robots::findFirst();
$robotsParts = $robot->robotsParts; // RobotsPartsの関連レコード

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

<?php

$robot = Robots::findFirst();
$robotsParts = $robot->getRobotsParts(); // RobotsPartsの関連レコード
$robotsParts = $robot->getRobotsParts(array('limit' => 5)); // パラメータを渡す

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

<?php

$robot = Robots::findFirst(2);

// Robotsと RobotsParts には1対多(hasMany)の関係がある
$robotsParts = $robot->robotsParts;

// 条件に合ったパーツだけを取得
$robotsParts = $robot->getRobotsParts("created_at = '2012-03-15'");

// パラメータをバインドする場合
$robotsParts = $robot->getRobotsParts(array(
    "created_at = :date:",
    "bind" => array("date" => "2012-03-15")
));

$robotPart = RobotsParts::findFirst(1);

// RobotsParts は Robots と多対1(belongsTo)の関係がある
$robot = $robotPart->robots;

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

<?php

$robot = Robots::findFirst(2);

// Robotsと RobotsParts には1対多(hasMany)の関係がある
$robotsParts = RobotsParts::find("robots_id = '" . $robot->id . "'");

// 条件に合ったパーツだけを取得
$robotsParts = RobotsParts::find(
    "robots_id = '" . $robot->id . "' AND created_at = '2012-03-15'"
);

$robotPart = RobotsParts::findFirst(1);

// RobotsParts は Robots と多対1(belongsTo)の関係がある
$robot = Robots::findFirst("id = '" . $robotPart->robots_id . "'");

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

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

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

<?php

$robot = Robots::findFirst(2);
echo "ロボットのパーツ数:", $robot->countRobotsParts(), "\n";

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

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

mysql> desc robots_similar;
+-------------------+------------------+------+-----+---------+----------------+
| Field             | Type             | Null | Key | Default | Extra          |
+-------------------+------------------+------+-----+---------+----------------+
| id                | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| robots_id         | int(10) unsigned | NO   | MUL | NULL    |                |
| similar_robots_id | int(10) unsigned | NO   |     | NULL    |                |
+-------------------+------------------+------+-----+---------+----------------+

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

ER:robots-robots_similar

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

<?php

class RobotsSimilar extends Phalcon\Mvc\Model
{

    public function initialize()
    {
        $this->belongsTo('robots_id', 'Robots', 'id');
        $this->belongsTo('similar_robots_id', 'Robots', 'id');
    }
}

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

<?php

$robotsSimilar = RobotsSimilar::findFirst();

// robots_idに基いて関連するレコードを返す
// belongsTo(多対1)なので返却されるのは1レコードのみ
// しかし、「getRobots」は複数のレコードを返すようにも思える
$robot = $robotsSimilar->getRobots();

// では、similar_robots_idに基いて関連するレコードを取得するにはどうすれば?
// リレーションの名前はどちらも同じ。。。

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

<?php

class RobotsSimilar extends Phalcon\Mvc\Model
{

    public function initialize()
    {
        $this->belongsTo('robots_id', 'Robots', 'id', array(
            'alias' => 'Robot'
        ));
        $this->belongsTo('similar_robots_id', 'Robots', 'id', array(
            'alias' => 'SimilarRobot'
        ));
    }

}

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

<?php

$robotsSimilar = RobotsSimilar::findFirst();

// robots_idに基いて関連レコードを返す
$robot = $robotsSimilar->getRobot();
$robot = $robotsSimilar->robot;

// similar_robots_idに基いて関連レコードを返す
$similarRobot = $robotsSimilar->getSimilarRobot();
$similarRobot = $robotsSimilar->similarRobot;

仮想外部キー制約

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

<?php

class RobotsParts extends \Phalcon\Mvc\Model
{

    public $id;

    public $robots_id;

    public $parts_id;

    public function initialize()
    {
        $this->belongsTo("robots_id", "Robots", "id", array(
            "foreignKey" => true
        ));

        $this->belongsTo("parts_id", "Parts", "id", array(
            "foreignKey" => array(
                "message" => "partsに存在しないpart_idを指定しています"
            )
        ));
    }

}

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

<?php

class Parts extends \Phalcon\Mvc\Model
{

    public function initialize()
    {
        $this->hasMany("id", "RobotsParts", "parts_id", array(
            "foreignKey" => array(
                "message" => "他のロボットが使用しているため、このパーツを削除できません"
            )
        ));
    }

}

CASCADEとRESTRICT

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

<?php

namespace Store\Models;

use Phalcon\Mvc\Model,
    Phalcon\Mvc\Model\Relation;

class Robots extends Model
{

    public $id;

    public $name;

    public function initialize()
    {
        $this->hasMany('id', 'Store\Models\Parts', 'robots_id', array(
            'foreignKey' => array(
                'action' => Relation::ACTION_CASCADE
            )
        ));
    }

}

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

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

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

<?php

namespace Store\Models;

use Phalcon\Mvc\Model;

class Robots extends Model
{

    public $id;

    public $name;

    public function initialize()
    {
        $this->hasMany('id', 'Store\Models\Parts', 'robots_id');
    }

}

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

$robot = new \Store\Models\Robots;
$parts = $robot->getRelations('Store\Models\Parts');

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

<?php

namespace Store\Models;

use Phalcon\Mvc\Model;

class Robots extends Model
{

    public $id;

    public $name;

    public function initialize()
    {
        $this->hasMany('id', 'Store\Models\Parts', 'robots_id', ['alias' => 'Parts']);
    }

}
$robot = new \Store\Models\Robots;
$parts = $robot->Parts;

今回はここまで

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

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

モデルの基本

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

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

class Robots extends \Phalcon\Mvc\Model
{

}

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

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

<?php

class Robots extends \Phalcon\Mvc\Model
{
    public function getSource()
    {
        return "the_robots";
    }
}

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

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

<?php

class Robots extends \Phalcon\Mvc\Model
{
    public function initialize()
    {
        $this->setSource("the_robots");
    }
}

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

<?php

class Robots extends \Phalcon\Mvc\Model
{
    public function onConstruct()
    {
        //...
    }
}

publicプロパティとgetter/setter

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

<?php

class Robots extends \Phalcon\Mvc\Model
{
    public $id;

    public $name;

    public $price;
}

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

<?php

class Robots extends \Phalcon\Mvc\Model
{
    protected $id;

    protected $name;

    protected $price;

    public function getId()
    {
        return $this->id;
    }

    public function setName($name)
    {
        // 名前の長さをチェック
        if (strlen($name) < 10) {
            throw new \InvalidArgumentException('名前が短すぎます');
        }
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setPrice($price)
    {
        // 値段をチェック
        if ($price < 0) {
            throw new \InvalidArgumentException('負の数の値段をつけることはできません');
        }
        $this->price = $price;
    }

    public function getPrice()
    {
        // 値段を浮動小数点数に変換
        return (double) $this->price;
    }
}

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

モデルの名前空間

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

<?php

namespace Store\Toys;

class Robots extends \Phalcon\Mvc\Model
{

}

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

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

mysql> select * from robots;
+----+------------+------------+------+
| id | name       | type       | year |
+----+------------+------------+------+
|  1 | Robotina   | mechanical | 1972 |
|  2 | Astro Boy  | mechanical | 1952 |
|  3 | Terminator | cyborg     | 2029 |
+----+------------+------------+------+

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

<?php

// id = 3 のレコードを検索
$robot = Robots::findFirst(3);

// "Terminator"と表示
echo $robot->name;

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

<?php

$robot = Robots::findFirst(3);
$robot->name = "RoboCop";
$robot->save();

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

レコードの検索

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

<?php

// ロボットは何体いる?
$robots = Robots::find();
echo "ロボットの数:", count($robots), "\n";

// typeがmechanicalであるロボットの数は?
$robots = Robots::find("type = 'mechanical'");
echo "mechanicalなロボットの数:", count($robots), "\n";

// typeがvirtualなロボットの一覧を取得して、name順にソート
$robots = Robots::find(array(
    "type = 'virtual'",
    "order" => "name"
));
foreach ($robots as $robot) {
    echo $robot->name, "\n";
}

// typeがvirtualなロボットの一覧を取得して、name順にソート(上限100件)
$robots = Robots::find(array(
    "type = 'virtual'",
    "order" => "name",
    "limit" => 100
));
foreach ($robots as $robot) {
   echo $robot->name, "\n";
}

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

<?php

// robotsテーブルの最初のロボットは?
$robot = Robots::findFirst();
echo "最初のロボットの名前:", $robot->name, "\n";

// typeがmechanicalな最初のロボットは?
$robot = Robots::findFirst("type = 'mechanical'");
echo "最初のmechanicalロボットの名前:", $robot->name, "\n";

// nameでソートした最初のvirtualロボットの名前
$robot = Robots::findFirst(array("type = 'virtual'", "order" => "name"));
echo "最初のvirtualロボットの名前:", $robot->name, "\n";

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

<?php

$robot = Robots::findFirst(array(
    "type = 'virtual'",
    "order" => "name DESC",
    "limit" => 30
));

$robots = Robots::find(array(
    "conditions" => "type = ?1",
    "bind"       => array(1 => "virtual")
));

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

パラメータ 説明
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

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

<?php

$robots = Robots::query()
    ->where("type = :type:")
    ->andWhere("year < 2000")
    ->bind(array("type" => "mechanical"))
    ->order("name")
    ->execute();

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

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

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

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

<?php

class Robots extends \Phalcon\Mvc\Model
{
    public $id;

    public $name;

    public $price;
}

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

<?php

$name = "Terminator";
$robot = Robots::findFirstByName($name);

モデルの結果セット

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

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

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

<?php

// 全てのロボットを取得
$robots = Robots::find();

// foreachでまわす
foreach ($robots as $robot) {
    echo $robot->name, "\n";
}

// whileでまわす
$robots->rewind();
while ($robots->valid()) {
    $robot = $robots->current();
    echo $robot->name, "\n";
    $robots->next();
}

// 結果数を数える
echo count($robots);

// 結果数を数える、もう1つの方法
echo $robots->count();

// 内部カーソルを3番めに進める
$robots->seek(2);
$robot = $robots->current();

// 結果セットの中での位置からロボットを取得
$robot = $robots[4];

// 特定の位置にレコードがあるかチェック
if (isset($robots[3])) {
   $robot = $robots[3];
}

// 結果セットの最初のレコードを取得
$robot = $robots->getFirst();

// 最後のレコードを取得
$robot = $robots->getLast();

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

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

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

<?php

// Partsモデルから全てのモデルを取得
$parts = Parts::find();

// 結果セットをファイルに保存
file_put_contents("cache.txt", serialize($parts));

// partsをファイルから取得
$parts = unserialize(file_get_contents("cache.txt"));

// partsをループでまわす
foreach ($parts as $part) {
   echo $part->id;
}

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

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

<?php

$customers = Customers::find()->filter(function($customer) {

    // 有効なEメールアドレスのあるcustomerだけを返す
    if (filter_var($customer->email, FILTER_VALIDATE_EMAIL)) {
        return $customer;
    }

});

パラメータのバインド

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

<?php

// 文字列のプレースホルダー(PDOと違って、「前後にコロン」なので注意!)
$conditions = "name = :name: AND type = :type:";
// $conditions = "name = :name AND type = :type"; //(「前にコロン」のPDO流だとNG)

// プレースホルダーと同じ文字列のキーの部分がパラメーターで置き換えられる
$parameters = array(
    "name" => "Robotina",
    "type" => "maid"
);

// クエリ実行
$robots = Robots::find(array(
    $conditions,
    "bind" => $parameters
));

// 数字のプレースホルダー(PDOと違って、?だけでなく、数字も必要)
$conditions = "name = ?0 AND type = ?1";
// $conditions = "name = ? AND type = ?"; // 「?だけ」のPDO流だとNG

$parameters = array("Robotina", "maid");
$robots     = Robots::find(array(
    $conditions,
    "bind" => $parameters
));

// プレースホルダーと同じ数字のキーの部分がパラメーターで置き換えられる
$conditions = "name = :name: AND type = ?1";

//Parameters whose keys are the same as placeholders
$parameters = array(
    "name" => "Robotina",
    1 => "maid"
);

// クエリ実行
$robots = Robots::find(array(
    $conditions,
    "bind" => $parameters
));

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

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

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

<?php

use \Phalcon\Db\Column;

// バインドするパラメータ
$parameters = array(
    "name" => "Robotina",
    "year" => 2008
);

// パラメーターの型(この型に自動で変換される)
$types = array(
    "year" => Column::BIND_PARAM_INT
);

// クエリ実行
$robots = Robots::find(array(
    "name = :name: AND year = :year:",
    "bind" => $parameters,
    "bindTypes" => $types
));

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

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

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

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

<?php

class Robots extends Phalcon\Mvc\Model
{

    public $id;

    public $name;

    public $status;

    // 保存前
    public function beforeSave()
    {
        // 配列を文字列に変換
        $this->status = join(',', $this->status);
    }

    // データ取得後
    public function afterFetch()
    {
        // 文字列を配列に変換
        $this->status = explode(',', $this->status);
    }
}

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

<?php

class Robots extends Phalcon\Mvc\Model
{
    protected $status;

    public function getStatus()
    {
        return explode(',', $this->status);
    }
}

今回はここまで

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

このページのトップヘ