Commit cdcaab1d authored by zYne's avatar zYne

enhanced component overview and moved it to its own chapter

parent 0a2ecb60
......@@ -5,6 +5,7 @@
+ Basic schema mapping
+ Relations
+ Working with objects
+ Component overview
+ Hierarchical data
+ Configuration
+ DQL (Doctrine Query Language)
......
This diff is collapsed.
......@@ -56,8 +56,6 @@ $user->save();
This is due to a fact that $user->GroupUser loads all group links for given user. This can time-consuming task if user belongs to many groups. Even if the user belongs to few groups this will still execute an unnecessary SELECT statement.
++ Component overview
++ Fetching objects
Normally when you fetch data from database the following phases are executed:
......@@ -130,7 +128,7 @@ This structure also applies to the hydration of objects(records) which is the de
You should always use array hydration when you only need to data for access-only purposes, whereas you should use the record hydration when you need to change the fetched data.
The constant O(n) performance of the hydration algorithm is ensured by a smart identifier caching solution.
The constant O(n) performance of the hydration algorithm is ensured by a smart identifier caching solution.
+++ Field lazy-loading
......
+++ Manager
+++ Connection
+++ Record
+++ Collection
+++ Table
++++ Introduction
{{Doctrine_Collection}} is a collection of records (see {{Doctrine_Record}}). As with records the collections can be deleted and saved using {{Doctrine_Collection::delete()}} and {{Doctrine_Collection::save()}} accordingly.
When fetching data from database with either DQL API (see {{Doctrine_Query}}) or rawSql API (see {{Doctrine_RawSql}}) the methods return an instance of {{Doctrine_Collection}} by default.
The following example shows how to initialize a new collection:
<code type="php">
$conn = Doctrine_Manager::getInstance()
->openConnection(new PDO("dsn", "username", "pw"));
// initalizing a new collection
$users = new Doctrine_Collection($conn->getTable('User'));
// alternative (propably easier)
$users = new Doctrine_Collection('User');
// adding some data
$users[0]->name = 'Arnold';
$users[1]->name = 'Somebody';
// finally save it!
$users->save();
</code>
++++ Accessing elements
You can access the elements of {{Doctrine_Collection}} with {{set()}} and {{get()}} methods or with {{ArrayAccess}} interface.
<code type="php">
$table = $conn->getTable("User");
$users = $table->findAll();
// accessing elements with ArrayAccess interface
$users[0]->name = "Jack Daniels";
$users[1]->name = "John Locke";
// accessing elements with get()
print $users->get(1)->name;
</code>
++++ Adding new elements
When accessing single elements of the collection and those elements (records) don't exist Doctrine auto-adds them.
In the following example we fetch all users from database (there are 5) and then add couple of users in the collection.
As with PHP arrays the indexes start from zero.
<code type="php">
$users = $table->findAll();
print count($users); // 5
$users[5]->name = "new user 1";
$users[6]->name = "new user 2";
</code>
++++ Getting collection count
The {{Doctrine_Collection}} method {{count()}} returns the number of elements currently in the collection.
<code type="php">
$users = $table->findAll();
$users->count();
// or
count($users); // Doctrine_Collection implements Countable interface
</code>
++++ Saving the collection
As with records the collection can be saved by calling the save method.
<code type="php">
$users = $table->findAll();
$users[0]->name = "Jack Daniels";
$users[1]->name = "John Locke";
$users->save();
</code>
++++ Deleting collection
Doctrine Collections can be deleted in very same way is Doctrine Records you just call {{delete()}} method. As for all collections Doctrine knows how to perform single-shot-delete meaning it only performs one database query for the each collection.
For example if we have collection of users which own [0-*] phonenumbers. When deleting the collection
of users doctrine only performs two queries for this whole transaction. The queries would look something like:
<code type="sql">
DELETE FROM user WHERE id IN (1,2,3, ... ,N)
DELETE FROM phonenumber WHERE id IN (1,2,3, ... ,M)
</code>
It should also be noted that Doctrine is smart enough to perform single-shot-delete per table when transactions are used. So if you are deleting a lot of records and want to optimize the operation just wrap the delete calls in {{Doctrine_Connection}} transaction.
<code type="php">
// delete all users with name 'John'
$users = $table->findByDql("name LIKE '%John%'");
$users->delete();
</code>
++++ Key mapping
Sometimes you may not want to use normal indexing for collection elements. For example in some cases mapping primary keys as collection keys might be useful. The following example demonstrates how this can be achieved.
<code type="php">
// mapping id column
$user = new User();
$user->setAttribute(Doctrine::ATTR_COLL_KEY, 'id');
// now user collections will use the values of
// id column as element indexes
$users = $user->getTable()->findAll();
foreach($users as $id => $user) {
print $id . $user->name;
}
// mapping name column
$user = new User();
$user->setAttribute(Doctrine::ATTR_COLL_KEY, 'name');
// now user collections will use the values of
// name column as element indexes
$users = $user->getTable()->findAll();
foreach($users as $name => $user) {
print $name . $user->type;
}
</code>
++++ Loading related records
Doctrine provides means for efficiently retrieving all related records for all record elements. That means when you have for example a collection of users you can load all phonenumbers for all users by simple calling the {{loadRelated()}} method.
<code type="php">
$users = $conn->query("FROM User");
// now lets load phonenumbers for all users
$users->loadRelated("Phonenumber");
foreach($users as $user) {
print $user->Phonenumber->phonenumber;
// no additional db queries needed here
}
// the loadRelated works an any relation, even associations:
$users->loadRelated("Group");
foreach($users as $user) {
print $user->Group->name;
}
</code>
++++ Collection expanding
++++ Introduction
Doctrine_Connection is a wrapper for database connection. It handles several things:
* Handles database portability things missing from PDO (eg. {{LIMIT}} / {{OFFSET}} emulation)
* Keeps track of {{Doctrine_Table}} objects
* Keeps track of records
* Keeps track of records that need to be updated / inserted / deleted
* Handles transactions and transaction nesting
* Handles the actual querying of the database in the case of {{INSERT}} / {{UPDATE}} / {{DELETE}} operations
* Can query the database using the DQL API (see {{Doctrine_Query}})
* Optionally validates transactions using {{Doctrine_Validator}} and gives full information of possible errors.
++++ Available drivers
Doctrine has drivers for every PDO-supported database. The supported databases are:
* FreeTDS / Microsoft SQL Server / Sybase
* Firebird/Interbase 6
* Informix
* Mysql
* Oracle
* Odbc
* PostgreSQL
* Sqlite
++++ Getting a table object
In order to get table object for specified record just call {{Doctrine_Record::getTable()}} or {{Doctrine_Connection::getTable()}}.
<code type="php">
$manager = Doctrine_Manager::getInstance();
// open new connection
$conn = $manager->openConnection(new PDO('dsn','username','password'));
// getting a table object
$table = $conn->getTable('User');
</code>
++++ Flushing the connection
Creating new record (database row) is very easy. You can either use the {{Doctrine_Connection::create()}} or {{Doctrine_Table::create()}} method to do this or just simply use the new operator.
<code type="php">
$user = new User();
$user->name = 'Jack';
$group = $conn->create('Group');
$group->name = 'Drinking Club';
// saves all the changed objects into database
$conn->flush();
</code>
++++ Querying the database
{{Doctrine_Connection::query()}} is a simple method for efficient object retrieval. It takes one parameter (DQL query) and optionally prepared statement params.
<code type="php">
// select all users
$users = $conn->query('FROM User');
// select all users where user email is jackdaniels@drinkmore.info
$users = $conn->query("FROM User WHERE User.Email.address = 'jackdaniels@drinkmore.info'");
// using prepared statements
$users = $conn->query('FROM User WHERE User.name = ?', array('Jack'));
</code>
++++ Getting connection state
Connection state gives you information about how active connection currently is. You can get the current state by calling {{Doctrine_Connection::getState()}}.
<code type="php">
switch($conn->getState()):
case Doctrine_Connection::STATE_ACTIVE:
// connection open and zero open transactions
break;
case Doctrine_Connection::STATE_ACTIVE:
// one open transaction
break;
case Doctrine_Connection::STATE_BUSY:
// multiple open transactions
break;
case Doctrine_Connection::STATE_CLOSED:
// connection closed
break;
endswitch;
</code>
++++ Introduction
{{Doctrine_Record}} is a wrapper for database row but along with that it speficies what relations it has
on other components and what columns it has. It may access the related components, hence its refered as an ActiveRecord.
The classes that inherit {{Doctrine_Record}} are called components. There should be atleast one component for each database table.
++++ Creating new records
There are couple of ways for creating new records. Propably the easiest is using native php new -operator. The other ways are calling {{Doctrine_Table::create()}} or {{Doctrine_Connection::create()}}. The last two exists only for backward compatibility. The recommended way of creating new objects is the new operator.
<code type="php">
$user = $conn->create("User");
// alternative way:
$table = $conn->getTable("User");
$user = $table->create();
// the simpliest way:
$user = new User();
// records support array access
$user["name"] = "John Locke";
// save user into database
$user->save();
</code>
++++ Retrieving existing records
Doctrine provides many ways for record retrieval. The fastest ways for retrieving existing records are the finder methods provided by {{Doctrine_Table}}. If you need to use more complex queries take a look at DQL API and {{Doctrine_Connection::query}} method.
<code type="php">
$table = $conn->getTable("User");
// find by primary key
$user = $table->find(2);
if($user !== false)
print $user->name;
// get all users
foreach($table->findAll() as $user) {
print $user->name;
}
// finding by dql
foreach($table->findByDql("name LIKE '%John%'") as $user) {
print $user->created;
}
// finding objects with DQL
$users = $conn->query("FROM User WHERE User.name LIKE '%John%'");
</code>
++++ Accessing properties
You can retrieve existing objects (database rows) with {{Doctrine_Table}} or {{Doctrine_Connection}}. {{Doctrine_Table}} provides simple methods like {{findBySql}}, {{findAll}} and find for finding objects whereas {{Doctrine_Connection}} provides complete OQL API for retrieving objects (see chapter [doc dql-doctrine-query-language).
<code type="php">
$user = $table->find(3);
// access property through overloading
$name = $user->name;
// access property with get()
$name = $user->get("name");
// access property with ArrayAccess interface
$name = $user['name'];
// iterating through properties
foreach($user as $key => $value) {
}
</code>
++++ Updating records
Updating objects is very easy, you just call the {{Doctrine_Record::save()}} method. The other way (perhaps even easier) is to call {{Doctrine_Connection::flush()}} which saves all objects. It should be noted though that flushing is a much heavier operation than just calling save method.
<code type="php">
$table = $conn->getTable('User');
$user = $table->find(2);
if($user !== false) {
$user->name = 'Jack Daniels';
$user->save();
}
</code>
++++ Deleting records
Deleting records in Doctrine is handled by {{Doctrine_Record::delete()}}, {{Doctrine_Collection::delete()}} and {{Doctrine_Connection::delete()}} methods.
<code type="php">
$table = $conn->getTable("User");
$user = $table->find(2);
// deletes user and all related composite objects
if($user !== false)
$user->delete();
$users = $table->findAll();
// delete all users and their related composite objects
$users->delete();
</code>
++++ Using expression values
There might be situations where you need to use SQL expressions as values of columns. This can be achieved by using Doctrine_Expression which converts portable DQL expressions to your native SQL expressions.
Lets say we have a class called event with columns timepoint(datetime) and name(string). Saving the record with the current timepoint can be achieved as follows:
<code type="php">
$event = new Event();
$event->name = 'Rock festival';
$event->timepoint = new Doctrine_Expression('NOW()');
$event->save();
</code>
The last line would execute sql (in sqlite):
<code>
INSERT INTO event (name, timepoint) VALUES (?, 'NOW()')
</code>
++++ Getting record state
{{Every Doctrine_Record}} has a state. First of all record can be transient or persistent. Every record that is retrieved from database is persistent and every newly created record is transient. If a {{Doctrine_Record}} is retrieved from database but the only loaded property is its primary key, then this record has a state called proxy.
Every transient and persistent {{Doctrine_Record}} is either clean or dirty. {{Doctrine_Record}} is clean when none of its properties are changed and dirty when atleast one of its properties has changed.
<code type="php">
$state = $record->state();
switch($state):
case Doctrine_Record::STATE_PROXY:
// record is in proxy state,
// meaning its persistent but not all of its properties are
// loaded from the database
break;
case Doctrine_Record::STATE_TCLEAN:
// record is transient clean,
// meaning its transient and
// none of its properties are changed
break;
case Doctrine_Record::STATE_TDIRTY:
// record is transient dirty,
// meaning its transient and
// some of its properties are changed
break;
case Doctrine_Record::STATE_DIRTY:
// record is dirty,
// meaning its persistent and
// some of its properties are changed
break;
case Doctrine_Record::STATE_CLEAN:
// record is clean,
// meaning its persistent and
// none of its properties are changed
break;
endswitch;
</code>
++++ Getting object copy
Sometimes you may want to get a copy of your object (a new object with all properties copied). Doctrine provides a simple method for this: {{Doctrine_Record::copy()}}.
<code type="php">
$copy = $user->copy();
</code>
++++ Saving a blank record
By default Doctrine doesn't execute when save() is being called on an unmodified record. There might be situations where you want to force-insert the record even if it has not been modified. This can be achieved by assigning the state of the record to Doctrine_Record::STATE_TDIRTY.
<code type="php">
$user = new User();
$user->state('TDIRTY');
$user->save();
$user->id; // 1
</code>
++++ Mapping custom values
There might be situations where you want to map custom values to records. For example values that depend on some outer sources and you only want these values to be availible at runtime not persisting those values into database. This can be achieved as follows:
<code type="php">
$user->mapValue('isRegistered', true);
$user->isRegistered; // true
</code>
++++ Serializing
Sometimes you may want to serialize your record objects (possibly for caching purposes). Records can be serialized, but remember: Doctrine cleans all relations, before doing this. So remember to persist your objects into database before serializing them.
<code type="php">
$string = serialize($user);
$user = unserialize($string);
</code>
++++ Checking Existence
<code type="php">
$record = new User();
$record->exists(); // false
$record->name = 'someone';
$record->save();
$record->exists(); // true
</code>
++++ Callbacks
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment