From 815ce4bcc616c4b0618046651174062efc93fd06 Mon Sep 17 00:00:00 2001 From: wei <> Date: Thu, 12 Apr 2007 08:10:23 +0000 Subject: Add TTableGateway --- .gitattributes | 3 + framework/Data/DataGateway/TDataGatewayCommand.php | 340 +++++++++++++++++++++ framework/Data/DataGateway/TSqlCriteria.php | 177 +++++++++++ framework/Data/DataGateway/TTableGateway.php | 305 ++++++++++++++++++ 4 files changed, 825 insertions(+) create mode 100644 framework/Data/DataGateway/TDataGatewayCommand.php create mode 100644 framework/Data/DataGateway/TSqlCriteria.php create mode 100644 framework/Data/DataGateway/TTableGateway.php diff --git a/.gitattributes b/.gitattributes index 51a89234..48644cc6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1652,6 +1652,9 @@ framework/Data/Common/TDbCommandBuilder.php -text framework/Data/Common/TDbMetaData.php -text framework/Data/Common/TDbTableColumn.php -text framework/Data/Common/TDbTableInfo.php -text +framework/Data/DataGateway/TDataGatewayCommand.php -text +framework/Data/DataGateway/TSqlCriteria.php -text +framework/Data/DataGateway/TTableGateway.php -text framework/Data/SqlMap/Configuration/TDiscriminator.php -text framework/Data/SqlMap/Configuration/TInlineParameterMapParser.php -text framework/Data/SqlMap/Configuration/TParameterMap.php -text diff --git a/framework/Data/DataGateway/TDataGatewayCommand.php b/framework/Data/DataGateway/TDataGatewayCommand.php new file mode 100644 index 00000000..25666059 --- /dev/null +++ b/framework/Data/DataGateway/TDataGatewayCommand.php @@ -0,0 +1,340 @@ +_builder = $builder; + } + + /** + * @return TDbTableInfo + */ + protected function getTableInfo() + { + return $this->_builder->getTableInfo(); + } + + /** + * @return TDbConnection + */ + protected function getDbConnection() + { + return $this->_builder->getDbConnection(); + } + + /** + * @return TDbCommandBuilder + */ + protected function getBuilder() + { + return $this->_builder; + } + + /** + * Executes a delete command. + * @param TSqlCriteria delete conditions and parameters. + * @return integer number of records affected. + */ + public function delete($criteria) + { + $where = $criteria->getCondition(); + $parameters = $criteria->getParameters()->toArray(); + $command = $this->getBuilder()->createDeleteCommand($where, $parameters); + $command->prepare(); + return $command->execute(); + } + + /** + * Updates the table with new data. + * @param array date for update. + * @param TSqlCriteria update conditions and parameters. + * @return integer number of records affected. + */ + public function update($data, $criteria) + { + $where = $criteria->getCondition(); + $parameters = $criteria->getParameters()->toArray(); + $command = $this->getBuilder()->createUpdateCommand($data,$where, $parameters); + $command->prepare(); + return $command->execute(); + } + + /** + * Find one record matching the critera. + * @param TSqlCriteria find conditions and parameters. + * @return array matching record. + */ + public function find($criteria) + { + return $this->getFindCommand($criteria)->queryRow(); + } + + /** + * Find one or more matching records. + * @param TSqlCriteria $criteria + * @return TDbDataReader record reader. + */ + public function findAll($criteria) + { + return $this->getFindCommand($criteria)->query(); + } + + /** + * @param mixed primary key value, or composite key values as array. + * @return array matching record. + */ + public function findByPk($keys) + { + list($where, $parameters) = $this->getPrimaryKeyCondition((array)$keys); + $command = $this->getBuilder()->createFindCommand($where, $parameters); + return $command->queryRow(); + } + + /** + * @param array multiple primary key values or composite value arrays + * @return TDbDataReader record reader. + */ + public function findAllByPk($keys) + { + $where = $this->getCompositeKeyCondition((array)$keys); + $command = $this->getBuilder()->createFindCommand($where); + return $command->query(); + } + + /** + * @param array multiple primary key values or composite value arrays + * @return integer number of rows affected. + */ + public function deleteByPk($keys) + { + $where = $this->getCompositeKeyCondition((array)$keys); + $command = $this->getBuilder()->createDeleteCommand($where); + $command->prepare(); + return $command->execute(); + } + + /** + * Construct a "pk IN ('key1', 'key2', ...)" criteria. + * @param array values for IN predicate + * @param string SQL string for primary keys IN a list. + */ + protected function getCompositeKeyCondition($values) + { + $primary = $this->getTableInfo()->getPrimaryKeys(); + $count = count($primary); + if($count===0) + { + throw new TDbException('dbtablegateway_no_primary_key_found', + $this->getTableInfo()->getTableFullName()); + } + if(!is_array($values) || count($values) === 0) + { + throw new TDbException('dbtablegateway_missing_pk_values', + $this->getTableInfo()->getTableFullName()); + } + if($count>1 && !is_array($values[0])) + $values = array($values); + if($count > 1 && count($values[0]) !== $count) + { + throw new TDbException('dbtablegateway_pk_value_count_mismatch', + $this->getTableInfo()->getTableFullName()); + } + + $columns = array(); + foreach($primary as $key) + $columns[] = $this->getTableInfo()->getColumn($key)->getColumnName(); + return '('.implode(', ',$columns).') IN '.$this->quoteTuple($values); + } + + /** + * @param TDbConnection database connection. + * @param array values + * @return string quoted recursive tuple values, e.g. "('val1', 'val2')". + */ + protected function quoteTuple($array) + { + $conn = $this->getDbConnection(); + $data = array(); + foreach($array as $k=>$v) + $data[] = is_array($v) ? $this->quoteTuple($v) : $conn->quoteString($v); + return '('.implode(', ', $data).')'; + } + + /** + * Create the condition and parameters for find by primary. + * @param array primary key values + * @return array tuple($where, $parameters) + */ + protected function getPrimaryKeyCondition($values) + { + $primary = $this->getTableInfo()->getPrimaryKeys(); + if(count($primary)===0) + { + throw new TDbException('dbtablegateway_no_primary_key_found', + $this->getTableInfo()->getTableFullName()); + } + $criteria=array(); + $bindings=array(); + $i = 0; + foreach($primary as $key) + { + $column = $this->getTableInfo()->getColumn($key)->getColumnName(); + $criteria[] = $column.' = :'.$key; + $bindings[$key] = $values[$i++]; + } + return array(implode(' AND ', $criteria), $bindings); + } + + /** + * Find one or more matching records for arbituary SQL. + * @param TSqlCriteria $criteria + * @return TDbDataReader record reader. + */ + public function findBySql($criteria) + { + return $this->getSqlCommand($criteria)->query(); + } + + /** + * Build sql command from the criteria. Limit, Offset and Ordering are applied if applicable. + * @param TSqlCriteria $criteria + * @return TDbCommand command corresponding to the criteria. + */ + protected function getSqlCommand($criteria) + { + $sql = $criteria->getCondition(); + $ordering = $criteria->getOrdersBy(); + $limit = $criteria->getLimit(); + $offset = $criteria->getOffset(); + if(count($ordering) > 0) + $sql = $this->getBuilder()->applyOrdering($sql, $ordering); + if($limit>=0 || $offset>=0) + $sql = $this->getBuilder()->applyLimitOffset($sql, $limit, $offset); + $command = $this->getBuilder()->createCommand($sql); + $this->getBuilder()->bindArrayValues($command, $criteria->getParameters()->toArray()); + return $command; + } + + /** + * Build the find command from the criteria. Limit, Offset and Ordering are applied if applicable. + * @param TSqlCriteria $criteria + * @return TDbCommand. + */ + protected function getFindCommand($criteria) + { + $where = $criteria->getCondition(); + $parameters = $criteria->getParameters()->toArray(); + $ordering = $criteria->getOrdersBy(); + $limit = $criteria->getLimit(); + $offset = $criteria->getOffset(); + return $this->getBuilder()->createFindCommand($where,$parameters,$ordering,$limit,$offset); + } + + /** + * @param TSqlCriteria $criteria + * @return integer number of records. + */ + public function count($criteria) + { + if($criteria===null) + return intval($this->getBuilder()->createCountCommand()->queryScalar()); + $where = $criteria->getCondition(); + $parameters = $criteria->getParameters()->toArray(); + $ordering = $criteria->getOrdersBy(); + $limit = $criteria->getLimit(); + $offset = $criteria->getOffset(); + $command = $this->getBuilder()->createCountCommand($where,$parameters,$ordering,$limit,$offset); + return intval($command->queryScalar()); + } + + /** + * Inserts a new record into the table. Each array key must + * correspond to a column name in the table unless a null value is permitted. + * @param array new record data. + * @return mixed last insert id if one column contains a serial or sequence, + * otherwise true if command executes successfully and affected 1 or more rows. + */ + public function insert($data) + { + $command=$this->getBuilder()->createInsertCommand($data); + $command->prepare(); + if($command->execute() > 0) + { + $value = $this->getLastInsertId(); + return $value !== null ? $value : true; + } + return false; + } + + /** + * Iterate through all the columns and returns the last insert id of the + * first column that has a sequence or serial. + * @return mixed last insert id, null if none is found. + */ + public function getLastInsertID() + { + foreach($this->getTableInfo()->getColumns() as $column) + { + if($column->hasSequence()) + return $this->getDbConnection()->getLastInsertID($column->getSequenceName()); + } + } + + /** + * @param string __call method name + * @param string criteria conditions + * @param array method arguments + * @return TActiveRecordCriteria criteria created from the method name and its arguments. + */ + public function createCriteriaFromString($method, $condition, $args) + { + $fields = $this->extractMatchingConditions($method, $condition); + $args=count($args) === 1 && is_array($args[0]) ? $args[0] : $args; + if(count($fields)>count($args)) + { + throw new TDbException('dbtablegateway_mismatch_args_exception', + $method,count($fields),count($args)); + } + return new TSqlCriteria(implode(' ',$fields), $args); + } + + /** + * Calculates the AND/OR condition from dynamic method substrings using + * table meta data, allows for any AND-OR combinations. + * @param string dynamic method name + * @param string dynamic method search criteria + * @return array search condition substrings + */ + protected function extractMatchingConditions($method, $condition) + { + $table = $this->getTableInfo(); + $columns = $table->getLowerCaseColumnNames(); + $regexp = '/('.implode('|', array_keys($columns)).')(and|_and_|or|_or_)?/i'; + $matches = array(); + if(!preg_match_all($regexp, strtolower($condition), $matches,PREG_SET_ORDER)) + { + throw new TDbException('dbtablegateway_mismatch_column_name', + $method, implode(', ', $columns), $table->getTableFullName()); + } + + $fields = array(); + foreach($matches as $match) + { + $key = $columns[$match[1]]; + $column = $table->getColumn($key)->getColumnName(); + $sql = $column . ' = ? '; + if(count($match) > 2) + $sql .= strtoupper(str_replace('_', '', $match[2])); + $fields[] = $sql; + } + return $fields; + } +} + +?> \ No newline at end of file diff --git a/framework/Data/DataGateway/TSqlCriteria.php b/framework/Data/DataGateway/TSqlCriteria.php new file mode 100644 index 00000000..ac5f00f2 --- /dev/null +++ b/framework/Data/DataGateway/TSqlCriteria.php @@ -0,0 +1,177 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2007 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id: TDbSqlCriteria.php 1835 2007-04-03 01:38:15Z wei $ + * @package System.Data.Common + */ + +/** + * Search criteria for TDbDataGateway. + * + * Criteria object for data gateway finder methods. Usage: + * + * $criteria = new TDbSqlCriteria; + * $criteria->Parameters[':name'] = 'admin'; + * $criteria->Parameters[':pass'] = 'prado'; + * $criteria->OrdersBy['level'] = 'desc'; + * $criteria->OrdersBy['name'] = 'asc'; + * $criteria->Limit = 10; + * $criteria->Offset = 20; + * + * + * @author Wei Zhuo + * @version $Id: TDbSqlCriteria.php 1835 2007-04-03 01:38:15Z wei $ + * @package System.Data.Common + * @since 3.1 + */ +class TSqlCriteria extends TComponent +{ + private $_condition; + private $_parameters; + private $_ordersBy; + private $_limit; + private $_offset; + + /** + * Creates a new criteria with given condition; + * @param string sql string after the WHERE stanza + * @param mixed named or indexed parameters, accepts as multiple arguments. + */ + public function __construct($condition=null, $parameters=array()) + { + if(!is_array($parameters) && func_num_args() > 1) + $parameters = array_slice(func_get_args(),1); + $this->_parameters=new TAttributeCollection; + $this->_parameters->setCaseSensitive(true); + $this->_parameters->copyFrom((array)$parameters); + $this->_ordersBy=new TAttributeCollection; + $this->_ordersBy->setCaseSensitive(true); + $this->setCondition($condition); + } + + /** + * @return string search conditions. + */ + public function getCondition() + { + return $this->_condition; + } + + /** + * Sets the search conditions to be placed after the WHERE clause in the SQL. + * @param string search conditions. + */ + public function setCondition($value) + { + $this->_condition=$value; + } + + /** + * @return TAttributeCollection list of named parameters and values. + */ + public function getParameters() + { + return $this->_parameters; + } + + /** + * @param ArrayAccess named parameters. + */ + public function setParameters($value) + { + if(!(is_array($value) || $value instanceof ArrayAccess)) + throw new TException('value must be array or ArrayAccess'); + $this->_parameters->copyFrom($value); + } + + /** + * @return boolean true if the parameter index are string base, false otherwise. + */ + public function getIsNamedParameters() + { + foreach($this->getParameters() as $k=>$v) + return is_string($k); + } + + /** + * @return TAttributeCollection ordering clause. + */ + public function getOrdersBy() + { + return $this->_ordersBy; + } + + /** + * @param ArrayAccess ordering clause. + */ + public function setOrdersBy($value) + { + if(!(is_array($value) || $value instanceof ArrayAccess)) + throw new TException('value must be array or ArrayAccess'); + $this->_ordersBy->copyFrom($value); + } + + /** + * @return int maximum number of records to return. + */ + public function getLimit() + { + return $this->_limit; + } + + /** + * @param int maximum number of records to return. + */ + public function setLimit($value) + { + $this->_limit=$value; + } + + /** + * @return int record offset. + */ + public function getOffset() + { + return $this->_offset; + } + + /** + * @param int record offset. + */ + public function setOffset($value) + { + $this->_offset=$value; + } + + /** + * @return string string representation of the parameters. Useful for debugging. + */ + public function __toString() + { + $str = ''; + if(strlen((string)$this->getCondition()) > 0) + $str .= '"'.(string)$this->getCondition().'"'; + $params = array(); + foreach($this->getParameters() as $k=>$v) + $params[] = "{$k} => ${v}"; + if(count($params) > 0) + $str .= ', "'.implode(', ',$params).'"'; + $orders = array(); + foreach($this->getOrdersBy() as $k=>$v) + $orders[] = "{$k} => ${v}"; + if(count($orders) > 0) + $str .= ', "'.implode(', ',$orders).'"'; + if($this->_limit !==null) + $str .= ', '.$this->_limit; + if($this->_offset !== null) + $str .= ', '.$this->_offset; + return $str; + } +} + +?> \ No newline at end of file diff --git a/framework/Data/DataGateway/TTableGateway.php b/framework/Data/DataGateway/TTableGateway.php new file mode 100644 index 00000000..6acdb837 --- /dev/null +++ b/framework/Data/DataGateway/TTableGateway.php @@ -0,0 +1,305 @@ +_connection=$connection; + $this->setTableName($tableName); + } + + protected function setTableName($tableName) + { + Prado::using('System.Data.Common.TDbMetaData'); + $meta = TDbMetaData::getMetaData($this->getDbConnection()); + $builder = $meta->createCommandBuilder($tableName); + $this->_command = new TDataGatewayCommand($builder); + } + + protected function getCommand() + { + return $this->_command; + } + + /** + * @return TDbConnection database connection. + */ + public function getDbConnection() + { + return $this->_connection; + } + + /** + * Execute arbituary sql command with binding parameters. + * @param string SQL query string. + * @param array binding parameters, positional or named. + * @return TDbDataReader query results. + */ + public function findBySql($sql, $parameters=array()) + { + $args = func_num_args() > 1 ? array_slice(func_get_args(),1) : null; + $criteria = $this->getCriteria($sql,$parameters, $args); + return $this->getCommand()->findBySql($criteria); + } + + /** + * Find one single record that matches the criteria. + * + * Usage: + * + * $table->find('username = :name AND password = :pass', + * array(':name'=>$name, ':pass'=>$pass)); + * $table->find('username = ? AND password = ?', array($name, $pass)); + * $table->find('username = ? AND password = ?', $name, $pass); + * //$criteria is of TSqlCriteria + * $table->find($criteria); //the 2nd parameter for find() is ignored. + * + * + * @param string|TSqlCriteria SQL condition or criteria object. + * @param mixed parameter values. + * @return array matching record object. + */ + public function find($criteria, $parameters=array()) + { + $args = func_num_args() > 1 ? array_slice(func_get_args(),1) : null; + $criteria = $this->getCriteria($criteria,$parameters, $args); + return $this->getCommand()->find($criteria); + } + + /** + * Accepts same parameters as find(), but returns TDbDataReader instead. + * @param string|TSqlCriteria SQL condition or criteria object. + * @param mixed parameter values. + * @return TDbDataReader matching records. + */ + public function findAll($criteria, $parameters=array()) + { + $args = func_num_args() > 1 ? array_slice(func_get_args(),1) : null; + $criteria = $this->getCriteria($criteria,$parameters, $args); + return $this->getCommand()->findAll($criteria); + } + + /** + * Find one record using only the primary key or composite primary keys. Usage: + * + * + * $table->findByPk($primaryKey); + * $table->findByPk($key1, $key2, ...); + * $table->findByPk(array($key1,$key2,...)); + * + * + * @param mixed primary keys + * @return array matching record. + */ + public function findByPk($keys) + { + if(func_num_args() > 1) + $keys = func_get_args(); + return $this->getCommand()->findByPk($keys); + } + + /** + * Similar to findByPk(), but returns TDbDataReader instead. + * + * For scalar primary keys: + * + * $table->findAllByPk($key1, $key2, ...); + * $table->findAllByPk(array($key1, $key2, ...)); + * + * + * For composite keys: + * + * $table->findAllByPk(array($key1, $key2), array($key3, $key4), ...); + * $table->findAllByPk(array(array($key1, $key2), array($key3, $key4), ...)); + * + * @param mixed primary keys + * @return TDbDataReader data reader. + */ + public function findAllByPks($keys) + { + if(func_num_args() > 1) + $keys = func_get_args(); + return $this->getCommand()->findAllByPk($keys); + } + + /** + * Delete records from the table with condition given by $where and + * binding values specified by $parameter argument. + * This method uses additional arguments as $parameters. E.g. + * + * $table->delete('age > ? AND location = ?', $age, $location); + * + * @param string delete condition. + * @param array condition parameters. + * @return integer number of records deleted. + */ + public function deleteAll($criteria, $parameters=array()) + { + $args = func_num_args() > 1 ? array_slice(func_get_args(),1) : null; + $criteria = $this->getCriteria($criteria,$parameters, $args); + return $this->getCommand()->delete($criteria); + } + + /** + * Delete records by primary key. Usage: + * + * + * $table->deleteByPk($primaryKey); //delete 1 record + * $table->deleteByPk($key1,$key2,...); //delete multiple records + * $table->deleteByPk(array($key1,$key2,...)); //delete multiple records + * + * + * For composite primary keys (determined from the table definitions): + * + * $table->deleteByPk(array($key1,$key2)); //delete 1 record + * + * //delete multiple records + * $table->deleteByPk(array($key1,$key2), array($key3,$key4),...); + * + * //delete multiple records + * $table->deleteByPk(array( array($key1,$key2), array($key3,$key4), .. )); + * + * + * @param mixed primary key values. + * @return int number of records deleted. + */ + public function deleteByPk($keys) + { + if(func_num_args() > 1) + $keys = func_get_args(); + return $this->getCommand()->deleteByPk($keys); + } + + /** + * Find the number of records. + * @param string|TSqlCriteria SQL condition or criteria object. + * @param mixed parameter values. + * @return int number of records. + */ + public function count($criteria=null,$parameters=array()) + { + $args = func_num_args() > 1 ? array_slice(func_get_args(),1) : null; + if($criteria!==null) + $criteria = $this->getCriteria($criteria,$parameters, $args); + return $this->getCommand()->count($criteria); + } + + /** + * Updates the table with new name-value pair $data. Each array key must + * correspond to a column name in the table. The update condition is + * specified by the $where argument and additional binding values can be + * specified using the $parameter argument. + * This method uses additional arguments as $parameters. E.g. + * + * $gateway->update($data, 'age > ? AND location = ?', $age, $location); + * + * @param array new record data. + * @param string update condition + * @param array additional binding name-value pairs. + * @return integer number of records updated. + */ + public function update($data, $criteria, $parameters=array()) + { + $args = func_num_args() > 2 ? array_slice(func_get_args(),2) : null; + $criteria = $this->getCriteria($criteria,$parameters, $args); + return $this->getCommand()->update($data, $criteria); + } + + /** + * Inserts a new record into the table. Each array key must + * correspond to a column name in the table unless a null value is permitted. + * @param array new record data. + * @return mixed last insert id if one column contains a serial or sequence, + * otherwise true if command executes successfully and affected 1 or more rows. + */ + public function insert($data) + { + return $this->getCommand()->insert($data); + } + + /** + * @return mixed last insert id, null if none is found. + */ + public function getLastInsertId() + { + return $this->getCommand()->getLastInsertId(); + } + + /** + * Create a new TSqlCriteria object from a string $criteria. The $args + * are additional parameters and are used in place of the $parameters + * if $parameters is not an array and $args is an arrary. + * @param string|TSqlCriteria sql criteria + * @param mixed parameters passed by the user. + * @param array additional parameters obtained from function_get_args(). + * @return TSqlCriteria criteria object. + */ + protected function getCriteria($criteria, $parameters, $args) + { + if(is_string($criteria)) + { + $useArgs = !is_array($parameters) && is_array($args); + return new TSqlCriteria($criteria,$useArgs ? $args : $parameters); + } + else if($criteria instanceof TSqlCriteria) + return $criteria; + else + throw new TDbException('dbtablegateway_invalid_criteria'); + } + + /** + * Dynamic find method using parts of method name as search criteria. + * Method name starting with "findBy" only returns 1 record. + * Method name starting with "findAllBy" returns 0 or more records. + * Method name starting with "deleteBy" deletes records by the trail criteria. + * The condition is taken as part of the method name after "findBy", "findAllBy" + * or "deleteBy". + * + * The following are equivalent: + * + * $table->findByName($name) + * $table->find('Name = ?', $name); + * + * + * $table->findByUsernameAndPassword($name,$pass); // OR may be used + * $table->findBy_Username_And_Password($name,$pass); // _OR_ may be used + * $table->find('Username = ? AND Password = ?', $name, $pass); + * + * + * $table->findAllByAge($age); + * $table->findAll('Age = ?', $age); + * + * + * $table->deleteAll('Name = ?', $name); + * $table->deleteByName($name); + * + * @return mixed single record if method name starts with "findBy", 0 or more records + * if method name starts with "findAllBy" + */ + public function __call($method,$args) + { + $delete =false; + if($findOne = substr(strtolower($method),0,6)==='findby') + $condition = $method[6]==='_' ? substr($method,7) : substr($method,6); + else if(substr(strtolower($method),0,9)==='findallby') + $condition = $method[9]==='_' ? substr($method,10) : substr($method,9); + else if($delete = substr(strtolower($method),0,8)==='deleteby') + $condition = $method[8]==='_' ? substr($method,9) : substr($method,8); + else + return null; + + $criteria = $this->getCommand()->createCriteriaFromString($method, $condition, $args); + if($delete) + return $this->deleteAll($criteria); + else + return $findOne ? $this->find($criteria) : $this->findAll($criteria); + } +} + +?> \ No newline at end of file -- cgit v1.2.3