1
0
mirror of synced 2024-12-05 03:06:05 +03:00

[2.0] Cleaning up old stuff.

This commit is contained in:
jwage 2009-02-14 22:45:08 +00:00
parent 9dcab5ee63
commit f2e9aa91b9
1027 changed files with 1 additions and 202351 deletions

View File

@ -1,20 +1 @@
Beta 3
------
* r3323: Added Doctrine_Record and Doctrine_Collection synchronizeWithArray method
* r3264: Added a $deep parameter to Doctrine_Record::refresh()
Beta 2
------
* r3183: NestedSet: 'level' column renamed to 'lvl' because LEVEL is an oracle keyword.
In order to upgrade existing trees you need to rename the level column to lvl
in your databases. This does not affect your code because the NestedSet now uses
a column alias (lvl as level). So your code still refers to the 'level' field.
* r3048: Doctrine::exportSchema() replaced by Doctrine::createTablesFromModels()
* r3048: Doctrine::exportSql() replaced by Doctrine::generateSqlFromModels()
* r3048: Doctrine::importSchema() replaced by Doctrine::generateModelsFromDb()
* r3048: loadAll() loadAllRuntimeClasses() removed
Beta 1
------

0
README
View File

1
docs/manual/en.txt Normal file
View File

@ -0,0 +1 @@
+ Query Language

View File

@ -1 +0,0 @@
deny from all

View File

@ -1,28 +0,0 @@
+ Introduction
+ Getting started
+ Connection management
+ Basic schema mapping
+ Relations
+ Working with objects
+ Component overview
+ Hierarchical data
+ Configuration
+ DQL (Doctrine Query Language)
+ Native SQL
+ Transactions
+ Caching
+ Event listeners
+ Class templates
+ Plugins
+ File parser
+ Migration
+ Data fixtures
+ Schema Files
+ Searching
+ Database abstraction
+ Improving Performance
+ Technology
+ Exceptions and warnings
+ Real world examples
+ Coding standards
+ Utilities

View File

@ -1,22 +0,0 @@
<code type="php">
class Customer extends Doctrine_Record {
public function setUp() {
// setup code goes here
}
public function setTableDefinition() {
// table definition code goes here
}
public function getAvailibleProducts() {
// some code
}
public function setName($name) {
if($this->isValidName($name))
$this->set("name",$name);
}
public function getName() {
return $this->get("name");
}
}
</code>

View File

@ -1,10 +0,0 @@
<code type="php">
// custom primary key column name
class Group extends Doctrine_Record {
public function setUp() {
$this->setPrimaryKeyColumn("group_id");
}
}
</code>

View File

@ -1,51 +0,0 @@
* Doctrine::ATTR_LISTENER
* Doctrine::ATTR_FETCHMODE = 2;
* Doctrine::ATTR_CACHE_DIR = 3;
* Doctrine::ATTR_CACHE_TTL = 4;
* Doctrine::ATTR_CACHE_SIZE = 5;
* Doctrine::ATTR_CACHE_SLAM = 6;
* Doctrine::ATTR_CACHE = 7;
* Doctrine::ATTR_BATCH_SIZE = 8;
* Doctrine::ATTR_PK_COLUMNS = 9;
/**
* primary key type attribute
*/
* Doctrine::ATTR_PK_TYPE = 10;
/**
* locking attribute
*/
* Doctrine::ATTR_LOCKMODE = 11;
/**
* validatate attribute
*/
* Doctrine::ATTR_VLD = 12;
/**
* name prefix attribute
*/
* Doctrine::ATTR_NAME_PREFIX = 13;
/**
* create tables attribute
*/
* Doctrine::ATTR_CREATE_TABLES = 14;
/**
* collection key attribute
*/
* Doctrine::ATTR_COLL_KEY = 15;
/**
* collection limit attribute
*/
* Doctrine::ATTR_COLL_LIMIT = 16;

View File

@ -1,11 +0,0 @@
<code type="php">
class Email extends Doctrine_Record {
public function setUp() {
$this->setAttribute(Doctrine::ATTR_LISTENER,new MyListener());
}
public function setTableDefinition() {
$this->hasColumn("address","string",150,"email|unique");
}
}
</code>

View File

@ -1,12 +0,0 @@
<code type="php">
// setting default fetchmode
// availible fetchmodes are Doctrine::FETCH_LAZY, Doctrine::FETCH_IMMEDIATE and Doctrine::FETCH_BATCH
// the default fetchmode is Doctrine::FETCH_LAZY
class Address extends Doctrine_Record {
public function setUp() {
$this->setAttribute(Doctrine::ATTR_FETCHMODE,Doctrine::FETCH_IMMEDIATE);
}
}
</code>

View File

@ -1,10 +0,0 @@
<code type="php">
// using sequences
class User extends Doctrine_Record {
public function setUp() {
$this->setSequenceName("user_seq");
}
}
</code>

View File

@ -1,8 +0,0 @@
<code type="php">
$q = new Doctrine_Query();
$q->from('User')->where('User.Phonenumber.phonenumber.contains(?,?,?)');
$users = $q->execute(array('123 123 123', '0400 999 999', '+358 100 100'));
</code>

View File

@ -1,8 +0,0 @@
<code type="php">
$q = new Doctrine_Query();
$q->from('User')->where('User.Phonenumber.phonenumber.like(?,?)');
$users = $q->execute(array('%123%', '456%'));
</code>

View File

@ -1,8 +0,0 @@
<code type="php">
$q = new Doctrine_Query();
$q->from('User')->where('User.Phonenumber.phonenumber.regexp(?,?)');
$users = $q->execute(array('[123]', '^[3-5]'));
</code>

View File

@ -1,167 +0,0 @@
*
NOT, !
Logical NOT. Evaluates to 1 if the
operand is 0, to 0 if
the operand is non-zero, and NOT NULL
returns NULL.
<b class='title'>DQL condition :** NOT 10
-&gt; 0
<b class='title'>DQL condition :** NOT 0
-&gt; 1
<b class='title'>DQL condition :** NOT NULL
-&gt; NULL
<b class='title'>DQL condition :** ! (1+1)
-&gt; 0
<b class='title'>DQL condition :** ! 1+1
-&gt; 1
</pre>
The last example produces 1 because the
expression evaluates the same way as
( ! 1)+1.
*
<a name="function_and"></a>
<a class="indexterm" name="id2965271"></a>
<a class="indexterm" name="id2965283"></a>
AND
Logical AND. Evaluates to 1 if all
operands are non-zero and not NULL, to
0 if one or more operands are
0, otherwise NULL is
returned.
<b class='title'>DQL condition :** 1 AND 1
-&gt; 1
<b class='title'>DQL condition :** 1 AND 0
-&gt; 0
<b class='title'>DQL condition :** 1 AND NULL
-&gt; NULL
<b class='title'>DQL condition :** 0 AND NULL
-&gt; 0
<b class='title'>DQL condition :** NULL AND 0
-&gt; 0
</pre>
*
OR
Logical OR. When both operands are
non-NULL, the result is
1 if any operand is non-zero, and
0 otherwise. With a
NULL operand, the result is
1 if the other operand is non-zero, and
NULL otherwise. If both operands are
NULL, the result is
NULL.
<b class='title'>DQL condition :** 1 OR 1
-&gt; 1
<b class='title'>DQL condition :** 1 OR 0
-&gt; 1
<b class='title'>DQL condition :** 0 OR 0
-&gt; 0
<b class='title'>DQL condition :** 0 OR NULL
-&gt; NULL
<b class='title'>DQL condition :** 1 OR NULL
-&gt; 1
</pre>
*
<a name="function_xor"></a>
<a class="indexterm" name="id2965520"></a>
XOR
Logical XOR. Returns NULL if either
operand is NULL. For
non-NULL operands, evaluates to
1 if an odd number of operands is
non-zero, otherwise 0 is returned.
<b class='title'>DQL condition :** 1 XOR 1
-&gt; 0
<b class='title'>DQL condition :** 1 XOR 0
-&gt; 1
<b class='title'>DQL condition :** 1 XOR NULL
-&gt; NULL
<b class='title'>DQL condition :** 1 XOR 1 XOR 1
-&gt; 1
</pre>
a XOR b is mathematically equal to
(a AND (NOT b)) OR ((NOT a) and b).

View File

@ -1,8 +0,0 @@
<code type="php">
$sess = Doctrine_Manager::getInstance()->openConnection(new PDO("dsn","username","password"));
// select first ten rows starting from the row 20
$sess->select("select * from user",10,20);
</code>

View File

@ -1,18 +0,0 @@
<code type="php">
try {
$conn->beginTransaction();
$user->save();
$conn->beginTransaction();
$group->save();
$email->save();
$conn->commit();
$conn->commit();
} catch(Exception $e) {
$conn->rollback();
}
</code>

View File

@ -1,16 +0,0 @@
<code type="php">
// works only if you use doctrine database handler
$dbh = $conn->getDBH();
$times = $dbh->getExecTimes();
// print all executed queries and their execution times
foreach($dbh->getQueries() as $index => $query) {
print $query." ".$times[$index];
}
</code>

View File

@ -1,8 +0,0 @@
<code type="php">
$sess = Doctrine_Manager::getInstance()->openConnection(new PDO("dsn","username","password"));
// gets the next ID from a sequence
$sess->getNextID($sequence);
</code>

View File

@ -1,15 +0,0 @@
<code type="php">
$sess = Doctrine_Manager::getInstance()->openConnection(new PDO("dsn","username","password"));
try {
$sess->beginTransaction();
// some database operations
$sess->commit();
} catch(Exception $e) {
$sess->rollback();
}
</code>

View File

@ -1,14 +0,0 @@
Doctrine supports aggregates and composites. When binding composites you can use methods Doctrine_Record::ownsOne() and Doctrine_Record::ownsMany(). When binding
aggregates you can use methods Doctrine_Record::hasOne() and Doctrine_Record::hasMany(). Basically using the owns* methods is like adding a database level ON CASCADE DELETE
constraint on related component with an exception that doctrine handles the deletion in application level.
In Doctrine if you bind an Email to a User using ownsOne or ownsMany methods, everytime User record calls delete the associated
Email record is also deleted.
Then again if you bind an Email to a User using hasOne or hasMany methods, everytime User record calls delete the associated
Email record is NOT deleted.

View File

@ -1 +0,0 @@
Doctrine_Association represents a many-to-many association between database tables.

View File

@ -1 +0,0 @@
Doctrine_Collection is a collection of Data Access Objects. Doctrine_Collection represents a record set.

View File

@ -1 +0,0 @@
Doctrine_Collection_Batch is a Doctrine_Collection with batch fetching strategy.

View File

@ -1 +0,0 @@
Doctrine_Collection_Immediate is a Doctrine_Collection with immediate fetching strategy.

View File

@ -1 +0,0 @@
Doctrine_Collection_Lazy is a Doctrine_Collection with lazy fetching strategy.

View File

@ -1 +0,0 @@
Doctrine_ForeignKey represents a one-to-many association or one-to-one association between two database tables.

View File

@ -1,2 +0,0 @@
Doctrine_Manager is the base component of Doctrine ORM framework. Doctrine_Manager is a colletion of Doctrine_Connections. All the new connections are being
opened by Doctrine_Manager::openConnection().

View File

@ -1,43 +0,0 @@
Doctrine_Record is a wrapper for database row.
<code type="php">
$user = $table->find(2);
// get state
$state = $user->getState();
print $user->name;
print $user["name"];
print $user->get("name");
$user->name = "Jack Daniels";
$user->set("name","Jack Daniels");
// serialize record
$serialized = serialize($user);
$user = unserialize($serialized);
// create a copy
$copy = $user->copy();
// get primary key
$id = $user->getID();
// print lots of useful info
print $user;
// save all the properties and composites
$user->save();
// delete this data access object and related objects
$user->delete();
</code>

View File

@ -1,34 +0,0 @@
Doctrine_Connection is a wrapper for database connection. It creates Doctrine_Tables and keeps track of all the created tables.
Doctrine_Connection provides things that are missing from PDO like sequence support and limit/offset emulation.
<code type="php">
$sess = $manager->openConnection(Doctrine_Db::getConnection("schema://username:password@hostname/database"));
// get connection state:
switch($sess):
case Doctrine_Connection::STATE_BUSY:
// multiple open transactions
break;
case Doctrine_Connection::STATE_ACTIVE:
// one open transaction
break;
case Doctrine_Connection::STATE_CLOSED:
// closed state
break;
case Doctrine_Connection::STATE_OPEN:
// open state and zero open transactions
break;
endswitch;
// getting database handler
$dbh = $sess->getDBH();
// flushing the connection
$sess->flush();
// print lots of useful info about connection:
print $sess;
</code>

View File

@ -1,2 +0,0 @@
Doctrine_Table creates records and holds info of all foreign keys and associations. Doctrine_Table
represents a database table.

View File

@ -1 +0,0 @@
This is the same as 'array' type in PHP.

View File

@ -1,2 +0,0 @@
A foreign key constraint specifies that the values in a column (or a group of columns) must match the values appearing in some row of another table.
In other words foreign key constraints maintain the referential integrity between two related tables.

View File

@ -1,124 +0,0 @@
Whenever you fetch records with eg. Doctrine_Table::findAll or Doctrine_Connection::query methods an instance of
Doctrine_Collection is returned. There are many types of collections in Doctrine and it is crucial to understand
the differences of these collections. Remember choosing the right fetching strategy (collection type) is one of the most
influental things when it comes to boosting application performance.
* Immediate Collection
Fetches all records and all record data immediately into collection memory. Use this collection only if you really need to show all that data
in web page.
Example query:
SELECT id, name, type, created FROM user
* Batch Collection
Fetches all record primary keys into colletion memory. When individual collection elements are accessed this collection initializes proxy objects.
When the non-primary-key-property of a proxy object is accessed that object sends request to Batch collection which loads the data
for that specific proxy object as well as other objects close to that proxy object.
Example queries:
SELECT id FROM user
SELECT id, name, type, created FROM user WHERE id IN (1,2,3,4,5)
SELECT id, name, type, created FROM user WHERE id IN (6,7,8,9,10)
[ ... ]
* Lazy Collection
Lazy collection is exactly same as Batch collection with batch size preset to one.
Example queries:
SELECT id FROM user
SELECT id, name, type, created FROM user WHERE id = 1
SELECT id, name, type, created FROM user WHERE id = 2
SELECT id, name, type, created FROM user WHERE id = 3
[ ... ]
* Offset Collection
Offset collection is the same as immediate collection with the difference that it uses database provided limiting of queries.
Example queries:
SELECT id, name, type, created FROM user LIMIT 5
SELECT id, name, type, created FROM user LIMIT 5 OFFSET 5
SELECT id, name, type, created FROM user LIMIT 5 OFFSET 10
[ ... ]
<code type="php">
$table = $conn->getTable("User");
$table->setAttribute(Doctrine::ATTR_FETCHMODE, Doctrine::FETCH_IMMEDIATE);
$users = $table->findAll();
// or
$users = $conn->query("FROM User-I"); // immediate collection
foreach($users as $user) {
print $user->name;
}
$table->setAttribute(Doctrine::ATTR_FETCHMODE, Doctrine::FETCH_LAZY);
$users = $table->findAll();
// or
$users = $conn->query("FROM User-L"); // lazy collection
foreach($users as $user) {
print $user->name;
}
$table->setAttribute(Doctrine::ATTR_FETCHMODE, Doctrine::FETCH_BATCH);
$users = $table->findAll();
// or
$users = $conn->query("FROM User-B"); // batch collection
foreach($users as $user) {
print $user->name;
}
$table->setAttribute(Doctrine::ATTR_FETCHMODE, Doctrine::FETCH_OFFSET);
$users = $table->findAll();
// or
$users = $conn->query("FROM User-O"); // offset collection
foreach($users as $user) {
print $user->name;
}
</code>

View File

@ -1,16 +0,0 @@
<code type="php">
$q = new Doctrine_Query();
$q->from('User(COUNT(id))');
// returns an array
$a = $q->execute();
// selecting multiple aggregate values:
$q = new Doctrine_Query();
$q->from('User(COUNT(id)).Phonenumber(MAX(phonenumber))');
$a = $q->execute();
</code>

View File

@ -1,7 +0,0 @@
<code type="php">
$query->from("User")
->where("User.name = ?");
$query->execute(array('Jack Daniels'));
</code>

View File

@ -1,63 +0,0 @@
<?php
/**
$str = "
The following examples should give a hint of how DQL is converted into SQL.
The classes used in here are the same as in chapter 14.1 (Users and groups are both entities etc).
DQL QUERY: FROM Email WHERE Email.address LIKE '%@example%'
SQL QUERY: SELECT email.id AS Email__id FROM email WHERE (email.address LIKE '%@example%')
DQL QUERY: FROM User(id) WHERE User.Phonenumber.phonenumber LIKE '%123%'
SQL QUERY: SELECT entity.id AS entity__id FROM entity LEFT JOIN phonenumber ON entity.id = phonenumber.entity_id WHERE phonenumber.phonenumber LIKE '%123%' AND (entity.type = 0)
DQL QUERY: FROM Forum_Board(id).Threads(id).Entries(id)
SQL QUERY: SELECT forum_board.id AS forum_board__id, forum_thread.id AS forum_thread__id, forum_entry.id AS forum_entry__id FROM forum_board LEFT JOIN forum_thread ON forum_board.id = forum_thread.board_id LEFT JOIN forum_entry ON forum_thread.id = forum_entry.thread_id
DQL QUERY: FROM User(id) WHERE User.Group.name = 'Action Actors'
SQL QUERY: SELECT entity.id AS entity__id FROM entity LEFT JOIN groupuser ON entity.id = groupuser.user_id LEFT JOIN entity AS entity2 ON entity2.id = groupuser.group_id WHERE entity2.name = 'Action Actors' AND (entity.type = 0 AND (entity2.type = 1 OR entity2.type IS NULL))
DQL QUERY: FROM User(id) WHERE User.Group.Phonenumber.phonenumber LIKE '123 123'
SQL QUERY: SELECT entity.id AS entity__id FROM entity LEFT JOIN groupuser ON entity.id = groupuser.user_id LEFT JOIN entity AS entity2 ON entity2.id = groupuser.group_id LEFT JOIN phonenumber ON entity2.id = phonenumber.entity_id WHERE phonenumber.phonenumber LIKE '123 123' AND (entity.type = 0 AND (entity2.type = 1 OR entity2.type IS NULL))
";
function renderQueries($str) {
$e = explode("\n",$str);
$color = "367FAC";
foreach($e as $line) {
if(strpos($line, "SQL") !== false)
$color = "A50A3D";
elseif(strpos($line, "DQL") !== false)
$color = "367FAC";
$l = str_replace("SELECT","
<font color='$color'>**SELECT**</font>",$line);
$l = str_replace("FROM","
<font color='$color'>**FROM**</font>",$l);
$l = str_replace("LEFT JOIN","
<font color='$color'>**LEFT JOIN**</font>",$l);
$l = str_replace("INNER JOIN","
<font color='$color'>**INNER JOIN**</font>",$l);
$l = str_replace("WHERE","
<font color='$color'>**WHERE**</font>",$l);
$l = str_replace("AS","<font color='$color'>**AS**</font>",$l);
$l = str_replace("ON","<font color='$color'>**ON**</font>",$l);
$l = str_replace("ORDER BY","<font color='$color'>**ORDER BY**</font>",$l);
$l = str_replace("LIMIT","<font color='$color'>**LIMIT**</font>",$l);
$l = str_replace("OFFSET","<font color='$color'>**OFFSET**</font>",$l);
$l = str_replace("DISTINCT","<font color='$color'>**DISTINCT**</font>",$l);
$l = str_replace(" ","<dd>",$l);
print $l."<br>";
if(substr($l,0,3) == "SQL") print "<hr valign='left' class='small'>";
}
}
renderQueries($str);
*/
?>

View File

@ -1,26 +0,0 @@
<code type="php">
// select all users and load the data directly (Immediate fetching strategy)
$coll = $conn->query("FROM User-I");
// or
$coll = $conn->query("FROM User-IMMEDIATE");
// select all users and load the data in batches
$coll = $conn->query("FROM User-B");
// or
$coll = $conn->query("FROM User-BATCH");
// select all user and use lazy fetching
$coll = $conn->query("FROM User-L");
// or
$coll = $conn->query("FROM User-LAZY");
</code>

View File

@ -1,7 +0,0 @@
<code type="php">
// retrieve all users with only their properties id and name loaded
$users = $conn->query("FROM User(id, name)");
</code>

View File

@ -1,22 +0,0 @@
You can overload the query object by calling the dql query parts as methods.
<code type="php">
$conn = Doctrine_Manager::getInstance()->openConnection(new PDO("dsn","username","password"));
$query = new Doctrine_Query($conn);
$query->from("User-b")
->where("User.name LIKE 'Jack%'")
->orderby("User.created")
->limit(5);
$users = $query->execute();
$query->from("User.Group.Phonenumber")
->where("User.Group.name LIKE 'Actors%'")
->orderby("User.name")
->limit(10)
->offset(5);
$users = $query->execute();
</code>

View File

@ -1,27 +0,0 @@
Doctrine provides two relation operators: '.' aka dot and ':' aka colon.
The dot-operator is used for SQL LEFT JOINs and the colon-operator is used
for SQL INNER JOINs. Basically you should use dot operator if you want for example
to select all users and their phonenumbers AND it doesn't matter if the users actually have any phonenumbers.
On the other hand if you want to select only the users which actually have phonenumbers you should use the colon-operator.
<code type="php">
$query->from('User u')->innerJoin('u.Email e');
$query->execute();
// executed SQL query:
// SELECT ... FROM user INNER JOIN email ON ...
$query->from('User u')->leftJoin('u.Email e');
$query->execute();
// executed SQL query:
// SELECT ... FROM user LEFT JOIN email ON ...
</code>

