From 03f362a40a8dd39f8c8b4bf816334922b7b264e4 Mon Sep 17 00:00:00 2001 From: wei <> Date: Tue, 9 Jan 2007 10:42:06 +0000 Subject: add TActiveRecord::findAllByPks() --- .../protected/pages/Database/ActiveRecord.page | 280 ++++++++++++--------- .../protected/pages/Database/SqlMap.page | 236 ++++++++--------- 2 files changed, 276 insertions(+), 240 deletions(-) (limited to 'demos') 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 @@

Active Record

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 - Products class, that match very closely the record structure - of an underlying database table. Each Active Record will be responsible for - saving and loading data to and from the database.

+ 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 + Products class, that match very closely the record structure + of an underlying database table. Each Active Record will be responsible for + saving and loading data to and from the database.

Info: - 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.

When to Use It

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.

+ 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.

-

However, as your business logic grows in complexity, you'll soon want - to use your object's direct relationships, collections, inheritance, and so +

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.

- -

The alternative is to use a Data Mapper that separates the roles of the - business object and how these objects are stored. - Prado provides a complimentary choice between Active Record and - SqlMap Data Mapper. - A SqlMap Data Mapper can be used to load Active Record objects, in turn, these - Active Record objects can be used to update the database. - The "relationship" between Active Records and SqlMap is illustrated in the - following diagram. More details regarding the SqlMap Data Mapper can be found in - the SqlMap Manual. - alt="Active Records and SqlMap DataMapper" id="fig:diagram.png" class="figure"/> -

- -

- The Active Record class has methods that do the following: -

-

-The Active Record implementation utilizes the Prado DAO 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.

+ +

The alternative is to use a Data Mapper that separates the roles of the + business object and how these objects are stored. + Prado provides a complimentary choice between Active Record and + SqlMap Data Mapper. + A SqlMap Data Mapper can be used to load Active Record objects, in turn, these + Active Record objects can be used to update the database. + The "relationship" between Active Records and SqlMap is illustrated in the + following diagram. More details regarding the SqlMap Data Mapper can be found in + the SqlMap Manual. + alt="Active Records and SqlMap DataMapper" id="fig:diagram.png" class="figure"/> +

+ +

+ The Active Record class has methods that do the following: +

+

+The Active Record implementation utilizes the Prado DAO classes for data access. The current Active Record implementation supports MySQL, Postgres SQL and @@ -56,14 +56,14 @@ The current Active Record implementation supports Support for other databases can be provided when there are sufficient demand.

Defining an Active Record

Let us - consider the following "users" table that contains two columns named "username" and "email", - where "username" is also the primary key. + consider the following "users" table that contains two columns named "username" and "email", + where "username" is also the primary key. 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 ) );

@@ -71,91 +71,98 @@ CREATE TABLE users 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'); + } }

Each property of the UserRecord class must correspond to a - column with the same name in the "users" table. The static class variable - $_tablename (must be public) is optional when the class name is the same as - the table name in the database, otherwise $_tablename 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 + $_tablename (must be public) is optional when the class name is the same as + the table name in the database, otherwise $_tablename must + specify the table name that corresponds to your Active Record class.

Tip: - Since TActiveRecord extends TComponent, setter and - getter methods can be defined to allow control over how variables - are set and returned. For example, adding a $level property to the UserRecord class: + Since TActiveRecord extends TComponent, setter and + getter methods can be defined to allow control over how variables + are set and returned. For example, adding a $level property to the UserRecord class: 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; + } }
+
Note: +TActiveRecord can also work with database views by specifying the value $_tablename +corresponding to the view name. However, objects returned +from views are read-only, calling the save() or delete() method +will raise an exception. +
+

- The static method finder() returns an UserRecord 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 TActiveRecord::getRecordFinder() - static method takes the name of the current Active Record class as parameter. + The static method finder() returns an UserRecord 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 TActiveRecord::getRecordFinder() + static method takes the name of the current Active Record class as parameter.

Setting up a database connection

