diff options
| author | wei <> | 2007-01-09 10:42:06 +0000 | 
|---|---|---|
| committer | wei <> | 2007-01-09 10:42:06 +0000 | 
| commit | 03f362a40a8dd39f8c8b4bf816334922b7b264e4 (patch) | |
| tree | 73efc71e946519d2086520102a6c8a022510463a | |
| parent | a59a458f9ae2b178d08854c112f34068b50ea243 (diff) | |
add TActiveRecord::findAllByPks()
| -rw-r--r-- | .gitattributes | 1 | ||||
| -rw-r--r-- | demos/quickstart/protected/pages/Database/ActiveRecord.page | 280 | ||||
| -rw-r--r-- | demos/quickstart/protected/pages/Database/SqlMap.page | 236 | ||||
| -rw-r--r-- | framework/Data/ActiveRecord/Exceptions/messages.txt | 4 | ||||
| -rw-r--r-- | framework/Data/ActiveRecord/TActiveRecord.php | 33 | ||||
| -rw-r--r-- | framework/Data/ActiveRecord/TActiveRecordGateway.php | 15 | ||||
| -rw-r--r-- | framework/Data/ActiveRecord/Vendor/TDbMetaData.php | 92 | ||||
| -rw-r--r-- | framework/Data/ActiveRecord/Vendor/TDbMetaDataCommon.php | 26 | ||||
| -rw-r--r-- | tests/simple_unit/ActiveRecord/FindByPksTestCase.php | 58 | ||||
| -rw-r--r-- | tests/test_tools/simpletest/errors.php | 2 | 
10 files changed, 441 insertions, 306 deletions
diff --git a/.gitattributes b/.gitattributes index 84c9874b..d43a248b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2352,6 +2352,7 @@ tests/simple_unit/ActiveRecord/ActiveRecordRegistryTestCase.php -text  tests/simple_unit/ActiveRecord/BaseActiveRecordTestCase.php -text  tests/simple_unit/ActiveRecord/CountRecordsTestCase.php -text  tests/simple_unit/ActiveRecord/DeleteByPkTestCase.php -text +tests/simple_unit/ActiveRecord/FindByPksTestCase.php -text  tests/simple_unit/ActiveRecord/FindBySqlTestCase.php -text  tests/simple_unit/ActiveRecord/SqliteTestCase.php -text  tests/simple_unit/ActiveRecord/UserRecordTestCase.php -text diff --git a/demos/quickstart/protected/pages/Database/ActiveRecord.page b/demos/quickstart/protected/pages/Database/ActiveRecord.page index e3da53c0..a1337ba3 100644 --- a/demos/quickstart/protected/pages/Database/ActiveRecord.page +++ b/demos/quickstart/protected/pages/Database/ActiveRecord.page @@ -2,53 +2,53 @@  <!-- $Id $ -->  <h1>Active Record</h1>  <p>Active Records are objects that wrap a row in a database table or view, -	encapsulates the database access and adds domain logic on that data. -	The basics of an Active Record is a business object class, e.g., a  -	<tt>Products</tt> 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. </p> +    encapsulates the database access and adds domain logic on that data. +    The basics of an Active Record is a business object class, e.g., a  +    <tt>Products</tt> 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. </p>  <div class="info"><b class="note">Info:</b> -	The data structure of an Active Record should  match exactly that of a table  -	in the database. -	Each field in the class must correspond to one column in the table. +    The data structure of an Active Record should  match exactly that of a table  +    in the database. +    Each field in the class must correspond to one column in the table.  </div>  <h2>When to Use It</h2>  <p>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.</p> +    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.</p> -	<p>However, as your business logic grows in complexity, you'll soon want  -	to use your object's direct relationships, collections, inheritance, and so +    <p>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.</p> -	 -	<p>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  -		<a href="?page=Database.SqlMap">SqlMap Data Mapper</a>.  -	  	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 <a href="?page=Database.SqlMap">SqlMap</a> is illustrated in the -	following diagram. More details regarding the SqlMap Data Mapper can be found in -	the <a href="http://www.pradosoft.com/demos/sqlmap/">SqlMap Manual</a>. -	<img src=<%~ sqlmap_active_record.png %> alt="Active Records and SqlMap DataMapper" id="fig:diagram.png" class="figure"/> -	</p> -	 -	<p> -		The Active Record class has methods that do the following: -	<ul> -		<li>Construct an instance of the Active Record from a SQL result set row.</li> -		<li>Construct a new instance for later insertion into the table.</li> -		<li>Finder methods to wrap commonly used SQL queries and return Active Record objects.</li> -		<li>Update existing records and insert new records into the database.</li> -	</ul> -	</p> -The Active Record implementation utilizes the <a href="?page=Database.DAO">Prado DAO</a> classes for data access. 	 +    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.</p> +     +    <p>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  +        <a href="?page=Database.SqlMap">SqlMap Data Mapper</a>.  +        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 <a href="?page=Database.SqlMap">SqlMap</a> is illustrated in the +    following diagram. More details regarding the SqlMap Data Mapper can be found in +    the <a href="http://www.pradosoft.com/demos/sqlmap/">SqlMap Manual</a>. +    <img src=<%~ sqlmap_active_record.png %> alt="Active Records and SqlMap DataMapper" id="fig:diagram.png" class="figure"/> +    </p> +     +    <p> +        The Active Record class has methods that do the following: +    <ul> +        <li>Construct an instance of the Active Record from a SQL result set row.</li> +        <li>Construct a new instance for later insertion into the table.</li> +        <li>Finder methods to wrap commonly used SQL queries and return Active Record objects.</li> +        <li>Update existing records and insert new records into the database.</li> +    </ul> +    </p> +The Active Record implementation utilizes the <a href="?page=Database.DAO">Prado DAO</a> classes for data access.     The current Active Record implementation supports   <a href="http://www.mysql.com">MySQL</a>,   <a href="http://www.postgres.com">Postgres SQL</a> and  @@ -56,14 +56,14 @@ The current Active Record implementation supports  Support for other databases can be provided when there are sufficient demand.  <h2>Defining an Active Record</h2>  <p>Let us -	consider the following "users" table that contains two columns named "username" and "email",  -	where "username" is also the primary key.  +    consider the following "users" table that contains two columns named "username" and "email",  +    where "username" is also the primary key.   <com:TTextHighlighter Language="sql" CssClass="source">  CREATE TABLE users  ( -	username VARCHAR( 20 ) NOT NULL , -	email VARCHAR( 200 ) , -	PRIMARY KEY ( username ) +    username VARCHAR( 20 ) NOT NULL , +    email VARCHAR( 200 ) , +    PRIMARY KEY ( username )  );  </com:TTextHighlighter>  </p> @@ -71,91 +71,98 @@ CREATE TABLE users  <com:TTextHighlighter Language="php" CssClass="source">  class UserRecord extends TActiveRecord  { -	public $username; //the column named "username" in the "users" table -	public $email; -	 -	public static $_tablename='users'; //table name  -	 -	/** -	 * @return TActiveRecord active record finder instance -	 */ -	public static function finder() -	{ -		return self::getRecordFinder('UserRecord'); -	} +    public $username; //the column named "username" in the "users" table +    public $email; +     +    public static $_tablename='users'; //table name  +     +    /** +     * @return TActiveRecord active record finder instance +     */ +    public static function finder() +    { +        return self::getRecordFinder('UserRecord'); +    }  }  </com:TTextHighlighter>  </p>  <p>Each property of the <tt>UserRecord</tt> class must correspond to a -	column with the same name in the "users" table. The static class variable -	<tt>$_tablename</tt> (must be public) is optional when the class name is the same as -	the table name in the database, otherwise <tt>$_tablename</tt> must -	specify the table name that corresponds to your Active Record class. +    column with the same name in the "users" table. The static class variable +    <tt>$_tablename</tt> (must be public) is optional when the class name is the same as +    the table name in the database, otherwise <tt>$_tablename</tt> must +    specify the table name that corresponds to your Active Record class.  </p>  <div class="tip"><b class="note">Tip:</b> -	Since <tt>TActiveRecord</tt> extends <tt>TComponent</tt>, setter and -	getter methods can be defined to allow control over how variables -	are set and returned. For example, adding a <tt>$level</tt> property to the UserRecord class: +    Since <tt>TActiveRecord</tt> extends <tt>TComponent</tt>, setter and +    getter methods can be defined to allow control over how variables +    are set and returned. For example, adding a <tt>$level</tt> property to the UserRecord class:  <com:TTextHighlighter Language="php" CssClass="source">  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; -	} +    ... //existing definitions as above +     +    private $_level; +    public function setLevel($value) { +        $this->_level=TPropertyValue::ensureInteger($value,0); +    } +    public function getLevel($value){ +        return $this->_level; +    }  }  </com:TTextHighlighter>  </div> +<div class="note"><b class="note">Note:</b> +<tt>TActiveRecord</tt> can also work with database views by specifying the value <tt>$_tablename</tt> +corresponding to the view name. However, objects returned +from views are read-only, calling the <tt>save()</tt> or <tt>delete()</tt> method +will raise an exception.  +</div> +  <p> -	The static method <tt>finder()</tt> returns an <tt>UserRecord</tt> instance -	that can be used to load records from the database. The loading of records -	using the finer methods is discuss a little later. The <tt>TActiveRecord::getRecordFinder()</tt> -	static method takes the name of the current Active Record class as parameter. +    The static method <tt>finder()</tt> returns an <tt>UserRecord</tt> instance +    that can be used to load records from the database. The loading of records +    using the finer methods is discuss a little later. The <tt>TActiveRecord::getRecordFinder()</tt> +    static method takes the name of the current Active Record class as parameter.  </p>  <h2>Setting up a database connection</h2>  <p> -	A default database connection for Active Record can be set as follows. -	See <a href="?page=Database.DAO">Establishing Database Connection</a> for -	futher details regarding creation of database connection in general. +    A default database connection for Active Record can be set as follows. +    See <a href="?page=Database.DAO">Establishing Database Connection</a> for +    futher details regarding creation of database connection in general.  <com:TTextHighlighter Language="php" CssClass="source">  //create a connection and give it to the ActiveRecord manager.  $dsn = 'pgsql:host=localhost;dbname=test'; //Postgres SQL  $conn = new TDbConnection($dsn, 'dbuser','dbpass');  TActiveRecordManager::getInstance()->setDbConnection($conn); -</com:TTextHighlighter>	 +</com:TTextHighlighter>   </p>  <p> -	The default database connection can also be configured using a <tt><module></tt> -	tag in the <a href="?page=Configurations.AppConfig">application.xml</a>  -	or <a href="?page=Configurations.PageConfig">config.xml</a> as follows. +    The default database connection can also be configured using a <tt><module></tt> +    tag in the <a href="?page=Configurations.AppConfig">application.xml</a>  +    or <a href="?page=Configurations.PageConfig">config.xml</a> as follows.  <com:TTextHighlighter Language="xml" CssClass="source">  <modules>    <module class="System.Data.ActiveRecord.TActiveRecordConfig" EnableCache="true">      <database ConnectionString="pgsql:host=localhost;dbname=test"          Username="dbuser" Password="dbpass" />    </module> -</modules>	 -</com:TTextHighlighter>	 +</modules>   +</com:TTextHighlighter>   <div class="tip"><b class="note">Tip:</b> -	The <tt>EnableCache</tt> 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 chanages made to your table definitions. A <a href="?page=Advanced.Performance#6402">cache -	module</a> must also be defined for the cache to function. +    The <tt>EnableCache</tt> 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 chanages made to your table definitions. A <a href="?page=Advanced.Performance#6402">cache +    module</a> must also be defined for the cache to function.  </div>  </p>  <p>A <tt>ConnectionID</tt> property can be specified with value corresponding -	to another <tt>TDataSourceConfig</tt> module configuration's ID value. This allows -	the same database connection to be used in other modules such as <a href="?page=Database.SqlMap">SqlMap</a>. +    to another <tt>TDataSourceConfig</tt> module configuration's ID value. This allows +    the same database connection to be used in other modules such as <a href="?page=Database.SqlMap">SqlMap</a>.  <com:TTextHighlighter Language="xml" CssClass="source">  <modules>    <module class="System.Data.TDataSourceConfig" ID="db1"> @@ -168,32 +175,52 @@ TActiveRecordManager::getInstance()->setDbConnection($conn);    <module class="System.Data.SqlMap.TSqlMapConfig"          ConnectionID="db1"  ... /> -</modules>	 -</com:TTextHighlighter>		 +</modules>   +</com:TTextHighlighter>       </p>  <h2>Loading data from the database</h2>  <p> -	The <tt>TActiveRecord</tt> class provides many convenient methods to find -	records from the database. The simplest is finding records by matching primary keys. -	See the <com:DocLink ClassPath="System.Data.ActiveRecord.TActiveRecord" /> for -	more details. +    The <tt>TActiveRecord</tt> class provides many convenient methods to find +    records from the database. The simplest is finding records by matching primary keys. +    See the <com:DocLink ClassPath="System.Data.ActiveRecord.TActiveRecord" /> for +    more details.  </p> -	<h3><tt>findByPk()</tt></h3> -	<p>Finds one record using only the primary key or composite primary keys. +    <h3><tt>findByPk()</tt></h3> +    <p>Finds one record using only the primary key or composite primary keys.  <com:TTextHighlighter Language="php" CssClass="source">  $finder = UserRecord::finder();  $user = $finder->findByPk($primaryKey);  //when the table uses composite keys -$record = $finder->findByPk($key1, $key2, ...); //for composite keys -$record = $finder->findByPk(array($key1, $key2,...)); //same as above +$record = $finder->findByPk($key1, $key2, ...); +$record = $finder->findByPk(array($key1, $key2,...));  </com:TTextHighlighter>  </p> +    <h3><tt>findAllByPks()</tt></h3> +    <p>Finds multiple records using a list of primary keys or composite primary keys. +The following are equivalent for scalar primary keys (primary key consisting of only one column/field). +<com:TTextHighlighter Language="php" CssClass="source"> +$finder = UserRecord::finder(); +$users = $finder->findAllByPk($key1, $key2, ...); +$users = $finder->findAllByPk(array($key1, $key2, ...)); +</com:TTextHighlighter> +The following are equivalent for composite keys. +<com:TTextHighlighter Language="php" CssClass="source"> +//when the table uses composite keys +$record = $finder->findAllByPks(array($key1, $key2), array($key3, $key4), ...); + +$keys = array(  array($key1, $key2), array($key3, $key4), ... ); +$record = $finder->findAllByPks($keys); + +</com:TTextHighlighter> +</p> + +  <h3><tt>find()</tt></h3>  <p>Finds <b>one single record</b> that matches the criteria. The criteria -	can be a partial SQL string or a <tt>TActiveRecordCriteria</tt> object. +    can be a partial SQL string or a <tt>TActiveRecordCriteria</tt> object.  <com:TTextHighlighter Language="php" CssClass="source">  $finder = UserRecord::finder(); @@ -212,13 +239,13 @@ $finder->find($criteria); //the 2nd parameter for find() is ignored.  </p>  <p>The <tt>TActiveRecordCriteria</tt> class has the following properties: -	<ul> -		<li><tt>Parameters</tt> -- name value parameter pairs.</li> -		<li><tt>OrderBy</tt> -- column name and ordering pairs.</li> -		<li><tt>Condition</tt> -- parts of the WHERE SQL conditions.</li> -		<li><tt>Limit</tt> -- maximum number of records to return.</li> -		<li><tt>Offset</tt> -- record offset in the table.</li> -	</ul> +    <ul> +        <li><tt>Parameters</tt> -- name value parameter pairs.</li> +        <li><tt>OrderBy</tt> -- column name and ordering pairs.</li> +        <li><tt>Condition</tt> -- parts of the WHERE SQL conditions.</li> +        <li><tt>Limit</tt> -- maximum number of records to return.</li> +        <li><tt>Offset</tt> -- record offset in the table.</li> +    </ul>  </p>  <com:TTextHighlighter Language="php" CssClass="source"> @@ -258,8 +285,8 @@ $finder->find('Username = ? AND Password = ?', $name, $pass);  $finder->findAllByAge($age);  $finder->findAll('Age = ?', $age);  </com:TTextHighlighter> -</p>	 -	 +</p>     +      <h3><tt>findBySql()</tt></h3>  <p>Finds records using full SQL, returns corresponding array of record objects.</p> @@ -291,7 +318,7 @@ incremented values.</div>  <p>  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 <tt>save()</tt> method.	 +call the <tt>save()</tt> method.      <com:TTextHighlighter Language="php" CssClass="source">  $user = UserRecord::finder()->findByName('admin'); @@ -303,14 +330,23 @@ $user->save(); //update it.  <p>  Active Record objects have a simple life-cycle illustrated in the following diagram.  <img src=<%~ object_states.png %> alt="Active Records Life Cycle" id="fig:cycle.png" class="figure"/> +We see that new ActiveRecord objects are created by either using one of the <tt>find*()</tt> +methods or using creating a new instance by using PHP's <tt>new</tt> keyword. Objects +created by a <tt>find*()</tt> method starts with <tt>clean</tt> state. New instance of  +ActiveRecords created other than by a <tt>find*()</tt> method starts with <tt>new</tt> state. +When ever you  +call the <tt>save()</tt> method on the ActiveRecord object, the object enters the <tt>clean</tt> +state. Objects in the <tt>clean</tt> becomes <tt>dirty</tt> whenever one of more of its +internal states are changed. Calling the <tt>delete()</tt> method on the object +ends the object life-cycle, no futher actions can be performed on the object.  </p>  <h2>Deleting existing records</h2>  <p> -	To delete an existing record that is already loaded, just call the <tt>delete()</tt> method. -	You can also delete records in the database by primary keys without -	loading any records using the <tt>deleteByPk()</tt> method.  -	For example, to delete one or records with tables having a scalar primary key. +    To delete an existing record that is already loaded, just call the <tt>delete()</tt> method. +    You can also delete records in the database by primary keys without +    loading any records using the <tt>deleteByPk()</tt> method.  +    For example, to delete one or records with tables having a scalar primary key.  <com:TTextHighlighter Language="php" CssClass="source">  $finder->deleteByPk($primaryKey); //delete 1 record  $finder->deleteByPk($key1,$key2,...); //delete multiple records @@ -333,7 +369,7 @@ $finder->deleteByPk(array( array($key1,$key2), array($key3,$key4), .. ));  <h2>Transactions</h2>  <p>All Active Record objects contains the property <tt>DbConnection</tt> -	that can be used to obtain a transaction object. +    that can be used to obtain a transaction object.  <com:TTextHighlighter Language="php" CssClass="source">  $finder = UserRecord::finder(); @@ -345,7 +381,7 @@ try      $user->save();      $transaction->commit();  } -catch(Exception $e) // an exception is raised if a query fails will be raised +catch(Exception $e) // an exception is raised if a query fails  {      $transaction->rollBack();  } @@ -353,8 +389,8 @@ catch(Exception $e) // an exception is raised if a query fails will be raised  <h2>References</h2>  <ul> -	<li>Fowler et. al. <i>Patterns of Enterprise Application Architecture</i>, -	Addison Wesley, 2002.</li> +    <li>Fowler et. al. <i>Patterns of Enterprise Application Architecture</i>, +    Addison Wesley, 2002.</li>  </ul>  </com:TContent>
\ No newline at end of file diff --git a/demos/quickstart/protected/pages/Database/SqlMap.page b/demos/quickstart/protected/pages/Database/SqlMap.page index 2b70e8f5..4b462168 100644 --- a/demos/quickstart/protected/pages/Database/SqlMap.page +++ b/demos/quickstart/protected/pages/Database/SqlMap.page @@ -3,78 +3,78 @@  <h1>Data Mapper</h1>  <p>Data Mappers moves data between objects and a database while keeping them  -	independent of each other and the mapper itself. If you started with -	<a href="?page=Database.ActiveRecord">Active Records</a>, you may eventually -	 faced with more complex business -	objects as your project progresses. When you build an object model with a -	 lot of business logic it's valuable to use these mechanisms to better organize -	 the data and the behavior that goes with it. Doing so leads to variant schemas; -	 that is, the object schema and the relational schema don't match up. +    independent of each other and the mapper itself. If you started with +    <a href="?page=Database.ActiveRecord">Active Records</a>, you may eventually +     faced with more complex business +    objects as your project progresses. When you build an object model with a +     lot of business logic it's valuable to use these mechanisms to better organize +     the data and the behavior that goes with it. Doing so leads to variant schemas; +     that is, the object schema and the relational schema don't match up.  </p>  <p>The Data Mapper separates the in-memory objects from the database. Its responsibility  -	is to transfer data between the two and also to isolate them from each other.  -	With Data Mapper the in-memory objects needn't know even that there's a database  -	present; they need no SQL interface code, and certainly no knowledge of the -	 database schema. (The database schema is always ignorant of the objects that use it.)  +    is to transfer data between the two and also to isolate them from each other.  +    With Data Mapper the in-memory objects needn't know even that there's a database  +    present; they need no SQL interface code, and certainly no knowledge of the +     database schema. (The database schema is always ignorant of the objects that use it.)   </p>  <h2>When to Use It</h2>  <p>The primary occasion for using Data Mapper is when you want the database schema -	 and the object model to evolve independently. Data Mapper's primary benefit is -	 that when working on the business (or domain) objects you can ignore the database, both in -	 design and in the build and testing process. The domain objects have no idea -	 what the database structure is, because all the correspondence is done by the mappers. +     and the object model to evolve independently. Data Mapper's primary benefit is +     that when working on the business (or domain) objects you can ignore the database, both in +     design and in the build and testing process. The domain objects have no idea +     what the database structure is, because all the correspondence is done by the mappers.  </p>  <p>This helps you in the code because you can understand and work with the domain objects  -	without having to understand how they're stored in the database. You can modify the  -	business models or the database without having to alter either. With complicated -	mappings, particularly those involving <b>existing databases</b>, this is very valuable. +    without having to understand how they're stored in the database. You can modify the  +    business models or the database without having to alter either. With complicated +    mappings, particularly those involving <b>existing databases</b>, this is very valuable.  </p>  <p>The price, of course, is the extra layer that you don't get with  -	<a href="?page=Database.ActiveRecord">Active Record</a>,  -	so the test for using these patterns is the complexity of the business logic.  -	If you have fairly simple business logic, an <a href="?page=Database.ActiveRecord">Active Record</a>  -	will probably work.  -	For more complicated logic a Data Mapper may be more suitable. +    <a href="?page=Database.ActiveRecord">Active Record</a>,  +    so the test for using these patterns is the complexity of the business logic.  +    If you have fairly simple business logic, an <a href="?page=Database.ActiveRecord">Active Record</a>  +    will probably work.  +    For more complicated logic a Data Mapper may be more suitable.  </p>  <h2>SqlMap Data Mapper</h2>  <p>The SqlMap DataMapper framework makes it easier to use a database with a PHP application.  -	SqlMap DataMapper couples objects with stored procedures or SQL statements using  -	a XML descriptor. Simplicity is the biggest advantage of the SqlMap DataMapper over  -	object relational mapping tools. To use SqlMap DataMapper you rely on your own objects,  -	XML, and SQL. There is little to learn that you don't already know.  -	With SqlMap DataMapper you have the full power of both SQL and stored procedures at -	your fingertip +    SqlMap DataMapper couples objects with stored procedures or SQL statements using  +    a XML descriptor. Simplicity is the biggest advantage of the SqlMap DataMapper over  +    object relational mapping tools. To use SqlMap DataMapper you rely on your own objects,  +    XML, and SQL. There is little to learn that you don't already know.  +    With SqlMap DataMapper you have the full power of both SQL and stored procedures at +    your fingertip  </p>  <p> -	<img src=<%~ diagram.png %> alt="SqlMap Data Mapper Overview" id="fig:sqlmap.png" class="figure"/> -	 -	Here's a high level description of the work flow illustrated in the figure abov.  -	Provide a parameter, either as an object or a primitive type. The parameter can be -	 used to set runtime values in your SQL statement or stored procedure. If a runtime value  -	is not needed, the parameter can be omitted. +    <img src=<%~ diagram.png %> alt="SqlMap Data Mapper Overview" id="fig:sqlmap.png" class="figure"/> +     +    Here's a high level description of the work flow illustrated in the figure abov.  +    Provide a parameter, either as an object or a primitive type. The parameter can be +     used to set runtime values in your SQL statement or stored procedure. If a runtime value  +    is not needed, the parameter can be omitted.  </p>  <p>Execute the mapping by passing the parameter and the name you gave the statement or  -	procedure in your XML descriptor. This step is where the magic happens. The framework -	 will prepare the SQL statement or stored procedure, set any runtime values using your  -	parameter, execute the procedure or statement, and return the result. +    procedure in your XML descriptor. This step is where the magic happens. The framework +     will prepare the SQL statement or stored procedure, set any runtime values using your  +    parameter, execute the procedure or statement, and return the result.  </p>  <p>In the case of an update, the number of rows affected is returned. In the case of a -	 query, a single object, or a collection of objects is returned. Like the parameter,  -	the result object, or collection of objects, can be a plain-old object or a primitive PHP type. +     query, a single object, or a collection of objects is returned. Like the parameter,  +    the result object, or collection of objects, can be a plain-old object or a primitive PHP type.  </p>  <h2>Setting up a database connection and initializing the SqlMap</h2>  <p> -	A database connection for SqlMap can be set as follows. -	See <a href="?page=Database.DAO">Establishing Database Connection</a> for -	futher details regarding creation of database connection in general. +    A database connection for SqlMap can be set as follows. +    See <a href="?page=Database.DAO">Establishing Database Connection</a> for +    futher details regarding creation of database connection in general.  <com:TTextHighlighter Language="php" CssClass="source">  //create a connection and give it to the SqlMap manager.  $dsn = 'pgsql:host=localhost;dbname=test'; //Postgres SQL @@ -82,21 +82,21 @@ $conn = new TDbConnection($dsn, 'dbuser','dbpass');  $manager = new TSqlMapManager($conn);  $manager->configureXml('my-sqlmap.xml');  $sqlmap = $manager->getSqlMapGateway();  -</com:TTextHighlighter>	 +</com:TTextHighlighter>   </p>  <p> -	The <tt>TSqlMapManager</tt> is responsible for setting up the database connection -	and configuring the SqlMap with given XML file(s). The <tt>configureXml()</tt> -	method accepts a string that points to a SqlMap XML configuration file. Once -	configured, call the <tt>getSqlMapGateway()</tt> method to obtain an instance -	of the SqlMap gateway interface (use this object to insert/delete/find records). +    The <tt>TSqlMapManager</tt> is responsible for setting up the database connection +    and configuring the SqlMap with given XML file(s). The <tt>configureXml()</tt> +    method accepts a string that points to a SqlMap XML configuration file. Once +    configured, call the <tt>getSqlMapGateway()</tt> method to obtain an instance +    of the SqlMap gateway interface (use this object to insert/delete/find records).  </p>  <p> -	SqlMap database connection can also be configured using a <tt><module></tt> -	tag in the <a href="?page=Configurations.AppConfig">application.xml</a>  -	or <a href="?page=Configurations.PageConfig">config.xml</a> as follows. +    SqlMap database connection can also be configured using a <tt><module></tt> +    tag in the <a href="?page=Configurations.AppConfig">application.xml</a>  +    or <a href="?page=Configurations.PageConfig">config.xml</a> as follows.  <com:TTextHighlighter Language="xml" CssClass="source">  <modules>    <module id="my-sqlmap" class="System.Data.SqlMap.TSqlMapConfig"  @@ -104,66 +104,66 @@ $sqlmap = $manager->getSqlMapGateway();      <database ConnectionString="pgsql:host=localhost;dbname=test"          Username="dbuser" Password="dbpass" />    </module> -</modules>	 -</com:TTextHighlighter>	 +</modules>   +</com:TTextHighlighter>   </p>  <p> -	The <tt>ConfigFile</tt> attribute should point to a SqlMap configuration file -	(to be detailed later) either using absolute path, relative path or the  -	Prado's namespace dot notation path (must omit the ".xml" extension). -	 -	<div class="tip"><b class="note">Tip:</b> -		The <tt>EnableCache</tt> attribute when set to "true" will cache the -		parsed configuration. You must clear or disable the cache if you -		make chanages your configuration file.  -		A <a href="?page=Advanced.Performance#6402">cache -		module</a> must also be defined for the cache to function. -	</div>	 +    The <tt>ConfigFile</tt> attribute should point to a SqlMap configuration file +    (to be detailed later) either using absolute path, relative path or the  +    Prado's namespace dot notation path (must omit the ".xml" extension). +     +    <div class="tip"><b class="note">Tip:</b> +        The <tt>EnableCache</tt> attribute when set to "true" will cache the +        parsed configuration. You must clear or disable the cache if you +        make chanages your configuration file.  +        A <a href="?page=Advanced.Performance#6402">cache +        module</a> must also be defined for the cache to function. +    </div>    </p>  <p>To obtain the SqlMap gateway interface from the <module> configuration, simply -	do, for example, +    do, for example,  <com:TTextHighlighter Language="php" CssClass="source">  class MyPage extends TPage  { -	public function onLoad($param) -	{ -		parent::onLoad($param); -		$sqlmap = $this->Application->Modules['my-sqlmap']->Client; -		$sqlmap->queryForObject(...); //query for some object -	} +    public function onLoad($param) +    { +        parent::onLoad($param); +        $sqlmap = $this->Application->Modules['my-sqlmap']->Client; +        $sqlmap->queryForObject(...); //query for some object +    }  }  </com:TTextHighlighter>  </p>  <h2>A quick example</h2>  <p>Let us -	consider the following "users" table that contains two columns named "username" and "email",  -	where "username" is also the primary key.  +    consider the following "users" table that contains two columns named "username" and "email",  +    where "username" is also the primary key.   <com:TTextHighlighter Language="sql" CssClass="source">  CREATE TABLE users  ( -	username VARCHAR( 20 ) NOT NULL , -	email VARCHAR( 200 ) , -	PRIMARY KEY ( username ) +    username VARCHAR( 20 ) NOT NULL , +    email VARCHAR( 200 ) , +    PRIMARY KEY ( username )  );  </com:TTextHighlighter>  </p>  <p>Next we define our plain <tt>User</tt> class as follows. Notice that -	the <tt>User</tt> is very simple. +    the <tt>User</tt> is very simple.  <com:TTextHighlighter Language="php" CssClass="source">  class User  { -	public $username; -	public $email; +    public $username; +    public $email;  }  </com:TTextHighlighter>  </p>  </p>  <p>Next, we need to define a SqlMap XMl configuration file, lets name -	the file as <tt>my-sqlmap.xml</tt> +    the file as <tt>my-sqlmap.xml</tt>  <com:TTextHighlighter Language="xml" CssClass="source">  <?xml version="1.0" encoding="utf-8" ?>  <sqlMapConfig> @@ -174,10 +174,10 @@ class User  </com:TTextHighlighter>  </p>  <p>The <select> tag returns defines an SQL statement. The <tt>id</tt> -	attribute will be used as the identifier for the query. The <tt>resultClass</tt> -	attribute value is the name of the class the the objects to be returned. -	We can now query the objects as follows: -	 +    attribute will be used as the identifier for the query. The <tt>resultClass</tt> +    attribute value is the name of the class the the objects to be returned. +    We can now query the objects as follows: +      <com:TTextHighlighter Language="php" CssClass="source">  //assume that $sqlmap is an TSqlMapGateway instance  $userList = $sqlmap->queryForList("SelectUsers"); @@ -188,49 +188,49 @@ $user = $sqlmap->queryForObject("SelectUsers");  </p>  <p>The above example shows demonstrates only a fraction of the capabilities -	of the SqlMap Data Mapper. Further details can be found in the  -	<a href="http://www.pradosoft.com/demo/sqlamp/">SqlMap Manual</a>. +    of the SqlMap Data Mapper. Further details can be found in the  +    <a href="http://www.pradosoft.com/demo/sqlamp/">SqlMap Manual</a>.  </p>  <h2>Combining SqlMap with Active Records</h2>  <p>The above example may seem trival and it also seems that there is -	alot work just to retrieve some data. However, notice that the <tt>User</tt> -	class is totally unware of been stored in the database, and the database is -	unware of the <tt>User</tt> class.  +    alot work just to retrieve some data. However, notice that the <tt>User</tt> +    class is totally unware of been stored in the database, and the database is +    unware of the <tt>User</tt> class.   </p>  <p> -	One of advantages of SqlMap is the -	ability to map complex object relationship, collections from an existing -	database. On the other hand, <a href="?page=Database.ActiveRecord">Active Record</a>  -	provide a very simple way -	to interact with the underlying database but unable to do more complicated -	relationship or collections. A good compromise is to use SqlMap to retrieve -	complicated relationships and collections as Active Record objects and then using -	these Active Records to do the updates, inserts and deletes. +    One of advantages of SqlMap is the +    ability to map complex object relationship, collections from an existing +    database. On the other hand, <a href="?page=Database.ActiveRecord">Active Record</a>  +    provide a very simple way +    to interact with the underlying database but unable to do more complicated +    relationship or collections. A good compromise is to use SqlMap to retrieve +    complicated relationships and collections as Active Record objects and then using +    these Active Records to do the updates, inserts and deletes.  </p>  <p>Continuing with the previous example, we change the definition of the -	<tt>User</tt> class to become an Active Record. +    <tt>User</tt> class to become an Active Record.  <com:TTextHighlighter Language="php" CssClass="source">  class UserRecord extends TActiveRecord  { -	public $username; //the column named "username" in the "users" table -	public $email; -	 -	private static $_tablename='users'; //table name  -	 -	/** -	 * @return TActiveRecord active record finder instance -	 */ -	public static function finder() -	{ -		return self::getRecordFinder('UserRecord'); -	} +    public $username; //the column named "username" in the "users" table +    public $email; +     +    private static $_tablename='users'; //table name  +     +    /** +     * @return TActiveRecord active record finder instance +     */ +    public static function finder() +    { +        return self::getRecordFinder('UserRecord'); +    }  }  </com:TTextHighlighter>  </p>  <p>We also need to change the definition of the SqlMap XML configuration. We -	just need to change the value of <tt>resultClass</tt> attribute to <tt>UserRecord</tt>. +    just need to change the value of <tt>resultClass</tt> attribute to <tt>UserRecord</tt>.  <com:TTextHighlighter Language="xml" CssClass="source">  <?xml version="1.0" encoding="utf-8" ?>  <sqlMapConfig> @@ -243,8 +243,8 @@ class UserRecord extends TActiveRecord  <p>The PHP code for retrieving the users remains the same, but SqlMap -	returns Active Records instead, and we can take advantage of the Active Record methods. -	 +    returns Active Records instead, and we can take advantage of the Active Record methods. +      <com:TTextHighlighter Language="php" CssClass="source">  //assume that $sqlmap is an TSqlMapGateway instance  $user = $sqlmap->queryForObject("SelectUsers"); @@ -256,10 +256,10 @@ $user->save(); //save it using Active Record  <h2>References</h2>  <ul> -	<li>Fowler et. al. <i>Patterns of Enterprise Application Architecture</i>, -	Addison Wesley, 2002.</li> -	<li>xxxx. <i>iBatis Data Mapper</i>,  -	<a href="http://www.apache.org/ibatis">http://www.apache.org/ibatis</a>.</li> +    <li>Fowler et. al. <i>Patterns of Enterprise Application Architecture</i>, +    Addison Wesley, 2002.</li> +    <li>xxxx. <i>iBatis Data Mapper</i>,  +    <a href="http://www.apache.org/ibatis">http://www.apache.org/ibatis</a>.</li>  </ul>  </com:TContent>
\ No newline at end of file diff --git a/framework/Data/ActiveRecord/Exceptions/messages.txt b/framework/Data/ActiveRecord/Exceptions/messages.txt index 92bdb30f..774c0275 100644 --- a/framework/Data/ActiveRecord/Exceptions/messages.txt +++ b/framework/Data/ActiveRecord/Exceptions/messages.txt @@ -10,4 +10,6 @@ ar_primary_key_is_scalar					= Primary key '{1}' in table '{0}' is NOT a composi  ar_invalid_db_connection					= Missing or invalid default database connection for ActiveRecord class '{0}', default connection is set by the DbConnection property of TActiveRecordManager.
  ar_mismatch_args_exception					= ActiveRecord finder method '{0}' expects {1} parameters but found only {2} parameters instead.
  ar_invalid_tablename_property				= ActiveRecord tablename property '{0}::${1}' must be static and not null.
 -ar_value_must_not_be_null					= Property '{0}::${2}' must not be null as defined by column '{2}' in table '{1}'.
\ No newline at end of file +ar_value_must_not_be_null					= Property '{0}::${2}' must not be null as defined by column '{2}' in table '{1}'.
 +ar_missing_pk_values						= Missing primary key values in forming IN(key1, key2, ...) for table '{0}'.
 +ar_pk_value_count_mismatch					= Composite key value count mismatch in forming IN( (key1, key2, ..), (key3, key4, ..)) for table '{0}'.
\ No newline at end of file diff --git a/framework/Data/ActiveRecord/TActiveRecord.php b/framework/Data/ActiveRecord/TActiveRecord.php index 68d63a23..d3e25dcf 100644 --- a/framework/Data/ActiveRecord/TActiveRecord.php +++ b/framework/Data/ActiveRecord/TActiveRecord.php @@ -38,7 +38,7 @@ Prado::using('System.Data.ActiveRecord.TActiveRecordCriteria');   *     public $username; //corresponds to the fieldname in the table
   *     public $email;
   *
 - *     private static final $_tablename='users'; //optional table name.
 + *     public static final $_tablename='users'; //optional table name.
   *
   *     //returns active record finder instance
   *     public static function finder()
 @@ -332,7 +332,7 @@ abstract class TActiveRecord extends TComponent  	 */
  	public function findByPk($keys)
  	{
 -		if(func_num_args() > 1 && !is_array($keys))
 +		if(func_num_args() > 1)
  			$keys = func_get_args();
  		$gateway = $this->getRecordManager()->getRecordGateway();
  		$data = $gateway->findRecordByPK($this,$keys);
 @@ -340,6 +340,35 @@ abstract class TActiveRecord extends TComponent  	}
  	/**
 +	 * Find multiple records matching a list of primary or composite keys.
 +	 *
 +	 * For scalar primary keys:
 +	 * <code>
 +	 * $finder->findAllByPk($key1, $key2, ...);
 +	 * $finder->findAllByPk(array($key1, $key2, ...));
 +	 * </code>
 +	 *
 +	 * For composite keys:
 +	 * <code>
 +	 * $finder->findAllByPk(array($key1, $key2), array($key3, $key4), ...);
 +	 * $finder->findAllByPk(array(array($key1, $key2), array($key3, $key4), ...));
 +	 * </code>
 +	 * @param mixed primary keys
 +	 * @return array matching ActiveRecords
 +	 */
 +	public function findAllByPks($keys)
 +	{
 +		if(func_num_args() > 1)
 +			$keys = func_get_args();
 +		$gateway = $this->getRecordManager()->getRecordGateway();
 +		$results = array();
 +		$class = get_class($this);
 +		foreach($gateway->findRecordsByPks($this,(array)$keys) as $data)
 +			$results[] = $this->populateObject($class,$data);
 +		return $results;
 +	}
 +
 +	/**
  	 * Find records using full SQL, returns corresponding record object.
  	 * @param string select SQL
  	 * @param array $parameters
 diff --git a/framework/Data/ActiveRecord/TActiveRecordGateway.php b/framework/Data/ActiveRecord/TActiveRecordGateway.php index 1cb1c79f..7bcd0eb2 100644 --- a/framework/Data/ActiveRecord/TActiveRecordGateway.php +++ b/framework/Data/ActiveRecord/TActiveRecordGateway.php @@ -147,6 +147,21 @@ class TActiveRecordGateway extends TComponent  	}
  	/**
 +	 * Returns records matching the list of given primary keys. 
 +	 * @param TActiveRecord active record instance.
 +	 * @param array list of primary name value pairs
 +	 * @return array matching data.
 +	 */
 +	public function findRecordsByPks(TActiveRecord $record, $keys)
 +	{
 +		$meta = $this->getMetaData($record);
 +		$command = $meta->getFindInPksCommand($record->getDbConnection(), $keys);
 +		$this->raiseCommandEvent(TActiveRecordStatementType::Select,$command,$record,$keys);
 +		return $meta->postQuery($command->query());
 +	}
 +	
 +
 +	/**
  	 * Returns record data matching the given critera. If $iterator is true, it will
  	 * return multiple rows as TDbDataReader otherwise it returns the <b>first</b> row data.
  	 * @param TActiveRecord active record finder instance.
 diff --git a/framework/Data/ActiveRecord/Vendor/TDbMetaData.php b/framework/Data/ActiveRecord/Vendor/TDbMetaData.php index efb7c467..38a82aef 100644 --- a/framework/Data/ActiveRecord/Vendor/TDbMetaData.php +++ b/framework/Data/ActiveRecord/Vendor/TDbMetaData.php @@ -137,6 +137,43 @@ abstract class TDbMetaData extends TComponent  	}
  	/**
 +	 * Construct a "pk IN ('key1', 'key2', ...)" criteria.
 +	 * @param TDbConnection database connection.
 +	 * @param array values for IN predicate
 +	 * @param string SQL string for primary keys IN a list.
 +	 */
 +	protected function getCompositeKeysCriteria($conn, $values)
 +	{
 +		$count = count($this->getPrimaryKeys());
 +		if($count===0)
 +			throw new TActiveRecordException('ar_no_primary_key_found',$this->getTableName());
 +		if(!is_array($values) || count($values) === 0)
 +			throw new TActiveRecordException('ar_missing_pk_values', $this->getTableName());
 +		if($count>1 && !is_array($values[0]))
 +			$values = array($values);
 +		if($count > 1 && count($values[0]) !== $count)
 +			throw new TActiveRecordException('ar_pk_value_count_mismatch', $this->getTableName());
 +
 +		$columns = array();
 +		foreach($this->getPrimaryKeys() as $key)
 +			$columns[] = $this->getColumn($key)->getName();
 +		return '('.implode(', ',$columns).') IN '.$this->quoteTuple($conn, $values);
 +	}
 +
 +	/**
 +	 * @param TDbConnection database connection.
 +	 * @param array values
 +	 * @return string quoted recursive tuple values, e.g. "('val1', 'val2')".
 +	 */
 +	protected function quoteTuple($conn, $array)
 +	{
 +		$data = array();
 +		foreach($array as $k=>$v)
 +			$data[] = is_array($v) ? $this->quoteTuple($conn, $v) : $conn->quoteString($v);
 +		return '('.implode(', ', $data).')';
 +	}
 +
 +	/**
  	 * Bind a list of variables in the command. The named parameters is taken
  	 * from the values of the $keys parameter. The bind value is taken from the
  	 * $values parameter using the index taken from the each value of $keys array.
 @@ -308,60 +345,5 @@ x	 * @param array name value pairs of columns for update.  		return implode(', ', $fields);
  	}
 -	/**
 -	 * @param TDbConnection database connection
 -	 * @param array primary key values.
 -	 * @return string delete criteria for multiple scalar primary keys.
 -	 */
 -	protected function getDeleteInPkCriteria($conn, $keys)
 -	{
 -		$pk = $this->getPrimaryKeys();
 -		$column = $this->getColumn($pk[0])->getName();
 -		$values = array();
 -		foreach($keys as $key)
 -		{
 -			if(is_array($key))
 -			{
 -				throw new TActiveRecordException('ar_primary_key_is_scalar',
 -					$this->getTableName(),$column,'array('.implode(', ',$key).')');
 -			}
 -			$values[] = $conn->quoteString($key);
 -		}
 -		$pks = implode(', ', $values);
 -		return "$column IN ($pks)";
 -	}
 -
 -	/**
 -	 * @param TDbConnection database connection
 -	 * @param array primary key values.
 -	 * @return string delete criteria for multiple composite primary keys.
 -	 */
 -	protected function getDeleteMultiplePkCriteria($conn,$pks)
 -	{
 -		//check for 1 set composite keys
 -		if(count($pks)>0 && !is_array($pks[0]))
 -			$pks = array($pks);
 -		$conditions=array();
 -		foreach($pks as $keys)
 -			$conditions[] = $this->getDeleteCompositeKeyCondition($conn,$keys);
 -		return implode(' OR ', $conditions);
 -	}
 -
 -	/**
 -	 * @return string delete criteria for 1 composite key.
 -	 */
 -	protected function getDeleteCompositeKeyCondition($conn,$keys)
 -	{
 -		$condition=array();
 -		$index = 0;
 -		foreach($this->getPrimarykeys() as $pk)
 -		{
 -			$name = $this->getColumn($pk)->getName();
 -			$value = isset($keys[$pk]) ? $keys[$pk] : $keys[$index];
 -			$condition[] = "$name = ".$conn->quoteString($value);
 -			$index++;
 -		}
 -		return '('.implode(' AND ', $condition).')';
 -	}
  }  ?>
