ActiveRecord

バージョン3.1から使用可能です。

Active Records are objects that wrap a row in a database table or view, encapsulate the database access and add domain logic on that data. The basics of an Active Record are business classes, e.g., a Products class, that match very closely the record structure of an underlying database table. Each Active Record will be responsible for saving and loading data to and from the database. ActiveRecords は、データベーステーブルまたはビューにおいてデータ列を内包し、データベースアクセスをカプセル化し、ドメインロジックをそのデータの上に追加するオブジェクトです。ActiveRecord の基本は、ビジネスクラス、例えば製品クラスの様なもので、それは非常に密接に、潜在的なデータベーステーブルのレコード構造とマッチしています。各ActiveRecordは、データをデータベースに(から)保存し、ロードする役割を担います。
Info
The data structure of an Active Record should match that of a table in the database. Each column of a table should have a corresponding member variable or property in the Active Record class represents the table.
一口メモ
ActiveRecordのデータ構造はデータベースにおけるテーブルの構造とマッチするはずです。テーブルの個々の項目は、テーブルを表現するActiveRecord クラスにおいて、各々の項目に対応した一連の変数もしくはプロパティとなるはずです。

ActiveRecord を使うとき

Active Record is a good choice for domain logic that isn't too complex, such as creates, reads, updates, and deletes. Derivations and validations based on a single record work well in this structure. Active Record has the primary advantage of simplicity. It's easy to build Active Records, and they are easy to understand. Active Recordは、作成、読込み、アップデート、 削除などの複雑ではないドメインロジックに対してよい働きをします。1つのレコードに基づいた派生と有効化は、この構造の中でよい働きをします。Active Recordは主にシンプルさを利点としています。Active Recordを構築することは容易で、理解しやすいといえます。 However, as your business logic grows in complexity, you'll soon want to use your object's direct relationships, collections, inheritance, and so forth. These don't map easily onto Active Record, and adding them piecemeal gets very messy. Another argument against Active Record is the fact that it couples the object design to the database design. This makes it more difficult to refactor as a project goes forward. しかし、あなたのビジネス論理が複雑さにおいて増加するにつれて、あなたは、すぐあなたのオブジェクトの直接関係、コレクション、継承などを使いたいでしょう。これらはActive Recordの上に容易に地図を作らなく、こま切れにそれらを追加することは非常に乱雑になります。Active Recordに反対する別の論拠は、それがデータベース設計にオブジェクトデザインを結び付けるという事実です。プロジェクトが前に行く時に、これは、再ファクターをより難しくします。 The alternative is to use a Data Mapper that separates the roles of the business object and how these objects are stored. Prado provides a complimentary choice between Active Record and SqlMap Data Mapper. A SqlMap Data Mapper can be used to load Active Record objects, in turn; these Active Record objects can be used to update the database. The "relationship" between Active Records and SqlMap is illustrated in the following diagram. More details regarding the SqlMap Data Mapper can be found in the SqlMap Manual. 選択肢は、ビジネスオブジェクトとどうこれらのオブジェクトが蓄えられるかの役割を分離するデータマッパーを使うことです。プラドはActive RecordとSqlMapデータ マッパーの間で無料の選択を提供します。SqlMapデータマッパーは、次々Active Recordオブジェクトをロードするために使われることができます;これらのActive Recordオブジェクトは、データベースをアップデートするために使われることができます。Active RecordとSqlMapの間の「関係」は以下の図において説明されます。SqlMapデータマッパーについてのより多くの詳細はSqlMapマニュアルの中で発見されることができます。
sqlmap_active_record.png
The Active Record class has functionality to perform the following tasks.
  • Create, Retrieve, Update and Delete records.
  • Finder methods to wrap commonly used SQL queries and return Active Record objects.
  • Fetch relationships (related foreign objects) such as "has many", "has one", "belongs to" and "has many" via association table.
  • Lazy loading of relationships.