View File

@ -1,3 +0,0 @@
There are three possible ways to access the properties of a record (fields of database row).
You can use overloading, ArrayAccess interface or simply Doctrine_Record::get() method.
**Doctrine_Record objects have always all properties in lowercase**.

View File

@ -1,7 +0,0 @@
++ Introduction
++ Table and class naming
++ Table options
++ Columns
++ Constraints and validators
++ Record identifiers
++ Indexes

View File

@ -1,5 +0,0 @@
+++ Column naming
+++ Column aliases
+++ Default values
+++ Data types
+++ About type conversion

View File

@ -1,14 +0,0 @@
Doctrine offers a way of setting column aliases. This can be very useful when you want to keep the application logic separate from the database logic. For example if you want to change the name of the database field all you need to change at your application is the column definition.
<code type="php">
class Book extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('bookName as name', 'string');
}
}
$book = new Book();
$book->name = 'Some book';
$book->save();
</code>

View File

@ -1,8 +0,0 @@
One problem with database compatibility is that many databases differ in their behaviour of how the result set of a query is returned. MySQL leaves the field names unchanged, which means if you issue a query of the form "SELECT myField FROM ..." then the result set will contain the field 'myField'.
Unfortunately, this is just the way MySql and some other databases do it. Postgres for example returns all field names in lowercase whilst Oracle returns all field names in uppercase. "So what? In what way does this influence me when using Doctrine?", you may ask. Fortunately, you don't have to bother about that issue at all.
Doctrine takes care of this problem transparently. That means if you define a derived Record class and define a field called 'myField' you will always access it through $record->myField (or $record['myField'], whatever you prefer) no matter whether you're using MySQL or Postgres or Oracle etc.
In short: You can name your fields however you want, using under_scores, camelCase or whatever you prefer.

View File

@ -1,344 +0,0 @@
++++ Introduction
All DBMS provide multiple choice of data types for the information that can be stored in their database table fields. However, the set of data types made available varies from DBMS to DBMS.
To simplify the interface with the DBMS supported by Doctrine, it was defined a base set of data types that applications may access independently of the underlying DBMS.
The Doctrine applications programming interface takes care of mapping data types when managing database options. It is also able to convert that is sent to and received from the underlying DBMS using the respective driver.
The following data type examples should be used with Doctrine's createTable() method. The example array at the end of the data types section may be used with createTable() to create a portable table on the DBMS of choice (please refer to the main Doctrine documentation to find out what DBMS back ends are properly supported). It should also be noted that the following examples do not cover the creation and maintenance of indices, this chapter is only concerned with data types and the proper usage thereof.
It should be noted that the length of the column affects in database level type as well as application level validated length (the length that is validated with Doctrine validators).
Example 1. Column named 'content' with type 'string' and length 3000 results in database type 'TEXT' of which has database level length of 4000. However when the record is validated it is only allowed to have 'content' -column with maximum length of 3000.
Example 2. Column with type 'integer' and length 1 results in 'TINYINT' on many databases.
In general Doctrine is smart enough to know which integer/string type to use depending on the specified length.
++++ Type modifiers
Within the Doctrine API there are a few modifiers that have been designed to aid in optimal table design. These are:
* The notnull modifiers
* The length modifiers
* The default modifiers
* unsigned modifiers for some field definitions, although not all DBMS's support this modifier for integer field types.
* zerofill modifiers (not supported by all drivers)
* collation modifiers (not supported by all drivers)
* fixed length modifiers for some field definitions.
Building upon the above, we can say that the modifiers alter the field definition to create more specific field types for specific usage scenarios. The notnull modifier will be used in the following way to set the default DBMS NOT NULL Flag on the field to true or false, depending on the DBMS's definition of the field value: In PostgreSQL the "NOT NULL" definition will be set to "NOT NULL", whilst in MySQL (for example) the "NULL" option will be set to "NO". In order to define a "NOT NULL" field type, we simply add an extra parameter to our definition array (See the examples in the following section)
<code type="php">
'sometime' = array(
'type' => 'time',
'default' => '12:34:05',
'notnull' => true,
),
</code>
Using the above example, we can also explore the default field operator. Default is set in the same way as the notnull operator to set a default value for the field. This value may be set in any character set that the DBMS supports for text fields, and any other valid data for the field's data type. In the above example, we have specified a valid time for the "Time" data type, '12:34:05'. Remember that when setting default dates and times, as well as datetimes, you should research and stay within the epoch of your chosen DBMS, otherwise you will encounter difficult to diagnose errors!
<code type="php">
'sometext' = array(
'type' => 'string',
'length' => 12,
),
</code>
The above example will create a character varying field of length 12 characters in the database table. If the length definition is left out, Doctrine will create a length of the maximum allowable length for the data type specified, which may create a problem with some field types and indexing. Best practice is to define lengths for all or most of your fields.
++++ Boolean
The boolean data type represents only two values that can be either 1 or 0. Do not assume that these data types are stored as integers because some DBMS drivers may implement this type with single character text fields for a matter of efficiency. Ternary logic is possible by using null as the third possible value that may be assigned to fields of this type.
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('booltest', 'boolean');
}
}
</code>
++++ Integer
The integer type is the same as integer type in PHP. It may store integer values as large as each DBMS may handle.
Fields of this type may be created optionally as unsigned integers but not all DBMS support it. Therefore, such option may be ignored. Truly portable applications should not rely on the availability of this option.
The integer type maps to different database type depending on the column length.
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('integertest', 'integer', 4, array('unsigned' => true));
}
}
</code>
++++ Float
The float data type may store floating point decimal numbers. This data type is suitable for representing numbers within a large scale range that do not require high accuracy. The scale and the precision limits of the values that may be stored in a database depends on the DBMS that it is used.
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('floattest', 'float');
}
}
</code>
++++ String
The text data type is available with two options for the length: one that is explicitly length limited and another of undefined length that should be as large as the database allows.
The length limited option is the most recommended for efficiency reasons. The undefined length option allows very large fields but may prevent the use of indexes, nullability and may not allow sorting on fields of its type.
The fields of this type should be able to handle 8 bit characters. Drivers take care of DBMS specific escaping of characters of special meaning with the values of the strings to be converted to this type.
By default Doctrine will use variable length character types. If fixed length types should be used can be controlled via the fixed modifier.
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('stringtest', 'string', 200, array('fixed' => true));
}
}
</code>
++++ Array
This is the same as 'array' type in PHP.
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('arraytest', 'array', 10000);
}
}
</code>
++++ Object
Doctrine supports objects as column types. Basically you can set an object to a field and Doctrine handles automatically the serialization / unserialization of that object.
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('objecttest', 'object');
}
}
</code>
++++ Blob
Blob (Binary Large OBject) data type is meant to store data of undefined length that may be too large to store in text fields, like data that is usually stored in files.
Blob fields are usually not meant to be used as parameters of query search clause (WHERE) unless the underlying DBMS supports a feature usually known as "full text search"
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('blobtest', 'blob');
}
}
</code>
++++ Clob
Clob (Character Large OBject) data type is meant to store data of undefined length that may be too large to store in text fields, like data that is usually stored in files.
Clob fields are meant to store only data made of printable ASCII characters whereas blob fields are meant to store all types of data.
Clob fields are usually not meant to be used as parameters of query search clause (WHERE) unless the underlying DBMS supports a feature usually known as "full text search"
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('clobtest', 'clob');
}
}
</code>
++++ Timestamp
The timestamp data type is a mere combination of the date and the time of the day data types. The representation of values of the time stamp type is accomplished by joining the date and time string values in a single string joined by a space. Therefore, the format template is YYYY-MM-DD HH:MI:SS. The represented values obey the same rules and ranges described for the date and time data types
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('timestamptest', 'timestamp');
}
}
</code>
++++ Time
The time data type may represent the time of a given moment of the day. DBMS independent representation of the time of the day is also accomplished by using text strings formatted according to the ISO-8601 standard.
The format defined by the ISO-8601 standard for the time of the day is HH:MI:SS where HH is the number of hour the day from 00 to 23 and MI and SS are respectively the number of the minute and of the second from 00 to 59. Hours, minutes and seconds numbered below 10 should be padded on the left with 0.
Some DBMS have native support for time of the day formats, but for others the DBMS driver may have to represent them as integers or text values. In any case, it is always possible to make comparisons between time values as well sort query results by fields of this type.
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('timetest', 'time');
}
}
</code>
++++ Date
The date data type may represent dates with year, month and day. DBMS independent representation of dates is accomplished by using text strings formatted according to the IS0-8601 standard.
The format defined by the ISO-8601 standard for dates is YYYY-MM-DD where YYYY is the number of the year (Gregorian calendar), MM is the number of the month from 01 to 12 and DD is the number of the day from 01 to 31. Months or days numbered below 10 should be padded on the left with 0.
Some DBMS have native support for date formats, but for others the DBMS driver may have to represent them as integers or text values. In any case, it is always possible to make comparisons between date values as well sort query results by fields of this type.
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('datetest', 'date');
}
}
</code>
++++ Enum
Doctrine has a unified enum type. Enum typed columns automatically convert the string values into index numbers and vice versa. The possible values for the column can be specified with Doctrine_Record::setEnumValues(columnName, array values).
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('enumtest', 'enum', 4,
array(
'values' => array(
'php',
'java',
'python'
)
)
);
}
}
</code>
++++ Gzip
Gzip datatype is the same as string except that its automatically compressed when persisted and uncompressed when fetched. This datatype can be useful when storing data with a large compressibility ratio, such as bitmap images.
<code type="php">
class Test extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('gziptest', 'gzip');
}
}
</code>
++++ Examples
Consider the following definition:
<code type="php">
class Example extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumns(array(
'id' => array(
'type' => 'text',
'length' => 32,
'fixed' => true,
),
'someint' => array(
'type' => 'integer',
'length' => 10,
'unsigned' => true,
),
'sometext' => array(
'type' => 'text',
'length' => 12,
),
'somedate' => array(
'type' => 'date',
),
'sometimestamp' => array(
'type' => 'timestamp',
),
'someboolean' => array(
'type' => 'boolean',
),
'somedecimal' => array(
'type' => 'decimal',
),
'somefloat' => array(
'type' => 'float',
),
'sometime' => array(
'type' => 'time',
'default' => '12:34:05',
'notnull' => true,
),
'somedate' => array(
'type' => 'date',
),
'someclob' => array(
'type' => 'clob',
),
'someblob' => array(
'type' => 'blob',
));
}
</code>
The above example will create a database table as such in Pgsql:
|| Column || Type || Not Null ||Default || comment ||
|| id || character(32) || || || ||
|| somename || character || varying(12) || || || ||
|| somedate || date || || || ||
|| sometimestamp || timestamp without time zone || || || ||
|| someboolean || boolean || || || ||
|| somedecimal || numeric(18,2) || || || ||
|| somefloat || double precision || || || ||
|| sometime || time without time zone || NOT NULL || '12:34:05' || ||
|| someclob || text || || || ||
|| someblob || bytea || || || ||
And the following table in Mysql:
|| Field || Type || Collation || Attributes || Null || Default || comment ||
|| id || char(32) || || || YES || || ||
|| somename || varchar(12) || latin1_swedish_ci || || YES || || ||
|| somedate || date || || || YES || || ||
|| sometimestamp || timestamp without time zone || || || YES || || ||
|| someboolean || tinyint(1) || || || YES || || ||
|| somedecimal || decimal(18,2) || || || YES || || ||
|| somefloat || double || || || YES || || ||
|| sometime || time || || || NO || 12:34:05 || ||
|| someclob || longtext || latin1_swedish_ci || || YES || || ||
|| someblob || longblob || || binary || YES || || ||

View File

@ -1,14 +0,0 @@
Doctrine supports default values for all data types. When default value is attached to a record column this means two of things.
First this value is attached to every newly created Record.
<code type="php">
class User extends Doctrine_record {
public function setTableDefinition() {
$this->hasColumn('name', 'string', 50, array('default' => 'default name'));
}
}
$user = new User();
print $user->name; // default name
</code>
Also when exporting record class to database DEFAULT //value// is attached to column definition statement.

View File

@ -1,166 +0,0 @@
+++ Introduction
//From [http://www.postgresql.org/docs/8.2/static/ddl-constraints.html PostgreSQL Documentation]://
> Data types are a way to limit the kind of data that can be stored in a table. For many applications, however, the constraint they provide is too coarse. For example, a column containing a product price should probably only accept positive values. But there is no standard data type that accepts only positive numbers. Another issue is that you might want to constrain column data with respect to other columns or rows. For example, in a table containing product information, there should be only one row for each product number.
Doctrine allows you to define *portable* constraints on columns and tables. Constraints give you as much control over the data in your tables as you wish. If a user attempts to store data in a column that would violate a constraint, an error is raised. This applies even if the value came from the default value definition.
Doctrine constraints act as database level constraints as well as application level validators. This means double security: the database doesn't allow wrong kind of values and neither does the application.
Here is a full list of available validators within Doctrine:
|| validator(arguments) || constraints || description ||
|| notnull || NOT NULL || Ensures the 'not null' constraint in both application and database level ||
|| email || || Checks if value is valid email. ||
|| notblank || NOT NULL || Checks if value is not blank. ||
|| notnull || || Checks if value is not null. ||
|| nospace || || Checks if value has no space chars. ||
|| past || CHECK constraint || Checks if value is a date in the past. ||
|| future || || Checks if value is a date in the future. ||
|| minlength(length) || || Checks if value satisfies the minimum length. ||
|| country || || Checks if value is a valid country code. ||
|| ip || || Checks if value is valid IP (internet protocol) address. ||
|| htmlcolor || || Checks if value is valid html color. ||
|| range(min, max) || CHECK constraint || Checks if value is in range specified by arguments. ||
|| unique || UNIQUE constraint || Checks if value is unique in its database table. ||
|| regexp(expression) || || Checks if value matches a given regexp. ||
|| creditcard || || Checks whether the string is a well formated credit card number ||
|| digits(int, frac) || Precision and scale || Checks if given value has //int// number of integer digits and //frac// number of fractional digits ||
+++ Notnull
A not-null constraint simply specifies that a column must not assume the null value. A not-null constraint is always written as a column constraint.
The following definition uses a notnull constraint for column {{name}}. This means that the specified column doesn't accept null values.
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string', 200, array('notnull' => true,
'primary' => true));
}
}
</code>
When this class gets exported to database the following SQL statement would get executed (in MySQL):
<code type="sql">
CREATE TABLE user (name VARCHAR(200) NOT NULL, PRIMARY KEY(name))
</code>
The notnull constraint also acts as an application level validator. This means that if Doctrine validators are turned on, Doctrine will automatically check that specified columns do not contain null values when saved.
If those columns happen to contain null values {{Doctrine_Validator_Exception}} is raised.
+++ Unique
Unique constraints ensure that the data contained in a column or a group of columns is unique with respect to all the rows in the table.
In general, a unique constraint is violated when there are two or more rows in the table where the values of all of the columns included in the constraint are equal. However, two null values are not considered equal in this comparison. That means even in the presence of a unique constraint it is possible to store duplicate rows that contain a null value in at least one of the constrained columns. This behavior conforms to the SQL standard, but some databases do not follow this rule. So be careful when developing applications that are intended to be portable.
The following definition uses a unique constraint for column {{name}}.
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string', 200, array('unique' => true));
}
}
</code>
>> Note: You should only use unique constraints for other than primary key columns. Primary key columns are always unique.
+++ Check
Some of the Doctrine validators also act as database level check constraints. When a record with these validators is exported additional CHECK constraints are being added to CREATE TABLE statement.
Consider the following example which uses 'min' validator:
<code type="php">
class Product extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('id', 'integer', 4, 'primary');
$this->hasColumn('price', 'decimal', 18, array('min' => 0));
}
}
</code>
When exported the given class definition would execute the following statement (in pgsql):
<code type="sql">
CREATE TABLE product (
id INTEGER,
price NUMERIC,
PRIMARY KEY(id),
CHECK (price >= 0))
</code>
So Doctrine optionally ensures even at the database level that the price of any product cannot be below zero.
You can also set the maximum value of a column by using the 'max' validator. This also creates the equivalent CHECK constraint.
<code type="php">
class Product extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('id', 'integer', 4, 'primary');
$this->hasColumn('price', 'decimal', 18, array('min' => 0, 'max' => 1000000));
}
}
</code>
Generates (in pgsql):
<code type="sql">
CREATE TABLE product (
id INTEGER,
price NUMERIC,
PRIMARY KEY(id),
CHECK (price >= 0),
CHECK (price <= 1000000))
</code>
Lastly you can create any kind of CHECK constraints by using the check() method of the Doctrine_Record. In the last example we add constraint to ensure that price is always higher than the discounted price.
<code type="php">
class Product extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('id', 'integer', 4, 'primary');
$this->hasColumn('price', 'decimal', 18, array('min' => 0, 'max' => 1000000));
$this->hasColumn('discounted_price', 'decimal', 18, array('min' => 0, 'max' => 1000000));
$this->check('price > discounted_price');
}
}
</code>
Generates (in pgsql):
<code type="sql">
CREATE TABLE product (
id INTEGER,
price NUMERIC,
PRIMARY KEY(id),
CHECK (price >= 0),
CHECK (price <= 1000000),
CHECK (price > discounted_price))
</code>
> NOTE: some databases don't support CHECK constraints. When this is the case Doctrine simple skips the creation of check constraints.
If the Doctrine validators are turned on the given definition would also ensure that when a record is being saved its price is always greater than zero.
If some of the prices of the saved products within a transaction is below zero, Doctrine throws Doctrine_Validator_Exception and automatically rolls back the transaction.

View File

@ -1,119 +0,0 @@
+++ Introduction
Indexes are used to find rows with specific column values quickly. Without an index, the database must begin with the first row and then read through the entire table to find the relevant rows.
The larger the table, the more this consumes time. If the table has an index for the columns in question, the database can quickly determine the position to seek to in the middle of the data file without having to look at all the data. If a table has 1,000 rows, this is at least 100 times faster than reading rows one-by-one.
Indexes come with a cost as they slow down the inserts and updates. However, in general you should **always** use indexes for the fields that are used in SQL where conditions.
+++ Adding indexes
You can add indexes by simple calling {{Doctrine_Record::index('indexName', $definition)}} where {{$definition}} is the definition array.
An example of adding a simple index to field called {{name}}:
<code type="php">
class IndexTest extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string');
$this->index('myindex', array('fields' => 'name'));
}
}
</code>
An example of adding a multi-column index to field called {{name}}:
<code type="php">
class MultiColumnIndexTest extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string');
$this->hasColumn('code', 'string');
$this->index('myindex', array('fields' => array('name', 'code')));
}
}
</code>
An example of adding a multiple indexes on same table:
<code type="php">
class MultipleIndexTest extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string');
$this->hasColumn('code', 'string');
$this->hasColumn('age', 'integer');
$this->index('myindex', array('fields' => array('name', 'code')));
$this->index('ageindex', array('fields' => array('age'));
}
}
</code>
+++ Index options
Doctrine offers many index options, some of them being db-specific. Here is a full list of available options:
<code>
sorting => string('ASC' / 'DESC')
what kind of sorting does the index use (ascending / descending)
length => integer
index length (only some drivers support this)
primary => boolean(true / false)
whether or not the index is primary index
type => string('unique', -- supported by most drivers
'fulltext', -- only availible on Mysql driver
'gist', -- only availible on Pgsql driver
'gin') -- only availible on Pgsql driver
</code>
<code type="php">
class MultipleIndexTest extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string');
$this->hasColumn('code', 'string');
$this->hasColumn('age', 'integer');
$this->index('myindex', array(
'fields' => array(
'name' =>
array('sorting' => 'ASC',
'length' => 10),
'code'),
'type' => 'unique',
));
}
}
</code>
+++ Special indexes
Doctrine supports many special indexes. These include Mysql FULLTEXT and Pgsql GiST indexes. In the following example we define a Mysql FULLTEXT index for the field 'content'.
<code type="php">
class Article
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string');
$this->hasColumn('content', 'string');
$this->index('content', array('fields' => 'content',
'type' => 'fulltext'));
}
}
</code>

View File

