Source for file ActiveRecord.php
Documentation is available at ActiveRecord.php
* Teeple2 - PHP5 Web Application Framework inspired by Seasar2
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
* @author Mitsutaka Sato <miztaka@gmail.com>
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* exception class for Teeple_ActiveRecord.
return parent::__construct($message);
* TODO oneToMany -- いったんなしにしとく。
* TODO oracle等のsequence対応。
* このクラスを継承して各テーブルのレコードを表すクラスを作成します。
* ファイルの配置場所は、ENTITY_DIR で定義されたディレクトリです。
* ファイル名は、`table名`.class.php, クラス名は Entity_`table名` とします。
* 各テーブルの以下のプロパティを定義する必要があります。
* // 使用するデータソース名(Teepleフレームワークを使用しない場合は不要。)
* public static $_DATASOURCE = "";
* public static $_TABLENAME = "";
* public static $_PK = array();
* public static $_COLUMNS = array();
* // PKが単一で、AUTOINCREMENT等の場合にTRUEをセット
* public static $_AUTO = TRUE;
* public static $_JOINCONFIG = array();
* 指定が無い場合は、DEFAULT_DATASOURCE で設定されているDataSource名が使用されます。
public static $_DATASOURCE =
"";
* スキーマを設定する場合は、"スキーマ.テーブル名"とします。
public static $_TABLENAME =
"";
* プライマリキーとなるカラム名を配列で指定します。
public static $_PK =
array('id');
* プライマリキーが自動セット(auto increment)かどうかを設定します。
public static $_AUTO =
TRUE;
* ここに設定してある定義は、$this->join('aliasname') を呼ぶことで初めて結合対象となる。<br/>
* 指定方法: 'アクセスするための別名' => 設定値の配列
* 'entity' => エンティティのクラス名
* 'columns' => 取得するカラム文字列(SQLにセットするのと同じ形式)
* 'type' => JOINのタイプ(SQLに書く形式と同じ)(省略した場合はINNER JOIN)
* 'relation' => JOINするためのリレーション設定
* 「本クラスのキー名 => 対象クラスのキー名」となります。
* public static $_JOINCONFIG = array(
* 'entity' => 'Entity_Fuga',
* 'columns' => 'foo, bar, hoge',
public static $_JOINCONFIG =
array();
* テーブル名が定義されていないときはクラス名称からEntity_を取り除いたものを
* @param object $pdo PDOインスタンスを指定します。
// TODO LoggerMangerに依存している
$this->_log =
LoggerManager::getLogger($class_name);
$ref =
new ReflectionClass($class_name);
$this->_tablename =
$ref->getStaticPropertyValue("_TABLENAME");
$this->_pk =
$ref->getStaticPropertyValue("_PK");
$this->_auto =
$ref->getStaticPropertyValue("_AUTO");
$this->_joinconfig =
$ref->getStaticPropertyValue("_JOINCONFIG");
if ($this->_tablename ==
"") {
* @return Teeple_ActiveRecord
$obj =
new $class_name($this->_pdo);
* この機能は必要? -> 必要 楽観的排他制御などに使う
* @param string $name カラム名称
* @param mixed $value カラム値
* @param string $name カラム名称
//--------------------- ここから流れるインターフェース ----------------------//
* $aliasnameで指定された _JOINCONFIGの設定で結合します。
* JOINする条件を追加する場合は第2引数以降に where()と同じ方法で指定します。
* $this->join('hoge')->join('hoge$fuga')
* のように、エイリアス名を $ で繋げて指定します。
* 'hoge'のEntityに定義されている _JOINCONFIG の 'fuga'が適用されます。
* @param mixed $aliasname エイリアス名
* @param string $condition 追加する条件
* @param string $params 可変長引数($condition)
* @return Teeple_ActiveRecord
$cond_params =
$args_ar[0];
if ($condition !=
null &&
$cond_params !=
null) {
$alias_ar =
explode('$', $aliasname);
if (count($alias_ar) ==
1) {
if (! isset
($this->_joinconfig[$aliasname])) {
$this->_join[$aliasname] =
$this->_joinconfig[$aliasname];
$base_name =
implode('$', $alias_ar);
if (! isset
($this->_join[$base_name])) {
$entity =
$this->_join[$base_name]['entity'];
if (! isset
($joinconfig[$child_name])) {
$this->_join[$aliasname] =
$joinconfig[$child_name];
if ($condition !=
null) {
$this->_join[$aliasname]['condition'] =
array($condition =>
$cond_params);
* @param String Where句です。プレースホルダーに?を使用します。カラム名は、エイリアス名.カラム名で指定します。(主テーブルのエイリアスは 'base'を指定します。)
* @param mixed プレースホルダーにセットする値です。複数ある場合は1つの配列を渡してもよいし、複数の引数として渡してもよいです。
* @return Teeple_ActiveRecord
public function where() {
if (! strlen($where_clause)) {
$this->_log->info("no where clause.");
//$this->_criteria[$where_clause] = $args_ar;
* property = ? の条件を追加します。
* $notnullonly が trueのときは $valueに値がセットされている場合のみ追加されます。
* falseのときは、 property IS NULL が追加されます。
* @param string $property プロパティ名
* @param boolean $notnullonly NULLチェックフラグ
* @return Teeple_ActiveRecord
public function eq($property, $value, $notnullonly=
true) {
if ($value ===
NULL ||
$value ===
"") {
$this->where("{
$property} IS NULL
");
$this->where("{
$property} = ?
", $value);
* property <> ? の条件を追加します。
* $notnullonly が trueのときは $valueに値がセットされている場合のみ追加されます。
* falseのときは、 property IS NOT NULL が追加されます。
* @param string $property プロパティ名
* @param boolean $notnullonly NULLチェックフラグ
* @return Teeple_ActiveRecord
public function ne($property, $value, $notnullonly=
true) {
if ($value ===
NULL ||
$value ===
"") {
$this->where("{
$property} IS NOT NULL
");
$this->where("{
$property} <> ?
", $value);
* property < ? の条件を追加します。
* $valueの値がセットされているときのみ追加します。
* @param string $property プロパティ名
* @return Teeple_ActiveRecord
public function lt($property, $value) {
if ($value ===
NULL ||
$value ===
"") {
$this->where("{
$property} < ?
", $value);
* property > ? の条件を追加します。
* $valueの値がセットされているときのみ追加します。
* @param string $property プロパティ名
* @return Teeple_ActiveRecord
public function gt($property, $value) {
if ($value ===
NULL ||
$value ===
"") {
$this->where("{
$property} > ?
", $value);
* property <= ? の条件を追加します。
* $valueの値がセットされているときのみ追加します。
* @param string $property プロパティ名
* @return Teeple_ActiveRecord
public function le($property, $value) {
if ($value ===
NULL ||
$value ===
"") {
$this->where("{
$property} <= ?
", $value);
* property >= ? の条件を追加します。
* $valueの値がセットされているときのみ追加します。
* @param string $property プロパティ名
* @return Teeple_ActiveRecord
public function ge($property, $value) {
if ($value ===
NULL ||
$value ===
"") {
$this->where("{
$property} >= ?
", $value);
* property in (?,?...) の条件を追加します。
* $valueの値がセットされているときのみ追加します。
* @param string $property プロパティ名
* @return Teeple_ActiveRecord
public function in($property, $value) {
for($i=
0; $i<
$num; $i++
) {
$placeholder =
substr($placeholder, 0, -
1);
$this->where("{
$property} IN ({
$placeholder})
", $value);
* property not in (?,?...) の条件を追加します。
* $valueの値がセットされているときのみ追加します。
* @param string $property プロパティ名
* @return Teeple_ActiveRecord
public function notin($property, $value) {
for($i=
0; $i<
$num; $i++
) {
$placeholder =
substr($placeholder, 0, -
1);
$this->where("{
$property} NOT IN ({
$placeholder})
", $value);
* property like ? の条件を追加します。
* $valueの値がセットされているときのみ追加します。
* @param string $property プロパティ名
* @return Teeple_ActiveRecord
public function like($property, $value) {
if ($value ===
NULL ||
$value ===
"") {
$this->where("{
$property} LIKE ?
", $value);
* property like ? の条件を追加します。
* $valueの値がセットされているときのみ追加します。
* @param string $property プロパティ名
* @return Teeple_ActiveRecord
public function starts($property, $value) {
if ($value ===
NULL ||
$value ===
"") {
* property like ? の条件を追加します。
* $valueの値がセットされているときのみ追加します。
* @param string $property プロパティ名
* @return Teeple_ActiveRecord
public function ends($property, $value) {
if ($value ===
NULL ||
$value ===
"") {
* property like ? の条件を追加します。
* $valueの値がセットされているときのみ追加します。
* @param string $property プロパティ名
* @return Teeple_ActiveRecord
public function contains($property, $value) {
if ($value ===
NULL ||
$value ===
"") {
* @param string $clause order by 句
* @return Teeple_ActiveRecord
public function order($clause) {
* @return Teeple_ActiveRecord
public function limit($num) {
* @return Teeple_ActiveRecord
public function offset($num) {
* @return array ResultSetをこのクラスの配列として返します。
$this->_log->debug("exec select: $sql");
$sth =
$this->_pdo->prepare($sql);
$err =
$this->_pdo->errorInfo();
$err =
$sth->errorInfo();
$row_list =
$sth->fetchAll(PDO::FETCH_ASSOC);
$this->_log->debug("exec select result: \n".
@var_export($row_list, TRUE));
foreach($row_list as $row) {
$item->_buildResultSet($row);
* paginationに対応したSELECTをします。
* @return array [0]=全件数, [1]=limit,offsetに対応した結果セット(entityの配列)
$result =
$this->limit($limit)->offset($offset)->select();
return array($total, $result);
* @param mixed $id 配列で無い場合は、単一PKの値として扱います。配列の場合は、カラム名 => 値 のHashとして扱います。
* @return Teeple_ActiveRecord
public function find($id=
null) {
if (count($this->_pk) !=
1) {
foreach($id as $col =>
$val) {
$this->_log->debug("exec find: $sql");
$sth =
$this->_pdo->prepare($sql);
$err =
$this->_pdo->errorInfo();
$err =
$sth->errorInfo();
$row_list =
$sth->fetchAll(PDO::FETCH_ASSOC);
$this->_log->debug("exec select result: \n".
@var_export($row_list, TRUE));
if (count($row_list) >
1) {
if (count($row_list) ==
0) {
$item->_buildResultSet($row_list[0]);
$select_str =
"SELECT COUNT(*)";
$sql =
implode(" ", array($select_str,$from_str,$where_str,$other_str));
$this->_log->debug("exec count: $sql");
$sth =
$this->_pdo->prepare($sql);
$err =
$this->_pdo->errorInfo();
$err =
$sth->errorInfo();
$count =
$sth->fetchColumn();
$this->_log->debug("count result: $count");
//$this->resetInstance();
* constraintとrowに設定されている値で、レコードを1つ作成します。
* PKが単一カラムで、$_auto がTRUEに設定されている場合で、INSERT値にPKが設定されていなかった場合は、
* INSERT後、インスタンスにPK値をセットします。
* @return bool 登録できたかどうか。
teeple_activerecord_before_insert($this);
$this->_log->info("insert ".
$this->_tablename.
": \n".
@var_export($row,TRUE));
$sql =
"INSERT INTO `".
$this->_tablename .
"` (" .
$this->_log->debug("sql: $sql");
$sth =
$this->_pdo->prepare($sql);
$err =
$this->_pdo->errorInfo();
if(! $sth->execute($binding_params)) {
$err =
$sth->errorInfo();
if (count($this->_pk) ==
1 &&
$this->_auto &&
(! isset
($this->{$this->_pk[0]}) ||
$this->{$this->_pk[0]} ==
"")) {
$this->{$this->_pk[0]} =
$this->_pdo->lastInsertId();
$this->_log->info("AUTO: ".
$this->_pk[0] .
" = {$this->{$this->_pk[0]}}");
$this->_log->info("insert ".
$this->_tablename .
": result=(".
$sth->rowCount().
")");
return $sth->rowCount() >
0;
* rowにセットされているPKで更新を行ないます。
* @return int 変更のあったレコード数
teeple_activerecord_before_update($this);
$this->_log->info("update ".
$this->_tablename .
": \n".
@var_export($values,TRUE));
foreach ($this->_pk as $pk) {
$sql =
"UPDATE `".
$this->_tablename .
"` ".
$this->_log->debug("update ".
$this->_tablename .
": {$sql}");
$sth =
$this->_pdo->prepare($sql);
$err =
$this->_pdo->errorInfo();
$err =
$sth->errorInfo();
$this->_log->info("update ".
$this->_tablename .
": result=(".
$sth->rowCount().
")");
if ($sth->rowCount() !=
1) {
* セットされているconstraints及びcriteriaに
teeple_activerecord_before_update($this);
$sql =
"UPDATE `".
$this->_tablename .
"` ".
$this->_log->info("updateAll ".
$this->_tablename .
": $sql");
$sth =
$this->_pdo->prepare($sql);
$err =
$this->_pdo->errorInfo();
$err =
$sth->errorInfo();
$this->_log->info("updateAll ".
$this->_tablename .
": result=(".
$sth->rowCount().
")");
* constraintまたは $idパラメータで指定されたPKに該当するレコードを削除します。
* $id がハッシュでないときは、id列の値とみなしてDELETEします。
* $id がハッシュのときは、key値をPKのカラム名とみなしてDELETEします。
public function delete($id =
null)
// rowにセットされているPKがあればconstraintに
foreach ($this->_pk as $pk) {
if (count($this->_pk) !=
1) {
foreach($id as $col =>
$val) {
$sql =
"DELETE FROM `".
$this->_tablename .
"` ".
$this->_log->info("delete ".
$this->_tablename .
": $sql");
$sth =
$this->_pdo->prepare($sql);
$err =
$this->_pdo->errorInfo();
$err =
$sth->errorInfo();
$this->_log->info("delete ".
$this->_tablename .
": result=(".
$sth->rowCount().
")");
foreach ($props as $key) {
return $sth->rowCount() >
0;
* セットされているconstraints及びcriteriaに
$sql =
"DELETE FROM `".
$this->_tablename .
"` ".
$this->_log->info("deleteAll ".
$this->_tablename .
": $sql");
$sth =
$this->_pdo->prepare($sql);
$err =
$this->_pdo->errorInfo();
$err =
$sth->errorInfo();
$this->_log->info("deleteAll ".
$this->_tablename .
": result=(".
$sth->rowCount().
")");
* @param array $bindvalues
$sth =
$this->getPDO()->prepare($query);
foreach($bindvalues as $i =>
$value) {
$sth->bindValue($i+
1, $value);
while ($row =
$sth->fetch()) {
foreach($row as $col =>
$value) {
* 指定されたSELECT文を実行します。(単一行)
* @param array $bindvalues
public function findQuery($query, $bindValues) {
if (count($result) >
0) {
* インスタンスのcriteriaをリセットします。
* ActionクラスのプロパティからEntityのプロパティを生成します。
* @param Object $obj Actionクラスのインスタンス
* @param array $colmap 'entityのカラム名' => 'Actionのプロパティ名' の配列
foreach($columns as $column) {
if (isset
($obj->$prop)) {
$this->$column =
$obj->$prop;
* EntityのプロパティからActionクラスのプロパティを生成します。
* @param Object $obj Actionクラスのインスタンス
* @param array $colmap 'entityのカラム名' => 'Actionのプロパティ名' の配列
foreach($columns as $column) {
if (@isset
($this->$column)) {
$obj->$prop =
$this->$column;
return date('Y-m-d H:i:s');
return implode(" \n", array($select_str, $from_str, $where_str, $other_str));
* @return String SELECT clause
foreach($columns as $col) {
$buff[] =
"base.{$col} AS base\${$col}";
foreach($this->_join as $alias =>
$config) {
foreach ($join_columns as $col) {
$buff[] =
"{
$alias}.{
$col} AS {
$alias}\${
$col}";
return "SELECT ".
implode(', ', $buff);
$buff[] =
"FROM ".
$this->_tablename .
" base";
foreach ($this->_join as $alias =>
$conf) {
if (count($alias_ar) >
1) {
if (! isset
($conf['type'])) {
$conf['type'] =
'INNER JOIN';
foreach ($conf['relation'] as $here =>
$there) {
array_push($conds, "{
$base}.{
$here} = {
$alias}.{
$there}");
if (isset
($conf['condition'])) {
foreach($conf['condition'] as $statement =>
$params) {
foreach($params as $item) {
$conditions =
"(".
implode(' AND ', $conds) .
")";
$buff[] =
"{
$conf['type']} {
$tablename} {
$alias} ON {
$conditions}";
$buff[] =
$usebase ?
"base.{$col} = ?" :
"{
$col} = ?
";
$buff[] =
$usebase ?
"base.{$col} IS NULL" :
"{
$col} IS NULL
";
//foreach($this->_criteria as $str => $val) {
return "WHERE (".
implode(") \n AND (", $buff) .
")";
$buff[] =
$usebase ?
"base.{$col} = ?" :
"{
$col} = ?
";
$buff[] =
$usebase ?
"base.{$col} IS NULL" :
"{
$col} IS NULL
";
return "WHERE ".
implode(' AND ', $buff);
* WHERE以降の clause を作成します。
$buff[] =
"ORDER BY {$this->_afterwhere['order']}";
$buff[] =
"LIMIT {$this->_afterwhere['limit']}";
$buff[] =
"OFFSET {$this->_afterwhere['offset']}";
* UPDATE文のVALUES部分を作成します。
* @param array $array アップデートする値の配列
* @return string SQL句の文字列
foreach($array as $key =>
$value) {
$expressions[] =
"{
$key} = ?
";
return "SET ".
implode(', ', $expressions);
* @param unknown_type $row
foreach($row as $key =>
$val) {
if (count($alias_ar) ==
1 &&
$alias_ar[0] ==
'base') {
$base .=
$base ==
"" ?
$alias :
"$".
$alias;
if ($ref->$alias ==
NULL) {
$class_name =
$this->_join[$base]['entity'];
$obj =
new $class_name($this->_pdo);
$param_num =
count($params);
if ($param_num !=
$holder_num) {
$ref =
new ReflectionClass($clsname);
return $ref->getStaticPropertyValue($property);
* @param array $array 制約値
* @return string SQL句の文字列
foreach($array as $key =>
$value) {
$expressions[] =
"{
$key} IS NULL
";
$expressions[] =
"{
$key}=:{
$key}";
return implode(' AND ', $expressions);
* @param array $array バインドする値の配列
* @return array バインドパラメータを名前にした配列
foreach( $array as $key=>
$value )
$params[":{$key}"] =
$value;
* @param array $array IDの配列
* @return string IN句に設定する文字列
foreach ($array as $id) {
$expressions[] =
"`".
$this->_tablename .
"`.id=".
$this->_pdo->quote($id, isset
($this->has_string_id) ?
PDO::PARAM_INT :
PDO::PARAM_STR);
return '('.
implode(' OR ', $expressions).
')';
* PKがセットされているかどうかをチェックします。
* @return PKがセットされている場合はTRUE
if (! isset
($this->_pk)) {
foreach ($this->_pk as $one) {
if (! isset
($this->$one)) {
* Entityのカラム値をArrayとして取り出す
* @param Teeple_ActiveRecord $obj
* @param boolean $excludeNull
foreach ($columns as $name) {
} else if (! $excludeNull ||
($val !==
NULL &&
strlen($val) >
0)) {
$result[$name] =
$this->_null($val);
foreach($vars as $name =>
$value) {
if (substr($name, 0, 1) ===
'_') {
// _joinconfigで指定されている名前は除外
protected function _null($str) {
return $str !==
NULL &&
strlen($str) >
0 ?
$str :
NULL;