Active Recordクラス、以下のタスクを実現するための機能を持っています。
  • レコードのcreate retrieve,update,delete。
  • 一般的に使われるSQLクエリーを包括し、Active Recordオブジェクトを返すファインダーメソッド
  • 関連しているテーブルを通して、「複数該当しました」、「一つ該当しました」、「含んでいます」などの関係(関連している外部のオブジェクト)の取り出し。
  • リレーションシップのぐーたらロード

インプリケーションのデザイン

Prado's implementation of Active Record does not maintain referential identity. Each object obtained using Active Record is a copy of the data in the database. For example, If you ask for a particular customer and get back a Customer object, the next time you ask for that customer you get back another instance of a Customer object. This implies that a strict comparison (i.e., using ===) will return false, while loose comparison (i.e., using ==) will return true if the object values are equal by loose comparison.
プラドのActive Recordのインプリメンテーションは参照用の同一性を保持していません。Active Recordを使って得られたオブジェクトはデータベース内のデータのコピーです。例えば、あなたが特定のカスタマーを問い合わせ、そのカスタマーオブジェクトを取得したとします、そして次にあなたがそのカスタマーを問い合わせたときには、あなたは別のカスタマーオブジェクトのインスタンスを取得します。これは、厳密な比較(すなわち === )では false が戻り値となりますが、オブジェクトが緩やかな比較において同等であるとみなされる様な、ゆるやかな比較(すなわち == )では true となることを示しています。
This is design implication related to the following question. "Do you think of the customer as an object, of which there's only one, or do you think of the objects you operate on as copies of the database?" Other O/R mappings will imply that there is only one Customer object with custID 100, and it literally is that customer. If you get the customer and change a field on it, then you have now changed that customer. "That contracts with: you have changed this copy of the customer, but not that copy. And if two people update the customer on two copies of the object, whoever updates first, or maybe last, wins." [A. Hejlsberg 2003]
これはインプリケーションのデザインであり、次の問いかけと関連しています。「あなたは、カスタマーを、オブジェクト(それが一人であっても)とみなしていますか?または、あなたは、あなたがデータベースのコピーとして作用するオブジェクトについて考えますか?」他のO/Rマッピング(Object-Relational Mapping)は、custID 100を持つカスタマーオブジェクトは1つしかないことを示すであろうし、それは文字どおりにそのカスタマーです。もしもそのカスタマーを取得して、その上のフィールドを変更するならば、そのカスタマーを変更したことになります。「(それとは対照的なのは・・)あなたは顧客のこのコピーを変更したけれども、そのコピーを交換しませんでした。そして、2人の人々がオブジェクトの2つのコピーの上でカスタマーを更新したとすると、一般的には後で更新したほうが優先されます。」[A. Hejlsberg 2003]

Database Supported

The Active Record implementation utilizes the Prado DAO classes for data access. The current Active Record implementation supports the following database. Active RecordインプリメンテーションはデータアクセスのためにプラドDAOクラスを利用します。現在のActive Recordインプリメンテーションは以下のデータベースをサポートします。
Support for other databases can be provided when there are sufficient demands.
多数ご要望があれば、その他のデータベースのサポートが提供されます。

Defining an Active Record

Let us consider the following "users" table that contains two columns named "username" and "email", where "username" is also the primary key. 「username」と「email」(「username」はプライマリキーです。)と名付けられた2つのカラムを含んでいる次のような「users」テーブルを想定してみましょう。
CREATE TABLE users
(
   username VARCHAR( 20 ) NOT NULL ,
   email VARCHAR( 200 ) ,
   PRIMARY KEY ( username )
);
Next we define our Active Record class that corresponds to the "users" table. 次に「users」テーブルに対応しているActive Recordクラスを定義します。
class UserRecord extends TActiveRecord
{
   const TABLE='users'; //table name

   public $username; //the column named "username" in the "users" table
   public $email;