@ -1,65 +0,0 @@
This chapter and its subchapters tell you how to do basic schema mappings with Doctrine. After you've come in terms with the concepts of this chapter you'll know how to:
1. Define columns for your record classes
2. Define table options
3. Define indexes
4. Define basic constraints and validators for columns
All column mappings within Doctrine are being done via the hasColumn() method of the Doctrine_Record. The hasColumn takes 4 arguments:
# **column name** String that specifies the column name and optional alias. This is needed for all columns. If you want to specify an alias for the column name you'll need to use the format '[columnName] as [columnAlias]'
# **column type** String that specifies the column type. See the column types section.
# **column length** Integer that specifies the column length. Some column types depend not only the given portable type but also on the given length. For example type string with length 1000 will be translated into native type TEXT on mysql.
# **column constraints and validators** An array that specifies the list of constraints and validators applied to given column.
Note that validators / column constraints and the column length fields are optional. The length may be omitted by using **null** for the length argument, allowing doctrine to use a default length and permitting a fourth argument for validation or column constraints.
Lets take our first example. The following definition defines a class called Email which refers to a table called 'emails'. The Email class has two columns id (an auto-incremented primary key column) and a string column called address.
Notice how we add two validators / constraints for the address column (notblank and email). The notblank validator assures that the address column isn't blank (so it must not contain space-characters only) whereas the email validator ensures that the address is a valid email address.
<code type="php">
class Email extends Doctrine_Record {
public function setTableDefinition() {
// setting custom table name:
$this->setTableName('emails');
$this->hasColumn('address', // name of the column
'string', // column type
'200', // column length
array('notblank' => true,
'email' => true // validators / constraints
)
);
}
}
</code>
Now lets create an export script for this class:
<code type="php">
require_once('Email.php');
require_once('path-to-Doctrine/Doctrine.php');
require_once('path-to-doctrine/lib/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
// in order to export we need a database connection
$manager = Doctrine_Manager::getInstance();
$conn = $manager->openConnection('mysql://user:pass@localhost/test');
$conn->export->exportClasses(array('Email'));
</code>
The script would execute the following sql (we are using Mysql here as the database backend):
<code>
CREATE TABLE emails (id INT NOT NULL AUTO_INCREMENT, address VARCHAR(200) NOT NULL)
</code>

View File

@ -1,133 +0,0 @@
+++ Introduction
Doctrine supports many kind of identifiers. For most cases it is recommended not to specify any primary keys (Doctrine will then use field name {{id}} as an autoincremented primary key). When using table creation Doctrine is smart enough to emulate the autoincrementation with sequences and triggers on databases that doesn't support it natively.
+++ Natural
Natural identifier is a property or combination of properties that is unique and non-null. The use of natural identifiers is encouraged.
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string', 200, array('primary' => true));
}
}
</code>
+++ Autoincremented
Autoincrement primary key is the most basic identifier and its usage is strongly encouraged. Sometimes you may want to use some other name than {{id}} for your autoinc primary key. It can be specified as follows:
<code type="php">
class User extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('uid', 'integer', 20, array('primary' => true, 'autoincrement' => true));
}
}
</code>
You should consider using autoincremented or sequential primary keys only when the record cannot be identified naturally (in other words it doesn't have a natural identifier).
The following example shows why natural identifiers are more efficient.
Consider three classes Permission, Role and RolePermission. Roles having many permissions and vice versa (so their relation is many-to-many). Now lets also assume that each role and permission are naturally identified by their names.
Now adding autoincremented primary keys to these classes would be simply stupid. It would require more data and it would make the queries more inefficient. For example fetching all permissions for role 'Admin' would be done as follows (when using autoinc pks):
<code type="sql">
SELECT p.*
FROM Permission p
LEFT JOIN RolePermission rp ON rp.permission_id = p.id
LEFT JOIN Role r ON rp.role_id = r.id
WHERE r.name = 'Admin'
</code>
Now remember sql JOINS are always expensive and here we are using two of those. When using natural identifiers the query would look like:
<code type="sql">
SELECT p.*
FROM Permission p
LEFT JOIN RolePermission rp ON rp.permission_name = p.name
WHERE rp.role_name = 'Admin'
</code>
Thats -1 JOIN !
+++ Composite
Composite primary key can be used efficiently in association tables (tables that connect two components together). It is not recommended to use composite primary keys in anywhere else as Doctrine does not support mapping relations on multiple columns.
Due to this fact your doctrine-based system will scale better if it has autoincremented primary key even for association tables.
<code type="php">
class Groupuser extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('user_id', 'integer', 20, array('primary' => true));
$this->hasColumn('group_id', 'integer', 20, array('primary' => true));
}
}
</code>
+++ Sequence
Doctrine supports sequences for generating record identifiers. Sequences are a way of offering unique IDs for data rows. If you do most of your work with e.g. MySQL, think of sequences as another way of doing {{AUTO_INCREMENT}}.
Doctrine knows how to do sequence generation in the background so you don't have to worry about calling database specific queries - Doctrine does it for you, all you need to do is define a column as a sequence column and optionally provide the name of the sequence table and the id column name of the sequence table.
Consider the following record definition:
<code type="php">
class Book extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('id', 'integer', null, array('primary' => true, 'sequence' => true));
$this->hasColumn('name', 'string');
}
}
</code>
By default Doctrine uses the following format for sequence tables {{[tablename]_seq}}. If you wish to change this you can use the following piece of code to change the formatting:
<code type="php">
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_SEQNAME_FORMAT, '%s_my_seq');
</code>
Doctrine uses column named id as the sequence generator column of the sequence table. If you wish to change this globally (for all connections and all tables) you can use the following code:
<code type="php">
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_SEQCOL_NAME, 'my_seq_column');
</code>
In the following example we do not wish to change global configuration we just want to make the {{id}} column to use sequence table called {{book_sequence}}. It can be done as follows:
<code type="php">
class Book extends Doctrine_Record {
public function setTableDefinition()
{
$this->hasColumn('id', 'integer', null, array('primary', 'sequence' => 'book_sequence'));
$this->hasColumn('name', 'string');
}
}
</code>
Here we take the preceding example a little further: we want to have a custom sequence column. Here it goes:
<code type="php">
class Book extends Doctrine_Record {
public function setTableDefinition()
{
$this->hasColumn('id', 'integer', null, array('primary', 'sequence' => array('book_sequence', 'sequence')));
$this->hasColumn('name', 'string');
}
}
</code>

View File

@ -1,12 +0,0 @@
Doctrine automatically creates table names from the record class names. For this reason, it is recommended to name your record classes using the following rules:
* Use {{CamelCase}} naming
* Underscores are allowed
* The first letter must be capitalized
* The class name cannot be one of the following (these keywords are reserved in DQL API):
* {{ALL}}, {{AND}}, {{ANY}}, {{AS}}, {{ASC}}, {{AVG}}, {{BETWEEN}}, {{BIT_LENGTH}}, {{BY}}, {{CHARACTER_LENGTH}}, {{CHAR_LENGTH}}, {{COUNT}}, {{CURRENT_DATE}}, {{CURRENT_TIME}}, {{CURRENT_TIMESTAMP}}, {{DELETE}}, {{DESC}}, {{DISTINCT}}, {{EMPTY}}, {{EXISTS}}, {{FALSE}}, {{FETCH}}, {{FROM}}, {{GROUP}}, {{HAVING}}, {{IN}}, {{INDEXBY}}, {{INNER}}, {{IS}}, {{JOIN}}, {{LEFT}}, {{LIKE}}, {{LOWER}}, {{MAX}}, {{MEMBER}}, {{MIN}}, {{MOD}}, {{NEW}}, {{NOT}}, {{NULL}}, {{OBJECT}}, {{OF}}, {{OR}}, {{ORDER}}, {{OUTER}}, {{POSITION}}, {{SELECT}}, {{SOME}}, {{SUM}}, {{TRIM}}, {{TRUE}}, {{UNKNOWN}}, {{UPDATE}}, {{UPPER}} and {{WHERE}}.
**Example:** {{My_PerfectClass}}
If you need to use a different naming schema, you can override this using the {{setTableName()}} method in the {{setTableDefinition()}} method.

View File

@ -1,50 +0,0 @@
Doctrine offers various table options. All table options can be set via {{Doctrine_Record::option($optionName, $value)}}.
For example if you are using MySQL and want to use INNODB tables it can be done as follows:
<code type="php">
class MyInnoDbRecord extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string');
$this->option('type', 'INNODB');
}
}
</code>
In the following example we set the collate and character set options:
<code type="php">
class MyCustomOptionRecord extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string');
$this->option('collate', 'utf8_unicode_ci');
$this->option('charset', 'utf8');
}
}
</code>
It is worth noting that for certain databases (Firebird, MySql and PostgreSQL) setting the charset option might not be enough for Doctrine to return data properly. For those databases, users are advised to also use the setCharset function of the database connection:
<code type="php">
Doctrine_Manager::connection($name)->setCharset("utf8");
</code>
Doctrine offers the ability to turn off foreign key constraints for specific Models.
<code type="php">
class MyCustomOptionRecord extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string');
$this->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL ^ Doctrine::EXPORT_CONSTRAINTS);
}
}
</code>

View File

@ -1,175 +0,0 @@
++ Introduction
{{Doctrine_Cache}} offers an intuitive and easy-to-use query caching solution. It provides the following things:
* Multiple cache backends to choose from (including Memcached, APC and Sqlite)
* Advanced options for fine-tuning. {{Doctrine_Cache}} has many options for fine-tuning performance.
Initializing a new cache driver instance:
<code type="php">
$cacheDriver = new Doctrine_Cache_Memcache($options);
</code>
++ Drivers
+++ Memcache
Memcache driver stores cache records into a memcached server. Memcached is a high-performance, distributed memory object caching system. In order to use this backend, you need a memcached daemon and the memcache PECL extension.
<code type="php">
// memcache allows multiple servers
$servers = array('host' => 'localhost',
'port' => 11211,
'persistent' => true);
$cacheDriver = new Doctrine_Cache_Memcache(array('servers' => $servers,
'compression' => false));
</code>
Available options for Memcache driver:
||~ Option ||~ Data Type ||~ Default Value ||~ Description ||
|| servers || array || array(array('host' => 'localhost','port' => 11211, 'persistent' => true)) || An array of memcached servers ; each memcached server is described by an associative array : 'host' => (string) : the name of the memcached server, 'port' => (int) : the port of the memcached server, 'persistent' => (bool) : use or not persistent connections to this memcached server ||
|| compression || boolean || false || true if you want to use on-the-fly compression ||
+++ APC
The Alternative PHP Cache (APC) is a free and open opcode cache for PHP. It was conceived of to provide a free, open, and robust framework for caching and optimizing PHP intermediate code.
The APC cache driver of Doctrine stores cache records in shared memory.
<code type="php">
$cacheDriver = new Doctrine_Cache_Apc();
</code>
+++ Db
Db caching backend stores cache records into given database. Usually some fast flat-file based database is used (such as sqlite).
Initializing sqlite cache driver can be done as above:
<code type="php">
$conn = Doctrine_Manager::connection(new PDO('sqlite::memory:'));
$cacheDriver = new Doctrine_Cache_Sqlite(array('connection' => $conn));
</code>
++ Query Cache & Result Cache
+++ Introduction
Doctrine provides means for caching the results of the DQL parsing process, as well as the end results of DQL queries (the data). These two caching mechanisms can greatly increase performance. Consider the standard workflow of DQL query execution:
# Init new DQL query
# Parse DQL query
# Build database specific SQL query
# Execute the SQL query
# Build the result set
# Return the result set
Now these phases can be very time consuming, especially phase 4 which sends the query to your database server. When Doctrine query cache is being used only the following phases occur:
# Init new DQL query
# Execute the SQL query (grabbed from the cache)
# Build the result set
# Return the result set
If a DQL query has a valid cache entry the cached SQL query is used, otherwise the phases 2-3 are executed normally and the result of these steps is then stored in the cache.
The query cache has no disadvantages, since you always get a fresh query result. You should therefore always use it in a production environment. That said, you can easily use it during development, too. Whenever you change a DQL query and execute it the first time Doctrine sees that is has been modified and will therefore create a new cache entry, so you dont even need to invalidate the cache. It's worth noting that the effectiveness of the query cache greatly relies on the usage of prepared staments (which are used by Doctrine by default anyway). You should not directly embed dynamic query parts and always use placeholders instead.
When using a result cache things get even better. Then your query process looks as follows (assuming a valid cache entry is found):
# Init new DQL query
# Return the result set
As you can see, the result cache implies the query cache shown previously.
You should always consider using a result cache if the data returned by the query does not need to be up-to-date at any time.
+++ Query Cache
++++ Using the query cache
You can set a connection or manager level query cache driver by using Doctrine::ATTR_QUERY_CACHE. Setting a connection level cache driver means that all queries executed with this connection use the specified cache driver whereas setting a manager level cache driver means that all connections (unless overridden at connection level) will use the given cache driver.
Setting a manager level query cache driver:
<code type="php">
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_QUERY_CACHE, $cacheDriver);
</code>
Setting a connection level cache driver:
<code type="php">
$manager = Doctrine_Manager::getInstance();
$conn = $manager->openConnection('pgsql://user:pass@localhost/test');
$conn->setAttribute(Doctrine::ATTR_QUERY_CACHE, $cacheDriver);
</code>
++++ Fine-tuning
In the previous chapter we used global caching attributes. These attributes can be overriden at the query level. You can override the cache driver by calling useQueryCache with a valid cacheDriver. This rarely makes sense for the query cache but is possible:
<code type="php">
$query = new Doctrine_Query();
$query->useQueryCache(new Doctrine_Cache_Apc());
</code>
+++ Result Cache
++++ Using the result cache
You can set a connection or manager level result cache driver by using Doctrine::ATTR_RESULT_CACHE. Setting a connection level cache driver means that all queries executed with this connection use the specified cache driver whereas setting a manager level cache driver means that all connections (unless overridden at connection level) will use the given cache driver.
Setting a manager level cache driver:
<code type="php">
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_RESULT_CACHE, $cacheDriver);
</code>
Setting a connection level cache driver:
<code type="php">
$manager = Doctrine_Manager::getInstance();
$conn = $manager->openConnection('pgsql://user:pass@localhost/test');
$conn->setAttribute(Doctrine::ATTR_RESULT_CACHE, $cacheDriver);
</code>
Usually the cache entries are valid for only some time. You can set global value for how long the cache entries should be considered valid by using Doctrine::ATTR_RESULT_CACHE_LIFESPAN.
<code type="php">
$manager = Doctrine_Manager::getInstance();
// set the lifespan as one hour (60 seconds * 60 minutes = 1 hour = 3600 secs)
$manager->setAttribute(Doctrine::ATTR_RESULT_CACHE_LIFESPAN, 3600);
</code>
Now as we have set a cache driver for use we can make a DQL query to use it:
<code type="php">
$query = new Doctrine_Query();
// fetch blog titles and the number of comments
$query->select('b.title, COUNT(c.id) count')
->from('Blog b')
->leftJoin('b.Comments c')
->limit(10)
->useResultCache(true);
$entries = $query->execute();
</code>
++++ Fine-tuning
In the previous chapter we used global caching attributes. These attributes can be overriden at the query level. You can override the cache driver by calling useCache with a valid cacheDriver:
<code type="php">
$query = new Doctrine_Query();
$query->useResultCache(new Doctrine_Cache_Apc());
</code>
Also you can override the lifespan attribute by calling setResultCacheLifeSpan():
<code type="php">
$query = new Doctrine_Query();
// set the lifespan as half an hour
$query->setResultCacheLifeSpan(60 * 30);
</code>

View File

@ -1,26 +0,0 @@
There are couple of availible Cache attributes on Doctrine:
* Doctrine::ATTR_CACHE_SIZE
* Defines which cache container Doctrine uses
* Possible values: Doctrine::CACHE_* (for example Doctrine::CACHE_FILE)
* Doctrine::ATTR_CACHE_DIR
* cache directory where .cache files are saved
* the default cache dir is %ROOT%/cachedir, where
%ROOT% is automatically converted to doctrine root dir
* Doctrine::ATTR_CACHE_SLAM
* On very busy servers whenever you start the server or modify files you can create a race of many processes all trying to cache the same file at the same time. This option sets the percentage of processes that will skip trying to cache an uncached file. Or think of it as the probability of a single process to skip caching. For example, setting apc.slam_defense to 75 would mean that there is a 75% chance that the process will not cache an uncached file. So, the higher the setting the greater the defense against cache slams. Setting this to 0 disables this feature
* Doctrine::ATTR_CACHE_SIZE
* Cache size attribute
* Doctrine::ATTR_CACHE_TTL
* How often the cache is cleaned

View File

@ -1,22 +0,0 @@
{{Doctrine_Cache}} offers an intuitive and easy-to-use query caching solution. It provides the following things:
* Multiple cache backends to choose from (including Memcached, APC and Sqlite)
* Manual tuning and/or self-optimization. {{Doctrine_Cache}} knows how to optimize itself, yet it leaves user full freedom of whether or not he/she wants to take advantage of this feature.
* Advanced options for fine-tuning. {{Doctrine_Cache}} has many options for fine-tuning performance.
* Cache hooks itself directly into {{Doctrine_Db}} eventlistener system allowing it to be easily added on-demand.
{{Doctrine_Cache}} hooks into {{Doctrine_Db}} eventlistener system allowing pluggable caching. It evaluates queries and puts {{SELECT}} statements in cache. The caching is based on propabalistics. For example if {{savePropability = 0.1}} there is a 10% chance that a query gets cached.
Now eventually the cache would grow very big, hence Doctrine uses propabalistic cache cleaning. When calling {{Doctrine_Cache::clean()}} with {{cleanPropability = 0.25}} there is a 25% chance of the clean operation being invoked. What the cleaning does is that it first reads all the queries in the stats file and sorts them by the number of times occurred. Then if the size is set to 100 it means the cleaning operation will leave 100 most issued queries in cache and delete all other cache entries.
Initializing a new cache instance:
<code type="php">
$dbh = new Doctrine_Db('mysql:host=localhost;dbname=test', $user, $pass);
$cache = new Doctrine_Cache('memcache');
// register it as a Doctrine_Db listener
$dbh->addListener($cache);
</code>
Now you know how to set up the query cache. In the next chapter you'll learn how to tweak the cache in order to get maximum performance.

View File

@ -1,49 +0,0 @@
Doctrine has very comprehensive and fast caching solution.
Its cache is **always up-to-date**.
In order to achieve this doctrine does the following things:
|| 1. Every Doctrine_Table has its own cache directory. The default is cache/componentname/. All the cache files are saved into that directory.
The format of each cache file is [primarykey].cache.
2. When retrieving records from the database doctrine always tries to hit the cache first.
3. If a record (Doctrine_Record) is retrieved from database or inserted into database it will be saved into cache.
4. When a Data Access Object is deleted or updated it will be deleted from the cache ||
Now one might wonder that this kind of solution won't work since eventually the cache will be a copy of database!
So doctrine does the following things to ensure the cache won't get too big:
|| 1. Every time a cache file is accessed the id of that record will be added into the $fetched property of Doctrine_Cache
2. At the end of each script the Doctrine_Cache destructor will write all these primary keys at the end of a stats.cache file
3. Doctrine does propabalistic cache cleaning. The default interval is 200 page loads (= 200 constructed Doctrine_Managers). Basically this means
that the average number of page loads between cache cleans is 200.
4. On every cache clean stats.cache files are being read and the least accessed cache files
(cache files that have the smallest id occurance in the stats file) are then deleted.
For example if the cache size is set to 200 and the number of files in cache is 300, then 100 least accessed files are being deleted.
Doctrine also clears every stats.cache file. ||
So for every 199 fast page loads there is one page load which suffers a little overhead from the cache cleaning operation.

View File