- A default database connection for Active Record can be set as follows. - See Establishing Database Connection for - futher details regarding creation of database connection in general. + A default database connection for Active Record can be set as follows. + See Establishing Database Connection for + futher details regarding creation of database connection in general. //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); - +

- The default database connection can also be configured using a <module> - tag in the application.xml - or config.xml as follows. + The default database connection can also be configured using a <module> + tag in the application.xml + or config.xml as follows. - - + +

Tip: - The EnableCache attribute when set to "true" will cache the table - meta data, that is, the table columns names, indexes and constraints are - saved in the cache and reused. You must clear or disable the cache if you - wish to see chanages made to your table definitions. A cache - module must also be defined for the cache to function. + The EnableCache attribute when set to "true" will cache the table + meta data, that is, the table columns names, indexes and constraints are + saved in the cache and reused. You must clear or disable the cache if you + wish to see chanages made to your table definitions. A cache + module must also be defined for the cache to function.

A ConnectionID property can be specified with value corresponding - to another TDataSourceConfig module configuration's ID value. This allows - the same database connection to be used in other modules such as SqlMap. + to another TDataSourceConfig module configuration's ID value. This allows + the same database connection to be used in other modules such as SqlMap. @@ -168,32 +175,52 @@ TActiveRecordManager::getInstance()->setDbConnection($conn); - - + +

Loading data from the database

- The TActiveRecord class provides many convenient methods to find - records from the database. The simplest is finding records by matching primary keys. - See the for - more details. + The TActiveRecord class provides many convenient methods to find + records from the database. The simplest is finding records by matching primary keys. + See the for + more details.

-

findByPk()

-

Finds one record using only the primary key or composite primary keys. +

findByPk()

+

Finds one record using only the primary key or composite primary keys. $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,...));

+

findAllByPks()

+

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). + +$finder = UserRecord::finder(); +$users = $finder->findAllByPk($key1, $key2, ...); +$users = $finder->findAllByPk(array($key1, $key2, ...)); + +The following are equivalent for composite keys. + +//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); + + +

+ +

find()

Finds one single record that matches the criteria. The criteria - can be a partial SQL string or a TActiveRecordCriteria object. + can be a partial SQL string or a TActiveRecordCriteria object. $finder = UserRecord::finder(); @@ -212,13 +239,13 @@ $finder->find($criteria); //the 2nd parameter for find() is ignored.

The TActiveRecordCriteria class has the following properties: -

+

@@ -258,8 +285,8 @@ $finder->find('Username = ? AND Password = ?', $name, $pass); $finder->findAllByAge($age); $finder->findAll('Age = ?', $age); -

- +

+

findBySql()

Finds records using full SQL, returns corresponding array of record objects.

@@ -291,7 +318,7 @@ incremented values.

To update a record in the database, just change one or more properties of the Active Record object that has been loaded from the database and then -call the save() method. +call the save() method. $user = UserRecord::finder()->findByName('admin'); @@ -303,14 +330,23 @@ $user->save(); //update it.

Active Record objects have a simple life-cycle illustrated in the following diagram. 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 find*() +methods or using creating a new instance by using PHP's new keyword. Objects +created by a find*() method starts with clean state. New instance of +ActiveRecords created other than by a find*() method starts with new state. +When ever you +call the save() method on the ActiveRecord object, the object enters the clean +state. Objects in the clean becomes dirty whenever one of more of its +internal states are changed. Calling the delete() method on the object +ends the object life-cycle, no futher actions can be performed on the object.

Deleting existing records

- To delete an existing record that is already loaded, just call the delete() method. - You can also delete records in the database by primary keys without - loading any records using the deleteByPk() 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 delete() method. + You can also delete records in the database by primary keys without + loading any records using the deleteByPk() method. + For example, to delete one or records with tables having a scalar primary key. $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), .. ));

Transactions

All Active Record objects contains the property DbConnection - that can be used to obtain a transaction object. + that can be used to obtain a transaction object. $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

References

\ 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 @@

Data Mapper

Data Mappers moves data between objects and a database while keeping them - independent of each other and the mapper itself. If you started with - Active Records, 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 + Active Records, 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.

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.)

When to Use It

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.

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 existing databases, 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 existing databases, this is very valuable.

