php-activerecord

- -

php-activerecord というORMライブラリを一部だけ読みました.

http://www.phpactiverecord.org/
github : jpfuentes2/php-activerecord

名前のとおり ActiveRecord パターンのphp実装で、Ruby on Rails に影響されているようです.

インストール

もちろん composer でインストールできます.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ curl -sS https://getcomposer.org/installer | php
$ vi composer.json
{
  "require": {
    "php-activerecord/php-activerecord": "*"
  }
}

$ php composer.phar install
Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing php-activerecord/php-activerecord (v1.1.2)
    Downloading: 100%
    Downloading: 100%

使い方

README に詳しく書いてあります.

読む

  • Convention over Configuration により少ないコード量でCRUD操作ができる
  • テーブルまたはビューのレコードとインスタンスが 1 対 1 で関連づく

このあたりの特徴の実装を、下記 Read 操作をした場合で見ていきます.

1
2
<?php
Post::find_by_name('foo');

未定義の static メソッドなので Model::__callStataic が呼ばれ、第1引数で受け取るメソッド名($method)を使って取得条件を決定します.

Model.php

1
2
3
4
5
6
7
8
9
10
<?php
public static function __callStatic($method, $args)
{

    // ...

    if (substr($method,0,7) === 'find_by')
    {
        $attributes = substr($method,8);
        $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),$attributes,$args,static::$alias_attribute);

取得条件を決定する SQLBuilder:: create_conditions_from_underscored_string は、find_by 以降の部分を正規表現で分解・置換して、SQLを組み立てています.

SQLBuilder.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
public static function create_conditions_from_underscored_string(Connection $connection, $name, &$values=array(), &$map=null)
{

    // ...

    $parts = preg_split('/(_and_|_or_)/i',$name,-1,PREG_SPLIT_DELIM_CAPTURE);


    // ...

    $conditions[0] .= preg_replace(array('/_and_/i','/_or_/i'),array(' AND ',' OR '),$parts[$i-1]);

    // ...

    $bind = is_array($values[$j]) ? ' IN(?)' : '=?';

これで、Read操作の動的メソッドの仕組みがわかりました.続いて、先ほどの Model::__callStatic に戻って、テーブルとの関連付け(参照先テーブルの決定)を見ていきます.

Model::find に取得条件を渡して、結果を返しています. $create は、 “find_or_create_by_***” で始まるメソッド(該当レコードがなければ作成)を呼び出した場合に true が入ります.

Model.php

1
2
3
4
5
<?php
if (!($ret = static::find('first',$options)) && $create)
    return static::create( ....

return $ret;

Model::find では、取得条件の妥当性を確認した後、Table::find に処理を委譲しています.

Model.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
public static function find(/* $type, $options */)
{

    //...

    $args = func_get_args();
    $options = static::extract_and_validate_options($args);

    // ...

    $list = static::table()->find($options);
    return $single ? (!empty($list) ? $list[0] : null) : $list;

}

public static function table()
{
    return Table::load(get_called_class());
}

Table::load() でインスタンスを生成するタイミングで、対象のテーブル名が決定します.

Table.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
public static function load($model_class_name)
{
    self::$cache[$model_class_name] = new Table($model_class_name);

    // ...
}

public function __construct($class_name)
{
    // ...

    $this->set_table_name();
}

テーブル名はデフォルトでクラス名が使われ(Utils::pluralizeによって複数形等の変換がされる)、 $table または $table_name スタティック変数が定義されていればそれが優先されます.

Table.php

1
2
3
4
5
6
7
8
9
<?php
private function set_table_name()
{
    if (($table = $this->class->getStaticPropertyValue('table',null)) || ($table = $this->class->getStaticPropertyValue('table_name',null)))
        $this->table = $table;
    else
    {
        $this->table = Inflector::instance()->tableize($this->class->getName());
    // ...

中途半端ですが集中力が切れたのでこれで終わりにします…

Comments