   /**
    * @return TActiveRecord active record finder instance
    */
   public static function finder($className=__CLASS__)
   {
       return parent::finder($className);
   }
}
Each column of the "users" table must have corresponding property of the same name as the column name in the UserRecord class. Of course, you also define additional member variables or properties that does not exist in the table structure. The class constant TABLE is optional when the class name is the same as the table name in the database, otherwise TABLE must specify the table name that corresponds to your Active Record class. 「users」テーブルの各コラムはUserRecordクラスにおけるカラム名と同じ名前の対応する特性を持たなければなりません。もちろん、あなたはまた、テーブル構造の中に存在していない追加のメンバー変数またはプロパティを定義します。クラス名がデータベースのテーブル名と同じ時には、クラス定数TABLEはオプションです。そうでない場合には、TABLEは、あなたのActive Recordクラスと一致しているテーブル名を指定しなければなりません。
Tip
You may specify qualified table names. E.g. for MySQL, TABLE = "`database1`.`table1`".
ワンポイント
あなたはのテーブル名を完全パスで指定してさしつかえありません。例えばMySQLでは、TABLE = "`database1`.`table1`" となります。
Since TActiveRecord extends TComponent, setter and getter methods can be defined to allow control over how variables are set and returned. For example, adding a $level property to the UserRecord class: TActiveRecordがTComponentを拡張するので、setter と getter のメソッドは、変数のセットと値取得をいかにするかについての制御の為に定義されることができます。例えば$levelプロパティをUserRecordクラスに追加するときは・・・。
class UserRecord extends TActiveRecord {
   ... //existing definitions as above

   private $_level;
   public function setLevel($value) {
       $this->_level=TPropertyValue::ensureInteger($value,0);
   }
   public function getLevel($value){
       return $this->_level;
   }
}
More details regarding TComponent can be found in the Components documentation. Later we shall use the getter/setters to allow for lazy loading of relationship objects. TComponentについてのより多くの詳細はコンポーネントドキュメンテーションの中をご参照ください。後で、オブジェクトリレーションの簡単ロード の為に、この getter/setter を使います。
Info
TActiveRecord can also work with database views by specifying the constant TABLE corresponding to the view name. However, objects returned from views are read-only, calling the save() or delete() method will raise an exception.
Info
TActiveRecordは、ビューネームと対応している定数TABLEを指定することによってデータベースビューとして作動することもできます。しかし、ビューから戻ったオブジェクトはリードオンリーであり、save()またはdelete()メソッドを呼ぶことは、例外 を引き起こすでしょう。
The static method finder() returns an UserRecord instance that can be used to load records from the database. The loading of records using the finder methods is discussed a little later. The TActiveRecord::finder() static method takes the name of an Active Record class as parameter. 静的なメソッドfinder()は、データベースからレコードをロードするために使われることができるUserRecordインスタンスを返します。finderメソッドを使っているレコードのロードはこの後で解説します。TActiveRecord::finder()静的なメソッドはパラメータとしてActive Recordクラスの名前で実行します。

Setting up a database connection

A default database connection for Active Record can be set as follows. See Establishing Database Connection for further details regarding creation of database connection in general. Active Recordのためのデフォルトデータベース接続は次の通り設定されることができます。一般的なデータベース接続構築についてより詳細な参照については、Estableshing Database Connectionを参照してください。
//create a connection and give it to the Active Record manager.
$dsn = 'pgsql:host=localhost;dbname=test'; //Postgres SQL
$conn = new TDbConnection($dsn, 'dbuser','dbpass');
TActiveRecordManager::getInstance()->setDbConnection($conn);
Alternatively, you can create a base class and override the getDbConnection() method to return a database connection. This is a simple way to permit multiple connections and multiple databases. The following code demonstrates defining the database connection in a base class (not need to set the DB connection anywhere else). 代替的に、ベースクラスを作成しデータベース接続を返すgetDbConnection()メソッドを無効にすることもできます。これは、複式接続と複数のデータベースを許す簡単な方法です。以下の例は、(他のどこにでも設定する必要でないDB接続の)ベースクラスにおいてデータベース接続を定義することをあらわしています。
class MyDb1Record extends TActiveRecord
{
   public function getDbConnection()
   {
       static $conn;
       if($conn===null)
           $conn = new TDbConnection('xxx','yyy','zzz');
       return $conn;
   }
}
class MyDb2Record extends TActiveRecord
{
   public function getDbConnection()
   {
       static $conn;
       if($conn===null)
           $conn = new TDbConnection('aaa','bbb','ccc');
       return $conn;
   }
}