@ -1,461 +0,0 @@
++ Introduction
Many times you may find classes having similar things within your models. These things may contain anything related to the schema of the component itself (relations, column definitions, index definitions etc.). One obvious way of refactoring the code is having a base class with some classes extending it.
However inheritance solves only a fraction of things. The following subchapters show how many times using Doctrine_Template is much more powerful and flexible than using inheritance.
Doctrine_Template is a class templating system. Templates are basically ready-to-use little components that your Record classes can load. When a template is being loaded its setTableDefinition() and setUp() methods are being invoked and the method calls inside them are being directed into the class in question.
++ Simple templates
In the following example we define a template called TimestampTemplate. Basically the purpose of this template is to add date columns 'created' and 'updated' to the record class that loads this template. Additionally this template uses a listener called Timestamp listener which updates these fields based on record actions.
<code type="php">
class TimestampListener extends Doctrine_Record_Listener
{
public function preInsert(Doctrine_Event $event)
{
$event->getInvoker()->created = date('Y-m-d', time());
$event->getInvoker()->updated = date('Y-m-d', time());
}
public function preUpdate(Doctrine_Event $event)
{
$event->getInvoker()->created = date('Y-m-d', time());
$event->getInvoker()->updated = date('Y-m-d', time());
}
}
class TimestampTemplate extends Doctrine_Template
{
public function setTableDefinition()
{
$this->hasColumn('created', 'date');
$this->hasColumn('updated', 'date');
$this->setListener(new TimestampListener());
}
}
</code>
Lets say we have a class called Blog that needs the timestamp functionality. All we need to do is to add loadTemplate() call in the class definition.
<code type="php">
class Blog extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('title', 'string', 200);
$this->hasColumn('content', 'string');
}
public function setUp()
{
$this->loadTemplate('TimestampTemplate');
}
}
</code>
++ Templates with relations
Many times the situations tend to be much more complex than the situation in the previous chapter. You may have model classes with relations to other model classes and you may want to replace given class with some extended class.
Consider we have two classes, User and Email, with the following definitions:
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string');
}
public function setUp()
{
$this->hasMany('Email', array('local' => 'id', 'foreign' => 'user_id'));
}
}
class Email extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('address', 'string');
$this->hasColumn('user_id', 'integer');
}
public function setUp()
{
$this->hasOne('User', array('local' => 'user_id', 'foreign' => 'id'));
}
}
</code>
Now if we extend the User and Email classes and create, for example, classes ExtendedUser and ExtendedEmail, the ExtendedUser will still have a relation to the Email class - not the ExtendedEmail class. We could of course override the setUp() method of the User class and define relation to the ExtendedEmail class, but then we lose the whole point of inheritance. Doctrine_Template can solve this problem elegantly with its dependency injection solution.
In the following example we'll define two templates, UserTemplate and EmailTemplate, with almost identical definitions as the User and Email class had.
<code type="php">
class UserTemplate extends Doctrine_Template
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string');
}
public function setUp()
{
$this->hasMany('EmailTemplate as Email', array('local' => 'id', 'foreign' => 'user_id'));
}
}
class EmailTemplate extends Doctrine_Template
{
public function setTableDefinition()
{
$this->hasColumn('address', 'string');
$this->hasColumn('user_id', 'integer');
}
public function setUp()
{
$this->hasOne('UserTemplate as User', array('local' => 'user_id', 'foreign' => 'id'));
}
}
</code>
Notice how we set the relations. We are not pointing to concrete Record classes, rather we are setting the relations to templates. This tells Doctrine that it should try to find concrete Record classes for those templates. If Doctrine can't find these concrete implementations the relation parser will throw an exception, but before we go ahead of things, here are the actual record classes:
<code type="php">
class User extends Doctrine_Record
{
public function setUp()
{
$this->loadTemplate('UserTemplate');
}
}
class Email extends Doctrine_Record
{
public function setUp()
{
$this->loadTemplate('EmailTemplate');
}
}
</code>
Now consider the following code snippet. This does NOT work since we haven't yet set any concrete implementations for the templates.
<code type="php">
$user = new User();
$user->Email; // throws an exception
</code>
The following version works. Notice how we set the concrete implementations for the templates globally using Doctrine_Manager.
<code type="php">
$manager = Doctrine_Manager::getInstance();
$manager->setImpl('UserTemplate', 'User')
->setImpl('EmailTemplate', 'Email');
$user = new User();
$user->Email;
</code>
The implementations for the templates can be set at manager, connection and even at the table level.
++ Delegate methods
Besides from acting as a full table definition delegate system, Doctrine_Template allows the delegation of method calls. This means that every method within the loaded templates is available in the record that loaded the templates. Internally the implementation uses magic method called __call() to achieve this functionality.
Lets take an example: we have a User class that loads authentication functionality through a template.
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('fullname', 'string', 30);
}
public function setUp()
{
$this->loadTemplate('AuthTemplate');
}
}
class AuthTemplate extends Doctrine_Template
{
public function setTableDefinition()
{
$this->hasColumn('username', 'string', 16);
$this->hasColumn('password', 'string', 16);
}
public function login($username, $password)
{
// some login functionality here
}
}
</code>
Now you can simply use the methods found in AuthTemplate within the User class as shown above.
<code type="php">
$user = new User();
$user->login($username, $password);
</code>
You can get the record that invoked the delegate method by using the getInvoker() method of Doctrine_Template. Consider the AuthTemplate example. If we want to have access to the User object we just need to do the following:
<code type="php">
class AuthTemplate extends Doctrine_Template
{
public function setTableDefinition()
{
$this->hasColumn('username', 'string', 16);
$this->hasColumn('password', 'string', 16);
}
public function login($username, $password)
{
// do something with the Invoker object here
$object = $this->getInvoker();
}
}
</code>
++ Working with multiple templates
Each class can consists of multiple templates. If the templates contain similar definitions the most recently loaded template always overrides the former.
++ Core Templates
Doctrine comes bundled with some templates that offer out of the box functionality for your models. You can enable these templates in your models very easily. You can do it directly in your Doctrine_Records or you can specify them in your yaml schema if you are managing your models with a yaml schema file.
+++ Versionable
PHP Example
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('username', 'string', 125);
$this->hasColumn('password', 'string', 255);
}
public function setUp()
{
$this->actAs('Versionable', array('versionColumn' => 'version', 'className' => '%CLASS%Version'));
}
}
</code>
YAML Example
<code type="yaml">
---
User:
actAs:
Versionable:
versionColumn: version
className: %CLASS%Version
columns:
username:
type: string(125)
password:
type: string(255)
</code>
+++ Timestampable
The 2nd argument array is not required. It defaults to all the values that are present in the example below.
PHP Example
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('username', 'string', 125);
$this->hasColumn('password', 'string', 255);
}
public function setUp()
{
$this->actAs('Timestampable', array('created' => array('name' => 'created_at',
'type' => 'timestamp',
'format' => 'Y-m-d H:i:s',
'disabled' => false,
'options' => array()),
'updated' => array('name' => 'updated_at',
'type' => 'timestamp',
'format' => 'Y-m-d H:i:s',
'disabled' => false,
'options' => array())));
}
}
</code>
YAML Example
<code type="yaml">
---
User:
actAs:
Timestampable:
created:
name: created_at
type: timestamp
format:Y-m-d H:i:s
options: []
updated:
name: updated_at
type: timestamp
format: Y-m-d H:i:s
options: []
columns:
username:
type: string(125)
password:
type: string(255)
</code>
If you are only interested in using only one of the columns, such as a created_at timestamp, but not a an updated_at field, set the flag disabled=>true for either of the fields as in the example below.
YAML Example
<code type="yaml">
---
User:
actAs:
Timestampable:
created:
name: created_at
type: timestamp
format:Y-m-d H:i:s
options: []
updated:
disabled: true
columns:
username:
type: string(125)
password:
type: string(255)
</code>
+++ Sluggable
If you do not specify the columns to create the slug from, it will default to just using the toString() method on the model.
PHP Example
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('username', 'string', 125);
$this->hasColumn('password', 'string', 255);
}
public function setUp()
{
$this->actAs('Sluggable', array('fields' => array('username')));
}
}
</code>
YAML Example
<code type="yaml">
---
User:
actAs:
Sluggable:
columns: [username]
fields:
username:
type: string(125)
password:
type: string(255)
</code>
+++ I18n
PHP Example
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('username', 'string', 125);
$this->hasColumn('password', 'string', 255);
}
public function setUp()
{
$this->actAs('I18n', array('fields' => array('title')));
}
}
</code>
YAML Example
<code type="yaml">
---
User:
actAs:
I18n:
fields: [title]
columns:
username:
type: string(125)
password:
type: string(255)
</code>
+++ NestedSet
PHP Example
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('username', 'string', 125);
$this->hasColumn('password', 'string', 255);
}
public function setUp()
{
$this->actAs('NestedSet', array('hasManyRoots' => true, 'rootColumnName' => 'root_id'));
}
}
</code>
YAML Example
<code type="yaml">
---
User:
actAs:
NestedSet:
hasManyRoots: true
rootColumnName: root_id
columns:
username:
type: string(125)
password:
type: string(255)
</code>
+++ Searchable
PHP Example
<code type="php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('username', 'string', 125);
$this->hasColumn('password', 'string', 255);
}
public function setUp()
{
$this->actAs('Searchable', array('fields' => array('title', 'content')));
}
}
</code>
YAML Example
<code type="yaml">
---
User:
actAs:
Searchable:
fields: [title, content]
columns:
username:
type: string(125)
password:
type: string(255)
</code>

View File

@ -1,5 +0,0 @@
++ Overview
++ PHP File Formatting
++ Naming Conventions
++ Coding Style
++ Testing

View File

@ -1,246 +0,0 @@
+++ PHP code demarcation
* PHP code must always be delimited by the full-form, standard PHP tags
* Short tags are never allowed. For files containing only PHP code, the closing tag must always be omitted
+++ Strings
* When a string is literal (contains no variable substitutions), the apostrophe or "single quote" must always used to demarcate the string:
<code type="php">
// literal string
$string = 'something';
</code>
* When a literal string itself contains apostrophes, it is permitted to demarcate the string with quotation marks or "double quotes". This is especially encouraged for SQL statements:
<code type="php">
// string contains apostrophes
$sql = "SELECT id, name FROM people WHERE name = 'Fred' OR name = 'Susan'";
</code>
* Variable substitution is permitted using the following form:
<code type="php">
// variable substitution
$greeting = "Hello $name, welcome back!";
</code>
* Strings may be concatenated using the "." operator. A space must always be added before and after the "." operator to improve readability:
<code type="php">
// concatenation
$framework = 'Doctrine' . ' ORM ' . 'Framework';
</code>
* When concatenating strings with the "." operator, it is permitted to break the statement into multiple lines to improve readability. In these cases, each successive line should be padded with whitespace such that the "."; operator is aligned under the "=" operator:
<code type="php">
// concatenation line breaking
$sql = "SELECT id, name FROM user "
. "WHERE name = ? "
. "ORDER BY name ASC";
</code>
+++ Arrays
* Negative numbers are not permitted as indices.
* An indexed array may be started with any non-negative number, however this is discouraged and it is recommended that all arrays have a base index of 0.
* When declaring indexed arrays with the array construct, a trailing space must be added after each comma delimiter to improve readability.
* It is also permitted to declare multiline indexed arrays using the "array" construct. In this case, each successive line must be padded with spaces.
* When declaring associative arrays with the array construct, it is encouraged to break the statement into multiple lines. In this case, each successive line must be padded with whitespace such that both the keys and the values are aligned:
<code type="php">
$sampleArray = array('Doctrine', 'ORM', 1, 2, 3);
$sampleArray = array(1, 2, 3,
$a, $b, $c,
56.44, $d, 500);
$sampleArray = array('first' => 'firstValue',
'second' => 'secondValue');
</code>
+++ Classes
* Classes must be named by following the naming conventions.
* The brace is always written next line after the class name (or interface declaration).
* Every class must have a documentation block that conforms to the PHPDocumentor standard.
* Any code within a class must be indented four spaces.
* Only one class is permitted per PHP file.
* Placing additional code in a class file is NOT permitted.
This is an example of an acceptable class declaration:
<code type="php">
/**
* Documentation here
*/
class Doctrine_SampleClass
{
// entire content of class
// must be indented four spaces
}
</code>
+++ Functions and methods
* Methods must be named by following the naming conventions.
* Methods must always declare their visibility by using one of the private, protected, or public constructs.
* Like classes, the brace is always written next line after the method name. There is no space between the function name and the opening parenthesis for the arguments.
* Functions in the global scope are strongly discouraged.
* This is an example of an acceptable function declaration in a class:
<code type="php">
/**
* Documentation Block Here
*/
class Foo
{
/**
* Documentation Block Here
*/
public function bar()
{
// entire content of function
// must be indented four spaces
}
}
</code>
* Passing by-reference is permitted in the function declaration only:
<code type="php">
/**
* Documentation Block Here
*/
class Foo
{
/**
* Documentation Block Here
*/
public function bar(&$baz)
{
}
}
</code>
* Call-time pass by-reference is prohibited.
* The return value must not be enclosed in parentheses. This can hinder readability and can also break code if a method is later changed to return by reference.
<code type="php">
/**
* Documentation Block Here
*/
class Foo
{
/**
* WRONG
*/
public function bar() {
return($this->bar);
}
/**
* RIGHT
*/
public function bar()
{
return $this->bar;
}
}
</code>
* Function arguments are separated by a single trailing space after the comma delimiter. This is an example of an acceptable function call for a function that takes three arguments:
<code type="php">
threeArguments(1, 2, 3);
</code>
* Call-time pass by-reference is prohibited. See above for the proper way to pass function arguments by-reference.
* For functions whose arguments permitted arrays, the function call may include the {{array}} construct and can be split into multiple lines to improve readability. In these cases, the standards for writing arrays still apply:
<code type="php">
threeArguments(array(1, 2, 3), 2, 3);
threeArguments(array(1, 2, 3, 'Framework',
'Doctrine', 56.44, 500), 2, 3);
</code>
+++ Control statements
* Control statements based on the {{if}} and {{elseif}} constructs must have a single space before the opening parenthesis of the conditional, and a single space after the closing parenthesis.
* Within the conditional statements between the parentheses, operators must be separated by spaces for readability. Inner parentheses are encouraged to improve logical grouping of larger conditionals.
* The opening brace is written on the same line as the conditional statement. The closing brace is always written on its own line. Any content within the braces must be indented four spaces.
<code type="php">
if ($foo != 2) {
$foo = 2;
}
</code>
* For {{if}} statements that include {{elseif}} or {{else}}, the formatting must be as in these examples:
<code type="php">
if ($foo != 1) {
$foo = 1;
} else {
$foo = 3;
}
if ($foo != 2) {
$foo = 2;
} elseif ($foo == 1) {
$foo = 3;
} else {
$foo = 11;
}
</code>
When ! operand is being used it must use the following formatting:
<code type="php">
if ( ! $foo) {
}
</code>
* Control statements written with the {{switch}} construct must have a single space before the opening parenthesis of the conditional statement, and also a single space after the closing parenthesis.
* All content within the {{switch}} statement must be indented four spaces. Content under each {{case}} statement must be indented an additional four spaces but the breaks must be at the same indentation level as the {{case}} statements.
<code type="php">
switch ($case) {
case 1:
case 2:
break;
case 3:
break;
default:
break;
}
</code>
* The construct default may never be omitted from a switch statement.
+++ Inline documentation
Documentation Format:
* All documentation blocks ("docblocks") must be compatible with the phpDocumentor format. Describing the phpDocumentor format is beyond the scope of this document. For more information, visit: http://phpdoc.org/
Methods:
* Every method, must have a docblock that contains at a minimum:
* A description of the function
* All of the arguments
* All of the possible return values
* It is not necessary to use the {{@access}} tag because the access level is already known from the {{public}}, {{private}}, or {{protected}} construct used to declare the function.
* If a function/method may throw an exception, use {{@throws}}:
* {{@throws exceptionclass [description]}}

View File

@ -1,93 +0,0 @@
+++ Classes
* The Doctrine ORM Framework uses the same class naming convention as PEAR and Zend framework, where the names of the classes directly map to the directories in which they are stored. The root level directory of the Doctrine Framework is the "Doctrine/" directory, under which all classes are stored hierarchially.
* Class names may only contain alphanumeric characters. Numbers are permitted in class names but are discouraged. Underscores are only permitted in place of the path separator, eg. the filename "Doctrine/Table/Exception.php" must map to the class name "Doctrine_Table_Exception".
* If a class name is comprised of more than one word, the first letter of each new word must be capitalized. Successive capitalized letters are not allowed, e.g. a class "XML_Reader" is not allowed while "Xml_Reader" is acceptable.
+++ Interfaces
* Interface classes must follow the same conventions as other classes (see above), however must end with the word "{{Interface}}" (unless the interface is approved not to contain it such as {{Doctrine_Overloadable}}). Some examples:
* {{Doctrine_Db_EventListener_Interface}}
* {{Doctrine_EventListener_Interface}}
+++ Filenames
* For all other files, only alphanumeric characters, underscores, and the dash character ("-") are permitted. Spaces are prohibited.
* Any file that contains any PHP code must end with the extension ".php". These examples show the acceptable filenames for containing the class names from the examples in the section above:
* {{Doctrine/Db.php}}
* {{Doctrine/Connection/Transaction.php}}
* File names must follow the mapping to class names described above.
+++ Functions and methods
* Function names may only contain alphanumeric characters. Underscores are not permitted. Numbers are permitted in function names but are discouraged.
* Function names must always start with a lowercase letter. When a function name consists of more than one word, the first letter of each new word must be capitalized. This is commonly called the "studlyCaps" or "camelCaps" method.
* Verbosity is encouraged. Function names should be as verbose as is practical to enhance the understandability of code.
* For object-oriented programming, accessors for objects should always be prefixed with either "get" or "set". This applies to all classes except for Doctrine_Record which has some accessor methods prefixed with 'obtain' and 'assign'. The reason for this is that since all user defined ActiveRecords inherit {{Doctrine_Record}}, it should populate the get / set namespace as little as possible.
* Functions in the global scope ("floating functions") are NOT permmitted. All static functions should be wrapped in a static class.
+++ Variables
All variables must satisfy the following conditions:
* Variable names may only contain alphanumeric characters. Underscores are not permitted. Numbers are permitted in variable names but are discouraged.
* Variable names must always start with a lowercase letter and follow the "camelCaps" capitalization convention.
* Verbosity is encouraged. Variables should always be as verbose as practical. Terse variable names such as "$i" and "$n" are discouraged for anything other than the smallest loop contexts. If a loop contains more than 20 lines of code, the variables for the indices need to have more descriptive names.
* Within the framework certain generic object variables should always use the following names:
||~ Object type ||~ Variable name ||
|| {{Doctrine_Connection}} || {{$conn}} ||
|| {{Doctrine_Collection}} || {{$coll}} ||
|| {{Doctrine_Manager}} || {{$manager}} ||
|| {{Doctrine_Query}} || {{$query}} ||
|| {{Doctrine_Db}} || {{$db}} ||
* There are cases when more descriptive names are more appropriate (for example when multiple objects of the same class are used in same context), in that case it is allowed to use different names than the ones mentioned.
+++ Constants
Following rules must apply to all constants used within Doctrine framework:
* Constants may contain both alphanumeric characters and the underscore.
* Constants must always have all letters capitalized.
* For readablity reasons, words in constant names must be separated by underscore characters. For example, {{ATTR_EXC_LOGGING}} is permitted but {{ATTR_EXCLOGGING}} is not.
* Constants must be defined as class members by using the "const" construct. Defining constants in the global scope with "define" is NOT permitted.
<code type="php">
class Doctrine_SomeClass
{
const MY_CONSTANT = 'something';
}
print Doctrine_SomeClass::MY_CONSTANT;
</code>
+++ Record columns
* All record columns must be in lowercase
* Usage of _ is encouraged for columns that consist of more than one word
<code type="php">
class User
{
public function setTableDefinition()
{
$this->hasColumn('home_address', 'string');
}
}
</code>
* Foreign key fields must be in format [tablename]_[column]
<code type="php">
class Phonenumber
{
public function setTableDefinition()
{
// this field is a foreign key that points to user(id)
$this->hasColumn('user_id', 'integer');
}
}
</code>

View File

@ -1,2 +0,0 @@
+++ Scope
+++ Goals

View File

@ -1,23 +0,0 @@
+++ General
For files that contain only PHP code, the closing tag ("{{?>}}") is never permitted. It is not required by PHP. Not including it prevents trailing whitespace from being accidentally injected into the output.
IMPORTANT: Inclusion of arbitrary binary data as permitted by {{__HALT_COMPILER()}} is prohibited from any Doctrine framework PHP file or files derived from them. Use of this feature is only permitted for special installation scripts.
+++ Indentation
Use an indent of 4 spaces, with no tabs.
+++ Maximum line length
The target line length is 80 characters, i.e. developers should aim keep code as close to the 80-column boundary as is practical. However, longer lines are acceptable. The maximum length of any line of PHP code is 120 characters.
+++ Line termination
* Line termination is the standard way for Unix text files. Lines must end only with a linefeed (LF). Linefeeds are represented as ordinal 10, or hexadecimal 0x0A.
* Do not use carriage returns (CR) like Macintosh computers (0x0D).
* Do not use the carriage return/linefeed combination (CRLF) as Windows computers (0x0D, 0x0A).

View File

@ -1,25 +0,0 @@
+++ Writing tests
++++ Classes
* All test classes should be referring to a class or specific testing aspect of some class.
* For example {{Doctrine_Record_TestCase}} is a valid name since its referring to class named {{Doctrine_Record}}.
* {{Doctrine_Record_State_TestCase}} is also a valid name since its referring to testing the state aspect of the {{Doctrine_Record}} class.
* However something like {{Doctrine_PrimaryKey_TestCase}} is not valid since its way too generic.
* Every class should have atleast one {{TestCase}} equivalent
* All testcase classes should inherit {{Doctrine_UnitTestCase}}
++++ Methods
* All methods should support agile documentation; if some method failed it should be evident from the name of the test method what went wrong. Also the test method names should give information of the system they test.
* For example {{Doctrine_Export_Pgsql_TestCase::testCreateTableSupportsAutoincPks()}} is a valid test method name. Just by looking at it we know what it is testing. Also we can run agile documentation tool to get little up-to-date system information.
NOTE: Commonly used testing method naming convention {{TestCase::test[methodName]}} is **not** allowed in Doctrine. So in this case {{Doctrine_Export_Pgsql_TestCase::testCreateTable()}} would not be allowed!
* Test method names can often be long. However the content within the methods should rarely be more than dozen lines long. If you need several assert-calls divide the method into smaller methods.
++++ Assertions
* There should never be assertions within any loops and rarely within functions.

View File