The price, of course, is the extra layer that you don't get with - Active Record, - so the test for using these patterns is the complexity of the business logic. - If you have fairly simple business logic, an Active Record - will probably work. - For more complicated logic a Data Mapper may be more suitable. + Active Record, + so the test for using these patterns is the complexity of the business logic. + If you have fairly simple business logic, an Active Record + will probably work. + For more complicated logic a Data Mapper may be more suitable.

SqlMap Data Mapper

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

- 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. + 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.

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.

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.

Setting up a database connection and initializing the SqlMap

- A database connection for SqlMap can be set as follows. - See Establishing Database Connection for - futher details regarding creation of database connection in general. + A database connection for SqlMap can be set as follows. + See Establishing Database Connection for + futher details regarding creation of database connection in general. //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(); - +

- The TSqlMapManager is responsible for setting up the database connection - and configuring the SqlMap with given XML file(s). The configureXml() - method accepts a string that points to a SqlMap XML configuration file. Once - configured, call the getSqlMapGateway() method to obtain an instance - of the SqlMap gateway interface (use this object to insert/delete/find records). + The TSqlMapManager is responsible for setting up the database connection + and configuring the SqlMap with given XML file(s). The configureXml() + method accepts a string that points to a SqlMap XML configuration file. Once + configured, call the getSqlMapGateway() method to obtain an instance + of the SqlMap gateway interface (use this object to insert/delete/find records).

- SqlMap database connection can also be configured using a <module> - tag in the application.xml - or config.xml as follows. + SqlMap database connection can also be configured using a <module> + tag in the application.xml + or config.xml as follows. getSqlMapGateway(); - - + +

- The ConfigFile 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). - -

Tip: - The EnableCache 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 cache - module must also be defined for the cache to function. -
+ The ConfigFile 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). + +
Tip: + The EnableCache 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 cache + module must also be defined for the cache to function. +

To obtain the SqlMap gateway interface from the <module> configuration, simply - do, for example, + do, for example, 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 + } }

A quick example

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. 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 ) );

Next we define our plain User class as follows. Notice that - the User is very simple. + the User is very simple. class User { - public $username; - public $email; + public $username; + public $email; }

Next, we need to define a SqlMap XMl configuration file, lets name - the file as my-sqlmap.xml + the file as my-sqlmap.xml @@ -174,10 +174,10 @@ class User

The <select> tag returns defines an SQL statement. The id - attribute will be used as the identifier for the query. The resultClass - 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 resultClass + attribute value is the name of the class the the objects to be returned. + We can now query the objects as follows: + //assume that $sqlmap is an TSqlMapGateway instance $userList = $sqlmap->queryForList("SelectUsers"); @@ -188,49 +188,49 @@ $user = $sqlmap->queryForObject("SelectUsers");

The above example shows demonstrates only a fraction of the capabilities - of the SqlMap Data Mapper. Further details can be found in the - SqlMap Manual. + of the SqlMap Data Mapper. Further details can be found in the + SqlMap Manual.

Combining SqlMap with Active Records

The above example may seem trival and it also seems that there is - alot work just to retrieve some data. However, notice that the User - class is totally unware of been stored in the database, and the database is - unware of the User class. + alot work just to retrieve some data. However, notice that the User + class is totally unware of been stored in the database, and the database is + unware of the User class.

- One of advantages of SqlMap is the - ability to map complex object relationship, collections from an existing - database. On the other hand, Active Record - 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, Active Record + 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.

Continuing with the previous example, we change the definition of the - User class to become an Active Record. + User class to become an Active Record. 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'); + } }

We also need to change the definition of the SqlMap XML configuration. We - just need to change the value of resultClass attribute to UserRecord. + just need to change the value of resultClass attribute to UserRecord. @@ -243,8 +243,8 @@ class UserRecord extends TActiveRecord

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. + //assume that $sqlmap is an TSqlMapGateway instance $user = $sqlmap->queryForObject("SelectUsers"); @@ -256,10 +256,10 @@ $user->save(); //save it using Active Record

References

\ No newline at end of file -- cgit v1.2.3