Using application.xml within the Prado Framework

The default database connection can also be configured using a <module> tag in the application.xml or config.xml as follows. デフォルトデータベース接続は、また、次の通りapplication.xmlまたはconfig.xmlにおいて<module>タグを使って、設定されることができます。
<modules>
 <module class="System.Data.ActiveRecord.TActiveRecordConfig" EnableCache="true">
   <database ConnectionString="pgsql:host=localhost;dbname=test"
       Username="dbuser" Password="dbpass" />
 </module>
</modules>
Tips
The EnableCache attribute when set to "true" will cache the table meta data, that is, the table columns names, indexes and constraints are saved in the cache and reused. You must clear or disable the cache if you wish to see changes made to your table definitions. A cache module must also be defined for the cache to function.
メモ
"true" へのセットするときのEnableCache属性は、テーブルメタデータをキャッシュします。すなわちテーブルカラム名、indexs、制約はキャッシュに保存されて、再利用されます。あなたが、あなたのテーブル定義に変化が起こされるのを見ることを望むならば、あなたはキャッシュをクリアするか、使用不可にしなければなりません。キャッシュモジュールは、また、キャッシュが作動するように定義されなければなりません。
A ConnectionID property can be specified with value corresponding to another TDataSourceConfig module configuration's ID value. This allows the same database connection to be used in other modules such as SqlMap. ConnectionIDプロパティは別のTDataSourceConfigモジュールコンフィギュレーションのID値と一致している値によって指定されることができます。これは、同じデータベース接続がSqlMapなどの他のモジュールの中で使われることを可能にします。
<modules>
 <module class="System.Data.TDataSourceConfig" id="db1">
   <database ConnectionString="pgsql:host=localhost;dbname=test"
       Username="dbuser" Password="dbpass" />
 </module>

 <module class="System.Data.ActiveRecord.TActiveRecordConfig"
       ConnectionID="db1" EnableCache="true"  />

 <module class="System.Data.SqlMap.TSqlMapConfig"
       ConnectionID="db1"  ... />
</modules>

Loading data from the database

The TActiveRecord class provides many convenient methods to find records from the database. The simplest is finding one record by matching a primary key or a composite key (primary keys that consists of multiple columns). See the API Manual for more details. TActiveRecordクラスは、データベースからレコードを見つける多くの便利な方法を提供します。最も簡単なものは、プライマリキーまたは複合キー(複数のカラムからなるプライマリキー)とマッチすることによって1つのレコードを見つけるものです。詳細についてAPIマニュアルを見てください。
Info
All finder methods that may return 1 record only will return null if no matching data is found. All finder methods that return an array of records will return an empty array if no matching data is found.
Info
マッチしているデータが一つも見つからないならば、1つのレコードだけを戻すかもしれないすべてのファインダーメソッドはNULLを戻すでしょう。マッチしているデータが一つも見つからないならば、レコードの配列を戻すすべてのファインダーメソッドは空の配列を戻すでしょう。

findByPk()

Finds one record using only a primary key or a composite key. プライマリキーもしくは複合キーを使い一つのレコードの検索
$finder = UserRecord::finder();
$user = $finder->findByPk($primaryKey);