@ -1,758 +0,0 @@
++ Record
{{Doctrine_Record}} is one of the most essential components of Doctrine ORM. The class 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.
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>
Every record has an object identifier, which is an internal unique identifier. You can get the object identifier with the oid() method. Basically two objects are considered the same if they share the same object identifier.
+++ Properties
Each assigned column property of {{Doctrine_Record}} represents a database table column. As you've learned in the previous chapters the column definitions can be achieved with the hasColumn() method. Now accessing the columns is easy. You can use any of the means described above. The recommended way is using the ArrayAccess as it makes it easy to switch between record and array fetching when needed.
<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'];
</code>
Iterating trhough the properties of a record can be done in similar way as iterating through an array - by using the foreach construct. This is possible since {{Doctrine_Record}} implements a magic IteratorAggregate interface.
<code type="php">
foreach ($user as $field => $value) {
}
</code>
As with arrays you can use the isset() for checking if given property exists and unset() for setting given property to null.
<code type="php">
// checking if property called 'name' exists
if (isset($user['name'])) {
}
// unsetting name property
unset($user['name']);
</code>
When you have set values for record properties you can get the names of modified properties with modifiedFields() method. This method returns an array of modified field names.
<code type="php">
$user['name'] = 'Jack Daniels';
$user['age'] = 100;
print_r($user->modifiedFields()); // array('name', 'age');
$user->isModified(); // true
</code>
Sometimes you may want to retrieve the column count of given record. In order to do this you can simply pass the record as an argument for the count() function. This is possible since {{Doctrine_Record}} implements a magic Countable interface. The other way would be calling the count() method.
<code type="php">
// get the number of columns
$colCount = $record->count();
$colCount = count($record);
</code>
{{Doctrine_Record}} offers a special method for accessing the identifier of given record. This method is called identifier() and it returns an array with identifier field names as keys and values as the associated property values.
<code type="php">
$user['name'] = 'Jack Daniels';
$user->save();
$user->identifier(); // array('id' => 1)
</code>
A common case is that you have an array of values which you need to assign to a given record. It may feel awkward and clumsy to set these values separately. No need to worry though, {{Doctrine_Record}} offers a way for merging given array to property values.
The merge() method iterates through the properties of given record and assigns the values of given array to the associated properties.
<code type="php">
$values = array('name' => 'someone',
'age' => 11,
'unknownproperty' => '...');
// notice that here the unknownproperty won't get assigned
// as the User class doesn't have a column with that name
$user->merge($values);
print $user->name; // someone
print $user->age; // 11
print $user->unknownproperty; // throws exception
</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 the DQL API.
<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 u WHERE u.name LIKE '%John%'");
</code>
+++ Updating records
Updating objects is very easy, you just call the {{Doctrine_Record::save()}} method. The other way 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>
Sometimes you may want to do a direct update. In direct update the objects aren't loaded from database, rather the state of the database is directly updated. In the following example we use DQL UPDATE statement to update all users.
<code type="php">
// make all usernames lowercased
Doctrine_Query::create()->update('User u')
->set('u.name', 'LOWER(u.name)')
->execute();
</code>
+++ Refreshing records
Sometimes you may want to refresh your record with data from the database, use {{Doctrine_Record::refresh()}}.
<code type="php">
$user = $conn->getTable('User')->find(2);
$user->name = 'New name';
// oups, I want to refresh the name
$user->refresh();
</code>
++++ Refreshing relationships
The {{Doctrine_Record::refresh()}} method can also refresh record relationships, but you need to specify them on the query.
<code type="php">
$user = Doctrine_Query::create()
->from('User')
->leftJoin('Groups')
->where('id = ?')
->fetchOne(array(1));
$group = Doctrine_Query::create()
->from('Group')
->leftJoin('Users')
->where('id = ?')
->fetchOne(array(1));
$userGroup = new UserGroup();
$userGroup->user_id = $user->id;
$userGroup->group_id = $group->id;
$userGroup->save();
// get new group on user
$user->refresh(true);
// get new user on group
$group->refresh(true);
</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 ('Rock festival', 'NOW()')
</code>
+++ Getting record state
{{Every Doctrine_Record}} has a state. First of all records can be transient or persistent. Every record that is retrieved from database is persistent and every newly created record is considered 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.
A record can also have a state called locked. In order to avoid infinite recursion in some rare circular reference cases Doctrine uses this state internally to indicate that a record is currently under a manipulation operation.
<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;
case Doctrine_Record::STATE_LOCKED:
// record is locked
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>
Notice that copying the record with copy() returns a new record (state TDIRTY) with the values of the old record, and it copies the relations of that record. If you do not want to copy the relations too, you need to use copy(false).
<code type="php">
// get a copy of user without the relations
$copy = $user->copy(false);
</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
Very commonly you'll need to know if given record exists in the database. You can use the exists() method for checking if given record has a database row equivalent.
<code type="php">
$record = new User();
$record->exists(); // false
$record->name = 'someone';
$record->save();
$record->exists(); // true
</code>
+++ Function callbacks for columns
{{Doctrine_Record}} offers a way for attaching callback calls for column values. For example if you want to trim certain column, you can simply type:
<code type="php">
$record->call('trim', 'column1');
</code>
++ Collection
{{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();
print $users->count();
</code>
Since {{Doctrine_Collection}} implements Countable interface a valid alternative for the previous example is to simply pass the collection as an argument for the count() function.
<code>
print count($users); // Doctrine_Collection implements Countable interface
</code>
+++ Saving the collection
Similar to {{Doctrine_Record}} the collection can be saved by calling the {{save()}} method. When save() gets called Doctrine issues save() operations an all records and wraps the whole procedure in a transaction.
<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. When deleting the collection
of users doctrine only performs one query for this whole transaction. The query would look something like:
<code type="sql">
DELETE FROM user WHERE id IN (1,2,3, ... ,N)
</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.
However, in most cases you don't need to load related elements explicitly, rather what you should do is try to load everything at once by using the DQL API and JOINS.
The following example uses three queries for retrieving users, their phonenumbers and the groups they belong to.
<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[0]->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[0]->name;
}
</code>
The example below shows how to do this more efficiently by using the DQL API.
<code type="php">
// load everything here
$users = $conn->query('FROM User u LEFT JOIN u.Phonenumber p LEFT JOIN u.Group g');
foreach($users as $user) {
// no additional db queries needed here
print $user->Phonenumber->phonenumber;
print $user->Group->name;
}
</code>
++ Connection
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>
++ Table
{{Doctrine_Table}} holds the schema information specified by the given component (record). For example if you have a User class that extends Doctrine_Record, each schema definition call gets delegated to a unique table object that holds the information for later use.
Each {{Doctrine_Table}} is registered by {{Doctrine_Connection}}, which means you can retrieve the tables from the connection by calling the getTable() method with the appropriate component name.
For example, lets say we want to retrieve the table object for the User class. We can do this by simply giving the 'User' as the first argument for the getTable() method.
<code>
// get the current connection
$conn = Doctrine_Manager::connection();
$table = $conn->getTable('User');
</code>
+++ Getting column information
You can retrieve the column definitions set in {{Doctrine_Record}} by using the appropriate {{Doctrine_Table}} methods. If you need all information of all columns you can simply use:
<code type="php">
// getting all information of all columns
$columns = $table->getColumns();
</code>
Sometimes this can be an overkill. The following example shows how to retrieve the column names as an array:
<code>
// getting column names
$names = $table->getColumnNames();
</code>
+++ Getting relation information
+++ Finder methods
{{Doctrine_Table}} provides basic finder methods. These finder methods are very fast and should be used if you only need to fetch data from one database table. If you need queries that use several components (database tables) use {{Doctrine_Connection::query()}}.
<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;
}
</code>
++++ Custom table classes
Adding custom table classes is very easy. Only thing you need to do is name the classes as {{[componentName]Table}} and make them inherit {{Doctrine_Table}}.
<code type="php">
// valid table object
class UserTable extends Doctrine_Table
{
}
// not valid [doesn't extend Doctrine_Table]
class GroupTable { }
</code>
+++ Custom finders
You can add custom finder methods to your custom table object. These finder methods may use fast {{Doctrine_Table}} finder methods or DQL API ({{Doctrine_Connection::query()}}).
<code type="php">
class UserTable extends Doctrine_Table {
/**
* you can add your own finder methods here
*/
public function findByName($name) {
return $this->getConnection()->query("FROM User WHERE name LIKE '%$name%'");
}
}
class User extends Doctrine_Record { }
$conn = Doctrine_Manager::getInstance()
->openConnection(new PDO("dsn","username","password"));
// doctrine will now check if a class called UserTable exists
// and if it inherits Doctrine_Table
$table = $conn->getTable("User");
print get_class($table); // UserTable
$users = $table->findByName("Jack");
</code>
++ Validators
++ Profiler
++ Locking manager
++ View

View File

@ -1,7 +0,0 @@
+++ Introduction
Many web applications have different kinds of lists. The lists may contain data from multiple components (= database tables) and they may have actions such as paging, sorting and setting conditions. {{Doctrine_Hook}} helps building these lists. It has a simple API for building search criteria forms as well as building a DQL query from the 'hooked' parameters.
+++ Building queries
+++ List of parsers

View File

@ -1,103 +0,0 @@
+++ Introduction
[**Note**: The term 'Transaction' doesnt refer to database transactions here but to the general meaning of this term]
[**Note**: This component is in **Alpha State**]
Locking is a mechanism to control concurrency. The two most well known locking strategies are optimistic and pessimistic locking. The following is a short description of these two strategies from which only pessimistic locking is currently supported by Doctrine.
**Optimistic Locking:**
The state/version of the object(s) is noted when the transaction begins. When the transaction finishes the noted state/version of the participating objects is compared to the current state/version. When the states/versions differ the objects have been modified by another transaction and the current transaction should fail. This approach is called 'optimistic' because it is assumed that it is unlikely that several users will participate in transactions on the same objects at the same time.
**Pessimistic Locking:**
The objects that need to participate in the transaction are locked at the moment the user starts the transaction. No other user can start a transaction that operates on these objects while the locks are active. This ensures that the user who starts the transaction can be sure that noone else modifies the same objects until he has finished his work.
Doctrine's pessimistic offline locking capabilities can be used to control concurrency during actions or procedures that take several HTTP request and response cycles and/or a lot of time to complete.
+++ Examples
The following code snippet demonstrates the use of Doctrine's pessimistic offline locking capabilities.
At the page where the lock is requested...
<code type="php">
// Get a locking manager instance
$lockingMngr = new Doctrine_Locking_Manager_Pessimistic();
try
{
// Ensure that old locks which timed out are released
// before we try to acquire our lock
// 300 seconds = 5 minutes timeout
$lockingMngr->releaseAgedLocks(300);
// Try to get the lock on a record
$gotLock = $lockingMngr->getLock(
// The record to lock. This can be any Doctrine_Record
$myRecordToLock,
// The unique identifier of the user who is trying to get the lock
'Bart Simpson'
);
if($gotLock)
{
echo "Got lock!";
// ... proceed
}
else
{
echo "Sorry, someone else is currently working on this record";
}
}
catch(Doctrine_Locking_Exception $dle)
{
echo $dle->getMessage();
// handle the error
}
</code>
At the page where the transaction finishes...
<code type="php">
// Get a locking manager instance
$lockingMngr = new Doctrine_Locking_Manager_Pessimistic();
try
{
if($lockingMngr->releaseLock($myRecordToUnlock, 'Bart Simpson'))
{
echo "Lock released";
}
else
{
echo "Record was not locked. No locks released.";
}
}
catch(Doctrine_Locking_Exception $dle)
{
echo $dle->getMessage();
// handle the error
}
</code>
+++ Planned
* Possibility to release locks of a specific Record type (i.e. releasing all locks on 'User' objects).
+++ Technical Details
The pessimistic offline locking manager stores the locks in the database (therefore 'offline'). The required locking table is automatically created when you try to instantiate an instance of the manager and the ATTR_CREATE_TABLES is set to TRUE. This behaviour may change in the future to provide a centralised and consistent table creation procedure for installation purposes.
+++ Maintainer
Roman Borschel - romanb at #doctrine (freenode)
Don't hesitate to contact me if you have questions, ideas, ect.

View File

@ -1,32 +0,0 @@
+++ Introduction
{{Doctrine_Connection_Profiler}} is an eventlistener for {{Doctrine_Connection}}. It provides flexible query profiling. Besides the SQL strings the query profiles include elapsed time to run the queries. This allows inspection of the queries that have been performed without the need for adding extra debugging code to model classes.
{{Doctrine_Connection_Profiler}} can be enabled by adding it as an eventlistener for {{Doctrine_Connection}}.
<code type="php">
$conn = Doctrine_Manager::connection($dsn);
$profiler = new Doctrine_Connection_Profiler();
$conn->setListener($profiler);
</code>
+++ Basic usage
Perhaps some of your pages is loading slowly. The following shows how to build a complete profiler report from the connection:
<code type="php">
$time = 0;
foreach ($profiler as $event) {
$time += $event->getElapsedSecs();
echo $event->getName() . " " . sprintf("%f", $event->getElapsedSecs()) . "<br>\n";
echo $event->getQuery() . "<br>\n";
$params = $event->getParams();
if( ! empty($params)) {
var_dump($params);
}
}
echo "Total time: " . $time . "<br>\n";
</code>
+++ Advanced usage

View File

@ -1,118 +0,0 @@
+++ Introduction
Validation in Doctrine is a way to enforce your business rules in the model part of the MVC architecture. You can think of this validation as a gateway that needs to be passed right before data gets into the persistent data store. The definition of these business rules takes place at the record level, that means in your active record model classes (classes derived from {{Doctrine_Record}}). The first thing you need to do to be able to use this kind of validation is to enable it globally. This is done through the {{Doctrine_Manager}} (see the code below).
Once you enabled validation, you'll get a bunch of validations automatically:
* **Data type validations**: All values assigned to columns are checked for the right type. That means if you specified a column of your record as type 'integer', Doctrine will validate that any values assigned to that column are of this type. This kind of type validation tries to be as smart as possible since PHP is a loosely typed language. For example 2 as well as "7" are both valid integers whilst "3f" is not. Type validations occur on every column (since every column definition needs a type).
* **Length validation**: As the name implies, all values assigned to columns are validated to make sure that the value does not exceed the maximum length.
<code type="php">
// turning on validation
Doctrine_Manager::getInstance()->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);
</code>
You can combine the following constants by using bitwise operations: VALIDATE_ALL, VALIDATE_TYPES, VALIDATE_LENGTHS,
VALIDATE_CONSTRAINTS, VALIDATE_NONE. For example to enable all validations except length validations you would use:
<code>
VALIDATE_ALL & ~VALIDATE_LENGTHS
</code>
+++ More Validation
The type and length validations are handy but most of the time they're not enough. Therefore Doctrine provides some mechanisms that can be used to validate your data in more detail.
Validators are an easy way to specify further validations. Doctrine has a lot of predefined validators that are frequently needed such as email, country, ip, range and regexp validators. You find a full list of available validators at the bottom of this page. You can specify which validators apply to which column through the 4th argument of the {{hasColumn()}} method. If that is still not enough and you need some specialized validation that is not yet available as a predefined validator you have three options:
* You can write the validator on your own.
* You can propose your need for a new validator to a Doctrine developer.
* You can use validation hooks.
The first two options are advisable if it is likely that the validation is of general use and is potentially applicable in many situations. In that case it is a good idea to implement a new validator. However if the validation is special it is better to use hooks provided by Doctrine:
* {{validate()}} (Executed every time the record gets validated)
* {{validateOnInsert()}} (Executed when the record is new and gets validated)
* {{validateOnUpdate()}} (Executed when the record is not new and gets validated)
If you need a special validation in your active record you can simply override one of these methods in your active record class (a descendant of {{Doctrine_Record}}). Within thess methods you can use all the power of PHP to validate your fields. When a field doesnt pass your validation you can then add errors to the record's error stack. The following code snippet shows an example of how to define validators together with custom validation:
<code type="php">
class User extends Doctrine_Record
{
public function setUp()
{
$this->ownsOne('Email', array('local' => 'email_id'));
}
public function setTableDefinition()
{
// no special validators used only types
// and lengths will be validated
$this->hasColumn('name', 'string', 15);
$this->hasColumn('email_id', 'integer');
$this->hasColumn('created', 'integer', 11);
}
// Our own validation
protected function validate()
{
if ($this->name == 'God') {
// Blasphemy! Stop that! ;-)
// syntax: add(<fieldName>, <error code/identifier>)
$this->getErrorStack()->add('name', 'forbiddenName');
}
}
}
class Email extends Doctrine_Record {
public function setTableDefinition() {
// validators 'email' and 'unique' used
$this->hasColumn("address","string",150, array("email", "unique"));
}
}
</code>
+++ Valid or Not Valid
Now that you know how to specify your business rules in your models, it is time to look at how to deal with these rules in the rest of your application.
++++ Implicit validation
Whenever a record is going to be saved to the persistent data store (i.e. through calling {{$record->save()}}) the full validation procedure is executed. If errors occur during that process an exception of the type {{Doctrine_Validator_Exception}} will be thrown. You can catch that exception and analyze the errors by using the instance method {{Doctine_Validator_Exception::getInvalidRecords()}}. This method returns an ordinary array with references to all records that did not pass validation. You can then further explore the errors of each record by analyzing the error stack of each record. The error stack of a record can be obtained with the instance method {{Doctrine_Record::getErrorStack()}}. Each error stack is an instance of the class {{Doctrine_Validator_ErrorStack}}. The error stack provides an easy to use interface to inspect the errors.
++++ Explicit validation
You can explicitly trigger the validation for any record at any time. For this purpose Doctrine_Record provides the instance method {{Doctrine_Record::isValid()}}. This method returns a boolean value indicating the result of the validation. If the method returns false, you can inspect the error stack in the same way as seen above except that no exception is thrown, so you simply obtain the error stack of the record that didnt pass validation through {{Doctrine_Record::getErrorStack()}}.
The following code snippet shows an example of handling implicit validation which caused a {{Doctrine_Validator_Exception}}.
<code type="php">
try {
$user->name = "this is an example of too long name";
$user->Email->address = "drink@@notvalid..";
$user->save();
} catch(Doctrine_Validator_Exception $e) {
// Note: you could also use $e->getInvalidRecords(). The direct way
// used here is just more simple when you know the records you're dealing with.
$userErrors = $user->getErrorStack();
$emailErrors = $user->Email->getErrorStack();
/* Inspect user errors */
foreach($userErrors as $fieldName => $errorCodes) {
switch ($fieldName) {
case 'name':
// $user->name is invalid. inspect the error codes if needed.
break;
}
}
/* Inspect email errors */
foreach($emailErrors as $fieldName => $errorCodes) {
switch ($fieldName) {
case 'address':
// $user->Email->address is invalid. inspect the error codes if needed.
break;
}
}
}
</code>

View File

@ -1,39 +0,0 @@
+++ Introduction
Database views can greatly increase the performance of complex queries. You can think of them as cached queries. {{Doctrine_View}} provides integration between database views and DQL queries.
+++ Managing views
<code type="php">
$conn = Doctrine_Manager::getInstance()
->openConnection(new PDO("dsn","username","password"));
$query = new Doctrine_Query($conn);
$query->from('User.Phonenumber')->limit(20);
$view = new Doctrine_View($query, 'MyView');
// creating a database view
$view->create();
// dropping the view from the database
$view->drop();
</code>
+++ Using views
<code type="php">
$conn = Doctrine_Manager::getInstance()
->openConnection(new PDO("dsn","username","password"));
$query = new Doctrine_Query($conn);
$query->from('User.Phonenumber')->limit(20);
// hook the query into appropriate view
$view = new Doctrine_View($query, 'MyView');
// now fetch the data from the view
$coll = $view->execute();
</code>

View File