\ No newline at end of file diff --git a/framework/Data/ActiveRecord/Vendor/TDbMetaDataCommon.php b/framework/Data/ActiveRecord/Vendor/TDbMetaDataCommon.php index fffdb6fb..7f7dad8b 100644 --- a/framework/Data/ActiveRecord/Vendor/TDbMetaDataCommon.php +++ b/framework/Data/ActiveRecord/Vendor/TDbMetaDataCommon.php @@ -41,6 +41,24 @@ abstract class TDbMetaDataCommon extends TDbMetaData  	}
  	/**
 +	 * SQL database command for finding records by a list of primary keys.
 +	 * @param TDbConnection database connection.
 +	 * @param array list of primary keys to match.
 +	 * @return TDbCommand find by list of primary keys command.
 +	 */
 +	public function getFindInPksCommand($conn, $keys)
 +	{
 +		$conn->setActive(true);
 +		$columns = $this->getSelectionColumns();
 +		$table = $this->getTableName();
 +		$criteria = $this->getCompositeKeysCriteria($conn,$keys);
 +		$sql = "SELECT {$columns} FROM {$table} WHERE {$criteria}";
 +		$command = $conn->createCommand($sql);
 +		$command->prepare();
 +		return $command;
 +	}
 +
 +	/**
  	 * SQL database command for finding records using a criteria object.
  	 * @param TDbConnection database connection.
  	 * @param TActiveRecordCriteria criteria object
 @@ -160,14 +178,8 @@ abstract class TDbMetaDataCommon extends TDbMetaData  	public function getDeleteByPkCommand($conn,$keys)
  	{
  		$conn->setActive(true);
 -		$numKeys = count($this->getPrimaryKeys());
  		$table = $this->getTableName();
 -		if($numKeys===0)
 -			throw new TActiveRecordException('ar_no_primary_key_found',$table);
 -		if($numKeys===1)
 -			$criteria = $this->getDeleteInPkCriteria($conn,$keys);
 -		else
 -			$criteria = $this->getDeleteMultiplePkCriteria($conn,$keys);
 +		$criteria = $this->getCompositeKeysCriteria($conn, $keys);
  		$sql = "DELETE FROM {$table} WHERE {$criteria}";
  		$command = $conn->createCommand($sql);
  		$command->prepare();
 diff --git a/tests/simple_unit/ActiveRecord/FindByPksTestCase.php b/tests/simple_unit/ActiveRecord/FindByPksTestCase.php new file mode 100644 index 00000000..e7f3e3d0 --- /dev/null +++ b/tests/simple_unit/ActiveRecord/FindByPksTestCase.php @@ -0,0 +1,58 @@ +<?php +
 +Prado::using('System.Data.ActiveRecord.TActiveRecord');
 +require_once(dirname(__FILE__).'/records/DepartmentRecord.php');
 +require_once(dirname(__FILE__).'/records/DepSections.php');
 +
 +class FindByPksTestCase extends UnitTestCase
 +{
 +	function setup()
 +	{
 +		$conn = new TDbConnection('pgsql:host=localhost;dbname=test', 'test','test');
 +		TActiveRecordManager::getInstance()->setDbConnection($conn);
 +	}
 +
 +	function test_find_by_1pk()
 +	{
 +		$dep = DepartmentRecord::finder()->findByPk(1);
 +		$this->assertNotNull($dep);
 +		$this->assertEqual($dep->department_id, 1);
 +	}
 +
 +	function test_find_by_pks()
 +	{
 +		$deps = DepartmentRecord::finder()->findAllByPks(1,2,4);
 +		$this->assertEqual(count($deps), 3);
 +
 +		$this->assertEqual($deps[0]->department_id, 1);
 +		$this->assertEqual($deps[1]->department_id, 2);
 +		$this->assertEqual($deps[2]->department_id, 4);
 +	}
 +
 +	function test_find_by_pks_with_invalid()
 +	{
 +		$deps = DepartmentRecord::finder()->findAllByPks(4,2,14);
 +		$this->assertEqual(count($deps), 2);
 +
 +		$this->assertEqual($deps[0]->department_id, 2);
 +		$this->assertEqual($deps[1]->department_id, 4);
 +	}
 +
 +	function test_find_by_composite_pks()
 +	{
 +		$ds = DepSections::finder()->findAllByPks(array(1,1), array(2,5));
 +		$this->assertEqual(count($ds), 2);
 +
 +		$this->assertIsDepSection($ds[0], 1, 1);
 +		$this->assertIsDepSection($ds[1], 2, 5);
 +	}
 +
 +	function assertIsDepSection($dep, $dep_id, $sec_id)
 +	{
 +		$this->assertTrue($dep instanceof DepSections);
 +		$this->assertEqual($dep->department_id, $dep_id);
 +		$this->assertEqual($dep->section_id, $sec_id);
 +	}
 +}
 + +?>
\ No newline at end of file diff --git a/tests/test_tools/simpletest/errors.php b/tests/test_tools/simpletest/errors.php index e2f6eb04..729883e9 100644 --- a/tests/test_tools/simpletest/errors.php +++ b/tests/test_tools/simpletest/errors.php @@ -150,7 +150,7 @@                      E_COMPILE_WARNING => 'E_COMPILE_WARNING',                      E_USER_ERROR => 'E_USER_ERROR',                      E_USER_WARNING => 'E_USER_WARNING', -                    E_USER_NOTICE => 'E_USER_NOTICE'); +                    E_USER_NOTICE => 'E_USER_NOTICE', 4096 => 'E??');              return $map[$severity];          }      }  | 