//when the table uses a composite key
$record = $finder->findByPk($key1, $key2, ...);
$record = $finder->findByPk(array($key1, $key2,...));

findAllByPks()

Finds multiple records using a list of primary keys or composite keys. The following are equivalent for primary keys (primary key consisting of only one column/field). プライマリキーまたは複合キーのリストを使い複数のレコードを検索します。以下は複数のプライマリキー(ただ1つのカラム/フィールドから成るプライマリキー)と同じ意味です。
$finder = UserRecord::finder();
$users = $finder->findAllByPks($key1, $key2, ...);
$users = $finder->findAllByPks(array($key1, $key2, ...));

find()

Finds one single record that matches the criteria. The criteria can be a partial SQL string or a TActiveRecordCriteria object. 条件式とマッチしている1つの単一のレコードを検索します。条件式は部分的なSQLストリングまたはTActiveRecordCriteriaオブジェクトでOKです。
$finder = UserRecord::finder();

//:name and :pass are place holders for specific values of $name and $pass
$finder->find('username = :name AND password = :pass',
                         array(':name'=>$name, ':pass'=>$pass));

//using position place holders
$finder->find('username = ? AND password = ?', array($name, $pass));
//same as above
$finder->find('username = ? AND password = ?', $name, $pass);

//$criteria is of TActiveRecordCriteria
$finder->find($criteria); //the 2nd parameter for find() is ignored.
The TActiveRecordCriteria class has the following properties: TActiveRecordCriteriaクラスは以下のプロパティを持っています。
  • Parameters -- name value parameter pairs.
  • OrdersBy -- column name and ordering pairs.
  • Condition -- parts of the WHERE SQL conditions.
  • Limit -- maximum number of records to return.
  • Offset -- record offset in the table.
$criteria = new TActiveRecordCriteria;
$criteria->Condition = 'username = :name AND password = :pass';
$criteria->Parameters[':name'] = 'admin';
$criteria->Parameters[':pass'] = 'prado';
$criteria->OrdersBy['level'] = 'desc';
$criteria->OrdersBy['name'] = 'asc';
$criteria->Limit = 10;
$criteria->Offset = 20;
Note
For MSSQL and when Limit and Offset are positive integer values. The actual query to be executed is modified by the TMssqlCommandBuilder class according to http://troels.arvin.dk/db/rdbms/ to emulate the Limit and Offset conditions.
メモ
MSSQLの場合やLimitとOffsetが正の整数の値である時の為に、http://troels.arvin.dk/db/rdbms/に従い、LimitとOffsetの条件をエミュレートする為に、実行される実際のクエリーはTMssqlCommandBuilderクラスによって修正されます。

findAll()

Same as find() but returns an array of objects.
find()メソッドと同じですが、返される値はオブジェクトの配列です。

findBy*() and findAllBy*()

Dynamic find method using parts of the method name as search criteria. Method names starting with findBy return 1 record only and method names starting with findAllBy return an array of records. The condition is taken as part of the method name after findBy or findAllBy. The following blocks of code are equivalent: 検索条件式としてメソッド名の一部を使っている動的なfindメソッドです。findByリターンによって1つのレコードだけを始めるメソッド名とfindAllByで始まるメソッド名はレコードの配列を返します。条件はfindByまたはfindAllByの後でメソッド名の一部として取られます。以下の一連のコードの通りです。:
$finder->findByName($name)
$finder->find('Name = ?', $name);

$finder->findByUsernameAndPassword($name,$pass);
$finder->findBy_Username_And_Password($name,$pass);
$finder->find('Username = ? AND Password = ?', $name, $pass);

$finder->findAllByAge($age);
$finder->findAll('Age = ?', $age);
Tip
You may also use a combination of AND and OR as a condition in the dynamic methods.
メモ
あなたは動的なメソッドにおける条件としてまたANDとORの組み合わせを使ってさしつかえありません。

findBySql() and findAllBySql()