@ -1,189 +0,0 @@
++ Introduction
++ Levels of configuration
++ General attributes
+++ Portability
Each database management system (DBMS) has it's own behaviors. For example, some databases capitalize field names in their output, some lowercase them, while others leave them alone. These quirks make it difficult to port your scripts over to another server type. Doctrine strives to overcome these differences so your program can switch between DBMS's without any changes.
You control which portability modes are enabled by using the portability configuration option. Configuration options are set via {{factory()}} and {{setOption()}}.
The portability modes are bitwised, so they can be combined using {{|}} and removed using {{^}}. See the examples section below on how to do this.
++++ Portability Mode Constants
: {{Doctrine::PORTABILITY_ALL}} (default) : turn on all portability features. this is the default setting.
: {{Doctrine::PORTABILITY_DELETE_COUNT}} : Force reporting the number of rows deleted. Some DBMS's don't count the number of rows deleted when performing simple {{DELETE FROM}} tablename queries. This mode tricks such DBMS's into telling the count by adding {{WHERE 1=1}} to the end of {{DELETE}} queries.
: {{Doctrine::PORTABILITY_EMPTY_TO_NULL}} : Convert empty strings values to null in data in and output. Needed because Oracle considers empty strings to be null, while most other DBMS's know the difference between empty and null.
: {{Doctrine::PORTABILITY_ERRORS}} : Makes certain error messages in certain drivers compatible with those from other DBMS's
: {{Doctrine::PORTABILITY_FIX_ASSOC_FIELD_NAMES}} : This removes any qualifiers from keys in associative fetches. some RDBMS , like for example SQLite, will be default use the fully qualified name for a column in assoc fetches if it is qualified in a query.
: {{Doctrine::PORTABILITY_FIX_CASE}} : Convert names of tables and fields to lower or upper case in all methods. The case depends on the {{field_case}} option that may be set to either {{CASE_LOWER}} (default) or {{CASE_UPPER}}
: {{Doctrine::PORTABILITY_NONE}} : Turn off all portability features
: {{Doctrine::PORTABILITY_NUMROWS}} : Enable hack that makes {{numRows()}} work in Oracle
: {{Doctrine::PORTABILITY_EXPR}} : Makes DQL API throw exceptions when non-portable expressions are being used.
: {{Doctrine::PORTABILITY_RTRIM}} : Right trim the data output for all data fetches. This does not applied in drivers for RDBMS that automatically right trim values of fixed length character values, even if they do not right trim value of variable length character values.
++++ Examples
Using {{setAttribute()}} to enable portability for lowercasing and trimming
<code type="php">
$conn->setAttribute('portability',
Doctrine::PORTABILITY_FIX_CASE | Doctrine::PORTABILITY_RTRIM);
</code>
Using {{setAttribute()}} to enable all portability options except trimming
<code type="php">
$conn->setAttribute('portability',
Doctrine::PORTABILITY_ALL ^ Doctrine::PORTABILITY_RTRIM);
</code>
+++ Identifier quoting
You can quote the db identifiers (table and field names) with {{quoteIdentifier()}}. The delimiting style depends on which database driver is being used.
NOTE: just because you CAN use delimited identifiers, it doesn't mean you SHOULD use them. In general, they end up causing way more problems than they solve. Anyway, it may be necessary when you have a reserved word as a field name (in this case, we suggest you to change it, if you can).
Some of the internal Doctrine methods generate queries. Enabling the {{quote_identifier}} attribute of Doctrine you can tell Doctrine to quote the identifiers in these generated queries. For all user supplied queries this option is irrelevant.
Portability is broken by using the following characters inside delimited identifiers:
* backtick (`) -- due to MySQL
* double quote (") -- due to Oracle
* brackets ([ or ]) -- due to Access
Delimited identifiers are known to generally work correctly under the following drivers:
* Mssql
* Mysql
* Oracle
* Pgsql
* Sqlite
* Firebird
When using the {{ATTR_QUOTE_IDENTIFIER}} option, all of the field identifiers will be automatically quoted in the resulting SQL statements:
<code type="php">
$conn->setAttribute(Doctrine::ATTR_QUOTE_IDENTIFIER, true);
</code>
will result in a SQL statement that all the field names are quoted with the backtick '`' operator (in MySQL).
<code type="sql">
SELECT * FROM `sometable` WHERE `id` = '123'
</code>
as opposed to:
<code type="sql">
SELECT * FROM sometable WHERE id='123'
</code>
+++ Exporting
The export attribute is used for telling Doctrine what it should export when exporting classes.
If you don't want to export anything when calling export() you can use:
<code type="php">
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_NONE);
</code>
For exporting tables only (but not constraints) you can use on of the following:
<code type="php">
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_TABLES);
// or you can use
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL ^ Doctrine::EXPORT_CONSTRAINTS);
</code>
For exporting everything (tables and constraints) you can use:
<code type="php">
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL);
</code>
+++ Event listener
<code type="php">
// setting default event listener
$manager->setAttribute(Doctrine::ATTR_LISTENER, new MyListener());
</code>
++ Naming convention attributes
Naming convention attributes affect on the naming of different database related elements such as tables, indexes and sequences. Basically every naming convention attribute has affect in both ways. When importing schemas from the database to classes and when exporting classes into database.
So for example by default Doctrine naming convention for indexes is %s_idx. Not only do the indexes you set get a special suffix, also the imported classes get their indexes mapped to their non-suffixed equivalents. This applies to all naming convention attributes.
+++ Index name format
Doctrine::ATTR_IDXNAME_FORMAT can be used for changing the naming convention of indexes. By default Doctrine uses the format [name]_idx. So defining an index called 'ageindex' will actually be converted into 'ageindex_idx'.
<code type="php">
// changing the index naming convention
$manager->setAttribute(Doctrine::ATTR_IDXNAME_FORMAT, '%s_index');
</code>
+++ Sequence name format
Similar to Doctrine::ATTR_IDXNAME_FORMAT, Doctrine::ATTR_SEQNAME_FORMAT can be used for changing the naming convention of sequences. By default Doctrine uses the format [name]_seq, hence creating a new sequence with the name of 'mysequence' will lead into creation of sequence called 'mysequence_seq'.
<code type="php">
// changing the sequence naming convention
$manager->setAttribute(Doctrine::ATTR_IDXNAME_FORMAT, '%s_sequence');
</code>
+++ Table name format
+++ Database name format
<code type="php">
// changing the database naming convention
$manager->setAttribute(Doctrine::ATTR_DBNAME_FORMAT, 'myframework_%s');
</code>
++ Validation attributes
Doctrine provides complete control over what it validates. The validation procedure can be controlled with Doctrine::ATTR_VALIDATE.
The validation modes are bitwised, so they can be combined using {{|}} and removed using {{^}}. See the examples section below on how to do this.
+++ Validation mode constants
: {Doctrine::VALIDATE_NONE} : Turns off the whole validation procedure. This is the default value.
: {Doctrine::VALIDATE_LENGTHS} : Makes Doctrine validate all field lengths.
: {Doctrine::VALIDATE_TYPES} : Makes Doctrine validate all field types. Doctrine does loose type validation. This means that for example string with value '13.3' will not pass as an integer but '13' will.
: {Doctrine::VALIDATE_CONSTRAINTS} : Makes Doctrine validate all field constraints such as notnull, email etc.
: {Doctrine::VALIDATE_ALL} : Turns on all validations.
+++ Examples
Turning on all validations:
<code type="php">
$manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);
</code>
Validating lengths and types, but not constraints:
<code type="php">
$manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_LENGTHS | Doctrine::VALIDATE_TYPES);
</code>

View File

@ -1,5 +0,0 @@
<code type="php">
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_LISTENER, new MyListener());
</code>

View File

@ -1,33 +0,0 @@
Doctrine has a three-level configuration structure. You can set configuration attributes in global, connection and table level. If the same attribute is set on both lower level and upper level, the uppermost attribute will always be used. So for example if user first sets default fetchmode in global level to {{Doctrine::FETCH_BATCH}} and then sets {{example}} table fetchmode to {{Doctrine::FETCH_LAZY}}, the lazy fetching strategy will be used whenever the records of 'example' table are being fetched.
: **Global level** : The attributes set in global level will affect every connection and every table in each connection.
: **Connection level** : The attributes set in connection level will take effect on each table in that connection.
: **Table level** : The attributes set in table level will take effect only on that table.
In the following example we set an attribute at the global level:
<code type="php">
// setting a global level attribute
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);
</code>
In the next example above we override the global attribute on given connection.
<code type="php">
// setting a connection level attribute
// (overrides the global level attribute on this connection)
$conn = $manager->openConnection(new PDO('dsn', 'username', 'pw'));
$conn->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_NONE);
</code>
In the last example we override once again the connection level attribute in the table level.
<code type="php">
// setting a table level attribute
// (overrides the connection/global level attribute on this table)
$table = $conn->getTable('User');
$table->setAttribute(Doctrine::ATTR_LISTENER, new UserListener());
</code>

View File

@ -1,5 +0,0 @@
++ DSN, the Data Source Name
++ Opening a new connection
++ Lazy-connecting to database
++ Managing connections
++ Connection-component binding

View File

@ -1,20 +0,0 @@
Doctrine allows you to bind connections to components (= your ActiveRecord classes). This means everytime a component issues a query or data is being fetched from the table the component is pointing at Doctrine will use the bound connection.
<code type="php">
$conn = $manager->openConnection(new PDO('dsn','username','password'), 'connection 1');
$conn2 = $manager->openConnection(new PDO('dsn2','username2','password2'), 'connection 2');
$manager->bindComponent('User', 'connection 1');
$manager->bindComponent('Group', 'connection 2');
$q = new Doctrine_Query();
// Doctrine uses 'connection 1' for fetching here
$users = $q->from('User u')->where('u.id IN (1,2,3)')->execute();
// Doctrine uses 'connection 2' for fetching here
$groups = $q->from('Group g')->where('g.id IN (1,2,3)')->execute();
</code>

View File

@ -1,102 +0,0 @@
In order to connect to a database through Doctrine, you have to create a valid DSN - data source name.
Doctrine supports both PEAR DB/MDB2 like data source names as well as PDO style data source names. The following section deals with PEAR like data source names. If you need more info about the PDO-style data source names see [[php PDO->__construct()]].
The DSN consists in the following parts:
||~ DSN part ||~ Description ||
|| phptype || Database backend used in PHP (i.e. mysql , pgsql etc.) ||
|| dbsyntax || Database used with regards to SQL syntax etc. ||
|| protocol || Communication protocol to use ( i.e. tcp, unix etc.) ||
|| hostspec || Host specification (hostname[:port]) ||
|| database || Database to use on the DBMS server ||
|| username || User name for login ||
|| password || Password for login ||
|| proto_opts || Maybe used with protocol ||
|| option || Additional connection options in URI query string format. Options are separated by ampersand (&). The Following table shows a non complete list of options: ||
**List of options**
||~ Name ||~ Description ||
|| charset || Some backends support setting the client charset.||
|| new_link || Some RDBMS do not create new connections when connecting to the same host multiple times. This option will attempt to force a new connection. ||
The DSN can either be provided as an associative array or as a string. The string format of the supplied DSN is in its fullest form:
<code>phptype(dbsyntax)://username:password@protocol+hostspec/database?option=value</code>
Most variations are allowed:
<code>
phptype://username:password@protocol+hostspec:110//usr/db_file.db
phptype://username:password@hostspec/database
phptype://username:password@hostspec
phptype://username@hostspec
phptype://hostspec/database
phptype://hostspec
phptype:///database
phptype:///database?option=value&anotheroption=anothervalue
phptype(dbsyntax)
phptype
</code>
The currently supported database backends are:
||~ Driver name ||~ Supported databases ||
|| firbird || Firebird ||
|| informix || Informix ||
|| mssql || Microsoft SQL Server ||
|| mysql || MySQL ||
|| oracle || Oracle ||
|| pgsql || PostgreSQL ||
|| sqlite || SQLite ||
A second DSN format supported is
<code>
phptype(syntax)://user:pass@protocol(proto_opts)/database
</code>
If your database, option values, username or password contain characters used to delineate DSN parts, you can escape them via URI hex encodings:
||~ Character ||~ Hex Code ||
|| : || %3a ||
|| / || %2f ||
|| @ || %40 ||
|| + || %2b ||
|| ( || %28 ||
|| ) || %29 ||
|| ? || %3f ||
|| = || %3d ||
|| & || %26 ||
Warning
Please note, that some features may be not supported by all database backends.
+++ Examples
**Example 1.** Connect to database through a socket
<code>
mysql://user@unix(/path/to/socket)/pear
</code>
**Example 2.** Connect to database on a non standard port
<code>
pgsql://user:pass@tcp(localhost:5555)/pear
</code>
**Example 3.** Connect to SQLite on a Unix machine using options
<code>
sqlite:////full/unix/path/to/file.db?mode=0666
</code>
**Example 4.** Connect to SQLite on a Windows machine using options
<code>
sqlite:///c:/full/windows/path/to/file.db?mode=0666
</code>

View File

@ -1,14 +0,0 @@
Lazy-connecting to database can save a lot of resources. There might be many pages where you don't need an actual database connection, hence its always recommended to use lazy-connecting (that means Doctrine will only connect to database when needed).
This feature can be very useful when using for example page caching, hence not actually needing a database connection on every request. Remember connecting to database is an expensive operation.
<code type="php">
$dsn = 'mysql://username:password@localhost/test';
// initalize a new Doctrine_Connection
$conn = Doctrine_Manager::connection($dsn);
// !! no actual database connection yet !!
// connects database and performs a query
$conn->query('FROM User u');
</code>

View File

@ -1,52 +0,0 @@
From the start Doctrine has been designed to work with multiple connections. Unless separately specified Doctrine always uses the current connection for executing the queries. The following example uses {{openConnection()}} second argument as an optional connection alias.
<code type="php">
// Doctrine_Manager controls all the connections
$manager = Doctrine_Manager::getInstance();
// open first connection
$conn = $manager->openConnection(new PDO('dsn','username','password'), 'connection 1');
</code>
For convenience {{Doctrine_Manager}} provides static method {{connection()}} which opens new connection when arguments are given to it and returns the current connection when no arguments have been speficied.
<code type="php">
// open first connection
$conn = Doctrine_Manager::connection(new PDO('dsn','username','password'), 'connection 1');
$conn2 = Doctrine_Manager::connection();
// $conn2 == $conn
</code>
The current connection is the lastly opened connection.
<code type="php">
// open second connection
$conn2 = $manager->openConnection(new PDO('dsn2','username2','password2'), 'connection 2');
$manager->getCurrentConnection(); // $conn2
</code>
You can change the current connection by calling {{setCurrentConnection()}}.
<code type="php">
$manager->setCurrentConnection('connection 1');
$manager->getCurrentConnection(); // $conn
</code>
You can iterate over the opened connection by simple passing the manager object to foreach clause. This is possible since {{Doctrine_Manager}} implements special {{IteratorAggregate}} interface.
<code type="php">
// iterating through connections
foreach($manager as $conn) {
}
</code>

View File

@ -1,13 +0,0 @@
Opening a new database connection in Doctrine is very easy. If you wish to use PDO (www.php.net/PDO) you can just initalize a new PDO object:
<code type="php">
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$user = 'dbuser';
$password = 'dbpass';
try {
$dbh = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
</code>

View File

@ -1,134 +0,0 @@
Doctrine Data uses the Doctrine Parser for the dumping and loading of fixtures data so it is possible to use any of the formats available in the Parser. Currently yml is the only fully supported format but xml and others are next.
++ Exporting
You can export data to fixtures file in many different formats
<code type="php">
// A few ways exist for specifying where you export the data
// Dump to one large fixture file
$data = new Doctrine_Data();
$data->exportData('data.yml', 'yml');
// Dump to individual files. One file per model. 3rd argument true specifies to dump to individual files
$data = new Doctrine_Data();
$data->exportData('path/to/directory', 'yml', true);
</code>
++ Importing
You can import data from fixtures files in many different formats
<code type="php">
// Path can be in a few different formats
$path = 'path/to/data.yml'; // Path directly to one yml file
$path = array('data.yml', 'data2.yml', 'more.yml'); // Array of yml file paths
$path = array('directory1', 'directory2', 'directory3'); // Array of directories which contain yml files. It will find all files with an extension of .yml
// Specify the format of the data you are importing
$format = 'yml'; // xml, yml, json
$models = array('User', 'Phonenumber'); // you can optionally specify an array of the models you wish to import the data for, by default it loads data for all the available loaded models and the data that exists
$data = new Doctrine_Data();
$data->importData($path, $format, $models);
</code>
++ Dummy Data
With Doctrine Data you can import dummy data to all your Doctrine Records
<code type="php">
$numRecords = 3; // Number of dummy records to populate for each model
$models = array('User', 'Email'); // Models to generate dummy data for. If none specified it generates dummy data for all loaded models.
$data = new Doctrine_Data();
$data->importDummyData($numRecords, $models);
</code>
++ Writing
You can write your fixtures files manually and load them in to your applications. Below is a sample data.yml fixtures file. You can also split your data fixtures file up in to multiple files. Doctrine will read all fixtures files and parse them, then load all data.
Imagine a schema with the following relationships:
<code type="php">
Resource hasMany Tag as Tags
Resource hasOne ResourceType as Type
ResourceType hasMany Resource as Resources
Tag hasMany Resource as Resources
</code>
<code type="yml">
---
Resource:
Resource_1:
name: Doctrine Video Tutorial
Type: Video
Tags: [tutorial, doctrine, help]
Resource_2:
name: Doctrine Cheat Sheet
Type: Image
Tags: [tutorial, cheat, help]
ResourceType:
Video:
name: Video
Image:
name: Image
Tag:
tutorial:
name: tutorial
doctrine:
name: doctrine
help:
name: help
cheat:
name: cheat
</code>
You could optionally specify the Resources each tag is related to instead of specifying the Tags a Resource has.
<code type="yml">
Tag:
tutorial:
name: tutorial
Resources: [Resource_1, Resource_2]
doctrine:
name: doctrine
Resources: [Resource_1]
help:
name: help
Resources: [Resource_1, Resource_2]
cheat:
name: cheat
Resources: [Resource_1]
</code>
Here is how you would write code to load the data from that data.yml file
<code type="php">
$data = new Doctrine_Data();
$data->importData('data.yml', 'yml');
</code>
++ Fixtures For Nested Sets
Writing a fixtures file for a nested set tree is slightly different from writing regular fixtures files. The structure of the tree is defined like this:
<code type="yml">
---
Category:
Category_1:
title: Categories # the root node
children:
Category_2:
title: Category 1
Category_3:
title: Category 2
children:
Category_4:
title: Subcategory of Category 2
</code>

View File

@ -1,442 +0,0 @@
++ Modules
++ Export
+++ Introduction
The Export module provides methods for managing database structure. The methods can be grouped based on their responsibility: create, edit (alter or update), list or delete (drop) database elements. The following document lists the available methods, providing examples of their use.
Every schema altering method in the Export module has an equivalent which returns the sql that is used for the altering operation. For example createTable() executes the query / queries returned by createTableSql().
In this chapter the following tables will be created, altered and finally dropped, in a database named "events_db":
events(id, name, datetime);
people(id, name);
event_participants(event_id, person_id);
+++ Creating a database
<code type="php">
$conn->export->createDatabase('events_db');
</code>
+++ Creating tables
Now that the database is created, we can proceed with adding some tables. The method createTable() takes three parameters: the table name, an array of field definition and some extra options (optional and RDBMS-specific). Now lets create the events table:
<code type="php">
$definition = array (
'id' => array (
'type' => 'integer',
'unsigned' => 1,
'notnull' => 1,
'default' => 0,
),
'name' => array (
'type' => 'string',
'length' => 255
),
'datetime' => array (
'type' => 'timestamp'
)
);
$conn->export->createTable('events', $definition);
</code>
The keys of the definition array are the names of the fields in the table. The values are arrays containing the required key 'type' as well as other keys, depending on the value of 'type'. The values for the 'type' key are the same as the possible Doctrine datatypes. Depending on the datatype, the other options may vary.
||~ Datatype ||~ length ||~ default ||~ not null ||~ unsigned ||~ autoincrement ||
|| string || x || x || x || || ||
|| boolean || || x || x || || ||
|| integer || x || x || x || x || x ||
|| decimal || || x || x || || ||
|| float || || x || x || || ||
|| timestamp || || x || x || || ||
|| time || || x || x || || ||
|| date || || x || x || || ||
|| clob || x || || x || || ||
|| blob || x || || x || || ||
Creating the people table:
<code type="php">
$options = array(
'comment' => 'Repository of people',
'character_set' => 'utf8',
'collate' => 'utf8_unicode_ci',
'type' => 'innodb',
);
$definition = array (
'id' => array (
'type' => 'integer',
'unsigned' => 1,
'notnull' => 1,
'default' => 0,
),
'name' => array (
'type' => 'string',
'length' => 255
)
);
$conn->export->createTable('people', $definition, $options);
</code>
+++ Creating foreign keys
Creating the event_participants table with a foreign key:
<code type="php">
$options = array(
'foreignKeys' => array('local' => 'event_id',
'foreign' => 'id'
'foreignTable' => 'events'
'onDelete' => 'CASCADE'),
'primary' => array('event_id', 'person_id'),
);
$definition = array (
'event_id' => array (
'type' => 'integer',
'unsigned' => 1,
'notnull' => 1,
'default' => 0,
),
'person_id' => array (
'type' => 'integer',
'unsigned' => 1,
'notnull' => 1,
'default' => 0,
),
);
$conn->export->createTable('event_participants', $definition, $options);
</code>
Now lets say we want to add foreign key on person_id too. This can be achieved as follows:
<code type="php">
$definition = array('local' => 'person_id',
'foreign' => 'id'
'foreignTable' => 'people'
'onDelete' => 'CASCADE'))
$conn->export->createForeignKey('event_participants', $definition);
</code>
+++ Altering table
Doctrine_Export drivers provide an easy database portable way of altering existing database tables.
NOTE: if you only want to get the generated sql (and not execute it) use Doctrine_Export::alterTableSql()
<code type="php">
$dbh = new PDO('dsn','username','pw');
$conn = Doctrine_Manager::getInstance()
->openConnection($dbh);
$a = array('add' => array('name' => array('type' => 'string', 'length' => 255)));
$conn->export->alterTableSql('mytable', $a);
// On mysql this method returns:
// ALTER TABLE mytable ADD COLUMN name VARCHAR(255)
</code>
Doctrine_Export::alterTable() takes two parameters:
: string //$name// : name of the table that is intended to be changed.
: array //$changes// : associative array that contains the details of each type of change that is intended to be performed.
An optional third parameter (default: false) is accepted in alterTable and alterTableSql; it is named //$check// and it identifies if the DBMS driver can perform the requested table alterations if the value is true or actually perform them otherwise.
The types of changes that are currently supported are defined as follows:
* //name//
New name for the table.
* //add//
Associative array with the names of fields to be added as indexes of the array. The value of each entry of the array should be set to another associative array with the properties of the fields to be added. The properties of the fields should be the same as defined by the Doctrine parser.
* //remove//
Associative array with the names of fields to be removed as indexes of the array. Currently the values assigned to each entry are ignored. An empty array should be used for future compatibility.
* //rename//
Associative array with the names of fields to be renamed as indexes of the array. The value of each entry of the array should be set to another associative array with the entry named name with the new field name and the entry named Declaration that is expected to contain the portion of the field declaration already in DBMS specific SQL code as it is used in the CREATE TABLE statement.
* //change//
Associative array with the names of the fields to be changed as indexes of the array. Keep in mind that if it is intended to change either the name of a field and any other properties, the change array entries should have the new names of the fields as array indexes.
The value of each entry of the array should be set to another associative array with the properties of the fields to that are meant to be changed as array entries. These entries should be assigned to the new values of the respective properties. The properties of the fields should be the same as defined by the Doctrine parser.
<code type="php">
$a = array('name' => 'userlist',
'add' => array(
'quota' => array(
'type' => 'integer',
'unsigned' => 1
)
),
'remove' => array(
'file_limit' => array(),
'time_limit' => array()
),
'change' => array(
'name' => array(
'length' => '20',
'definition' => array(
'type' => 'string',
'length' => 20
)
)
),
'rename' => array(
'sex' => array(
'name' => 'gender',
'definition' => array(
'type' => 'string',
'length' => 1,
'default' => 'M'
)
)
)
);
$dbh = new PDO('dsn','username','pw');
$conn = Doctrine_Manager::getInstance()->openConnection($dbh);
$conn->export->alterTable('mytable', $a);
</code>
+++ Creating indices
To create an index, the method createIndex() is used, which has similar signature as createConstraint(), so it takes table name, index name and a definition array. The definition array has one key fields with a value which is another associative array containing fields that will be a part of the index. The fields are defined as arrays with possible keys:
sorting, with values ascending and descending
length, integer value
Not all RDBMS will support index sorting or length, in these cases the drivers will ignore them. In the test events database, we can assume that our application will show events occuring in a specific timeframe, so the selects will use the datetime field in WHERE conditions. It will help if there is an index on this field.
<code type="php">
$definition = array(
'fields' => array(
'datetime' => array()
)
);
$conn->export->createIndex('events', 'event_timestamp', $definition);
</code>
+++ Deleting database elements
For every create*() method as shown above, there is a corresponding drop*() method to delete a database, a table, field, index or constraint. The drop*() methods do not check if the item to be deleted exists, so it's developer's responsibility to check for exceptions.
<code type="php">
// drop a sequence
try {
$conn->export->dropSequence('nonexisting');
} catch(Doctrine_Exception $e) {
}
// another sequence
$result = $conn->export->dropSequence('people');
// drop a constraint
$conn->export->dropConstraint('events', 'PRIMARY', true);
// note: the third parameter gives a hint
// that this is a primary key constraint
$conn->export->dropConstraint('event_participants', 'unique_participant');
// drop an index
$conn->export->dropIndex('events', 'event_timestamp');
// drop a table
$conn->export->dropTable('events');
// drop the database already!
$conn->export->dropDatabase('events_db');
</code>
++ Import
+++ Introduction
To see what's in the database, you can use the list*() family of functions in the Import module.
* listDatabases()
* listFunctions()
* listSequences(): takes optional database name as a parameter. If not supplied, the currently selected database is assumed.
* listTableConstraints(): takes a table name
* listTableFields(): takes a table name
* listTableIndexes(): takes a table name
* listTables(): takes an optional database name
* listTableTriggers(): takes a table name
* listTableViews(): takes a table name
* listUsers()
* listViews(): takes an optional database name
+++ Listing databases
<code type="php">
$dbs = $conn->import->listDatabases();
print_r($dbs);
</code>
+++ Listing sequences
<code type="php">
$seqs = $conn->import->listSequences('events_db');
print_r($seqs);
</code>
+++ Listing constraints
<code type="php">
$cons = $conn->import->listTableConstraints('event_participants');
</code>
+++ Listing table fields
<code type="php">
$fields = $conn->import->listTableFields('events');
print_r($fields);
/*
prints:
Array
(
[0] => id
[1] => name
[2] => datetime
)
*/
</code>
+++ Listing table indices
<code type="php">
$idx = $conn->import->listTableIndexes('events');
print_r($idx);
/*
prints:
Array
(
[0] => event_timestamp
)
*/
</code>
+++ Listing tables
<code type="php">
$tables = $conn->import->listTables();
print_r($tables);
/*
prints:
Array
(
[0] => event_participants
[1] => events
[2] => people
)
*/
</code>
+++ Listing views
<code type="php">
// currently there is no method to create a view,
// so let's do it "manually"
$sql = "CREATE VIEW names_only AS SELECT name FROM people";
$conn->exec($sql);
$sql = "CREATE VIEW last_ten_events AS SELECT * FROM events ORDER BY id DESC LIMIT 0,10";
$conn->exec($sql);
// list views
$views = $conn->import->listViews();
print_r($views);
/*
prints:
Array
(
[0] => last_ten_events
[1] => names_only
)
*/
</code>
++ DataDict
+++ Introduction
Doctrine uses DataDict module internally to convert native RDBMS types to Doctrine types and the reverse. DataDict module uses two methods for the conversions:
1. getPortableDeclaration(), which is used for converting native RDBMS type declaration to portable Doctrine declaration
2. getNativeDeclaration(), which is used for converting portable Doctrine declaration to driver specific type declaration
+++ Getting portable declaration
<code type="php">
$dbh = new PDO('mysql:host=localhost;dbname=test', 'username', 'pw');
$conn = Doctrine_Manager::getInstance()->openConnection($dbh);
$decl = $conn->dataDict->getPortableDeclaration('VARCHAR(255)');
print_r($decl);
/*
array('type' => 'string',
'length' => 255,
'fixed' => false,
'unsigned' => false
);
*/
</code>
+++ Getting native declaration
<code type="php">
$dbh = new PDO('mysql:host=localhost;dbname=test', 'username', 'pw');
$conn = Doctrine_Manager::getInstance()->openConnection($dbh);
$portableDecl = array('type' => 'string',
'length' => 20,
'fixed' => true);
$nativeDecl = $conn->dataDict->getNativeDeclaration($portableDecl);
print $nativeDecl; // CHAR(20)
</code>
++ Drivers
+++ Mysql
++++ Setting table type
<code type="php">
$dbh = new PDO('dsn','username','pw');
$conn = Doctrine_Manager::getInstance()->openConnection($dbh);
$fields = array('id' => array(
'type' => 'integer',
'autoincrement' => true),
'name' => array(
'type' => 'string',
'fixed' => true,
'length' => 8)
);
// the following option is mysql specific and
// skipped by other drivers
$options = array('type' => 'MYISAM');
$conn->export->createTable('mytable', $fields);
// on mysql this executes query:
// CREATE TABLE mytable (id INT AUTO_INCREMENT PRIMARY KEY,
// name CHAR(8));
</code>