Finds records using full SQL where findBySql() return an Active Record and findAllBySql()returns an array of record objects. For each column returned, the corresponding Active Record class must define a member variable or property for each corresponding column name.
完全なSQLを使って、findBySql()はActive Recordを返し、findAllBySql()はレコードオブジェクトの配列を返します。各項目を返すために、対応するActive Recordクラスは各対応するカラム名のメンバー変数またはプロパティを定義しなければなりません。
class UserRecord2 extends UserRecord
{
   public $another_value;
}
$sql = "SELECT users.*, 'hello' as another_value FROM users";
$users = TActiveRecord::finder('UserRecord2')->findAllBySql($sql);

count()

Find the number of matchings records, accepts same parameters as the findAll() method.
合致したレコード数の検索であり、findAll()メソッドと同じパラメータとなります。

Inserting and updating records

Add a new record using TActiveRecord is very simple, just create a new Active Record object and call the save() method. E.g. TActiveRecordを使っている新しいレコードを追加することは非常に簡単であり、新しいActive Recordオブジェクトを作成し、save()メソッドを呼び出すだけです。例えば
$user1 = new UserRecord();
$user1->username = "admin";
$user1->email = "admin@example.com";
$user1->save(); //insert a new record

$data = array('username'=>'admin', 'email'=>'admin@example.com');
$user2 = new UserRecord($data); //create by passing some existing data
$user2->save(); //insert a new record
Tip
The objects are updated with the primary key of those the tables that contains definitions that automatically creates a primary key for the newly insert records. For example, if you insert a new record into a MySQL table that has columns defined with "autoincrement", the Active Record objects will be updated with the new incremented value.
メモ
オブジェクトは、それら 新しい挿入レコードのためのプライマリキーを自動的に作成する定義を含んでいるテーブル のプライマリキーを同時に生成して、アップデートされます。例えば、あなたが新しいレコードを、"autoincrement" によって定義されたカラムを持っているMySQLテーブルに挿入するならば、Active Recordオブジェクトは新しいインクリメントされた値によってアップデートされるでしょう。
To update a record in the database, just change one or more properties of the Active Record object that has been loaded from the database and then call the save() method.
データベースでレコードをアップデートするために、ただ、データベースからロードされているActive Recordオブジェクトの1つ以上のプロパティを変更してください、それから、save()メソッドを呼び出してください。
$user = UserRecord::finder()->findByName('admin');
$user->email="test@example.com"; //change property
$user->save(); //update it.
Active Record objects have a simple life-cycle illustrated in the following diagram.
Active Recordオブジェクトは以下の図において説明された簡単なライフサイクルを持っています。
object_states.png
We see that new TActiveRecord objects are created by either using one of the find*() methods or using creating a new instance by using PHP's new keyword. Objects created by a find*() method starts with clean state. New instance of TActiveRecord created other than by a find*() method starts with new state. Whenever you call the save() method on the TActiveRecord object, the object enters the clean state. Objects in the clean becomes dirty whenever one of more of its internal states are changed. Calling the delete() method on the object ends the object life-cycle, no further actions can be performed on the object.
私達は、新しいTActiveRecordオブジェクトが、発見*()メソッドの1つを使うか、PHPの新しいキーワードを使って新しいインスタンスを作成して使って作成されるとわかります。 オブジェクトは発見*()によってクリーンな状態を持つメソッド開始を作成しました。 発見*()メソッドによる以外作成されたTActiveRecordの新しいインスタンスは新しい状態で始まります。 あなたがTActiveRecordオブジェクトの上のsave()メソッドを呼び出す時はいつでも、オブジェクトはクリーンな状態に入ります。 その内部状態のより多くの1つが変更される時はいつでも、クリーンにおけるオブジェクトは汚くなります。 オブジェクト終わりのdelete()メソッドをオブジェクトライフサイクルと呼んで、どのこれ以上の行動もオブジェクトの上で実行されることができません。