View File

@ -1,71 +0,0 @@
++ Introduction
++ SELECT queries
++ UPDATE queries
++ DELETE queries
++ FROM clause
++ JOIN syntax
++ INDEXBY keyword
++ WHERE clause
++ Conditional expressions
++ Functional Expressions
++ Subqueries
++ GROUP BY, HAVING clauses
++ ORDER BY clause
++ LIMIT and OFFSET clauses
++ Examples
++ The Query Registry
Doctrine_Query_Registry is a class for registering and naming queries. It helps with the organization of your applications queries and along with that it offers some very nice convenience stuff.
The queries are added using the add() method of the registry object. It takes two parameters, the query name and the actual DQL query.
<code type="php">
$r = Doctrine_Manager::getInstance()->getQueryRegistry();
$r->add('all-users', 'FROM User u');
</code>
+++ Namespaces
The Query registry supports namespaces. The namespace is separated from the actual name with / -mark. If the name of the namespace is a record name the given record has all the named queries available in its local scope.
<code type="php">
$r = Doctrine_Manager::getInstance()->getQueryRegistry();
$r->add('User/all', 'FROM User u');
$r->add('User/byName', 'FROM User u WHERE u.name = ?');
$user = new User();
// find the user named Jack Daniels
$user = $user->findOne('byName', array('Jack Daniels'));
// find all users
$users = $user->find('all');
</code>
++ BNF
++ Magic Finders
Doctrine offers some magic finders for your Doctrine models that allow you to find a record by any column that is present in the model. This is helpful for simply finding a user by their username, or finding a group by the name of it. Normally this would require writing a Doctrine_Query instance and storing this somewhere so it can be reused. That is no longer needed for simple situations like that.
The basic pattern for the finder methods are as follows: findBy%s($value) or findOneBy%s($value). The %s can be a column name or a relation alias. If you give a column name you must give the value you are looking for. If you specify a relationship alias, you can either pass an instance of the relation class to find, or give the actual primary key value.
Examples:
<code type="php">
// The normal find by primary key method
$userTable = Doctrine::getTable('User');
$user = $userTable->find(1);
// Find one user by the username
$userTable = Doctrine::getTable('User');
$user = $userTable->findOneByUsername('jonwage');
// Find phonenumbers for the user above
$phoneTable = Doctrine::getTable('Phonenumber');
$phonenumbers = $phoneTable->findByUser($user);
</code>

View File

@ -1,185 +0,0 @@
<code>
QL_statement ::= select_statement | update_statement | delete_statement
select_statement ::= select_clause from_clause [where_clause] [groupby_clause]
[having_clause] [orderby_clause]
update_statement ::= update_clause [where_clause]
delete_statement ::= delete_clause [where_clause]
from_clause ::=
FROM identification_variable_declaration
{, {identification_variable_declaration | collection_member_declaration}}*
identification_variable_declaration ::= range_variable_declaration { join | fetch_join }*
range_variable_declaration ::= abstract_schema_name [AS ] identification_variable
join ::= join_spec join_association_path_expression [AS ] identification_variable
fetch_join ::= join_specFETCH join_association_path_expression
association_path_expression ::=
collection_valued_path_expression | single_valued_association_path_expression
join_spec::= [LEFT [OUTER ] |INNER ]JOIN
join_association_path_expression ::= join_collection_valued_path_expression |
join_single_valued_association_path_expression
join_collection_valued_path_expression::=
identification_variable.collection_valued_association_field
join_single_valued_association_path_expression::=
identification_variable.single_valued_association_field
collection_member_declaration ::=
IN ( collection_valued_path_expression) [AS ] identification_variable
single_valued_path_expression ::=
state_field_path_expression | single_valued_association_path_expression
state_field_path_expression ::=
{identification_variable | single_valued_association_path_expression}.state_field
single_valued_association_path_expression ::=
identification_variable.{single_valued_association_field.}* single_valued_association_field
collection_valued_path_expression ::=
identification_variable.{single_valued_association_field.}*collection_valued_association_field
state_field ::= {embedded_class_state_field.}*simple_state_field
update_clause ::=UPDATE abstract_schema_name [[AS ] identification_variable]
SET update_item {, update_item}*
update_item ::= [identification_variable.]{state_field | single_valued_association_field} =
new_value
new_value ::=
simple_arithmetic_expression |
string_primary |
datetime_primary |
boolean_primary |
enum_primary
simple_entity_expression |
NULL
delete_clause ::=DELETE FROM abstract_schema_name [[AS ] identification_variable]
select_clause ::=SELECT [DISTINCT ] select_expression {, select_expression}*
select_expression ::=
single_valued_path_expression |
aggregate_expression |
identification_variable |
OBJECT( identification_variable) |
constructor_expression
constructor_expression ::=
NEW constructor_name( constructor_item {, constructor_item}*)
constructor_item ::= single_valued_path_expression | aggregate_expression
aggregate_expression ::=
{AVG |MAX |MIN |SUM }( [DISTINCT ] state_field_path_expression) |
COUNT ( [DISTINCT ] identification_variable | state_field_path_expression |
single_valued_association_path_expression)
where_clause ::=WHERE conditional_expression
groupby_clause ::=GROUP BY groupby_item {, groupby_item}*
groupby_item ::= single_valued_path_expression | identification_variable
having_clause ::=HAVING conditional_expression
orderby_clause ::=ORDER BY orderby_item {, orderby_item}*
orderby_item ::= state_field_path_expression [ASC |DESC ]
subquery ::= simple_select_clause subquery_from_clause [where_clause]
[groupby_clause] [having_clause]
subquery_from_clause ::=
FROM subselect_identification_variable_declaration
{, subselect_identification_variable_declaration}*
subselect_identification_variable_declaration ::=
identification_variable_declaration |
association_path_expression [AS ] identification_variable |
collection_member_declaration
simple_select_clause ::=SELECT [DISTINCT ] simple_select_expression
simple_select_expression::=
single_valued_path_expression |
aggregate_expression |
identification_variable
conditional_expression ::= conditional_term | conditional_expressionOR conditional_term
conditional_term ::= conditional_factor | conditional_termAND conditional_factor
conditional_factor ::= [NOT ] conditional_primary
conditional_primary ::= simple_cond_expression |( conditional_expression)
simple_cond_expression ::=
comparison_expression |
between_expression |
like_expression |
in_expression |
null_comparison_expression |
empty_collection_comparison_expression |
collection_member_expression |
exists_expression
between_expression ::=
arithmetic_expression [NOT ]BETWEEN
arithmetic_expressionAND arithmetic_expression |
string_expression [NOT ]BETWEEN string_expressionAND string_expression |
datetime_expression [NOT ]BETWEEN
datetime_expressionAND datetime_expression
in_expression ::=
state_field_path_expression [NOT ]IN ( in_item {, in_item}* | subquery)
in_item ::= literal | input_parameter
like_expression ::=
string_expression [NOT ]LIKE pattern_value [ESCAPE escape_character]
null_comparison_expression ::=
{single_valued_path_expression | input_parameter}IS [NOT ] NULL
empty_collection_comparison_expression ::=
collection_valued_path_expressionIS [NOT] EMPTY
collection_member_expression ::= entity_expression
[NOT ]MEMBER [OF ] collection_valued_path_expression
exists_expression::= [NOT ]EXISTS (subquery)
all_or_any_expression ::= {ALL |ANY |SOME } (subquery)
comparison_expression ::=
string_expression comparison_operator {string_expression | all_or_any_expression} |
boolean_expression {= |<> } {boolean_expression | all_or_any_expression} |
enum_expression {= |<> } {enum_expression | all_or_any_expression} |
datetime_expression comparison_operator
{datetime_expression | all_or_any_expression} |
entity_expression {= |<> } {entity_expression | all_or_any_expression} |
arithmetic_expression comparison_operator
{arithmetic_expression | all_or_any_expression}
comparison_operator ::== |> |>= |< |<= |<>
arithmetic_expression ::= simple_arithmetic_expression | (subquery)
simple_arithmetic_expression ::=
arithmetic_term | simple_arithmetic_expression {+ |- } arithmetic_term
arithmetic_term ::= arithmetic_factor | arithmetic_term {* |/ } arithmetic_factor
arithmetic_factor ::= [{+ |- }] arithmetic_primary
arithmetic_primary ::=
state_field_path_expression |
numeric_literal |
(simple_arithmetic_expression) |
input_parameter |
functions_returning_numerics |
aggregate_expression
string_expression ::= string_primary | (subquery)
string_primary ::=
state_field_path_expression |
string_literal |
input_parameter |
functions_returning_strings |
aggregate_expression
datetime_expression ::= datetime_primary | (subquery)
datetime_primary ::=
state_field_path_expression |
input_parameter |
functions_returning_datetime |
aggregate_expression
boolean_expression ::= boolean_primary | (subquery)
boolean_primary ::=
state_field_path_expression |
boolean_literal |
input_parameter |
enum_expression ::= enum_primary | (subquery)
enum_primary ::=
state_field_path_expression |
enum_literal |
input_parameter |
entity_expression ::=
single_valued_association_path_expression | simple_entity_expression
simple_entity_expression ::=
identification_variable |
input_parameter
functions_returning_numerics::=
LENGTH( string_primary) |
LOCATE( string_primary, string_primary[, simple_arithmetic_expression]) |
ABS( simple_arithmetic_expression) |
SQRT( simple_arithmetic_expression) |
MOD( simple_arithmetic_expression, simple_arithmetic_expression) |
SIZE( collection_valued_path_expression)
functions_returning_datetime ::=
CURRENT_DATE |
CURRENT_TIME |
CURRENT_TIMESTAMP
functions_returning_strings ::=
CONCAT( string_primary, string_primary) |
SUBSTRING( string_primary,
simple_arithmetic_expression, simple_arithmetic_expression)|
TRIM( [[trim_specification] [trim_character]FROM ] string_primary) |
LOWER( string_primary) |
UPPER( string_primary)
trim_specification ::=LEADING | TRAILING | BOTH
</code>

View File

@ -1,241 +0,0 @@
+++ Literals
**Strings**
A string literal is enclosed in single quotes; for example: 'literal'. A string literal that includes a single quote is represented by two single quotes; for example: 'literal''s'.
<code type="sql">
FROM User WHERE User.name = 'Vincent'
</code>
**Integers**
Integer literals support the use of PHP integer literal syntax.
<code type="sql">
FROM User WHERE User.id = 4
</code>
**Floats**
Float literals support the use of PHP float literal syntax.
<code type="sql">
FROM Account WHERE Account.amount = 432.123
</code>
**Booleans**
The boolean literals are true and false.
<code type="sql">
FROM User WHERE User.admin = true
FROM Session WHERE Session.is_authed = false
</code>
**Enums**
The enumerated values work in the same way as string literals.
<code type="sql">
FROM User WHERE User.type = 'admin'
</code>
Predefined reserved literals are case insensitive, although its a good standard to write them in uppercase.
+++ Input parameters
<code type="php">
// POSITIONAL PARAMETERS:
$users = $conn->query("FROM User WHERE User.name = ?", array('Arnold'));
$users = $conn->query("FROM User WHERE User.id > ? AND User.name LIKE ?", array(50, 'A%'));
// NAMED PARAMETERS:
$users = $conn->query("FROM User WHERE User.name = :name", array(':name' => 'Arnold'));
$users = $conn->query("FROM User WHERE User.id > :id AND User.name LIKE :name", array(':id' => 50, ':name' => 'A%'));
</code>
+++ Operators and operator precedence
The operators are listed below in order of decreasing precedence.
||~ Operator ||~ Description ||
|| . || Navigation operator ||
|| || //Arithmetic operators: // ||
|| +, - || unary ||
|| *, / || multiplication and division ||
|| +, - || addition and subtraction ||
|| =, >, >=, <, <=, <> (not equal), || Comparison operators ||
|| [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY || ||
|| || //Logical operators: // ||
|| NOT || ||
|| AND || ||
|| OR || ||
+++ Between expressions
+++ In expressions
Syntax:
<code>
<operand> IN (<subquery>|<value list>)
</code>
An IN conditional expression returns true if the //operand// is found from result of the //subquery// or if its in the specificied comma separated //value list//, hence the IN expression is always false if the result of the subquery is empty.
When //value list// is being used there must be at least one element in that list.
<code type="sql">
FROM C1 WHERE C1.col1 IN (FROM C2(col1));
FROM User WHERE User.id IN (1,3,4,5)
</code>
The keyword IN is an alias for = ANY. Thus, these two statements are equal:
<code type="sql">
FROM C1 WHERE C1.col1 = ANY (FROM C2(col1));
FROM C1 WHERE C1.col1 IN (FROM C2(col1));
</code>
+++ Like Expressions
Syntax:
<code>
string_expression [NOT] LIKE pattern_value [ESCAPE escape_character]
</code>
The string_expression must have a string value. The pattern_value is a string literal or a string-valued input parameter in which an underscore (_) stands for any single character, a percent (%) character stands for any sequence of characters (including the empty sequence), and all other characters stand for themselves. The optional escape_character is a single-character string literal or a character-valued input parameter (i.e., char or Character) and is used to escape the special meaning of the underscore and percent characters in pattern_value.
Examples:
* address.phone LIKE '12%3' is true for '123' '12993' and false for '1234'
* asentence.word LIKE 'l_se' is true for 'lose' and false for 'loose'
* aword.underscored LIKE '\_%' ESCAPE '\' is true for '_foo' and false for 'bar'
* address.phone NOT LIKE '12%3' is false for '123' and '12993' and true for '1234'
If the value of the string_expression or pattern_value is NULL or unknown, the value of the LIKE expression is unknown. If the escape_characteris specified and is NULL, the value of the LIKE expression is unknown.
<code type="php">
// finding all users whose email ends with '@gmail.com'
$users = $conn->query("FROM User u, u.Email e WHERE e.address LIKE '%@gmail.com'");
// finding all users whose name starts with letter 'A'
$users = $conn->query("FROM User u WHERE u.name LIKE 'A%'");
</code>
+++ Null Comparison Expressions
+++ Empty Collection Comparison Expressions
+++ Collection Member Expressions
+++ Exists Expressions
Syntax:
<code>
<operand> [NOT ]EXISTS (<subquery>)
</code>
The EXISTS operator returns TRUE if the subquery returns one or more rows and FALSE otherwise.
The NOT EXISTS operator returns TRUE if the subquery returns 0 rows and FALSE otherwise.
Finding all articles which have readers:
<code type="sql">
FROM Article a
WHERE EXISTS (SELECT r.id FROM ReaderLog r
WHERE r.article_id = a.id)
</code>
Finding all articles which don't have readers:
<code type="sql">
FROM Article a
WHERE NOT EXISTS (SELECT r.id FROM ReaderLog r
WHERE r.article_id = a.id)
</code>
+++ All and Any Expressions
Syntax:
<code>
operand comparison_operator ANY (subquery)
operand comparison_operator SOME (subquery)
operand comparison_operator ALL (subquery)
</code>
An ALL conditional expression returns true if the comparison operation is true for all values in the result of the subquery or the result of the subquery is empty. An ALL conditional expression is false if the result of the comparison is false for at least one row, and is unknown if neither true nor false.
<code type="sql">
FROM C WHERE C.col1 < ALL (FROM C2(col1))
</code>
An ANY conditional expression returns true if the comparison operation is true for some value in the result of the subquery. An ANY conditional expression is false if the result of the subquery is empty or if the comparison operation is false for every value in the result of the subquery, and is unknown if neither true nor false.
<code type="sql">
FROM C WHERE C.col1 > ANY (FROM C2(col1))
</code>
The keyword SOME is an alias for ANY.
<code type="sql">
FROM C WHERE C.col1 > SOME (FROM C2(col1))
</code>
The comparison operators that can be used with ALL or ANY conditional expressions are =, <, <=, >, >=, <>. The result of the subquery must be same type with the conditional expression.
NOT IN is an alias for <> ALL. Thus, these two statements are equal:
<code type="sql">
FROM C WHERE C.col1 <> ALL (FROM C2(col1));
FROM C WHERE C.col1 NOT IN (FROM C2(col1));
</code>
+++ Subqueries
A subquery can contain any of the keywords or clauses that an ordinary SELECT query can contain.
Some advantages of the subqueries:
* They allow queries that are structured so that it is possible to isolate each part of a statement.
* They provide alternative ways to perform operations that would otherwise require complex joins and unions.
* They are, in many people's opinion, readable. Indeed, it was the innovation of subqueries that gave people the original idea of calling the early SQL "Structured Query Language."
<code type="php">
// finding all users which don't belong to any group 1
$query = "FROM User WHERE User.id NOT IN
(SELECT u.id FROM User u
INNER JOIN u.Group g WHERE g.id = ?)";
$users = $conn->query($query, array(1));
// finding all users which don't belong to any groups
// Notice:
// the usage of INNER JOIN
// the usage of empty brackets preceding the Group component
$query = "FROM User WHERE User.id NOT IN
(SELECT u.id FROM User u
INNER JOIN u.Group g)";
$users = $conn->query($query);
</code>

View File

@ -1,28 +0,0 @@
<code type="sql">
DELETE FROM <component_name>
[WHERE <where_condition>]
[ORDER BY ...]
[LIMIT <record_count>]
</code>
* The {{DELETE}} statement deletes records from {{component_name}} and returns the number of records deleted.
* The optional {{WHERE}} clause specifies the conditions that identify which records to delete. Without {{WHERE}} clause, all records are deleted.
* If the {{ORDER BY}} clause is specified, the records are deleted in the order that is specified.
* The {{LIMIT}} clause places a limit on the number of rows that can be deleted. The statement will stop as soon as it has deleted {{record_count}} records.
<code type="php">
$q = 'DELETE FROM Account WHERE id > ?';
$rows = $this->conn->query($q, array(3));
// the same query using the query interface
$q = new Doctrine_Query();
$rows = $q->delete('Account')
->from('Account a')
->where('a.id > ?', 3)
->execute();
print $rows; // the number of affected rows
</code>

View File

@ -1,22 +0,0 @@
Syntax:
<code type="sql">
FROM <component_reference> [[LEFT | INNER] JOIN <component_reference>] ...
</code>
The {{FROM}} clause indicates the component or components from which to retrieve records. If you name more than one component, you are performing a join. For each table specified, you can optionally specify an alias.
Consider the following DQL query:
<code type="sql">
FROM User u
</code>
Here 'User' is the name of the class (component) and 'u' is the alias. You should always use short aliases, since most of the time those make the query much shorther and also because when using for example caching the cached form of the query takes less space when short aliases are being used.
The following example shows how to fetch all records from class 'User'.
<code type="php">
$users = Doctrine_Query::create()
->from('User u')
->execute();
</code>

View File

@ -1,73 +0,0 @@
+++ String functions
* The //CONCAT// function returns a string that is a concatenation of its arguments. In the example above we map the concatenation of users firstname and lastname to a value called name
<code type="php">
$q = new Doctrine_Query();
$users = $q->select('CONCAT(u.firstname, u.lastname) name')->from('User u')->execute();
foreach($users as $user) {
// here 'name' is not a property of $user,
// its a mapped function value
print $user->name;
}
</code>
* The second and third arguments of the //SUBSTRING// function denote the starting position and length of the substring to be returned. These arguments are integers. The first position of a string is denoted by 1. The //SUBSTRING// function returns a string.
<code type="php">
$q = new Doctrine_Query();
$users = $q->select('u.name')->from('User u')->where("SUBSTRING(u.name, 0, 1) = 'z'")->execute();
foreach($users as $user) {
print $user->name;
}
</code>
* The //TRIM// function trims the specified character from a string. If the character to be trimmed is not specified, it is assumed to be space (or blank). The optional trim_character is a single-character string literal or a character-valued input parameter (i.e., char or Character)[30]. If a trim specification is not provided, BOTH is assumed. The //TRIM// function returns the trimmed string.
<code type="php">
$q = new Doctrine_Query();
$users = $q->select('u.name')->from('User u')->where("TRIM(u.name) = 'Someone'")->execute();
foreach($users as $user) {
print $user->name;
}
</code>
* The //LOWER// and //UPPER// functions convert a string to lower and upper case, respectively. They return a string.
<code type="php">
$q = new Doctrine_Query();
$users = $q->select('u.name')->from('User u')->where("LOWER(u.name) = 'someone'")->execute();
foreach($users as $user) {
print $user->name;
}
</code>
* The //LOCATE// function returns the position of a given string within a string, starting the search at a specified position. It returns the first position at which the string was found as an integer. The first argument is the string to be located; the second argument is the string to be searched; the optional third argument is an integer that represents the string position at which the search is started (by default, the beginning of the string to be searched). The first position in a string is denoted by 1. If the string is not found, 0 is returned.
* The //LENGTH// function returns the length of the string in characters as an integer.
+++ Arithmetic functions
Availible DQL arithmetic functions:
<code>
ABS(simple_arithmetic_expression)
SQRT(simple_arithmetic_expression)
MOD(simple_arithmetic_expression, simple_arithmetic_expression)
</code>
* The //ABS// function returns the absolute value for given number.
* The //SQRT// function returns the square root for given number.
* The //MOD// function returns the modulus of first argument using the second argument.
+++ Datetime functions

View File

@ -1,48 +0,0 @@
DQL GROUP BY syntax:
<code>
GROUP BY groupby_item {, groupby_item}*
</code>
DQL HAVING syntax:
<code>
HAVING conditional_expression
</code>
* GROUP BY and HAVING clauses can be used for dealing with aggregate functions
* Following aggregate functions are available on DQL: COUNT, MAX, MIN, AVG, SUM
Selecting alphabetically first user by name.
<code type="sql">
SELECT MIN(u.name) FROM User u
</code>
Selecting the sum of all Account amounts.
<code type="sql">
SELECT SUM(a.amount) FROM Account a
</code>
* Using an aggregate function in a statement containing no GROUP BY clause, results in grouping on all rows. In the example above we fetch all users and the number of phonenumbers they have.
<code type="sql">
SELECT u.*, COUNT(p.id) FROM User u, u.Phonenumber p GROUP BY u.id
</code>
* The HAVING clause can be used for narrowing the results using aggregate values. In the following example we fetch all users which have atleast 2 phonenumbers
<code type="sql">
SELECT u.* FROM User u, u.Phonenumber p HAVING COUNT(p.id) >= 2
</code>
<code type="php">
// retrieve all users and the phonenumber count for each user
$users = $conn->query("SELECT u.*, COUNT(p.id) count FROM User u, u.Phonenumber p GROUP BY u.id");
foreach($users as $user) {
print $user->name . ' has ' . $user->Phonenumber[0]->count . ' phonenumbers';
}
</code>

View File

@ -1,30 +0,0 @@
The INDEXBY keyword offers a way of mapping certain columns as collection / array keys. By default Doctrine indexes multiple elements to numerically indexed arrays / collections. The mapping starts from zero. In order to override this behaviour you need to use INDEXBY keyword as shown above:
<code type="php">
$q = new Doctrine_Query();
$q->from('User u INDEXBY u.name');
$users = $q->execute();
</code>
Now the users in $users collection are accessible through their names.
<code type="php">
print $user['jack daniels']->id;
</code>
The INDEXBY keyword can be applied to any given JOIN. This means that any given component can have each own indexing behaviour. In the following we use distinct indexing for both Users and Groups.
<code type="php">
$q = new Doctrine_Query();
$q->from('User u INDEXBY u.name')->innerJoin('u.Group g INDEXBY g.name');
$users = $q->execute();
</code>
Now lets print out the drinkers club's creation date.
<code type="php">
print $users['jack daniels']->Group['drinkers club']->createdAt;
</code>

View File

@ -1,45 +0,0 @@
Doctrine Query Language (DQL) is an Object Query Language created for helping users in complex object retrieval. You should always consider using DQL (or raw SQL) when retrieving relational data efficiently (eg. when fetching users and their phonenumbers).
When compared to using raw SQL, DQL has several benefits:
* From the start it has been designed to retrieve records(objects) not result set rows
* DQL understands relations so you don't have to type manually sql joins and join conditions
* DQL is portable on different databases
* DQL has some very complex built-in algorithms like (the record limit algorithm) which can help developer to efficiently retrieve objects
* It supports some functions that can save time when dealing with one-to-many, many-to-many relational data with conditional fetching.
If the power of DQL isn't enough, you should consider using the rawSql API for object population.
You may already be familiar with the following syntax:
<code type="php">
// DO NOT USE THE FOLLOWING CODE
// (uses many sql queries for object population)
$users = $conn->getTable('User')->findAll();
foreach($users as $user) {
print $user->name . ' has phonenumbers: ';
foreach($user->Phonenumber as $phonenumber) {
print $phonenumber . ' ';
}
}
</code>
However you should not use it. Below is the same behaviour implemented much more efficiently:
<code type="php">
// same thing implemented much more efficiently:
// (using only one sql query for object population)
$users = $conn->query('FROM User u LEFT JOIN u.Phonenumber p');
foreach($users as $user) {
print $user->name . ' has phonenumbers: ';
foreach($user->Phonenumber as $phonenumber) {
print $phonenumber . ' ';
}
}
</code>

View File

@ -1,77 +0,0 @@
DQL JOIN Syntax:
<code>
[[LEFT | INNER] JOIN <component_reference1>] [ON | WITH] <join_condition1> [INDEXBY] <map_condition1>,
[[LEFT | INNER] JOIN <component_reference2>] [ON | WITH] <join_condition2> [INDEXBY] <map_condition2>,
...
[[LEFT | INNER] JOIN <component_referenceN>] [ON | WITH] <join_conditionN> [INDEXBY] <map_conditionN>
</code>
DQL supports two kinds of joins INNER JOINs and LEFT JOINs. For each joined component, you can optionally specify an alias.
* The default join type is {{LEFT JOIN}}. This join can be indicated by the use of either {{LEFT JOIN}} clause or simply '{{,}}', hence the following queries are equal:
<code type="sql">
SELECT u.*, p.* FROM User u LEFT JOIN u.Phonenumber
SELECT u.*, p.* FROM User u, u.Phonenumber p
</code>
The recommended form is the first one.
* {{INNER JOIN}} produces an intersection between two specified components (that is, each and every record in the first component is joined to each and every record in the second component). So basically {{INNER JOIN}} can be used when you want to efficiently fetch for example all users which have one or more phonenumbers.
<code type="sql">
SELECT u.*, p.* FROM User u INNER JOIN u.Phonenumber p
</code>
By default DQL auto-adds the primary key join condition, so for DQL query:
<code type="sql">
SELECT u.id, p.id FROM User u LEFT JOIN u.Phonenumber
</code>
Would have a SQL equivalent:
<code type="sql">
SELECT u.id AS u__id, p.id AS p__id FROM User u LEFT JOIN Phonenumber p ON u.id = p.user_id
</code>
+++ ON keyword
If you want to override this behaviour and add your own custom join condition you can do it with the {{ON}} keyword. Consider the following DQL query:
<code type="sql">
SELECT u.id, p.id FROM User u LEFT JOIN u.Phonenumber ON u.id = 2
</code>
This query would be converted into SQL:
<code type="sql">
SELECT u.id AS u__id, p.id AS p__id FROM User u LEFT JOIN Phonenumber p ON u.id = 2
</code>
+++ WITH keyword
Most of the time you don't need to override the primary join condition, rather you may want to add some custom conditions. This can be achieved with the {{WITH}} keyword.
DQL:
<code type="sql">
SELECT u.id, p.id FROM User u LEFT JOIN u.Phonenumber WITH u.id = 2
</code>
SQL:
<code type="sql">
SELECT u.id AS u__id, p.id AS p__id FROM User u LEFT JOIN Phonenumber p ON u.id = p.user_id AND u.id = 2
</code>
The Doctrine_Query API offers two convenience methods for adding JOINS. These are called innerJoin() and leftJoin(), which usage should be quite intuitive as shown below:
<code type="php">
$q = new Doctrine_Query();
$q->from('User u')
->leftJoin('u.Group g')
->innerJoin('u.Phonenumber p WITH u.id > 3')
->leftJoin('u.Email e');
$users = $q->execute();
</code>

View File

@ -1,90 +0,0 @@
Propably the most complex feature DQL parser has to offer is its LIMIT clause parser. Not only does the DQL LIMIT clause parser take care of LIMIT database portability it is capable of limiting the number of records instead of rows by using complex query analysis and subqueries.
<code type="php">
// retrieve the first 20 users and all their associated phonenumbers
$users = $conn->query("SELECT u.*, p.* FROM User u, u.Phonenumber p LIMIT 20");
foreach($users as $user) {
print ' --- '.$user->name.' --- \n';
foreach($user->Phonenumber as $p) {
print $p->phonenumber.'\n';
}
}
</code>
+++ Driver portability
DQL LIMIT clause is portable on all supported databases. Special attention have been paid to following facts:
* Only Mysql, Pgsql and Sqlite implement LIMIT / OFFSET clauses natively
* In Oracle / Mssql / Firebird LIMIT / OFFSET clauses need to be emulated in driver specific way
* The limit-subquery-algorithm needs to execute to subquery separately in mysql, since mysql doesn't yet support LIMIT clause in subqueries
* Pgsql needs the order by fields to be preserved in SELECT clause, hence limit-subquery-algorithm needs to take this into consideration when pgsql driver is used
* Oracle only allows < 30 object identifiers (= table/column names/aliases), hence the limit subquery must use as short aliases as possible and it must avoid alias collisions with the main query.
+++ The limit-subquery-algorithm
The limit-subquery-algorithm is an algorithm that DQL parser uses internally when one-to-many / many-to-many relational data is being fetched simultaneously. This kind of special algorithm is needed for the LIMIT clause to limit the number of records instead of sql result set rows.
This behaviour can be overwritten using the configuration system (at global, connection or table level) using:
<code type="php">
$table->setAttribute(Doctrine::ATTR_QUERY_LIMIT, Doctrine::LIMIT_ROWS);
$table->setAttribute(Doctrine::ATTR_QUERY_LIMIT, Doctrine::LIMIT_RECORDS); // revert
</code>
In the following example we have users and phonenumbers with their relation being one-to-many. Now lets say we want fetch the first 20 users and all their related phonenumbers.
Now one might consider that adding a simple driver specific LIMIT 20 at the end of query would return the correct results. Thats wrong, since we you might get anything between 1-20 users as the first user might have 20 phonenumbers and then record set would consist of 20 rows.
DQL overcomes this problem with subqueries and with complex but efficient subquery analysis. In the next example we are going to fetch first 20 users and all their phonenumbers with single efficient query. Notice how the DQL parser is smart enough to use column aggregation inheritance even in the subquery and how it's smart enough to use different aliases for the tables in the subquery to avoid alias collisions.
DQL QUERY:
<code type="sql">
SELECT u.id, u.name, p.* FROM User u LEFT JOIN u.Phonenumber p LIMIT 20
</code>
SQL QUERY:
<code type="sql">
SELECT
e.id AS e__id,
e.name AS e__name,
p.id AS p__id,
p.phonenumber AS p__phonenumber,
p.entity_id AS p__entity_id
FROM entity e
LEFT JOIN phonenumber p ON e.id = p.entity_id
WHERE e.id IN (
SELECT DISTINCT e2.id
FROM entity e2
WHERE (e2.type = 0) LIMIT 20) AND (e.type = 0)
</code>
In the next example we are going to fetch first 20 users and all their phonenumbers and only those users that actually have phonenumbers with single efficient query, hence we use an INNER JOIN. Notice how the DQL parser is smart enough to use the INNER JOIN in the subquery.
DQL QUERY:
<code type="sql">
SELECT u.id, u.name, p.* FROM User u LEFT JOIN u.Phonenumber p LIMIT 20
</code>
SQL QUERY:
<code type="sql">
SELECT
e.id AS e__id,
e.name AS e__name,
p.id AS p__id,
p.phonenumber AS p__phonenumber,
p.entity_id AS p__entity_id
FROM entity e
LEFT JOIN phonenumber p ON e.id = p.entity_id
WHERE e.id IN (
SELECT DISTINCT e2.id
FROM entity e2
INNER JOIN phonenumber p2 ON e2.id = p2.entity_id
WHERE (e2.type = 0) LIMIT 20) AND (e.type = 0)
</code>

View File

@ -1,58 +0,0 @@
+++ Introduction
Record collections can be sorted efficiently at the database level using the ORDER BY clause.
Syntax:
<code>
[ORDER BY {ComponentAlias.columnName}
[ASC | DESC], ...]
</code>
Examples:
<code type="sql">
FROM User u LEFT JOIN u.Phonenumber p
ORDER BY u.name, p.phonenumber
FROM User u, u.Email e
ORDER BY e.address, u.id
</code>
In order to sort in reverse order you can add the DESC (descending) keyword to the name of the column in the ORDER BY clause that you are sorting by. The default is ascending order; this can be specified explicitly using the ASC keyword.
<code type="sql">
FROM User u LEFT JOIN u.Email e
ORDER BY e.address DESC, u.id ASC;
</code>
+++ Sorting by an aggregate value
In the following example we fetch all users and sort those users by the number of phonenumbers they have.
<code type="php">
$q = new Doctrine_Query();
$users = $q->select('u.*, COUNT(p.id) count')
->from('User u')
->innerJoin('u.Phonenumber p')
->orderby('count');
</code>
+++ Using random order
In the following example we use random in the ORDER BY clause in order to fetch random post.
<code type="php">
$q = new Doctrine_Query();
$posts = $q->select('p.*, RANDOM() rand')
->from('Post p')
->orderby('rand')
->limit(1)
->execute();
$randomPost = $posts[0];
</code>

View File

@ -1,86 +0,0 @@
{{SELECT}} statement syntax:
<code type="sql">
SELECT
[ALL | DISTINCT]
<select_expr>, ...
[FROM <components>
[WHERE <where_condition>]
[GROUP BY <groupby_expr>
[ASC | DESC], ... ]
[HAVING <where_condition>]
[ORDER BY <orderby_expr>
[ASC | DESC], ...]
[LIMIT <row_count> OFFSET <offset>}]
</code>
The {{SELECT}} statement is used for the retrieval of data from one or more components.
* Each {{select_expr}} indicates a column or an aggregate function value that you want to retrieve. There must be at least one {{select_expr}} in every {{SELECT}} statement.
<code type="sql">
SELECT a.name, a.amount FROM Account a
</code>
* An asterisk can be used for selecting all columns from given component. Even when using an asterisk the executed sql queries never actually use it (Doctrine converts asterisk to appropriate column names, hence leading to better performance on some databases).
<code type="sql">
SELECT a.* FROM Account a
</code>
* {{FROM}} clause {{components}} indicates the component or components from which to retrieve records.
<code type="sql">
SELECT a.* FROM Account a
SELECT u.*, p.*, g.* FROM User u LEFT JOIN u.Phonenumber p LEFT JOIN u.Group g
</code>
* The {{WHERE}} clause, if given, indicates the condition or conditions that the records must satisfy to be selected. {{where_condition}} is an expression that evaluates to true for each row to be selected. The statement selects all rows if there is no {{WHERE}} clause.
<code type="sql">
SELECT a.* FROM Account a WHERE a.amount > 2000
</code>
* In the {{WHERE}} clause, you can use any of the functions and operators that DQL supports, except for aggregate (summary) functions
* The {{HAVING}} clause can be used for narrowing the results with aggregate functions
<code type="sql">
SELECT u.* FROM User u LEFT JOIN u.Phonenumber p HAVING COUNT(p.id) > 3
</code>
* The {{ORDER BY}} clause can be used for sorting the results
<code type="sql">
SELECT u.* FROM User u ORDER BY u.name
</code>
* The {{LIMIT}} and {{OFFSET}} clauses can be used for efficiently limiting the number of records to a given {{row_count}}
<code type="sql">
SELECT u.* FROM User u LIMIT 20
</code>
+++ DISTINCT keyword
+++ Aggregate values
Aggregate value {{SELECT}} syntax:
<code type="php">
// SELECT u.*, COUNT(p.id) num_posts FROM User u, u.Posts p WHERE u.id = 1 GROUP BY u.id
$query = new Doctrine_Query();
$query->select('u.*, COUNT(p.id) num_posts')
->from('User u, u.Posts p')
->where('u.id = ?', 1)
->groupby('u.id');
$users = $query->execute();
echo $users->Posts[0]->num_posts . ' posts found';
</code>

View File

@ -1,8 +0,0 @@
+++ Introduction
+++ Comparisons using subqueries
+++ Conditional expressions
++++ ANY, IN and SOME
++++ ALL
++++ EXISTS and NOT EXISTS
+++ Correlated subqueries
+++ Subqueries in FROM clause

View File

@ -1,34 +0,0 @@
{{UPDATE}} statement syntax:
<code type="sql">
UPDATE //component_name//
SET //col_name1//=//expr1// [, //col_name2//=//expr2// ...]
[WHERE //where_condition//]
[ORDER BY ...]
[LIMIT //record_count//]
</code>
* The {{UPDATE}} statement updates columns of existing records in {{component_name}} with new values and returns the number of affected records.
* The {{SET}} clause indicates which columns to modify and the values they should be given.
* The optional {{WHERE}} clause specifies the conditions that identify which records to update. Without {{WHERE}} clause, all records are updated.
* The optional {{ORDER BY}} clause specifies the order in which the records are being updated.
* The {{LIMIT}} clause places a limit on the number of records that can be updated. You can use {{LIMIT row_count}} to restrict the scope of the {{UPDATE}}.
A {{LIMIT}} clause is a **rows-matched restriction** not a rows-changed restriction.
The statement stops as soon as it has found {{record_count}} rows that satisfy the {{WHERE}} clause, whether or not they actually were changed.
<code type="php">
$q = 'UPDATE Account SET amount = amount + 200 WHERE id > 200';
$rows = $this->conn->query($q);
// the same query using the query interface
$q = new Doctrine_Query();
$rows = $q->update('Account')
->set('amount', 'amount + 200')
->where('id > 200')
->execute();
print $rows; // the number of affected rows
</code>

Some files were not shown because too many files have changed in this diff Show More