diff --git a/manual/new/tmp/.tex b/manual/new/tmp/.tex
deleted file mode 100644
index 95fe490d1..000000000
--- a/manual/new/tmp/.tex
+++ /dev/null
@@ -1,6911 +0,0 @@
-\documentclass[11pt,a4paper]{report}
-\usepackage[latin1]{inputenc}
-\usepackage{fancybox}
-\usepackage[pdftex]{hyperref}
-\setcounter{tocdepth}{5}
-\setcounter{secnumdepth}{5}
-\pdfadjustspacing=1
- itle{Doctrine Manual}
-
-\begin{document}
-\maketitle
- ableofcontents\chapter{Getting started}
-\section{Requirements}
-Doctrine requires PHP >= 5.2. it doesn't require any external libraries. For database function call abstraction Doctrine uses PDO which is bundled with php by default.
-
-\section{Installation}
-The installation of doctrine is very easy. Just get the latest revision of Doctrine from http://doctrine.pengus.net/svn/trunk\footnote{http://doctrine.pengus.net/svn/trunk}.
-
-You need a SVN (Subversion) client for downloading Doctrine.
-
-In order to check out Doctrine in the current directory using the \textbf{svn} command line tool use the following code:
-
-\begin{verbatim}
-svn co http://doctrine.pengus.net/svn/trunk .
-\end{verbatim}
-
-If you do not have a SVN client, chose one from the list below. Find the \textbf{Checkout} option and enter http://doctrine.pengus.net/svn/trunk\footnote{http://doctrine.pengus.net/svn/trunk} in the \textbf{path} or \textbf{repository url} parameter. There is no need for a username or password to check out Doctrine.
-
-\begin{itemize}
-\item{TortoiseSVN\footnote{http://tortoisesvn.tigris.org/} a Windows application that integrates into Windows Explorer}
-\item{svnx\footnote{http://www.apple.com/downloads/macosx/development\_tools/svnx.html} a Mac OS X GUI svn application}
-\item{Eclipse has SVN integration through the subeclipse\footnote{http://subclipse.tigris.org/} plugin}
-\end{itemize}
-\subsection{Include and autoload}
-In order to use Doctrine in your project it must first be included.
-
-\begin{verbatim}
-require_once('path-to-doctrine/lib/Doctrine.php');
-\end{verbatim}
-
-Doctrine support Autoloading\footnote{http://www.php.net/autoload} for including files so that you do not have to include anything more then the base file. There are two different strategies that can be used to do this:
-
-If you do use the \textbf{\_\_autoload} function for your own logic you can use it.
-
-\begin{verbatim}
-function __autoload($class) {
- Doctrine::autoload($class);
-}
-\end{verbatim}
-
-If your project uses autoload and/or you have other libraries that use it you could use spl\_autoload\_register\footnote{http://www.php.net/manual/en/function.spl-autoload-register.php} to register more then one autoloading function.
-
-\begin{verbatim}
-spl_autoload_register(array('Doctrine', 'autoload'));
-\end{verbatim}
-
-\section{Compiling}
-Doctrine is quite big framework and usually dozens of files are being included on each request. This brings a lot of overhead. In fact these file operations are as time consuming as sending multiple queries to database server. The clean separation of class per file works well in developing environment, however when project goes commercial distribution the speed overcomes the clean separation of class per file -convention.
-
-Doctrine offers method called \texttt{compile()} to solve this issue. The compile method makes a single file of most used Doctrine components which can then be included on top of your script. By default the file is created into Doctrine root by the name \texttt{Doctrine.compiled.php}.
-
-Compiling is a method for making a single file of most used doctrine runtime components including the compiled file instead of multiple files (in worst cases dozens of files) can improve performance by an order of magnitude. In cases where this might fail, a \texttt{Doctrine\_Exception} is throw detailing the error.
-
-\begin{verbatim}
-Doctrine::compile();
-// on some other script:
-require_once('path_to_doctrine/Doctrine.compiled.php');
-\end{verbatim}
-
-\section{Starting new project}
-Doctrine\_Record is the basic component of every doctrine-based project. There should be atleast one Doctrine\_Record for each of your database tables. Doctrine\_Record follows the Active Record pattern\footnote{http://www.martinfowler.com/eaaCatalog/activeRecord.html}
-
-Doctrine always adds a primary key column named 'id' to tables that doesn't have any primary keys specified. Only thing you need to for creating database tables is defining a class which extends Doctrine\_Record and setting a setTableDefinition method with hasColumn() method calls and by exporting those classes.
-
-Lets say we want to create a database table called 'user' with columns id(primary key), name, username, password and created. Provided that you have already installed Doctrine these few lines of code are all you need:
-
-User.php :
-
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- // set 'user' table columns, note that
- // id column is auto-created as no primary key is specified
-
- $this->hasColumn('name', 'string',30);
- $this->hasColumn('username', 'string',20);
- $this->hasColumn('password', 'string',16);
- $this->hasColumn('created', 'integer',11);
- }
-}
-\end{verbatim}
-
-For exporting the user class into database we need a simple build script:
-
-\begin{verbatim}
-require_once('lib/Doctrine.php');
-require_once('User.php');
-
-spl_autoload_register(array('Doctrine', 'autoload'));
-
-$conn = Doctrine_Manager::connection('pgsql://user:pass@localhost/test');
-
-$conn->export->exportClasses(array('User'));
-\end{verbatim}
-
-We now have a user model that supports basic CRUD opperations!
-
-\section{Working with existing databases}
-\subsection{Introduction}
-A common case when looking for ORM tools like Doctrine is that the database and the code that access it is growing large/complex. A more substantial tool is needed then manual SQL code.
-
-Doctrine has support for generating Doctrine\_Record classes from your existing database. There is no need for you to manually write all the Doctrine\_Record classes for your domain model.
-
-\subsection{Making the first import}
-Let's consider we have a mysql database called test with a single table called 'file'.
-
-The file table has been created with the following sql statement:
-
-\begin{verbatim}
-CREATE TABLE file (
- id INT UNSIGNED AUTO_INCREMENT NOT NULL,
- name VARCHAR(150),
- size BIGINT,
- modified BIGINT,
- type VARCHAR(10),
- content TEXT,
- path TEXT,
- PRIMARY KEY(id))
-\end{verbatim}
-
-Now we would like to convert it into Doctrine record class. It can be achieved easily with the following code snippet:
-
-\begin{verbatim}
-require_once('lib/Doctrine.php');
-
-spl_autoload_register(array('Doctrine', 'autoload'));
-$conn = Doctrine_Manager::connection('mysql://root:dc34@localhost/test');
-
-// import method takes one parameter: the import directory (the directory where
-// the generated record files will be put in
-$conn->import->import('myrecords');
-\end{verbatim}
-
-That's it! Now there should be a file called File.php in your myrecords directory. The file should look like:
-
-\begin{verbatim}
-/**
- * This class has been auto-generated by the Doctrine ORM Framework
- * Created: Saturday 10th of February 2007 01:03:15 PM
- */
-class File extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('id', 'integer', 4, array('notnull' => true,
- 'primary' => true,
- 'unsigned' > true,
- 'autoincrement' => true));
- $this->hasColumn('name', 'string', 150);
- $this->hasColumn('size', 'integer', 8);
- $this->hasColumn('modified', 'integer', 8);
- $this->hasColumn('type', 'string', 10);
- $this->hasColumn('content', 'string', null);
- $this->hasColumn('path', 'string', null);
- }
- public function setUp()
- {
-
- }
-}
-\end{verbatim}
-
-\subsection{Import options}
-\section{Exporting classes}
-\subsection{Introduction}
-Doctrine supports exporting record classes into database. This means that based on the definitions given in your record definitions Doctrine will alter your database schema.
-
-Lets say we have a classes called User and Phonenumber with the following definitions:
-
-\begin{verbatim}
-// file User.php
-class User extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 20);
- }
- public function setUp()
- {
- $this->hasMany('Phonenumber', array('local' => 'id',
- 'foreign' => 'user_id'));
- }
-}
-// file Phonenumber.php
-class Phonenumber extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('phonenumber', 'string', 20);
- $this->hasColumn('user_id', 'integer');
- }
- public function setUp()
- {
- $this->hasOne('User', array('local' => 'user_id',
- 'foreign' => 'id',
- 'onDelete' => 'CASCADE'));
- }
-}
-\end{verbatim}
-
-Now lets say these classes are in directory 'models/'. We can make Doctrine to iterate through this directory and attach these classes into your database structure with the following script:
-
-\begin{verbatim}
-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('pgsql://user:pass@localhost/test');
-
-Doctrine::export('models');
-\end{verbatim}
-
-This would execute the following queries on mysql.
-
-\begin{verbatim}
-CREATE TABLE user (id BIGINT AUTO_INCREMENT, name VARCHAR(20), PRIMARY KEY(id), INDEX(id));
-CREATE TABLE phonenumber (id INT AUTO_INCREMENT, phonenumber VARCHAR(20), user_id BIGINT, PRIMARY KEY(id), INDEX(user_id));
-ALTER TABLE phonenumber ADD CONSTRAINT FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE;
-\end{verbatim}
-
-Pay attention to the following things:
-
-\renewcommand{\labelenumi}{\arabic{enumi}}
-\begin{enumerate}
-\item{The autoincrement primary key columns are auto-added since we didn't specify any primary key columns}
-\item{Doctrine auto-adds indexes to the referenced relation columns (this is needed in mysql)}
-\end{enumerate}
-\subsection{Getting export queries}
-There might be situations where you don't want to execute the export queries immediately rather you want to get the query strings and maybe attach them to a build.sql file. This can be easily achieved as follows:
-
-\begin{verbatim}
-require_once('path-to-doctrine/lib/Doctrine.php');
-
-spl_autoload_register(array('Doctrine', 'autoload'));
-
-$manager = Doctrine_Manager::getInstance();
-$conn = $manager->openConnection('pgsql://user:pass@localhost/test');
-
-$queries = Doctrine::exportSql('models');
-
-print_r($queries);
-\end{verbatim}
-
-Notice that the methods Doctrine::export() and Doctrine::exportSql() are just short cuts for Doctrine\_Export::export() and Doctrine\_Export::exportSql(). So the following code is equivalent with the previous example:
-
-\begin{verbatim}
-require_once('path-to-doctrine/lib/Doctrine.php');
-
-spl_autoload_register(array('Doctrine', 'autoload'));
-
-$manager = Doctrine_Manager::getInstance();
-$conn = $manager->openConnection('pgsql://user:pass@localhost/test');
-
-$queries = $conn->export->exportSql('models');
-
-print_r($queries);
-\end{verbatim}
-
-\subsection{Convenience methods}
-In the previous example we exported all record classes within a directory. Sometimes you may want to export specific classes. It can be achieved by giving exportClasses() an array of class names:
-
-\begin{verbatim}
-require_once('path-to-doctrine/lib/Doctrine.php');
-
-spl_autoload_register(array('Doctrine', 'autoload'));
-
-$manager = Doctrine_Manager::getInstance();
-$conn = $manager->openConnection('pgsql://user:pass@localhost/test');
-
-$conn->export->exportClasses(array('User', 'Phonenumber'));
-\end{verbatim}
-
-Consider the same situation and you want to get the array of sql queries needed to perform the exporting. It can be achieved with Doctrine\_Export::exportClassesSql().
-
-\subsection{Export options}
-\begin{verbatim}
-// export everything, table definitions and constraints
-
-$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL);
-
-// export classes without constraints
-
-$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_TABLES ^
- Doctrine::EXPORT_CONSTRAINTS);
-
-// turn off exporting
-
-$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_NONE);
-\end{verbatim}
-
-\chapter{Connection management}
-\section{DSN, the Data Source Name}
-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 Phplookup: NI.
-
-The DSN consists in the following parts:
-
-\begin{tabular}{|l|l|}
-\hline
-DSN part & Description\\
-\hline
-phptype & Database backend used in PHP (i.e. mysql , pgsql etc.)\\
-\hline
-dbsyntax & Database used with regards to SQL syntax etc.\\
-\hline
-protocol & Communication protocol to use ( i.e. tcp, unix etc.)\\
-\hline
-hostspec & Host specification (hostname[:port])\\
-\hline
-database & Database to use on the DBMS server\\
-\hline
-username & User name for login\\
-\hline
-password & Password for login\\
-\hline
-proto\_opts & Maybe used with protocol\\
-\hline
-option & Additional connection options in URI query string format. Options are separated by ampersand (\&). The Following table shows a non complete list of options:\\
-\hline
-\end{tabular}
-\textbf{List of options}
-
-\begin{tabular}{|l|l|}
-\hline
-Name & Description\\
-\hline
-charset & Some backends support setting the client charset.\\
-\hline
-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.\\
-\hline
-\end{tabular}
-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:
-
-\begin{verbatim}
-phptype(dbsyntax)://username:password@protocol+hostspec/database?option=value
-\end{verbatim}
-
-Most variations are allowed:
-
-\begin{verbatim}
-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
-\end{verbatim}
-
-The currently supported database backends are:
-
-\begin{tabular}{|l|l|}
-\hline
-Driver name & Supported databases\\
-\hline
-fbsql & FrontBase\\
-\hline
-ibase & InterBase / Firebird (requires PHP 5)\\
-\hline
-mssql & Microsoft SQL Server (NOT for Sybase. Compile PHP --with-mssql)\\
-\hline
-mysql & MySQL\\
-\hline
-mysqli & MySQL (supports new authentication protocol) (requires PHP 5)\\
-\hline
-oci8 & Oracle 7/8/9/10\\
-\hline
-pgsql & PostgreSQL\\
-\hline
-querysim & QuerySim\\
-\hline
-sqlite & SQLite 2\\
-\hline
-\end{tabular}
-A second DSN format supported is
-
-\begin{verbatim}
-phptype(syntax)://user:pass@protocol(proto_opts)/database
-\end{verbatim}
-
-If your database, option values, username or password contain characters used to delineate DSN parts, you can escape them via URI hex encodings:
-
-\begin{tabular}{|l|l|}
-\hline
-Character & Hex Code\\
-\hline
-: & \%3a\\
-\hline
-/ & \%2f\\
-\hline
-@ & \%40\\
-\hline
-+ & \%2b\\
-\hline
-( & \%28\\
-\hline
-) & \%29\\
-\hline
-? & \%3f\\
-\hline
-= & \%3d\\
-\hline
-\& & \%26\\
-\hline
-\end{tabular}
-Warning\newline
-Please note, that some features may be not supported by all database backends.
-
-\subsection{Examples}
-\textbf{Example 1.} Connect to database through a socket
-
-\begin{verbatim}
-mysql://user@unix(/path/to/socket)/pear
-\end{verbatim}
-
-\textbf{Example 2.} Connect to database on a non standard port
-
-\begin{verbatim}
-pgsql://user:pass@tcp(localhost:5555)/pear
-\end{verbatim}
-
-\textbf{Example 3.} Connect to SQLite on a Unix machine using options
-
-\begin{verbatim}
-sqlite:////full/unix/path/to/file.db?mode=0666
-\end{verbatim}
-
-\textbf{Example 4.} Connect to SQLite on a Windows machine using options
-
-\begin{verbatim}
-sqlite:///c:/full/windows/path/to/file.db?mode=0666
-\end{verbatim}
-
-\textbf{Example 5.} Connect to MySQLi using SSL
-
-\begin{verbatim}
-mysqli://user:pass@localhost/pear?key=client-key.pem&cert=client-cert.pem
-\end{verbatim}
-
-\section{Opening a new connection}
-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:
-
-\begin{verbatim}
-$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();
-}
-\end{verbatim}
-
-If your database extension isn't supported by PDO you can use special \texttt{Doctrine\_Adapter} class (if availible). The following example uses db2 adapter:
-
-\begin{verbatim}
-$dsn = 'db2:dbname=testdb;host=127.0.0.1';
-$user = 'dbuser';
-$password = 'dbpass';
-
-try {
- $dbh = Doctrine_Adapter::connect($dsn, $user, $password);
-} catch (PDOException $e) {
- echo 'Connection failed: ' . $e->getMessage();
-}
-\end{verbatim}
-
-The next step is opening a new \texttt{Doctrine\_Connection}.
-
-\begin{verbatim}
-$conn = Doctrine_Manager::connection($dbh);
-\end{verbatim}
-
-\section{Lazy-connecting to database}
-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.
-
-\begin{verbatim}
-$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');
-\end{verbatim}
-
-\section{Managing connections}
-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 \texttt{openConnection()} second argument as an optional connection alias.
-
-\begin{verbatim}
-// Doctrine_Manager controls all the connections
-
-$manager = Doctrine_Manager::getInstance();
-
-// open first connection
-
-$conn = $manager->openConnection(new PDO('dsn','username','password'), 'connection 1');
-\end{verbatim}
-
-For convenience \texttt{Doctrine\_Manager} provides static method \texttt{connection()} which opens new connection when arguments are given to it and returns the current connection when no arguments have been speficied.
-
-\begin{verbatim}
-// open first connection
-
-$conn = Doctrine_Manager::connection(new PDO('dsn','username','password'), 'connection 1');
-
-$conn2 = Doctrine_Manager::connection();
-
-// $conn2 == $conn
-\end{verbatim}
-
-The current connection is the lastly opened connection.
-
-\begin{verbatim}
-// open second connection
-
-$conn2 = $manager->openConnection(new PDO('dsn2','username2','password2'), 'connection 2');
-
-$manager->getCurrentConnection(); // $conn2
-\end{verbatim}
-
-You can change the current connection by calling \texttt{setCurrentConnection()}.
-
-\begin{verbatim}
-$manager->setCurrentConnection('connection 1');
-
-$manager->getCurrentConnection(); // $conn
-\end{verbatim}
-
-You can iterate over the opened connection by simple passing the manager object to foreach clause. This is possible since \texttt{Doctrine\_Manager} implements special \texttt{IteratorAggregate} interface.
-
-\begin{verbatim}
-// iterating through connections
-
-foreach($manager as $conn) {
-
-}
-\end{verbatim}
-
-\section{Connection-component binding}
-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.
-
-\begin{verbatim}
-$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();
-\end{verbatim}
-
-\chapter{Object relational mapping}
-\section{Introduction}
-Doctrine\_Record::hasColumn() takes 4 arguments:
-
-\renewcommand{\labelenumi}{\arabic{enumi}}
-\begin{enumerate}
-\item{\textbf{column name}}
-\item{\textbf{column type}}
-\item{\textbf{column length}}
-\item{\textbf{column constraints and validators}}
-\end{enumerate}
-\begin{verbatim}
-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
- )
- );
-
- $this->hasColumn('address2', // name of the column
- 'string', // column type
- '200', // column length
- // validators / constraints without arguments can be
- // specified also as as string with | separator
- 'notblank|email'
- );
-
- // Doctrine even supports the following format for
- // validators / constraints which have no arguments:
-
- $this->hasColumn('address3', // name of the column
- 'string', // column type
- '200', // column length
- array('notblank', 'email')
- );
- }
-}
-\end{verbatim}
-
-Note that validators / column constraints and the column length fields are optional. The length may be omitted by using \textbf{null} for the length argument, allowing doctrine to use a default length and permitting a fourth argument for validation or column constraints.
-
-\section{Table and class naming}
-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:
-
-\begin{itemize}
-\item{Use \texttt{CamelCase} naming}
-\item{Underscores are allowed}
-\item{The first letter must be capitalized}
-\item{The class name cannot be one of the following (these keywords are reserved in DQL API):}
-\begin{itemize}
-\item{\texttt{ALL}, \texttt{AND}, \texttt{ANY}, \texttt{AS}, \texttt{ASC}, \texttt{AVG}, \texttt{BETWEEN}, \texttt{BIT\_LENGTH}, \texttt{BY}, \texttt{CHARACTER\_LENGTH}, \texttt{CHAR\_LENGTH}, \texttt{COUNT}, \texttt{CURRENT\_DATE}, \texttt{CURRENT\_TIME}, \texttt{CURRENT\_TIMESTAMP}, \texttt{DELETE}, \texttt{DESC}, \texttt{DISTINCT}, \texttt{EMPTY}, \texttt{EXISTS}, \texttt{FALSE}, \texttt{FETCH}, \texttt{FROM}, \texttt{GROUP}, \texttt{HAVING}, \texttt{IN}, \texttt{INNER}, \texttt{IS}, \texttt{JOIN}, \texttt{LEFT}, \texttt{LIKE}, \texttt{LOWER}, \texttt{MAX}, \texttt{MEMBER}, \texttt{MIN}, \texttt{MOD}, \texttt{NEW}, \texttt{NOT}, \texttt{NULL}, \texttt{OBJECT}, \texttt{OF}, \texttt{OR}, \texttt{ORDER}, \texttt{OUTER}, \texttt{POSITION}, \texttt{SELECT}, \texttt{SOME}, \texttt{SUM}, \texttt{TRIM}, \texttt{TRUE}, \texttt{UNKNOWN}, \texttt{UPDATE}, \texttt{UPPER} and \texttt{WHERE}.}
-\end{itemize}
-\end{itemize}
-\textbf{Example:} \texttt{My\_PerfectClass}
-
-If you need to use a different naming schema, you can override this using the \texttt{setTableName()} method in the \texttt{setTableDefinition()} method.
-
-\section{Table options}
-Doctrine offers various table options. All table options can be set via \texttt{Doctrine\_Record::option(\$optionName, \$value)}.
-
-For example if you are using MySQL and want to use INNODB tables it can be done as follows:
-
-\begin{verbatim}
-class MyInnoDbRecord extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string');
-
- $this->option('type', 'INNODB');
- }
-}
-\end{verbatim}
-
-In the following example we set the collate and character set options:
-
-\begin{verbatim}
-class MyCustomOptionRecord extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string');
-
- $this->option('collate', 'utf8_unicode_ci');
- $this->option('charset', 'utf8');
- }
-}
-\end{verbatim}
-
-\section{Columns}
-\subsection{Column naming}
-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 \ldots" 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.
-
-\subsection{Column aliases}
-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.
-
-\begin{verbatim}
-class Book extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('bookName as name', 'string');
- }
-}
-$book = new Book();
-$book->name = 'Some book';
-$book->save();
-\end{verbatim}
-
-\subsection{Default values}
-Doctrine supports default values for all data types. When default value is attached to a record column this means two of things.\newline
-First this value is attached to every newly created Record.
-
-\begin{verbatim}
-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
-\end{verbatim}
-
-Also when exporting record class to database DEFAULT \textsl{value} is attached to column definition statement.
-
-\subsection{Data types}
-\subsubsection{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.
-
-\subsubsection{Type modifiers}
-Within the Doctrine API there are a few modifiers that have been designed to aid in optimal table design. These are:
-
-\begin{itemize}
-\item{The notnull modifiers}
-\item{The length modifiers}
-\item{The default modifiers}
-\item{unsigned modifiers for some field definitions, although not all DBMS's support this modifier for integer field types.}
-\item{zerofill modifiers (not supported by all drivers)}
-\item{collation modifiers (not supported by all drivers)}
-\item{fixed length modifiers for some field definitions.}
-\end{itemize}
-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)
-
-\begin{verbatim}
-'sometime' = array(
- 'type' => 'time',
- 'default' => '12:34:05',
- 'notnull' => true,
-),
-\end{verbatim}
-
-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!
-
-\begin{verbatim}
-'sometext' = array(
- 'type' => 'text',
- 'length' => 12,
-),
-\end{verbatim}
-
-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.
-
-\subsubsection{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.
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('booltest', 'boolean');
- }
-}
-\end{verbatim}
-
-\subsubsection{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.
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('integertest', 'integer', 4, array('unsigned' => true));
- }
-}
-\end{verbatim}
-
-\subsubsection{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.
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('floattest', 'float');
- }
-}
-\end{verbatim}
-
-\subsubsection{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.
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('stringtest', 'string', 200, array('fixed' => true));
- }
-}
-\end{verbatim}
-
-\subsubsection{Array}
-This is the same as 'array' type in PHP.
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('arraytest', 'array', 10000);
- }
-}
-\end{verbatim}
-
-\subsubsection{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.
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('objecttest', 'object');
- }
-}
-\end{verbatim}
-
-\subsubsection{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"
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('blobtest', 'blob');
- }
-}
-\end{verbatim}
-
-\subsubsection{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"
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('clobtest', 'clob');
- }
-}
-\end{verbatim}
-
-\subsubsection{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
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('timestamptest', 'timestamp');
- }
-}
-\end{verbatim}
-
-\subsubsection{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.
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('timetest', 'time');
- }
-}
-\end{verbatim}
-
-\subsubsection{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.
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('datetest', 'date');
- }
-}
-\end{verbatim}
-
-\subsubsection{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).
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('enumtest', 'enum', 4,
- array(
- 'values' => array(
- 'php',
- 'java',
- 'python'
- )
- )
- );
- }
-}
-\end{verbatim}
-
-\subsubsection{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.
-
-\begin{verbatim}
-class Test extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('gziptest', 'gzip');
- }
-}
-\end{verbatim}
-
-\subsection{About type conversion}
-\section{Constraints and validators}
-\subsection{Introduction}
-\textsl{From PostgreSQL Documentation\footnote{http://www.postgresql.org/docs/8.2/static/ddl-constraints.html}:}
-
-\begin{quote}
-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.\end{quote}
-
-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.
-
-\subsection{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 \texttt{name}. This means that the specified column doesn't accept null values.
-
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 200, array('notnull' => true,
- 'primary' => true));
- }
-}
-\end{verbatim}
-
-When this class gets exported to database the following SQL statement would get executed (in MySQL):
-
-\begin{verbatim}
-CREATE TABLE user (name VARCHAR(200) NOT NULL, PRIMARY KEY(name))
-\end{verbatim}
-
-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 \texttt{Doctrine\_Validator\_Exception} is raised.
-
-\subsection{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 \texttt{name}.
-
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 200, array('unique' => true));
- }
-}
-\end{verbatim}
-
-\begin{quote}
-\begin{quote}
-Note: You should only use unique constraints for other than primary key columns. Primary key columns are always unique.\end{quote}
-
-\end{quote}
-
-The following definition adds a unique constraint for columns \texttt{name} and \texttt{age}.
-
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 200);
- $this->hasColumn('age', 'integer', 2);
-
- $this->unique(array('name', 'age'));
- }
-}
-\end{verbatim}
-
-\subsection{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:
-
-\begin{verbatim}
-class Product extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('id', 'integer', 4, 'primary');
- $this->hasColumn('price', 'decimal', 18, array('min' => 0));
- }
-}
-\end{verbatim}
-
-When exported the given class definition would execute the following statement (in pgsql):
-
-\begin{verbatim}
-CREATE TABLE product (
- id INTEGER,
- price NUMERIC,
- PRIMARY KEY(id),
- CHECK (price >= 0))
-\end{verbatim}
-
-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.
-
-\begin{verbatim}
-class Product extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('id', 'integer', 4, 'primary');
- $this->hasColumn('price', 'decimal', 18, array('min' => 0, 'max' => 1000000));
- }
-}
-\end{verbatim}
-
-Generates (in pgsql):
-
-\begin{verbatim}
-CREATE TABLE product (
- id INTEGER,
- price NUMERIC,
- PRIMARY KEY(id),
- CHECK (price >= 0),
- CHECK (price <= 1000000))
-\end{verbatim}
-
-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.
-
-\begin{verbatim}
-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');
- }
-}
-\end{verbatim}
-
-Generates (in pgsql):
-
-\begin{verbatim}
-CREATE TABLE product (
- id INTEGER,
- price NUMERIC,
- PRIMARY KEY(id),
- CHECK (price >= 0),
- CHECK (price <= 1000000),
- CHECK (price > discounted_price))
-\end{verbatim}
-
-\begin{quote}
-NOTE: some databases don't support CHECK constraints. When this is the case Doctrine simple skips the creation of check constraints.\end{quote}
-
-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.
-
-\section{Record identifiers}
-\subsection{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 \texttt{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.
-
-\subsection{Natural}
-Natural identifier is a property or combination of properties that is unique and non-null. The use of natural identifiers is encouraged.
-
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 200, array('primary' => true));
- }
-}
-\end{verbatim}
-
-\subsection{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 \texttt{id} for your autoinc primary key. It can be specified as follows:
-
-\begin{verbatim}
-class User extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn('uid', 'integer', 20, array('primary' => true, 'autoincrement' => true));
-
- }
-}
-\end{verbatim}
-
-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):
-
-\begin{verbatim}
-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'
-\end{verbatim}
-
-Now remember sql JOINS are always expensive and here we are using two of those. When using natural identifiers the query would look like:
-
-\begin{verbatim}
-SELECT p.*
- FROM Permission p
- LEFT JOIN RolePermission rp ON rp.permission_name = p.name
- WHERE rp.role_name = 'Admin'
-\end{verbatim}
-
-Thats -1 JOIN !
-
-\subsection{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.
-
-\begin{verbatim}
-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));
- }
-}
-\end{verbatim}
-
-\subsection{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 \texttt{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:
-
-\begin{verbatim}
-class Book extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('id', 'integer', null, array('primary' => true, 'sequence' => true));
- $this->hasColumn('name', 'string');
- }
-}
-\end{verbatim}
-
-By default Doctrine uses the following format for sequence tables \texttt{[tablename]\_seq}. If you wish to change this you can use the following piece of code to change the formatting:
-
-\begin{verbatim}
-$manager = Doctrine_Manager::getInstance();
-$manager->setAttribute(Doctrine::ATTR_SEQNAME_FORMAT, '%s_my_seq');
-\end{verbatim}
-
-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:
-
-\begin{verbatim}
-$manager = Doctrine_Manager::getInstance();
-$manager->setAttribute(Doctrine::ATTR_SEQCOL_NAME, 'my_seq_column');
-\end{verbatim}
-
-In the following example we do not wish to change global configuration we just want to make the \texttt{id} column to use sequence table called \texttt{book\_sequence}. It can be done as follows:
-
-\begin{verbatim}
-class Book extends Doctrine_Record {
- public function setTableDefinition()
- {
- $this->hasColumn('id', 'integer', null, array('primary', 'sequence' => 'book_sequence'));
- $this->hasColumn('name', 'string');
- }
-}
-\end{verbatim}
-
-Here we take the preceding example a little further: we want to have a custom sequence column. Here it goes:
-
-\begin{verbatim}
-class Book extends Doctrine_Record {
- public function setTableDefinition()
- {
- $this->hasColumn('id', 'integer', null, array('primary', 'sequence' => array('book_sequence', 'sequence')));
- $this->hasColumn('name', 'string');
- }
-}
-\end{verbatim}
-
-\section{Indexes}
-\subsection{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 \textbf{always} use indexes for the fields that are used in SQL where conditions.
-
-\subsection{Adding indexes}
-You can add indexes by simple calling \texttt{Doctrine\_Record::index('indexName', \$definition)} where \texttt{\$definition} is the definition array.
-
-An example of adding a simple index to field called \texttt{name}:
-
-\begin{verbatim}
-class IndexTest extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string');
-
- $this->index('myindex', array('fields' => 'name');
- }
-}
-\end{verbatim}
-
-An example of adding a multi-column index to field called \texttt{name}:
-
-\begin{verbatim}
-class MultiColumnIndexTest extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string');
- $this->hasColumn('code', 'string');
-
- $this->index('myindex', array('fields' => array('name', 'code')));
- }
-}
-\end{verbatim}
-
-An example of adding a multiple indexes on same table:
-
-\begin{verbatim}
-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'));
- }
-}
-\end{verbatim}
-
-\subsection{Index options}
-Doctrine offers many index options, some of them being db-specific. Here is a full list of available options:
-
-\begin{verbatim}
-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
-\end{verbatim}
-
-\begin{verbatim}
-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',
- ));
- }
-}
-\end{verbatim}
-
-\subsection{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'.
-
-\begin{verbatim}
-class Article
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string');
- $this->hasColumn('content', 'string');
-
- $this->index('content', array('fields' => 'content',
- 'type' => 'fulltext'));
- }
-}
-\end{verbatim}
-
-\section{Hierarchical data}
-\subsection{Introduction}
-\subsubsection{About}
-Most users at one time or another have dealt with hierarchical data in a SQL database and no doubt learned that the management of hierarchical data is not what a relational database is intended for. The tables of a relational database are not hierarchical (like XML), but are simply a flat list. Hierarchical data has a parent-child relationship that is not naturally represented in a relational database table.
-
-For our purposes, hierarchical data is a collection of data where each item has a single parent and zero or more children (with the exception of the root item, which has no parent). Hierarchical data can be found in a variety of database applications, including forum and mailing list threads, business organization charts, content management categories, and product categories.
-
-In a hierarchical data model, data is organized into a tree-like structure. The tree structure allows repeating information using parent/child relationships. For an explanation of the tree data structure, see here\footnote{http://en.wikipedia.org/wiki/Tree\_data\_structure}.
-
-There are three major approaches to managing tree structures in relational databases, these are:
-
-\begin{itemize}
-\item{the adjacency list model}
-\item{the nested set model (otherwise known as the modified pre-order tree traversal algorithm)}
-\item{materialized path model}
-\end{itemize}
-These are explained in more detail in the following chapters, or see
-
-\begin{itemize}
-\item{http://www.dbazine.com/oracle/or-articles/tropashko4\footnote{http://www.dbazine.com/oracle/or-articles/tropashko4}}
-\item{http://dev.mysql.com/tech-resources/articles/hierarchical-data.html\footnote{http://dev.mysql.com/tech-resources/articles/hierarchical-data.html}}
-\end{itemize}
-\subsubsection{Setting up}
-Managing tree structures in doctrine is easy. Doctrine currently fully supports Nested Set, and plans to support the other implementations soon. To set your model to act as a tree, simply add the code below to your models table definition.
-
-Now that Doctrine knows that this model acts as a tree, it will automatically add any required columns for your chosen implementation, so you do not need to set any tree specific columns within your table definition.
-
-Doctrine has standard interface's for managing tree's, that are used by all the implementations. Every record in the table represents a node within the tree (the table), so doctrine provides two interfaces, Tree and Node.
-
-\begin{verbatim}
-class Menu extends Doctrine_Record {
- public function setTableDefinition() {
-
- $this->setTableName('menu');
-
- // add this your table definition to set the table as NestedSet tree implementation
- // $implName is 'NestedSet' or 'AdjacencyList' or 'MaterializedPath'
- // $options is an assoc array of options, see implementation docs for options
- $this->option('treeImpl', $implName);
- $this->option('treeOptions', $options);
-
- // you do not need to add any columns specific to the nested set implementation,
- // these are added for you
- $this->hasColumn("name","string",30);
-
- }
-
- // this __toString() function is used to get the name for the path, see node::getPath()
- public function __toString() {
- return $this->get('name');
- }
-}
-\end{verbatim}
-
-\subsubsection{Node interface}
-The node interface, for inserting and manipulating nodes within the tree, is accessed on a record level. A full implementation of this interface will be as follows:
-
-\begin{verbatim}
-interface Doctrine_Node_Interface {
-
- /**
- * insert node into tree
- */
- public function insertAsParentOf(Doctrine_Record $dest);
-
- public function insertAsPrevSiblingOf(Doctrine_Record $dest);
-
- public function insertAsNextSiblingOf(Doctrine_Record $dest);
-
- public function insertAsFirstChildOf(Doctrine_Record $dest);
-
- public function insertAsLastChildOf(Doctrine_Record $dest);
-
- public function addChild(Doctrine_Record $record);
-
- /**
- * moves node (if has children, moves branch)
- *
- */
- public function moveAsPrevSiblingOf(Doctrine_Record $dest);
-
- public function moveAsNextSiblingOf(Doctrine_Record $dest);
-
- public function moveAsFirstChildOf(Doctrine_Record $dest);
-
- public function moveAsLastChildOf(Doctrine_Record $dest);
-
- /**
- * node information
- */
- public function getPrevSibling();
-
- public function getNextSibling();
-
- public function getSiblings($includeNode = false);
-
- public function getFirstChild();
-
- public function getLastChild();
-
- public function getChildren();
-
- public function getDescendants();
-
- public function getParent();
-
- public function getAncestors();
-
- public function getPath($seperator = ' > ', $includeNode = false);
-
- public function getLevel();
-
- public function getNumberChildren();
-
- public function getNumberDescendants();
-
- /**
- * node checks
- */
- public function hasPrevSibling();
-
- public function hasNextSibling();
-
- public function hasChildren();
-
- public function hasParent();
-
- public function isLeaf();
-
- public function isRoot();
-
- public function isEqualTo(Doctrine_Record $subj);
-
- public function isDescendantOf(Doctrine_Record $subj);
-
- public function isDescendantOfOrEqualTo(Doctrine_Record $subj);
-
- public function isValidNode();
-
- /**
- * deletes node and it's descendants
- */
- public function delete();
-}
-
-// if your model acts as tree you can retrieve the associated node object as follows
-$record = $manager->getTable('Model')->find($pk);
-$nodeObj = $record->getNode();
-\end{verbatim}
-
-\subsubsection{Tree interface}
-The tree interface, for creating and accessing the tree, is accessed on a table level. A full implementation of this interface would be as follows:
-
-\begin{verbatim}
-interface Doctrine_Tree_Interface {
-
- /**
- * creates root node from given record or from a new record
- */
- public function createRoot(Doctrine_Record $record = null);
-
- /**
- * returns root node
- */
- public function findRoot($root_id = 1);
-
- /**
- * optimised method to returns iterator for traversal of the entire tree
- * from root
- */
- public function fetchTree($options = array());
-
- /**
- * optimised method that returns iterator for traversal of the tree from the
- * given record's primary key
- */
- public function fetchBranch($pk, $options = array());
-}
-
-// if your model acts as tree you can retrieve the associated tree object as follows
-$treeObj = $manager->getTable('Model')->getTree();
-\end{verbatim}
-
-\subsubsection{Traversing or Walking Trees}
-You can traverse a Tree in different ways, please see here for more information http://en.wikipedia.org/wiki/Tree\_traversal\footnote{http://en.wikipedia.org/wiki/Tree\_traversal}.
-
-The most common way of traversing a tree is Pre Order Traversal as explained in the link above, this is also what is known as walking the tree, this is the default approach when traversing a tree in Doctrine, however Doctrine does plan to provide support for Post and Level Order Traversal (not currently implemented)
-
-\begin{verbatim}
-/*
- * traverse the entire tree from root
- */
-$root = $manager->getTable('Model')->getTree()->fetchRoot();
-if($root->exists())
-{
- $tree = $root->traverse();
- while($node = $tree->next())
- {
- // output your tree here
- }
-}
-
-// or the optimised approach using tree::fetchTree
-$tree = $manager->getTable('Model')->getTree()->fetchTree();
-while($node = $tree->next())
-{
- // output tree here
-}
-
-/*
- * traverse a branch of the tree
- */
-$record = $manager->getTable('Model')->find($pk);
-if($record->exists())
-{
- $branch = $record->traverse();
- while($node = $branch->next())
- {
- // output your tree here
- }
-}
-
-// or the optimised approach
-$branch = $manager->getTable('Model')->getTree()->fetchBranch($pk);
-while($node = $branch->traverse())
-{
- // output your tree here
-}
-\end{verbatim}
-
-\subsubsection{Read me}
-If performing batch tree manipulation tasks, then remember to refresh your records (see record::refresh()), as any transformations of the tree are likely to affect all instances of records that you have in your scope.
-
-You can save an already existing node using record::save() without affecting it's position within the tree. Remember to never set the tree specific record attributes manually.
-
-If you are inserting or moving a node within the tree, you must use the appropriate node method. Note: you do not need to save a record once you have inserted it or moved it within the tree, any other changes to your record will also be saved within these operations. You cannot save a new record without inserting it into the tree.
-
-If you wish to delete a record, you MUST delete the node and not the record, using \$record->deleteNode() or \$record->getNode()->delete(). Deleting a node, will by default delete all its descendants. if you delete a record without using the node::delete() method you tree is likely to become corrupt (and fall down)!
-
-The difference between descendants and children is that descendants include children of children whereas children are direct descendants of their parent (real children not gran children and great gran children etc etc).
-
-\subsection{Adjacency list}
-\subsection{Nested set}
-\subsubsection{Introduction}
-Basically Nested Set is optimized for traversing trees, as this can be done with minimal queries, however updating the tree can be costly as it will affect all rows within the table.
-
-For more information, read here:
-
-\begin{itemize}
-\item{http://www.sitepoint.com/article/hierarchical-data-database/2\footnote{http://www.sitepoint.com/article/hierarchical-data-database/2}}
-\item{http://dev.mysql.com/tech-resources/articles/hierarchical-data.html\footnote{http://dev.mysql.com/tech-resources/articles/hierarchical-data.html}}
-\end{itemize}
-\subsubsection{Setting up}
-To set up your model as Nested Set, you must add the following code to your model's table definition.
-
-\begin{verbatim}
-class Menu extends Doctrine_Record {
- public function setTableDefinition() {
-
- $this->setTableName('menu');
-
- // add this your table definition to set the table as NestedSet tree
- // implementation
- $this->option('treeImpl', 'NestedSet');
- $this->option('treeOptions', array());
-
- // you do not need to add any columns specific to the nested set
- // implementation, these are added for you
- $this->hasColumn("name","string",30);
-
- }
-
- // this __toString() function is used to get the name for the path, see
- // node::getPath()
- public function __toString() {
- return $this->get('name');
- }
-}
-\end{verbatim}
-
-\subsubsection{Tree options}
-The nested implementation can be configured to allow your table to have multiple root nodes, and therefore multiple trees within the same table.
-
-The example below shows how to setup and use multiple roots based upon the set up above:
-
-\begin{verbatim}
-//use these options in the setTableDefinition
-$options = array('hasManyRoots' => true, // enable many roots
- 'rootColumnName' => 'root_id'); // set root column name, defaults to 'root_id'
-
-// To create new root nodes, if you have manually set the root_id, then it will be used
-// otherwise it will automatically use the next available root id
-$root = new Menu();
-$root->set('name', 'root');
-
-// insert first root, will auto be assigned root_id = 1
-$manager->getTable('Menu')->getTree()->createRoot($root);
-
-$another_root = new Menu();
-$another_root->set('name', 'another root');
-
-// insert another root, will auto be assigned root_id = 2
-$manager->getTable('Menu')->getTree()->createRoot($another_root);
-
-// fetching a specifc root
-$root = $manager->getTable('Menu')->getTree()->fetchRoot(1);
-$another_root = $manager->getTable('Menu')->getTree()->fetchRoot(2);
-
-// fetching all roots
-$roots = $manager->getTable('Menu')->getTree()->fetchRoots();
-\end{verbatim}
-
-\subsubsection{Node support}
-The nested set implementation fully supports the full Doctrine node interface
-
-\subsubsection{Tree support}
-The nested set implementation fully supports the full Doctrine tree interface
-
-\subsubsection{Read me}
-The most effective way to traverse a tree from the root node, is to use the \texttt{tree::fetchTree()} method. It will by default include the root node in the tree and will return an iterator to traverse the tree.
-
-To traverse a tree from a given node, it will normally cost 3 queries, one to fetch the starting node, one to fetch the branch from this node, and one to determine the level of the start node, the traversal algorithm with then determine the level of each subsequent node for you.
-
-\subsection{Materialized path}
-Not yet implemented
-
-\subsection{Examples}
-This is an example to show how you would set up and use the doctrine tree interface with the \texttt{NestedSet} implementation (currently the most comprehensively supported by Doctrine)
-
-\begin{verbatim}
-require_once("path/to/Doctrine.php");
-
-function __autoload($classname) {
-
- return Doctrine::autoload($classname);
-
-}
-
-// define our tree
-class Menu extends Doctrine_Record {
- public function setTableDefinition() {
-
- $this->setTableName('menu');
-
- // add this your table definition to set the table as NestedSet tree implementation
- $this->actsAsTree('NestedSet');
-
- // you do not need to add any columns specific to the nested set implementation
- // these are added for you
- $this->hasColumn("name","string",30);
-
- }
-
- // this __toString() function is used to get the name for the path, see node::getPath
- public function __toString() {
- return $this->get('name');
- }
-}
-
-// set connections to database
-$dsn = 'mysql:dbname=nestedset;host=localhost';
-$user = 'user';
-$password = 'pass';
-
-try {
- $dbh = new PDO($dsn, $user, $password);
-} catch (PDOException $e) {
- echo 'Connection failed: ' . $e->getMessage();
-}
-
-$manager = Doctrine_Manager::getInstance();
-
-$conn = $manager->openConnection($dbh);
-
-// create root
-$root = new Menu();
-$root->set('name', 'root');
-
-$manager->getTable('Menu')->getTree()->createRoot($root);
-
-// build tree
-$two = new Menu();
-$two->set('name', '2');
-$root->getNode()->addChild($two);
-
-$one = new Menu();
-$one->set('name', '1');
-$one->getNode()->insertAsPrevSiblingOf($two);
-
-// refresh as node's lft and rgt values have changed
-$two->refresh();
-
-$three = new Menu();
-$three->set('name', '3');
-$three->getNode()->insertAsNextSiblingOf($two);
-$two->refresh();
-
-$one_one = new Menu();
-$one_one->set('name', '1.1');
-$one_one->getNode()->insertAsFirstChildOf($one);
-$one->refresh();
-
-$one_two = new Menu();
-$one_two->set('name', '1.2');
-$one_two->getNode()->insertAsLastChildOf($one);
-$one_two->refresh();
-
-$one_two_one = new Menu();
-$one_two_one->set('name', '1.2.1');
-$one_two->getNode()->addChild($one_two_one);
-
-$root->refresh();
-$four = new Menu();
-$four->set('name', '4');
-$root->getNode()->addChild($four);
-
-$root->refresh();
-$five = new Menu();
-$five->set('name', '5');
-$root->getNode()->addChild($five);
-
-$root->refresh();
-$six = new Menu();
-$six->set('name', '6');
-$root->getNode()->addChild($six);
-
-output_message('initial tree');
-output_tree($root);
-
-$one_one->refresh();
-$six->set('name', '1.0 (was 6)');
-$six->getNode()->moveAsPrevSiblingOf($one_one);
-
-$one_two->refresh();
-$five->refresh();
-$five->set('name', '1.3 (was 5)');
-$five->getNode()->moveAsNextSiblingOf($one_two);
-
-$one_one->refresh();
-$four->refresh();
-$four->set('name', '1.1.1 (was 4)');
-$four->getNode()->moveAsFirstChildOf($one_one);
-
-$root->refresh();
-$one_two_one->refresh();
-$one_two_one->set('name', 'last (was 1.2.1)');
-$one_two_one->getNode()->moveAsLastChildOf($root);
-
-output_message('transformed tree');
-output_tree($root);
-
-$one_one->refresh();
-$one_one->deleteNode();
-
-output_message('delete 1.1');
-output_tree($root);
-
-// now test fetching root
-$tree_root = $manager->getTable('Menu')->getTree()->findRoot();
-output_message('testing fetch root and outputting tree from the root node');
-output_tree($tree_root);
-
-// now test fetching the tree
-output_message('testing fetching entire tree using tree::fetchTree()');
-$tree = $manager->getTable('Menu')->getTree()->fetchTree();
-while($node = $tree->next())
-{
- output_node($node);
-}
-
-// now test fetching the tree
-output_message('testing fetching entire tree using tree::fetchTree(), excluding root node');
-$tree = $manager->getTable('Menu')->getTree()->fetchTree(array('include_record' => false));
-while($node = $tree->next())
-{
- output_node($node);
-}
-
-// now test fetching the branch
-output_message('testing fetching branch for 1, using tree::fetchBranch()');
-$one->refresh();
-$branch = $manager->getTable('Menu')->getTree()->fetchBranch($one->get('id'));
-while($node = $branch->next())
-{
- output_node($node);
-}
-
-// now test fetching the tree
-output_message('testing fetching branch for 1, using tree::fetchBranch() excluding node 1');
-$tree = $manager->getTable('Menu')->getTree()->fetchBranch($one->get('id'), array('include_record' => false));
-while($node = $tree->next())
-{
- output_node($node);
-}
-
-// now perform some tests
-output_message('descendants for 1');
-$descendants = $one->getNode()->getDescendants();
-while($descendant = $descendants->next())
-{
- output_node($descendant);
-}
-
-// move one and children under two
-$two->refresh();
-$one->getNode()->moveAsFirstChildOf($two);
-
-output_message('moved one as first child of 2');
-output_tree($root);
-
-output_message('descendants for 2');
-$two->refresh();
-$descendants = $two->getNode()->getDescendants();
-while($descendant = $descendants->next())
-{
- output_node($descendant);
-}
-
-output_message('number descendants for 2');
-echo $two->getNode()->getNumberDescendants() .'';
-
-output_message('children for 2 (notice excludes children of children, known as descendants)');
-$children = $two->getNode()->getChildren();
-while($child = $children->next())
-{
- output_node($child);
-}
-
-output_message('number children for 2');
-echo $two->getNode()->getNumberChildren() .'';
-
-output_message('path to 1');
-$path = $one->getNode()->getPath(' > ');
-echo $path .'
-';
-
-output_message('path to 1 (including 1)');
-$path = $one->getNode()->getPath(' > ', true);
-echo $path .'
-';
-
-output_message('1 has parent');
-$hasParent = $one->getNode()->hasParent();
-$msg = $hasParent ? 'true' : 'false';
-echo $msg . '';
-
-output_message('parent to 1');
-$parent = $one->getNode()->getParent();
-if($parent->exists())
-{
- echo $parent->get('name') .'
-';
-}
-
-output_message('root isRoot?');
-$isRoot = $root->getNode()->isRoot();
-$msg = $isRoot ? 'true' : 'false';
-echo $msg . '';
-
-output_message('one isRoot?');
-$isRoot = $one->getNode()->isRoot();
-$msg = $isRoot ? 'true' : 'false';
-echo $msg . '';
-
-output_message('root hasParent');
-$hasParent = $root->getNode()->hasParent();
-$msg = $hasParent ? 'true' : 'false';
-echo $msg . '';
-
-output_message('root getParent');
-$parent = $root->getNode()->getParent();
-if($parent->exists())
-{
- echo $parent->get('name') .'
-';
-}
-
-output_message('get first child of root');
-$record = $root->getNode()->getFirstChild();
-if($record->exists())
-{
- echo $record->get('name') .'
-';
-}
-
-output_message('get last child of root');
-$record = $root->getNode()->getLastChild();
-if($record->exists())
-{
- echo $record->get('name') .'
-';
-}
-
-$one_two->refresh();
-
-output_message('get prev sibling of 1.2');
-$record = $one_two->getNode()->getPrevSibling();
-if($record->exists())
-{
- echo $record->get('name') .'
-';
-}
-
-output_message('get next sibling of 1.2');
-$record = $one_two->getNode()->getNextSibling();
-if($record->exists())
-{
- echo $record->get('name') .'
-';
-}
-
-output_message('siblings of 1.2');
-$siblings = $one_two->getNode()->getSiblings();
-foreach($siblings as $sibling)
-{
- if($sibling->exists())
- echo $sibling->get('name') .'
-';
-}
-
-output_message('siblings of 1.2 (including 1.2)');
-$siblings = $one_two->getNode()->getSiblings(true);
-foreach($siblings as $sibling)
-{
- if($sibling->exists())
- echo $sibling->get('name') .'
-';
-}
-
-$new = new Menu();
-$new->set('name', 'parent of 1.2');
-$new->getNode()->insertAsParentOf($one_two);
-
-output_message('added a parent to 1.2');
-output_tree($root);
-
-try {
- $dummy = new Menu();
- $dummy->set('name', 'dummy');
- $dummy->save();
-}
-catch (Doctrine_Exception $e)
-{
- output_message('You cannot save a node unless it is in the tree');
-}
-
-try {
- $fake = new Menu();
- $fake->set('name', 'dummy');
- $fake->set('lft', 200);
- $fake->set('rgt', 1);
- $fake->save();
-}
-catch (Doctrine_Exception $e)
-{
- output_message('You cannot save a node with bad lft and rgt values');
-}
-
-// check last remaining tests
-output_message('New parent is descendant of 1');
-$one->refresh();
-$res = $new->getNode()->isDescendantOf($one);
-$msg = $res ? 'true' : 'false';
-echo $msg . '';
-
-output_message('New parent is descendant of 2');
-$two->refresh();
-$res = $new->getNode()->isDescendantOf($two);
-$msg = $res ? 'true' : 'false';
-echo $msg . '';
-
-output_message('New parent is descendant of 1.2');
-$one_two->refresh();
-$res = $new->getNode()->isDescendantOf($one_two);
-$msg = $res ? 'true' : 'false';
-echo $msg . '';
-
-output_message('New parent is descendant of or equal to 1');
-$one->refresh();
-$res = $new->getNode()->isDescendantOfOrEqualTo($one);
-$msg = $res ? 'true' : 'false';
-echo $msg . '';
-
-output_message('New parent is descendant of or equal to 1.2');
-$one_two->refresh();
-$res = $new->getNode()->isDescendantOfOrEqualTo($one_two);
-$msg = $res ? 'true' : 'false';
-echo $msg . '';
-
-output_message('New parent is descendant of or equal to 1.3');
-$five->refresh();
-$res = $new->getNode()->isDescendantOfOrEqualTo($new);
-$msg = $res ? 'true' : 'false';
-echo $msg . '';
-
-function output_tree($root)
-{
- // display tree
- // first we must refresh the node as the tree has been transformed
- $root->refresh();
-
- // next we must get the iterator to traverse the tree from the root node
- $traverse = $root->getNode()->traverse();
-
- output_node($root);
- // now we traverse the tree and output the menu items
- while($item = $traverse->next())
- {
- output_node($item);
- }
-
- unset($traverse);
-}
-
-function output_node($record)
-{
- echo str_repeat('-', $record->getNode()->getLevel()) . $record->get('name')
- . ' (has children:'.$record->getNode()->hasChildren().') '
- . ' (is leaf:'.$record->getNode()->isLeaf().') '.'
';
-}
-
-function output_message($msg)
-{
- echo "
-**//$msg//**".'
-';
-}
-\end{verbatim}
-
-\chapter{Mapping relations}
-\section{Introduction}
-In Doctrine all record relations are being set with \texttt{hasMany}, \texttt{hasOne} methods. Doctrine supports almost any kind of database relation from simple one-to-one foreign key relations to join table self-referencing relations.
-
-\section{Relation aliases}
-Doctrine supports relation aliases through \texttt{as} keyword.
-
-\begin{verbatim}
-class Forum_Board extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 100);
- $this->hasColumn('description', 'string', 5000);
- }
- public function setUp()
- {
- // notice the 'as' keyword here
- $this->hasMany('Forum_Thread as Threads', array('local' => 'id',
- 'foreign' => 'board_id');
- }
-}
-
-class Forum_Thread extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('board_id', 'integer', 10);
- $this->hasColumn('updated', 'integer', 10);
- $this->hasColumn('closed', 'integer', 1);
- }
- public function setUp()
- {
- // notice the 'as' keyword here
- $this->hasOne('Forum_Board as Board', array('local' => 'board_id',
- 'foreign' => 'id');
- }
-}
-$board = new Board();
-$board->Threads[0]->updated = time();
-\end{verbatim}
-
-\section{Foreign key associations}
-\subsection{One-To-One}
-Binding One-To-One foreign key associations is done with \texttt{Doctrine\_Record::ownsOne()} and \texttt{Doctrine\_Record::hasOne()} methods. In the following example user owns one email and has one address. So the relationship between user and email is one-to-one composite. The relationship between user and address is one-to-one aggregate.
-
-The \texttt{Email} component here is mapped to \texttt{User} component's column \texttt{email\_id} hence their relation is called LOCALKEY relation. On the other hand the \texttt{Address} component is mapped to \texttt{User} by it's \texttt{user\_id} column hence the relation between \texttt{User} and \texttt{Address} is called FOREIGNKEY relation.
-
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setUp()
- {
- $this->hasOne('Address', array('local' => 'id', 'foreign' => 'user_id'));
- $this->hasOne('Email', array('local' => 'email_id', 'foreign' => 'id'));
- $this->hasMany('Phonenumber', array('local' => 'id', 'foreign' => 'user_id'));
- }
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string',50);
- $this->hasColumn('loginname', 'string',20);
- $this->hasColumn('password', 'string',16);
-
- // foreign key column for email id
- $this->hasColumn('email_id', 'integer');
- }
-}
-class Email extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('address', 'string', 150);
- }
-}
-class Address extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('street', 'string', 50);
- $this->hasColumn('user_id', 'integer');
- }
-}
-\end{verbatim}
-
-\subsection{One-to-Many, Many-to-One}
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setUp()
- {
- $this->hasMany('Phonenumber', array('local' => 'id', 'foreign' => 'user_id'));
- }
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 50);
- $this->hasColumn('loginname', 'string', 20);
- $this->hasColumn('password', 'string', 16);
- }
-}
-class Phonenumber extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('phonenumber', 'string', 50);
- $this->hasColumn('user_id', 'integer');
- }
-}
-\end{verbatim}
-
-\subsection{Tree structure}
-\begin{verbatim}
-class Task extends Doctrine_Record
-{
- public function setUp()
- {
- $this->hasOne('Task as Parent', array('local' => 'parent_id', 'foreign' => 'id'));
- $this->hasMany('Task as Subtask', array('local' => 'id', 'foreign' => 'parent_id'));
- }
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 100);
- $this->hasColumn('parent_id', 'integer');
- }
-}
-\end{verbatim}
-
-\section{Join table associations}
-\subsection{Many-to-Many}
-If you are coming from relational database background it may be familiar to you how many-to-many associations are handled: an additional association table is needed.
-
-In many-to-many relations the relation between the two components is always an aggregate relation and the association table is owned by both ends. For example in the case of users and groups when user is being deleted the groups it belongs to are not being deleted and the associations between this user and the groups it belongs to are being deleted.
-
-Sometimes you may not want that association table rows are being deleted when user / group is being deleted. You can override this behoviour by setting the relations to association component (in this case \texttt{Groupuser}) explicitly.
-
-In the following example we have Groups and Users of which relation is defined as many-to-many. In this case we also need to define an additional class called \texttt{Groupuser}.
-
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setUp()
- {
- $this->hasMany('Group', array('local' => 'user_id',
- 'foreign' => 'group_id',
- // the following line is needed in many-to-many relations!
- 'refClass' => 'GroupUser'));
-
- }
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 30);
- }
-}
-
-class Group extends Doctrine_Record
-{
- public function setUp() {
- $this->hasMany('User', array('local' => 'group_id',
- 'foreign' => 'user_id',
- // the following line is needed in many-to-many relations!
- 'refClass' => 'GroupUser'));
- }
- public function setTableDefinition() {
- $this->hasColumn('name', 'string', 30);
- }
-}
-
-class GroupUser extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('user_id', 'integer', null, array('primary' => true));
- $this->hasColumn('group_id', 'integer', null, array('primary' => true));
- }
-}
-
-$user = new User();
-
-// add two groups
-$user->Group[0]->name = 'First Group';
-
-$user->Group[1]->name = 'Second Group';
-
-// save changes into database
-$user->save();
-
-// deleting the associations between user and groups it belongs to
-
-$user->Groupuser->delete();
-
-$groups = new Doctrine_Collection($conn->getTable('Group'));
-
-$groups[0]->name = 'Third Group';
-
-$groups[1]->name = 'Fourth Group';
-
-$user->Group[2] = $groups[0];
-// $user will now have 3 groups
-
-$user->Group = $groups;
-// $user will now have two groups 'Third Group' and 'Fourth Group'
-\end{verbatim}
-
-\subsection{Self-referencing (Nest relations)}
-\subsubsection{Non-equal nest relations}
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setUp()
- {
- $this->hasMany('User as Parents', array('local' => 'parent_id',
- 'foreign' => 'child_id',
- 'refClass' => 'UserReference'
- );
-
- $this->hasMany('User as Children', array('local' => 'child_id',
- 'foreign' => 'parent_id',
- 'refClass' => 'UserReference'
- );
-
- }
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 30);
- }
-}
-class UserReference extends Doctrine_Record
-{
- public function setTableDefinition() {
- $this->hasColumn('parent_id', 'integer', null, array('primary' => true));
- $this->hasColumn('child_id', 'integer', null, array('primary' => true));
- }
-}
-\end{verbatim}
-
-\subsubsection{Equal nest relations}
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setUp()
- {
- $this->hasMany('User as Friend', array('local' => 'user1',
- 'foreign' => 'user2',
- 'refClass' => 'UserReference'
- 'equal' => true,
- );
- }
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 30);
- }
-}
-class UserReference extends Doctrine_Record
-{
- public function setTableDefinition() {
- $this->hasColumn('user1', 'integer', null, array('primary' => true));
- $this->hasColumn('user2', 'integer', null, array('primary' => true));
- }
-}
-\end{verbatim}
-
-\section{Inheritance}
-\subsection{One table, many classes}
-When it comes to handling inheritance Doctrine is very smart. In the following example we have one database table called \texttt{entity}. Users and groups are both entities and they share the same database table. The only thing we have to make is 3 records (\texttt{Entity}, \texttt{Group} and \texttt{User}).
-
-Doctrine is smart enough to know that the inheritance type here is one-table-many-classes.
-
-\begin{verbatim}
-class Entity extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 30);
- $this->hasColumn('username', 'string', 20);
- $this->hasColumn('password', 'string', 16);
- $this->hasColumn('created', 'integer', 11);
- }
-}
-
-class User extends Entity
-{ }
-
-class Group extends Entity
-{ }
-\end{verbatim}
-
-\subsection{One table, one class}
-In the following example we have three database tables called \texttt{entity}, \texttt{user} and \texttt{group}. Users and groups are both entities. The only thing we have to do is write 3 classes (\texttt{Entity}, \texttt{Group} and \texttt{User}) and make iterative \texttt{setTableDefinition} method calls.
-
-\begin{verbatim}
-class Entity extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 30);
- $this->hasColumn('username', 'string', 20);
- $this->hasColumn('password', 'string', 16);
- $this->hasColumn('created', 'integer', 11);
- }
-}
-
-class User extends Entity
-{
- public function setTableDefinition()
- {
- // the following method call is needed in
- // one-table-one-class inheritance
- parent::setTableDefinition();
- }
-}
-
-class Group extends Entity
-{
- public function setTableDefinition()
- {
- // the following method call is needed in
- // one-table-one-class inheritance
- parent::setTableDefinition();
- }
-}
-\end{verbatim}
-
-\subsection{Column aggregation}
-In the following example we have one database table called \texttt{entity}. Users and groups are both entities and they share the same database table.
-
-The entity table has a column called \texttt{type} which tells whether an entity is a group or a user. Then we decide that users are type 1 and groups type 2.
-
-The only thing we have to do is to create 3 records (the same as before) and add\newline
-call the \texttt{Doctrine\_Table::setSubclasses()} method from the parent class.
-
-\begin{verbatim}
-class Entity extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 30);
- $this->hasColumn('username', 'string', 20);
- $this->hasColumn('password', 'string', 16);
- $this->hasColumn('created', 'integer', 11);
-
- // this column is used for column
- // aggregation inheritance
- $this->hasColumn('type', 'integer', 11);
- $this->setSubclasses("User" => array("type" => 1"), "Group" =>
- array("type" => 2));
- }
-}
-
-class User extends Entity {}
-class Group extends Entity {}
-\end{verbatim}
-
-This feature also enable us to query the \texttt{Entity} table and get a \texttt{User} or\newline
-\texttt{Group} object back if the returned object matches the constraints set in the\newline
-parent class. See the code example below for an example of this.
-
-\begin{verbatim}
-$user = new User();
-$user->name = 'Bjarte S. Karlsen';
-$user->username = 'meus';
-$user->password = 'rat';
-$user->save();
-
-$group = new Group();
-$group->name = 'Users';
-$group->username = 'users';
-$group->password = 'password';
-$group->save();
-
-$q = new Doctrine_Query();
-$user = $q->from('Entity')->where('id=?')->execute(array($user->id))->getFirst();
-assert($user instanceOf User);
-
-$q = new Doctrine_Query();
-$group = $q->from('Entity')->where('id=?')->execute(array($group->id))->getFirst();
-assert($group instanceOf Group);
-\end{verbatim}
-
-\section{Foreign key constraints}
-\subsection{Introduction}
-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.
-
-Say you have the product table with the following definition:
-
-\begin{verbatim}
-class Product extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('id', 'integer', null, 'primary');
- $this->hasColumn('name', 'string');
- $this->hasColumn('price', 'decimal', 18);
- }
-}
-\end{verbatim}
-
-Let's also assume you have a table storing orders of those products. We want to ensure that the order table only contains orders of products that actually exist. So we define a foreign key constraint in the orders table that references the products table:
-
-\begin{verbatim}
-class Order extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('order_id', 'integer', null, 'primary');
- $this->hasColumn('product_id', 'integer');
- $this->hasColumn('quantity', 'integer');
- }
- public function setUp()
- {
- $this->hasOne('Product', array('local' => 'product_id', 'foreign' => 'id'));
-
- // foreign key columns should *always* have indexes
-
- $this->index('product_id', array('fields' => 'product_id'));
- }
-}
-\end{verbatim}
-
-When exported the class \texttt{Order} would execute the following SQL:
-
-\begin{verbatim}
-CREATE TABLE orders (
- order_id integer PRIMARY KEY,
- product_id integer REFERENCES products (id),
- quantity integer,
- INDEX product_id_idx (product_id)
-)
-\end{verbatim}
-
-Now it is impossible to create orders with product\_no entries that do not appear in the products table.
-
-We say that in this situation the orders table is the referencing table and the products table is the referenced table. Similarly, there are referencing and referenced columns.
-
-\subsection{Integrity actions}
-\textsl{CASCADE}:\newline
-Delete or update the row from the parent table and automatically delete or update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported. Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column in the parent table or in the child table.
-
-\textsl{SET NULL} :\newline
-Delete or update the row from the parent table and set the foreign key column or columns in the child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported.
-
-\textsl{NO ACTION} :\newline
-In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary key value is not allowed to proceed if there is a related foreign key value in the referenced table.
-
-\textsl{RESTRICT} :\newline
-Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as omitting the ON DELETE or ON UPDATE clause.
-
-\textsl{SET DEFAULT} :
-
-In the following example we define two classes, User and Phonenumber with their relation being one-to-many. We also add a foreign key constraint with onDelete cascade action. This means that everytime a users is being deleted its associated phonenumbers will also be deleted.
-
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 50);
- $this->hasColumn('loginname', 'string', 20);
- $this->hasColumn('password', 'string', 16);
- }
- public function setUp()
- {
- $this->index('id', array('fields' => 'id'));
-
- $this->hasMany('Phonenumber', array('local' => 'id',
- 'foreign' => 'user_id'));
- }
-}
-class Phonenumber extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('phonenumber', 'string', 50);
- $this->hasColumn('user_id', 'integer');
- }
- public function setUp()
- {
- $this->index('product_id', array('fields' => 'user_id'));
-
- $this->hasMany('User', array('local' => 'user_id',
- 'foreign' => 'id',
- 'onDelete' => 'CASCADE'));
- }
-}
-\end{verbatim}
-
-\chapter{Working with objects}
-\section{Dealing with relations}
-\subsection{Creating related records}
-When accessing related records and if those records do not exists Doctrine automatically creates new records.
-
-\begin{verbatim}
-// NOTE: related record have always the first letter in uppercase
-$email = $user->Email;
-
-$email->address = 'jackdaniels@drinkmore.info';
-
-$user->save();
-
-// alternative:
-
-$user->Email->address = 'jackdaniels@drinkmore.info';
-
-$user->save();
-\end{verbatim}
-
-\subsection{Retrieving related records}
-You can retrieve related records by the very same \texttt{Doctrine\_Record} methods you've already propably used for accessing record properties. When accessing related record you just simply use the class names.
-
-\begin{verbatim}
-print $user->Email['address'];
-
-print $user->Phonenumber[0]->phonenumber;
-
-print $user->Group[0]->name;
-\end{verbatim}
-
-\subsection{Updating related records}
-You can update the related records by calling save for each related object / collection individually or by calling save on the object that owns the other objects. You can also call \texttt{Doctrine\_Connection::flush} which saves all pending objects.
-
-\begin{verbatim}
-$user->Email['address'] = 'koskenkorva@drinkmore.info';
-
-$user->Phonenumber[0]->phonenumber = '123123';
-
-$user->save();
-
-// saves the email and phonenumber
-\end{verbatim}
-
-\subsection{Deleting related records}
-You can delete related records individually be calling \texttt{delete()} on each record. If you want to delete a whole record graph just call delete on the owner record.
-
-\begin{verbatim}
-$user->Email->delete();
-
-$user->Phonenumber[3]->delete();
-
-// deleting user and all related objects:
-
-$user->delete();
-\end{verbatim}
-
-\subsection{Working with associations}
-\section{Component overview}
-\subsection{Manager}
-\subsubsection{Introduction}
-\texttt{Doctrine\_Manager} is the heart of every Doctrine based application. \texttt{Doctrine\_Manager} handles all connections (database connections).
-
-\subsubsection{Opening a new connection}
-In order to get your first application started you first need to get an instance of Doctrine\_Manager which handles all the connections (database connections). The second thing to do is to open a new connection.
-
-\begin{verbatim}
-// Doctrine_Manager controls all the connections
-
-$manager = Doctrine_Manager::getInstance();
-
-// Doctrine_Connection
-// a script may have multiple open connections
-// (= multiple database connections)
-$dbh = new PDO('dsn','username','password');
-$conn = $manager->openConnection();
-
-// or if you want to use Doctrine Doctrine_Db and its
-// performance monitoring capabilities
-
-$dsn = 'schema://username:password@dsn/dbname';
-$dbh = Doctrine_Db::getConnection($dsn);
-$conn = $manager->openConnection();
-\end{verbatim}
-
-\subsubsection{Managing connections}
-Switching between connections in Doctrine is very easy, you just call \texttt{Doctrine\_Manager::setCurrentConnection()} method. You can access the connection by calling \texttt{Doctrine\_Manager::getConnection()} or \texttt{Doctrine\_Manager::getCurrentConnection()} if you only want to get the current connection.
-
-\begin{verbatim}
-// Doctrine_Manager controls all the connections
-
-$manager = Doctrine_Manager::getInstance();
-
-// open first connection
-
-$conn = $manager->openConnection(new PDO('dsn','username','password'), 'connection 1');
-
-// open second connection
-
-$conn2 = $manager->openConnection(new PDO('dsn2','username2','password2'), 'connection 2');
-
-$manager->getCurrentConnection(); // $conn2
-
-$manager->setCurrentConnection('connection 1');
-
-$manager->getCurrentConnection(); // $conn
-
-// iterating through connections
-
-foreach($manager as $conn) {
-
-}
-\end{verbatim}
-
-\subsection{Connection}
-\subsubsection{Introduction}
-Doctrine\_Connection is a wrapper for database connection. It handles several things:
-
-\begin{itemize}
-\item{ Handles database portability things missing from PDO (eg. \texttt{LIMIT} / \texttt{OFFSET} emulation)}
-\item{ Keeps track of \texttt{Doctrine\_Table} objects}
-\item{ Keeps track of records}
-\item{ Keeps track of records that need to be updated / inserted / deleted}
-\item{ Handles transactions and transaction nesting}
-\item{ Handles the actual querying of the database in the case of \texttt{INSERT} / \texttt{UPDATE} / \texttt{DELETE} operations}
-\item{ Can query the database using the DQL API (see \texttt{Doctrine\_Query})}
-\item{ Optionally validates transactions using \texttt{Doctrine\_Validator} and gives full information of possible errors.}
-\end{itemize}
-\subsubsection{Available drivers}
-Doctrine has drivers for every PDO-supported database. The supported databases are:
-
-\begin{itemize}
-\item{FreeTDS / Microsoft SQL Server / Sybase}
-\item{Firebird/Interbase 6}
-\item{Informix}
-\item{Mysql}
-\item{Oracle}
-\item{Odbc}
-\item{PostgreSQL}
-\item{Sqlite}
-\end{itemize}
-\subsubsection{Getting a table object}
-In order to get table object for specified record just call \texttt{Doctrine\_Record::getTable()} or \texttt{Doctrine\_Connection::getTable()}.
-
-\begin{verbatim}
-$manager = Doctrine_Manager::getInstance();
-
-// open new connection
-
-$conn = $manager->openConnection(new PDO('dsn','username','password'));
-
-// getting a table object
-
-$table = $conn->getTable('User');
-\end{verbatim}
-
-\subsubsection{Flushing the connection}
-Creating new record (database row) is very easy. You can either use the \texttt{Doctrine\_Connection::create()} or \texttt{Doctrine\_Table::create()} method to do this or just simply use the new operator.
-
-\begin{verbatim}
-$user = new User();
-$user->name = 'Jack';
-
-$group = $conn->create('Group');
-$group->name = 'Drinking Club';
-
-// saves all the changed objects into database
-
-$conn->flush();
-\end{verbatim}
-
-\subsubsection{Querying the database}
-\texttt{Doctrine\_Connection::query()} is a simple method for efficient object retrieval. It takes one parameter (DQL query) and optionally prepared statement params.
-
-\begin{verbatim}
-// 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'));
-\end{verbatim}
-
-\subsubsection{Getting connection state}
-Connection state gives you information about how active connection currently is. You can get the current state by calling \texttt{Doctrine\_Connection::getState()}.
-
-\begin{verbatim}
-switch($conn->getState()):
- case Doctrine_Connection::STATE_ACTIVE:
- // connection open and zero open transactions
- break;
- case Doctrine_Connection::STATE_ACTIVE:
- // one open transaction
- break;
- case Doctrine_Connection::STATE_BUSY:
- // multiple open transactions
- break;
- case Doctrine_Connection::STATE_CLOSED:
- // connection closed
- break;
-endswitch;
-\end{verbatim}
-
-\subsection{Record}
-\subsubsection{Introduction}
-\texttt{Doctrine\_Record} is a wrapper for database row but along with that it speficies what relations it has\newline
-on other components and what columns it has. It may access the related components, hence its refered as an ActiveRecord.
-
-The classes that inherit \texttt{Doctrine\_Record} are called components. There should be atleast one component for each database table.
-
-\subsubsection{Creating new records}
-There are couple of ways for creating new records. Propably the easiest is using native php new -operator. The other ways are calling \texttt{Doctrine\_Table::create()} or \texttt{Doctrine\_Connection::create()}. The last two exists only for backward compatibility. The recommended way of creating new objects is the new operator.
-
-\begin{verbatim}
-$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();
-\end{verbatim}
-
-\subsubsection{Retrieving existing records}
-Doctrine provides many ways for record retrieval. The fastest ways for retrieving existing records are the finder methods provided by \texttt{Doctrine\_Table}. If you need to use more complex queries take a look at DQL API and \texttt{Doctrine\_Connection::query} method.
-
-\begin{verbatim}
-$table = $conn->getTable("User");
-
-// find by primary key
-
-$user = $table->find(2);
-if($user !== false)
- print $user->name;
-
-// get all users
-foreach($table->findAll() as $user) {
- print $user->name;
-}
-
-// finding by dql
-foreach($table->findByDql("name LIKE '%John%'") as $user) {
- print $user->created;
-}
-
-// finding objects with DQL
-
-$users = $conn->query("FROM User WHERE User.name LIKE '%John%'");
-\end{verbatim}
-
-\subsubsection{Accessing properties}
-You can retrieve existing objects (database rows) with \texttt{Doctrine\_Table} or \texttt{Doctrine\_Connection}. \texttt{Doctrine\_Table} provides simple methods like \texttt{findBySql}, \texttt{findAll} and find for finding objects whereas \texttt{Doctrine\_Connection} provides complete OQL API for retrieving objects (see chapter 9).
-
-\begin{verbatim}
-$user = $table->find(3);
-
-// access property through overloading
-
-$name = $user->name;
-
-// access property with get()
-
-$name = $user->get("name");
-
-// access property with ArrayAccess interface
-
-$name = $user['name'];
-
-// iterating through properties
-
-foreach($user as $key => $value) {
-
-}
-\end{verbatim}
-
-\subsubsection{Updating records}
-Updating objects is very easy, you just call the \texttt{Doctrine\_Record::save()} method. The other way (perhaps even easier) is to call \texttt{Doctrine\_Connection::flush()} which saves all objects. It should be noted though that flushing is a much heavier operation than just calling save method.
-
-\begin{verbatim}
-$table = $conn->getTable('User');
-
-$user = $table->find(2);
-
-if($user !== false) {
- $user->name = 'Jack Daniels';
-
- $user->save();
-}
-\end{verbatim}
-
-\subsubsection{Deleting records}
-Deleting records in Doctrine is handled by \texttt{Doctrine\_Record::delete()}, \texttt{Doctrine\_Collection::delete()} and \texttt{Doctrine\_Connection::delete()} methods.
-
-\begin{verbatim}
-$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();
-\end{verbatim}
-
-\subsubsection{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:
-
-\begin{verbatim}
-$event = new Event();
-$event->name = 'Rock festival';
-$event->timepoint = new Doctrine_Expression('NOW()');
-
-$event->save();
-\end{verbatim}
-
-The last line would execute sql (in sqlite):
-
-\begin{verbatim}
-INSERT INTO event (name, timepoint) VALUES (?, 'NOW()')
-\end{verbatim}
-
-\subsubsection{Getting record state}
-\texttt{Every Doctrine\_Record} has a state. First of all record can be transient or persistent. Every record that is retrieved from database is persistent and every newly created record is transient. If a \texttt{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 \texttt{Doctrine\_Record} is either clean or dirty. \texttt{Doctrine\_Record} is clean when none of its properties are changed and dirty when atleast one of its properties has changed.
-
-\begin{verbatim}
-$state = $record->state();
-
-switch($state):
- case Doctrine_Record::STATE_PROXY:
- // record is in proxy state,
- // meaning its persistent but not all of its properties are
- // loaded from the database
- break;
- case Doctrine_Record::STATE_TCLEAN:
- // record is transient clean,
- // meaning its transient and
- // none of its properties are changed
- break;
- case Doctrine_Record::STATE_TDIRTY:
- // record is transient dirty,
- // meaning its transient and
- // some of its properties are changed
- break;
- case Doctrine_Record::STATE_DIRTY:
- // record is dirty,
- // meaning its persistent and
- // some of its properties are changed
- break;
- case Doctrine_Record::STATE_CLEAN:
- // record is clean,
- // meaning its persistent and
- // none of its properties are changed
- break;
-endswitch;
-\end{verbatim}
-
-\subsubsection{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: \texttt{Doctrine\_Record::copy()}.
-
-\begin{verbatim}
-$copy = $user->copy();
-\end{verbatim}
-
-\subsubsection{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.
-
-\begin{verbatim}
-$user = new User();
-$user->state('TDIRTY');
-$user->save();
-
-$user->id; // 1
-\end{verbatim}
-
-\subsubsection{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:
-
-\begin{verbatim}
-$user->mapValue('isRegistered', true);
-
-$user->isRegistered; // true
-\end{verbatim}
-
-\subsubsection{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.
-
-\begin{verbatim}
-$string = serialize($user);
-
-$user = unserialize($string);
-\end{verbatim}
-
-\subsubsection{Checking Existence}
-\begin{verbatim}
-$record = new User();
-
-$record->exists(); // false
-
-$record->name = 'someone';
-$record->save();
-
-$record->exists(); // true
-\end{verbatim}
-
-\subsubsection{Callbacks}
-\subsection{Collection}
-\subsubsection{Introduction}
-\texttt{Doctrine\_Collection} is a collection of records (see \texttt{Doctrine\_Record}). As with records the collections can be deleted and saved using \texttt{Doctrine\_Collection::delete()} and \texttt{Doctrine\_Collection::save()} accordingly.
-
-When fetching data from database with either DQL API (see \texttt{Doctrine\_Query}) or rawSql API (see \texttt{Doctrine\_RawSql}) the methods return an instance of \texttt{Doctrine\_Collection} by default.
-
-The following example shows how to initialize a new collection:
-
-\begin{verbatim}
-$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
-$coll[0]->name = 'Arnold';
-
-$coll[1]->name = 'Somebody';
-
-// finally save it!
-$coll->save();
-\end{verbatim}
-
-\subsubsection{Accessing elements}
-You can access the elements of \texttt{Doctrine\_Collection} with \texttt{set()} and \texttt{get()} methods or with \texttt{ArrayAccess} interface.
-
-\begin{verbatim}
-$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;
-\end{verbatim}
-
-\subsubsection{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.
-
-\begin{verbatim}
-$users = $table->findAll();
-
-print count($users); // 5
-
-$users[5]->name = "new user 1";
-$users[6]->name = "new user 2";
-\end{verbatim}
-
-\subsubsection{Getting collection count}
-The \texttt{Doctrine\_Collection} method \texttt{count()} returns the number of elements currently in the collection.
-
-\begin{verbatim}
-$users = $table->findAll();
-
-$users->count();
-
-// or
-
-count($users); // Doctrine_Collection implements Countable interface
-\end{verbatim}
-
-\subsubsection{Saving the collection}
-As with records the collection can be saved by calling the save method.
-
-\begin{verbatim}
-$users = $table->findAll();
-
-$users[0]->name = "Jack Daniels";
-
-$users[1]->name = "John Locke";
-
-$users->save();
-\end{verbatim}
-
-\subsubsection{Deleting collection}
-Doctrine Collections can be deleted in very same way is Doctrine Records you just call \texttt{delete()} method. As for all collections Doctrine knows how to perform single-shot-delete meaning it only performs one database query for the each collection.
-
-For example if we have collection of users which own [0-*] phonenumbers. When deleting the collection\newline
-of users doctrine only performs two queries for this whole transaction. The queries would look something like:
-
-\begin{verbatim}
-DELETE FROM user WHERE id IN (1,2,3, ... ,N)
-DELETE FROM phonenumber WHERE id IN (1,2,3, ... ,M)
-\end{verbatim}
-
-It should also be noted that Doctrine is smart enough to perform single-shot-delete per table when transactions are used. So if you are deleting a lot of records and want to optimize the operation just wrap the delete calls in \texttt{Doctrine\_Connection} transaction.
-
-\begin{verbatim}
-// delete all users with name 'John'
-
-$users = $table->findByDql("name LIKE '%John%'");
-
-$users->delete();
-\end{verbatim}
-
-\subsubsection{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.
-
-\begin{verbatim}
-// 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;
-}
-\end{verbatim}
-
-\subsubsection{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 \texttt{loadRelated()} method.
-
-\begin{verbatim}
-$users = $conn->query("FROM User");
-
-// now lets load phonenumbers for all users
-
-$users->loadRelated("Phonenumber");
-
-foreach($users as $user) {
- print $user->Phonenumber->phonenumber;
- // no additional db queries needed here
-}
-
-// the loadRelated works an any relation, even associations:
-
-$users->loadRelated("Group");
-
-foreach($users as $user) {
- print $user->Group->name;
-}
-\end{verbatim}
-
-\subsubsection{Collection expanding}
-\subsection{Table}
-\subsubsection{Introduction}
-\subsubsection{Getting table information}
-\begin{verbatim}
-$table = $conn->getTable('User');
-
-// getting column names
-
-$names = $table->getColumnNames();
-
-// getting column information
-
-$columns = $table->getColumns();
-\end{verbatim}
-
-\subsubsection{Finder methods}
-\texttt{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 \texttt{Doctrine\_Connection::query()}.
-
-\begin{verbatim}
-$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;
-}
-\end{verbatim}
-
-\subsubsection{Custom table classes}
-Adding custom table classes is very easy. Only thing you need to do is name the classes as \texttt{[componentName]Table} and make them inherit \texttt{Doctrine\_Table}.
-
-\begin{verbatim}
-// valid table object
-
-class UserTable extends Doctrine_Table {
-
-}
-
-// not valid [doesn't extend Doctrine_Table]
-class GroupTable { }
-\end{verbatim}
-
-\subsubsection{Custom finders}
-You can add custom finder methods to your custom table object. These finder methods may use fast \texttt{Doctrine\_Table} finder methods or DQL API (\texttt{Doctrine\_Connection::query()}).
-
-\begin{verbatim}
-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");
-\end{verbatim}
-
-\subsubsection{Getting relation objects}
-\section{Fetching objects}
-\subsection{Field lazy-loading}
-Whenever you fetch an object that has not all of its fields loaded from database then the state of this object is called proxy. Proxy objects can load the unloaded fields lazily.
-
-Lets say we have a User class with the following definition:
-
-\begin{verbatim}
-class User extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 20);
- $this->hasColumn('password', 'string', 16);
- $this->hasColumn('description', 'string');
- }
-}
-\end{verbatim}
-
-In the following example we fetch all the Users with the fields name and password loaded directly. Then we lazy-load a huge field called description for one user.
-
-\begin{verbatim}
-$users = Doctrine_Query::create()->select('u.name, u.password')->from('User u');
-
-// the following lazy-loads the description fields and executes one additional database query
-$users[0]->description;
-\end{verbatim}
-
-Doctrine does the proxy evaluation based on loaded field count. It does not evaluate which fields are loaded on field-by-field basis. The reason for this is simple: performance. Field lazy-loading is very rarely needed in PHP world, hence introducing some kind of variable to check which fields are loaded would introduce unnecessary overhead to basic fetching.
-
-\chapter{Configuration}
-\section{Introduction}
-\begin{verbatim}
-$manager = Doctrine_Manager::getInstance();
-
-$manager->setAttribute(Doctrine::ATTR_LISTENER, new MyListener());
-\end{verbatim}
-
-\section{Levels of configuration}
-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 \texttt{Doctrine::FETCH\_BATCH} and then sets \texttt{example} table fetchmode to \texttt{Doctrine::FETCH\_LAZY}, the lazy fetching strategy will be used whenever the records of 'example' table are being fetched.
-
-\begin{description}
-\item[\textbf{Global level}] {The attributes set in global level will affect every connection and every table in each connection.}
-\item[\textbf{Connection level}] {The attributes set in connection level will take effect on each table in that connection.}
-\item[\textbf{Table level}] {The attributes set in table level will take effect only on that table.}
-\end{description}
-
-In the following example we set an attribute at the global level:
-
-\begin{verbatim}
-// setting a global level attribute
-$manager = Doctrine_Manager::getInstance();
-
-$manager->setAttribute(Doctrine::ATTR_VLD, false);
-\end{verbatim}
-
-In the next example above we override the global attribute on given connection.
-
-\begin{verbatim}
-// 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_VLD, true);
-\end{verbatim}
-
-In the last example we override once again the connection level attribute in the table level.
-
-\begin{verbatim}
-// 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());
-\end{verbatim}
-
-\section{General attributes}
-\subsection{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 \texttt{factory()} and \texttt{setOption()}.
-
-The portability modes are bitwised, so they can be combined using \texttt{|} and removed using \texttt{\^}. See the examples section below on how to do this.
-
-\subsubsection{Portability Mode Constants}
-\begin{description}
-\item[\texttt{Doctrine::PORTABILITY\_ALL} (default)] {turn on all portability features. this is the default setting.}
-\item[\texttt{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 \texttt{DELETE FROM} tablename queries. This mode tricks such DBMS's into telling the count by adding \texttt{WHERE 1=1} to the end of \texttt{DELETE} queries.}
-\item[\texttt{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.}
-\item[\texttt{Doctrine::PORTABILITY\_ERRORS}] {Makes certain error messages in certain drivers compatible with those from other DBMS's}
-\item[\texttt{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.}
-\item[\texttt{Doctrine::PORTABILITY\_FIX\_CASE}] {Convert names of tables and fields to lower or upper case in all methods. The case depends on the \texttt{field\_case} option that may be set to either \texttt{CASE\_LOWER} (default) or \texttt{CASE\_UPPER}}
-\item[\texttt{Doctrine::PORTABILITY\_NONE}] {Turn off all portability features}
-\item[\texttt{Doctrine::PORTABILITY\_NUMROWS}] {Enable hack that makes \texttt{numRows()} work in Oracle}
-\item[\texttt{Doctrine::PORTABILITY\_EXPR}] {Makes DQL API throw exceptions when non-portable expressions are being used.}
-\item[\texttt{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.}
-\end{description}
-
-\subsubsection{Examples}
-Using \texttt{setAttribute()} to enable portability for lowercasing and trimming
-
-\begin{verbatim}
-$conn->setAttribute('portability',
- Doctrine::PORTABILITY_FIX_CASE | Doctrine::PORTABILITY_RTRIM);
-\end{verbatim}
-
-Using \texttt{setAttribute()} to enable all portability options except trimming
-
-\begin{verbatim}
-$conn->setAttribute('portability',
- Doctrine::PORTABILITY_ALL ^ Doctrine::PORTABILITY_RTRIM);
-\end{verbatim}
-
-\subsection{Identifier quoting}
-You can quote the db identifiers (table and field names) with \texttt{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 \texttt{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:
-
-\begin{itemize}
-\item{backtick (`) -- due to MySQL}
-\item{double quote (") -- due to Oracle}
-\item{brackets ([ or ]) -- due to Access}
-\end{itemize}
-Delimited identifiers are known to generally work correctly under the following drivers:
-
-\begin{itemize}
-\item{Mssql}
-\item{Mysql}
-\item{Oracle}
-\item{Pgsql}
-\item{Sqlite}
-\item{Firebird}
-\end{itemize}
-When using the \texttt{quote\_identifiers} option, all of the field identifiers will be automatically quoted in the resulting SQL statements:
-
-\begin{verbatim}
-$conn->setAttribute('quote_identifiers', true);
-\end{verbatim}
-
-will result in a SQL statement that all the field names are quoted with the backtick '`' operator (in MySQL).
-
-\begin{verbatim}
-SELECT * FROM `sometable` WHERE `id` = '123'
-\end{verbatim}
-
-as opposed to:
-
-\begin{verbatim}
-SELECT * FROM sometable WHERE id='123'
-\end{verbatim}
-
-\subsection{Exporting}
-\begin{verbatim}
-$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_NONE);
-\end{verbatim}
-
-\subsection{Fetching strategy}
-\begin{verbatim}
-// sets the default collection type (fetching strategy)
-$manager->setAttribute(Doctrine::ATTR_FETCHMODE, Doctrine::FETCH_LAZY);
-\end{verbatim}
-
-\subsection{Event listener}
-\begin{verbatim}
-// setting default event listener
-$manager->setAttribute(Doctrine::ATTR_LISTENER, new MyListener());
-\end{verbatim}
-
-\section{Naming convention attributes}
-Naming convention attributes affect on the naming of different database related elements such as tables, indexes and sequences.
-
-\subsection{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'.
-
-\begin{verbatim}
-// changing the index naming convention
-$manager->setAttribute(Doctrine::ATTR_IDXNAME_FORMAT, '%s_index');
-\end{verbatim}
-
-\subsection{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'.
-
-\begin{verbatim}
-// changing the sequence naming convention
-$manager->setAttribute(Doctrine::ATTR_IDXNAME_FORMAT, '%s_sequence');
-\end{verbatim}
-
-\subsection{Table name format}
-\subsection{Database name format}
-\begin{verbatim}
-// changing the database naming convention
-$manager->setAttribute(Doctrine::ATTR_DBNAME_FORMAT, 'myframework_%s');
-\end{verbatim}
-
-\section{Validation attributes}
-\subsection{Validation}
-\begin{verbatim}
-// turns transactional validation on
-$manager->setAttribute(Doctrine::ATTR_VLD, true);
-\end{verbatim}
-
-\subsection{Automatic length validation}
-\subsection{Automatic type validation}
-\chapter{DQL (Doctrine Query Language)}
-\section{Introduction}
-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:
-
-\begin{itemize}
-\item{From the start it has been designed to retrieve records(objects) not result set rows}
-\item{DQL understands relations so you don't have to type manually sql joins and join conditions}
-\item{DQL is portable on different databases}
-\item{DQL has some very complex built-in algorithms like (the record limit algorithm) which can help developer to efficiently retrieve objects}
-\item{It supports some functions that can save time when dealing with one-to-many, many-to-many relational data with conditional fetching.}
-\end{itemize}
-If the power of DQL isn't enough, you should consider using the rawSql API for object population.
-
-\begin{verbatim}
-// DO NOT USE THE FOLLOWING CODE
-// (using many sql queries for object population):
-
-$users = $conn->getTable('User')->findAll();
-
-foreach($users as $user) {
- print $user->name."
-";
- foreach($user->Phonenumber as $phonenumber) {
- print $phonenumber."
-";
- }
-}
-
-// same thing implemented much more efficiently:
-// (using only one sql query for object population)
-
-$users = $conn->query("FROM User.Phonenumber");
-
-foreach($users as $user) {
- print $user->name."
-";
- foreach($user->Phonenumber as $phonenumber) {
- print $phonenumber."
-";
- }
-}
-\end{verbatim}
-
-\section{SELECT queries}
-\texttt{SELECT} statement syntax:
-
-\begin{verbatim}
-SELECT
- [ALL | DISTINCT]
- , ...
- [FROM
- [WHERE ]
- [GROUP BY
- [ASC | DESC], ... ]
- [HAVING ]
- [ORDER BY
- [ASC | DESC], ...]
- [LIMIT OFFSET }]
-\end{verbatim}
-
-The \texttt{SELECT} statement is used for the retrieval of data from one or more components.
-
-\begin{itemize}
-\item{Each \texttt{select\_expr} indicates a column or an aggregate function value that you want to retrieve. There must be at least one \texttt{select\_expr} in every \texttt{SELECT} statement.}
-\end{itemize}
-\begin{verbatim}
-SELECT a.name, a.amount FROM Account a
-\end{verbatim}
-
-\begin{itemize}
-\item{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).}
-\end{itemize}
-\begin{verbatim}
-SELECT a.* FROM Account a
-\end{verbatim}
-
-\begin{itemize}
-\item{\texttt{FROM} clause \texttt{components} indicates the component or components from which to retrieve records.}
-\end{itemize}
-\begin{verbatim}
-SELECT a.* FROM Account a
-
-SELECT u.*, p.*, g.* FROM User u LEFT JOIN u.Phonenumber p LEFT JOIN u.Group g
-\end{verbatim}
-
-\begin{itemize}
-\item{The \texttt{WHERE} clause, if given, indicates the condition or conditions that the records must satisfy to be selected. \texttt{where\_condition} is an expression that evaluates to true for each row to be selected. The statement selects all rows if there is no \texttt{WHERE} clause.}
-\end{itemize}
-\begin{verbatim}
-SELECT a.* FROM Account a WHERE a.amount > 2000
-\end{verbatim}
-
-\begin{itemize}
-\item{In the \texttt{WHERE} clause, you can use any of the functions and operators that DQL supports, except for aggregate (summary) functions}
-\item{The \texttt{HAVING} clause can be used for narrowing the results with aggregate functions}
-\end{itemize}
-\begin{verbatim}
-SELECT u.* FROM User u LEFT JOIN u.Phonenumber p HAVING COUNT(p.id) > 3
-\end{verbatim}
-
-\begin{itemize}
-\item{The \texttt{ORDER BY} clause can be used for sorting the results}
-\end{itemize}
-\begin{verbatim}
-SELECT u.* FROM User u ORDER BY u.name
-\end{verbatim}
-
-\begin{itemize}
-\item{The \texttt{LIMIT} and \texttt{OFFSET} clauses can be used for efficiently limiting the number of records to a given \texttt{row\_count}}
-\end{itemize}
-\begin{verbatim}
-SELECT u.* FROM User u LIMIT 20
-\end{verbatim}
-
-\subsection{DISTINCT keyword}
-\subsection{Aggregate values}
-Aggregate value \texttt{SELECT} syntax:
-
-\begin{verbatim}
-// 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';
-\end{verbatim}
-
-\section{UPDATE queries}
-\texttt{UPDATE} statement syntax:
-
-\begin{verbatim}
-UPDATE //component_name//
- SET //col_name1//=//expr1// [, //col_name2//=//expr2// ...]
- [WHERE //where_condition//]
- [ORDER BY ...]
- [LIMIT //record_count//]
-\end{verbatim}
-
-\begin{itemize}
-\item{The \texttt{UPDATE} statement updates columns of existing records in \texttt{component\_name} with new values and returns the number of affected records.}
-\item{The \texttt{SET} clause indicates which columns to modify and the values they should be given.}
-\item{The optional \texttt{WHERE} clause specifies the conditions that identify which records to update. Without \texttt{WHERE} clause, all records are updated.}
-\item{The optional \texttt{ORDER BY} clause specifies the order in which the records are being updated.}
-\item{The \texttt{LIMIT} clause places a limit on the number of records that can be updated. You can use \texttt{LIMIT row\_count} to restrict the scope of the \texttt{UPDATE}.}
-\end{itemize}
-A \texttt{LIMIT} clause is a \textbf{rows-matched restriction} not a rows-changed restriction.\newline
-The statement stops as soon as it has found \texttt{record\_count} rows that satisfy the \texttt{WHERE} clause, whether or not they actually were changed.
-
-\begin{verbatim}
-$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
-\end{verbatim}
-
-\section{DELETE queries}
-\begin{verbatim}
-DELETE FROM
- [WHERE ]
- [ORDER BY ...]
- [LIMIT ]
-\end{verbatim}
-
-\begin{itemize}
-\item{The \texttt{DELETE} statement deletes records from \texttt{component\_name} and returns the number of records deleted.}
-\item{The optional \texttt{WHERE} clause specifies the conditions that identify which records to delete. Without \texttt{WHERE} clause, all records are deleted.}
-\item{If the \texttt{ORDER BY} clause is specified, the records are deleted in the order that is specified.}
-\item{The \texttt{LIMIT} clause places a limit on the number of rows that can be deleted. The statement will stop as soon as it has deleted \texttt{record\_count} records.}
-\end{itemize}
-\begin{verbatim}
-$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
-\end{verbatim}
-
-\section{FROM clause}
-Syntax:
-
-\begin{verbatim}
-FROM [[LEFT | INNER] JOIN ] ...
-\end{verbatim}
-
-The \texttt{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:
-
-\begin{verbatim}
-FROM User u
-\end{verbatim}
-
-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'.
-
-\begin{verbatim}
-$users = Doctrine_Query::create()
- ->from('User u')
- ->execute();
-\end{verbatim}
-
-\section{JOIN syntax}
-Syntax:
-
-\begin{verbatim}
-[[LEFT | INNER] JOIN ] [ON | WITH] ,
-[[LEFT | INNER] JOIN ] [ON | WITH] ,
-...
-[[LEFT | INNER] JOIN ] [ON | WITH]
-\end{verbatim}
-
-DQL supports two kinds of joins INNER JOINs and LEFT JOINs. For each joined component, you can optionally specify an alias.
-
-\begin{itemize}
-\item{The default join type is \texttt{LEFT JOIN}. This join can be indicated by the use of either \texttt{LEFT JOIN} clause or simply '\texttt{,}', hence the following queries are equal:}
-\end{itemize}
-\begin{verbatim}
-SELECT u.*, p.* FROM User u LEFT JOIN u.Phonenumber
-
-SELECT u.*, p.* FROM User u, u.Phonenumber p
-\end{verbatim}
-
-The recommended form is the first one.
-
-\begin{itemize}
-\item{\texttt{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 \texttt{INNER JOIN} can be used when you want to efficiently fetch for example all users which have one or more phonenumbers.}
-\end{itemize}
-\begin{verbatim}
-SELECT u.*, p.* FROM User u INNER JOIN u.Phonenumber p
-\end{verbatim}
-
-By default DQL auto-adds the primary key join condition, so for DQL query:
-
-\begin{verbatim}
-SELECT u.id, p.id FROM User u LEFT JOIN u.Phonenumber
-\end{verbatim}
-
-Would have a SQL equivalent:
-
-\begin{verbatim}
-SELECT u.id AS u__id, p.id AS p__id FROM User u LEFT JOIN Phonenumber p ON u.id = p.user_id
-\end{verbatim}
-
-If you want to override this behaviour and add your own custom join condition you can do it with the \texttt{ON} keyword. Consider the following DQL query:
-
-\begin{verbatim}
-SELECT u.id, p.id FROM User u LEFT JOIN u.Phonenumber ON u.id = 2
-\end{verbatim}
-
-This query would be converted into SQL:
-
-\begin{verbatim}
-SELECT u.id AS u__id, p.id AS p__id FROM User u LEFT JOIN Phonenumber p ON u.id = 2
-\end{verbatim}
-
-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 \texttt{WITH} keyword.
-
-DQL:
-
-\begin{verbatim}
-SELECT u.id, p.id FROM User u LEFT JOIN u.Phonenumber WITH u.id = 2
-\end{verbatim}
-
-SQL:
-
-\begin{verbatim}
-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
-\end{verbatim}
-
-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:
-
-\begin{verbatim}
-$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();
-\end{verbatim}
-
-\section{WHERE clause}
-Syntax:
-
-\begin{verbatim}
-WHERE
-\end{verbatim}
-
-\begin{itemize}
-\item{The \texttt{WHERE} clause, if given, indicates the condition or conditions that the records must satisfy to be selected.}
-\item{\texttt{where\_condition} is an expression that evaluates to true for each row to be selected.}
-\item{The statement selects all rows if there is no \texttt{WHERE} clause.}
-\item{When narrowing results with aggregate function values \texttt{HAVING} clause should be used instead of \texttt{WHERE} clause}
-\end{itemize}
-\section{Conditional expressions}
-\subsection{Literals}
-\textbf{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'.
-
-\begin{verbatim}
-FROM User WHERE User.name = 'Vincent'
-\end{verbatim}
-
-\textbf{Integers}
-
-Integer literals support the use of PHP integer literal syntax.
-
-\begin{verbatim}
-FROM User WHERE User.id = 4
-\end{verbatim}
-
-\textbf{Floats}
-
-Float literals support the use of PHP float literal syntax.
-
-\begin{verbatim}
-FROM Account WHERE Account.amount = 432.123
-\end{verbatim}
-
-\textbf{Booleans}
-
-The boolean literals are true and false.
-
-\begin{verbatim}
-FROM User WHERE User.admin = true
-
-FROM Session WHERE Session.is_authed = false
-\end{verbatim}
-
-\textbf{Enums}
-
-The enumerated values work in the same way as string literals.
-
-\begin{verbatim}
-FROM User WHERE User.type = 'admin'
-\end{verbatim}
-
-Predefined reserved literals are case insensitive, although its a good standard to write them in uppercase.
-
-\subsection{Input parameters}
-\begin{verbatim}
-// 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%'));
-\end{verbatim}
-
-\subsection{Operators and operator precedence}
-The operators are listed below in order of decreasing precedence.
-
-\begin{tabular}{|l|l|}
-\hline
-Operator & Description\\
-\hline
-. & Navigation operator\\
-\hline
- & \textsl{Arithmetic operators: }\\
-\hline
-+, - & unary\\
-\hline
-*, / & multiplication and division\\
-\hline
-+, - & addition and subtraction\\
-\hline
-=, >, >=, <, <=, <> (not equal), & Comparison operators\\
-\hline
-[NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY & \\
-\hline
- & \textsl{Logical operators: }\\
-\hline
-NOT & \\
-\hline
-AND & \\
-\hline
-OR & \\
-\hline
-\end{tabular}
-\subsection{Between expressions}
-\subsection{In expressions}
-Syntax:
-
-\begin{verbatim}
- IN (|)
-\end{verbatim}
-
-An IN conditional expression returns true if the \textsl{operand} is found from result of the \textsl{subquery} or if its in the specificied comma separated \textsl{value list}, hence the IN expression is always false if the result of the subquery is empty.
-
-When \textsl{value list} is being used there must be at least one element in that list.
-
-\begin{verbatim}
-FROM C1 WHERE C1.col1 IN (FROM C2(col1));
-
-FROM User WHERE User.id IN (1,3,4,5)
-\end{verbatim}
-
-The keyword IN is an alias for = ANY. Thus, these two statements are equal:
-
-\begin{verbatim}
-FROM C1 WHERE C1.col1 = ANY (FROM C2(col1));
-FROM C1 WHERE C1.col1 IN (FROM C2(col1));
-\end{verbatim}
-
-\subsection{Like Expressions}
-Syntax:
-
-\begin{verbatim}
-string_expression [NOT] LIKE pattern_value [ESCAPE escape_character]
-\end{verbatim}
-
-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:
-
-\begin{itemize}
-\item{address.phone LIKE '12\%3' is true for '123' '12993' and false for '1234'}
-\item{asentence.word LIKE 'l\_se' is true for 'lose' and false for 'loose'}
-\item{aword.underscored LIKE '\\\_\%' ESCAPE '\\' is true for '\_foo' and false for 'bar'}
-\item{address.phone NOT LIKE '12\%3' is false for '123' and '12993' and true for '1234'}
-\end{itemize}
-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.
-
-\begin{verbatim}
-// 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%'");
-\end{verbatim}
-
-\subsection{Null Comparison Expressions}
-\subsection{Empty Collection Comparison Expressions}
-\subsection{Collection Member Expressions}
-\subsection{Exists Expressions}
-Syntax:
-
-\begin{verbatim}
- [NOT ]EXISTS ()
-\end{verbatim}
-
-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:
-
-\begin{verbatim}
-FROM Article
- WHERE EXISTS (FROM ReaderLog(id)
- WHERE ReaderLog.article_id = Article.id)
-\end{verbatim}
-
-Finding all articles which don't have readers:
-
-\begin{verbatim}
-FROM Article
- WHERE NOT EXISTS (FROM ReaderLog(id)
- WHERE ReaderLog.article_id = Article.id)
-\end{verbatim}
-
-\subsection{All and Any Expressions}
-Syntax:
-
-\begin{verbatim}
-operand comparison_operator ANY (subquery)
-operand comparison_operator SOME (subquery)
-operand comparison_operator ALL (subquery)
-\end{verbatim}
-
-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.
-
-\begin{verbatim}
-FROM C WHERE C.col1 < ALL (FROM C2(col1))
-\end{verbatim}
-
-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.
-
-\begin{verbatim}
-FROM C WHERE C.col1 > ANY (FROM C2(col1))
-\end{verbatim}
-
-The keyword SOME is an alias for ANY.
-
-\begin{verbatim}
-FROM C WHERE C.col1 > SOME (FROM C2(col1))
-\end{verbatim}
-
-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:
-
-\begin{verbatim}
-FROM C WHERE C.col1 <> ALL (FROM C2(col1));
-FROM C WHERE C.col1 NOT IN (FROM C2(col1));
-\end{verbatim}
-
-\subsection{Subqueries}
-A subquery can contain any of the keywords or clauses that an ordinary SELECT query can contain.
-
-Some advantages of the subqueries:
-
-\begin{itemize}
-\item{They allow queries that are structured so that it is possible to isolate each part of a statement.}
-\item{They provide alternative ways to perform operations that would otherwise require complex joins and unions.}
-\item{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."}
-\end{itemize}
-\begin{verbatim}
-// 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);
-\end{verbatim}
-
-\section{Functional Expressions}
-\subsection{String functions}
-\begin{itemize}
-\item{The \textsl{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}
-\end{itemize}
-\begin{verbatim}
-$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;
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{The second and third arguments of the \textsl{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 \textsl{SUBSTRING} function returns a string.}
-\end{itemize}
-\begin{verbatim}
-$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;
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{The \textsl{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 \textsl{TRIM} function returns the trimmed string.}
-\end{itemize}
-\begin{verbatim}
-$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;
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{The \textsl{LOWER} and \textsl{UPPER} functions convert a string to lower and upper case, respectively. They return a string.}
-\end{itemize}
-\begin{verbatim}
-$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;
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{The \textsl{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.}
-\item{The \textsl{LENGTH} function returns the length of the string in characters as an integer.}
-\end{itemize}
-\subsection{Arithmetic functions}
-\subsection{Datetime functions}
-\subsection{Collection functions}
-\section{Subqueries}
-\subsection{Introduction}
-\subsection{Comparisons using subqueries}
-\subsection{Conditional expressions}
-\subsubsection{ANY, IN and SOME}
-\subsubsection{ALL}
-\subsubsection{EXISTS and NOT EXISTS}
-\subsection{Correlated subqueries}
-\subsection{Subqueries in FROM clause}
-\section{GROUP BY, HAVING clauses}
-\begin{itemize}
-\item{GROUP BY and HAVING clauses can be used for dealing with aggregate functions}
-\item{Following aggregate functions are availible on DQL: COUNT, MAX, MIN, AVG, SUM}
-\end{itemize}
-Selecting alphabetically first user by name.
-
-\begin{verbatim}
-SELECT MIN(u.name) FROM User u
-\end{verbatim}
-
-Selecting the sum of all Account amounts.
-
-\begin{verbatim}
-SELECT SUM(a.amount) FROM Account a
-\end{verbatim}
-
-\begin{itemize}
-\item{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.}
-\end{itemize}
-\begin{verbatim}
-SELECT u.*, COUNT(p.id) FROM User u, u.Phonenumber p GROUP BY u.id
-\end{verbatim}
-
-\begin{itemize}
-\item{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}
-\end{itemize}
-\begin{verbatim}
-SELECT u.* FROM User u, u.Phonenumber p HAVING COUNT(p.id) >= 2
-\end{verbatim}
-
-\begin{verbatim}
-// 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';
-}
-\end{verbatim}
-
-\section{ORDER BY clause}
-\subsection{Introduction}
-Record collections can be sorted efficiently at the database level using the ORDER BY clause.
-
-Syntax:
-
-\begin{verbatim}
-[ORDER BY {ComponentAlias.columnName}
- [ASC | DESC], ...]
-\end{verbatim}
-
-Examples:
-
-\begin{verbatim}
-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
-\end{verbatim}
-
-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.
-
-\begin{verbatim}
-FROM User u LEFT JOIN u.Email e
- ORDER BY e.address DESC, u.id ASC;
-\end{verbatim}
-
-\subsection{Sorting by an aggregate value}
-In the following example we fetch all users and sort those users by the number of phonenumbers they have.
-
-\begin{verbatim}
-$q = new Doctrine_Query();
-
-$users = $q->select('u.*, COUNT(p.id) count')
- ->from('User u')
- ->innerJoin('u.Phonenumber p')
- ->orderby('count');
-\end{verbatim}
-
-\subsection{Using random order}
-In the following example we use random in the ORDER BY clause in order to fetch random post.
-
-\begin{verbatim}
-$q = new Doctrine_Query();
-
-$posts = $q->select('p.*, RANDOM() rand')
- ->from('Post p')
- ->orderby('rand')
- ->limit(1)
- ->execute();
-
-$randomPost = $posts[0];
-\end{verbatim}
-
-\section{LIMIT and OFFSET clauses}
-\begin{verbatim}
-// 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';
- }
-}
-\end{verbatim}
-
-\subsection{Introduction}
-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.
-
-\subsection{Driver portability}
-DQL LIMIT clause is portable on all supported databases. Special attention have been paid to following facts:
-
-\begin{itemize}
-\item{Only Mysql, Pgsql and Sqlite implement LIMIT / OFFSET clauses natively}
-\item{In Oracle / Mssql / Firebird LIMIT / OFFSET clauses need to be emulated in driver specific way}
-\item{The limit-subquery-algorithm needs to execute to subquery separately in mysql, since mysql doesn't yet support LIMIT clause in subqueries}
-\item{Pgsql needs the order by fields to be preserved in SELECT clause, hence LS-algorithm needs to take this into consideration when pgsql driver is used}
-\item{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.}
-\end{itemize}
-\subsection{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.
-
-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 its smart enough to use different aliases for the tables in the subquery to avoid alias collisions.
-
-DQL QUERY:
-
-\begin{verbatim}
-SELECT u.id, u.name, p.* FROM User u LEFT JOIN u.Phonenumber p LIMIT 20
-\end{verbatim}
-
-SQL QUERY:
-
-\begin{verbatim}
-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)
-\end{verbatim}
-
-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:
-
-\begin{verbatim}
-SELECT u.id, u.name, p.* FROM User u LEFT JOIN u.Phonenumber p LIMIT 20
-\end{verbatim}
-
-SQL QUERY:
-
-\begin{verbatim}
-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)
-\end{verbatim}
-
-\section{Examples}
-\section{BNF}
-\begin{verbatim}
-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
-\end{verbatim}
-
-\chapter{Native SQL}
-\section{Introduction}
-Doctrine\_RawSql provides convient interface for building raw sql queries. Similar to Doctrine\_Query, Doctrine\_RawSql provides means for fetching arrays and objects, the way you prefer.
-
-Using raw sql for fetching might be useful when you want to utilize database specific features such as query hints or the CONNECT keyword in Oracle.
-
-Creating Doctrine\_RawSql object is easy:
-
-\begin{verbatim}
-$q = new Doctrine_RawSql();
-\end{verbatim}
-
-Optionally a connection parameter can be given:
-
-\begin{verbatim}
-$q = new Doctrine_RawSql($conn); // here $conn is an instance of Doctrine_Connection
-\end{verbatim}
-
-\section{Component queries}
-The first thing to notice when using Doctrine\_RawSql is that you always have to place the fields you are selecting in curly brackets \{\}. Also for every selected component you have to call addComponent().
-
-The following example should clarify the usage of these:
-
-\begin{verbatim}
-$q = new Doctrine_RawSql();
-
-$q->select('{u.*}')
- ->from('user')
- ->addComponent('user', 'User'); // here we tell that user table is bound to class called 'User'
-
-$users = $q->execute();
-$user[0]; // User object
-\end{verbatim}
-
-Pay attention to following things:
-
-\renewcommand{\labelenumi}{\arabic{enumi}}
-\begin{enumerate}
-\item{Fields must be in curly brackets}
-\item{For every selected table there must be one addComponent call}
-\end{enumerate}
-\section{Fetching from multiple components}
-When fetching from multiple components the addComponent calls become a bit more complicated as not only do we have to tell which tables are bound to which components, we also have to tell the parser which components belongs to which.
-
-Consider the following model:
-
-\begin{verbatim}
-// file User.php
-class User extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', 20);
- }
- public function setUp()
- {
- $this->hasMany('Phonenumber', array('local' => 'id',
- 'foreign' => 'user_id'));
- }
-}
-// file Phonenumber.php
-class Phonenumber extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('phonenumber', 'string', 20);
- $this->hasColumn('user_id', 'integer');
- }
- public function setUp()
- {
- $this->hasOne('User', array('local' => 'user_id',
- 'foreign' => 'id',
- 'onDelete' => 'CASCADE'));
- }
-}
-\end{verbatim}
-
-In the following example we fetch all users and their phonenumbers:
-
-\begin{verbatim}
-$q = new Doctrine_RawSql();
-
-$q->select('{u.*}, {p.*}')
- ->from('user u LEFT JOIN phonenumber p ON u.id = p.user_id')
- // here we tell that user table is bound to class called 'User'
- // we also add an alias for User class called 'u'
- // this alias will be used when referencing to User class
- ->addComponent('u', 'User u')
- // here we add another component that is bound to table phonenumber
- // notice how we reference that the Phonenumber class is "User's phonenumber"
- ->addComponent('p', 'u.Phonenumber p');
-
-$users = $q->execute();
-$user[0]; // User object
-\end{verbatim}
-
-\chapter{Transactions}
-\section{Introduction}
-A database transaction is a unit of interaction with a database management system or similar system that is treated in a coherent and reliable way independent of other transactions that must be either entirely completed or aborted. Ideally, a database system will guarantee all of the ACID (Atomicity, Consistency, Isolation, and Durability) properties for each transaction.
-
-\begin{itemize}
-\item{Atomicity\footnote{http://en.wikipedia.org/wiki/Atomicity} refers to the ability of the DBMS to guarantee that either all of the tasks of a transaction are performed or none of them are. The transfer of funds can be completed or it can fail for a multitude of reasons, but atomicity guarantees that one account won't be debited if the other is not credited as well.}
-\item{Consistency\footnote{http://en.wikipedia.org/wiki/Database\_consistency} refers to the database being in a legal state when the transaction begins and when it ends. This means that a transaction can't break the rules, or \textsl{integrity constraints}, of the database. If an integrity constraint states that all accounts must have a positive balance, then any transaction violating this rule will be aborted.}
-\item{Isolation\footnote{http://en.wikipedia.org/wiki/Isolation\_\%28computer\_science\%29} refers to the ability of the application to make operations in a transaction appear isolated from all other operations. This means that no operation outside the transaction can ever see the data in an intermediate state; a bank manager can see the transferred funds on one account or the other, but never on both - even if she ran her query while the transfer was still being processed. More formally, isolation means the transaction history (or schedule\footnote{http://en.wikipedia.org/wiki/Schedule\_\%28computer\_science\%29}) is serializable\footnote{http://en.wikipedia.org/wiki/Serializability}. For performance reasons, this ability is the most often relaxed constraint. See the isolation\footnote{http://en.wikipedia.org/wiki/Isolation\_\%28computer\_science\%29} article for more details.}
-\item{Durability\footnote{http://en.wikipedia.org/wiki/Durability\_\%28computer\_science\%29} refers to the guarantee that once the user has been notified of success, the transaction will persist, and not be undone. This means it will survive system failure, and that the database system\footnote{http://en.wikipedia.org/wiki/Database\_system} has checked the integrity constraints and won't need to abort the transaction. Typically, all transactions are written into a log\footnote{http://en.wikipedia.org/wiki/Database\_log} that can be played back to recreate the system to its state right before the failure. A transaction can only be deemed committed after it is safely in the log.}
-\end{itemize}
-- \textsl{from wikipedia\footnote{http://www.wikipedia.org}}
-
-In Doctrine all operations are wrapped in transactions by default. There are some things that should be noticed about how Doctrine works internally:
-
-\begin{itemize}
-\item{Doctrine uses application level transaction nesting.}
-\item{Doctrine always executes \texttt{INSERT} / \texttt{UPDATE} / \texttt{DELETE} queries at the end of transaction (when the outermost commit is called). The operations are performed in the following order: all inserts, all updates and last all deletes. Doctrine knows how to optimize the deletes so that delete operations of the same component are gathered in one query.}
-\end{itemize}
-\begin{verbatim}
-$conn->beginTransaction();
-
-$user = new User();
-$user->name = 'New user';
-$user->save();
-
-$user = $conn->getTable('User')->find(5);
-$user->name = 'Modified user';
-$user->save();
-
-$conn->commit(); // all the queries are executed here
-\end{verbatim}
-
-\section{Unit of work}
-\begin{verbatim}
-$conn->beginTransaction();
-
-$user = new User();
-$user->name = 'New user';
-$user->save();
-
-$user = $conn->getTable('User')->find(5);
-$user->name = 'Modified user';
-$user->save();
-
-$pending = $conn->getInserts(); // an array containing one element
-
-$pending = $conn->getUpdates(); // an array containing one element
-
-$conn->commit(); // all the queries are executed here
-\end{verbatim}
-
-\section{Nesting}
-\begin{verbatim}
-function saveUserAndGroup(Doctrine_Connection $conn, User $user, Group $group) {
- $conn->beginTransaction();
-
- $user->save();
-
- $group->save();
-
- $conn->commit();
-}
-
-try {
- $conn->beginTransaction();
-
- saveUserAndGroup($conn,$user,$group);
- saveUserAndGroup($conn,$user2,$group2);
- saveUserAndGroup($conn,$user3,$group3);
-
- $conn->commit();
-} catch(Doctrine_Exception $e) {
- $conn->rollback();
-}
-\end{verbatim}
-
-\section{Savepoints}
-Doctrine supports transaction savepoints. This means you can set named transactions and have them nested.
-
-The \texttt{Doctrine\_Transaction::beginTransaction(\$savepoint)} sets a named transaction savepoint with a name of \texttt{\$savepoint}. If the current transaction has a savepoint with the same name, the old savepoint is deleted and a new one is set.
-
-\begin{verbatim}
-try {
- $conn->beginTransaction();
- // do some operations here
-
- // creates a new savepoint called mysavepoint
- $conn->beginTransaction('mysavepoint');
- try {
- // do some operations here
-
- $conn->commit('mysavepoint');
- } catch(Exception $e) {
- $conn->rollback('mysavepoint');
- }
- $conn->commit();
-} catch(Exception $e) {
- $conn->rollback();
-}
-\end{verbatim}
-
-The \texttt{Doctrine\_Transaction::rollback(\$savepoint)} rolls back a transaction to the named savepoint. Modifications that the current transaction made to rows after the savepoint was set are undone in the rollback.
-
-NOTE: Mysql, for example, does not release the row locks that were stored in memory after the savepoint.
-
-Savepoints that were set at a later time than the named savepoint are deleted.
-
-The \texttt{Doctrine\_Transaction::commit(\$savepoint)} removes the named savepoint from the set of savepoints of the current transaction.
-
-All savepoints of the current transaction are deleted if you execute a commit or rollback is being called without savepoint name parameter.
-
-\begin{verbatim}
-try {
- $conn->beginTransaction();
- // do some operations here
-
- // creates a new savepoint called mysavepoint
- $conn->beginTransaction('mysavepoint');
-
- // do some operations here
-
- $conn->commit(); // deletes all savepoints
-} catch(Exception $e) {
- $conn->rollback(); // deletes all savepoints
-}
-\end{verbatim}
-
-\section{Locking strategies}
-\subsection{Pessimistic locking}
-\subsection{Optimistic locking}
-\section{Lock modes}
-\section{Isolation levels}
-A transaction isolation level sets the default transactional behaviour. As the name 'isolation level' suggests, the setting determines how isolated each transation is, or what kind of locks are associated with queries inside a transaction. The four availible levels are (in ascending order of strictness):
-
-\begin{description}
-\item[\texttt{READ UNCOMMITTED}] {Barely transactional, this setting allows for so-called 'dirty reads', where queries inside one transaction are affected by uncommitted changes in another transaction.}
-\item[\texttt{READ COMMITTED}] {Committed updates are visible within another transaction. This means identical queries within a transaction can return differing results. This is the default in some DBMS's.}
-\item[\texttt{REPEATABLE READ}] {Within a transaction, all reads are consistent. This is the default of Mysql INNODB engine.}
-\item[\texttt{SERIALIZABLE}] {Updates are not permitted in other transactions if a transaction has run an ordinary \texttt{SELECT} query.}
-\end{description}
-
-\begin{verbatim}
-$tx = $conn->transaction; // get the transaction module
-
-// sets the isolation level to READ COMMITTED
-$tx->setIsolation('READ COMMITTED');
-
-// sets the isolation level to SERIALIZABLE
-$tx->setIsolation('SERIALIZABLE');
-
-// Some drivers (like Mysql) support the fetching of current transaction
-// isolation level. It can be done as follows:
-$level = $tx->getIsolation();
-\end{verbatim}
-
-\section{Deadlocks}
-\chapter{Caching}
-\section{Introduction}
-\texttt{Doctrine\_Cache} offers an intuitive and easy-to-use query caching solution. It provides the following things:
-
-\begin{itemize}
-\item{Multiple cache backends to choose from (including Memcached, APC and Sqlite)}
-\item{Advanced options for fine-tuning. \texttt{Doctrine\_Cache} has many options for fine-tuning performance.}
-\end{itemize}
-Initializing a new cache driver instance:
-
-\begin{verbatim}
-$cache = new Doctrine_Cache_Memcache($options);
-\end{verbatim}
-
-\section{Drivers}
-\subsection{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.
-
-\begin{verbatim}
-// memcache allows multiple servers
-$servers = array('host' => 'localhost',
- 'port' => 11211,
- 'persistent' => true);
-
-$cache = new Doctrine_Cache_Memcache(array('servers' => $servers,
- 'compression' => false));
-\end{verbatim}
-
-Availible options for Memcache driver:
-
-\begin{tabular}{|l|l|l|l|}
-\hline
-Option & Data Type & Default Value & Description\\
-\hline
-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\\
-\hline
-compression & boolean & false & true if you want to use on-the-fly compression\\
-\hline
-\end{tabular}
-\subsection{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.
-
-\begin{verbatim}
-$cache = new Doctrine_Cache_Apc();
-\end{verbatim}
-
-\subsection{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:
-
-\begin{verbatim}
-$conn = Doctrine_Manager::connection(new PDO('sqlite::memory:'));
-
-$cache = new Doctrine_Cache_Sqlite(array('connection' => $conn));
-\end{verbatim}
-
-\section{Caching queries}
-\subsection{Introduction}
-Doctrine provides means for caching DQL queries. Caching DQL queries can greatly increase performance. Consider the standard workflow of DQL query execution:
-
-\renewcommand{\labelenumi}{\arabic{enumi}}
-\begin{enumerate}
-\item{Init new DQL query}
-\item{Parse DQL query}
-\item{Build database specific SQL query}
-\item{Execute the SQL query}
-\item{Build the result set}
-\item{Return the result set}
-\end{enumerate}
-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:
-
-\renewcommand{\labelenumi}{\arabic{enumi}}
-\begin{enumerate}
-\item{Init new DQL query}
-\item{If DQL query exists in cache return the cached result set, otherwise do normal phases 2\ldots6 and save the result set into cache.}
-\end{enumerate}
-So not only does the DQL query cache skip the standard database query execution phases it also skips the building of the result set and parsing of the DQL query. You should always consider using query caching for queries that are issued often.
-
-\subsection{Using a cache driver}
-You can set a connection or manager level cache driver by using Doctrine::ATTR\_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:
-
-\begin{verbatim}
-$manager = Doctrine_Manager::getInstance();
-
-$manager->setAttribute(Doctrine::ATTR_CACHE, $cacheDriver);
-\end{verbatim}
-
-Setting a connection level cache driver:
-
-\begin{verbatim}
-$manager = Doctrine_Manager::getInstance();
-$conn = $manager->openConnection('pgsql://user:pass@localhost/test');
-
-$conn->setAttribute(Doctrine::ATTR_CACHE, $cacheDriver);
-\end{verbatim}
-
-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\_CACHE\_LIFESPAN.
-
-\begin{verbatim}
-$manager = Doctrine_Manager::getInstance();
-
-// set the lifespan as one hour (60 seconds * 60 minutes = 1 hour = 3600 secs)
-$manager->setAttribute(Doctrine::ATTR_CACHE_LIFESPAN, 3600);
-\end{verbatim}
-
-Now as we have set a cache driver for use we can make a DQL query to use it:
-
-\begin{verbatim}
-$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)
- ->useCache(true);
-
-$entries = $query->execute();
-\end{verbatim}
-
-\subsection{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:
-
-\begin{verbatim}
-$query = new Doctrine_Query();
-
-$query->useCache(new Doctrine_Cache_Apc());
-\end{verbatim}
-
-Also you can override the lifespan attribute by calling setCacheLifeSpan():
-
-\begin{verbatim}
-$query = new Doctrine_Query();
-
-// set the lifespan as half an hour
-$query->setCacheLifeSpan(60 * 30);
-\end{verbatim}
-
-\chapter{Event listeners}
-\section{Introduction}
-\textbf{* THIS CHAPTER AND THE CODE IT REFERS TO IS UNDER CONSTRUCTION }*
-
-Doctrine provides flexible event listener architecture that not only allows listening for different events but also for altering the execution of the listened methods.
-
-There are several different listeners and hooks for various Doctrine components. Listeners are separate classes where as hooks are empty template methods which are defined in the base class.
-
-An example of using Doctrine\_Record hooks:
-
-\begin{verbatim}
-class Blog extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('title', 'string', 200);
- $this->hasColumn('content', 'string');
- $this->hasColumn('created', 'date');
- }
- public function preInsert()
- {
- $this->created = date('Y-m-d', time());
- }
-}
-
-// initialize connection etc, then:
-
-$blog = new Blog();
-$blog->title = 'New title';
-$blog->content = 'Some content';
-$blog->save();
-
-$blog->created; // 2007-06-20 (format: YYYY-MM-DD)
-\end{verbatim}
-
-\section{Connection listeners}
-Connection listeners are used for listening the methods of Doctrine\_Connection and its modules (such as Doctrine\_Transaction). All listener methods take one argument Doctrine\_Event which holds information about the listened event.
-
-\subsection{Creating a new listener}
-There are three different ways of defining a listener. First you can create a listener by making a class that inherits Doctrine\_EventListener:
-
-\begin{verbatim}
-class MyListener extends Doctrine_EventListener
-{
- public function preExec(Doctrine_Event $event)
- {
-
- }
-}
-\end{verbatim}
-
-Note that by declaring a class that extends Doctrine\_EventListener you don't have to define all the methods within the Doctrine\_EventListener\_Interface. This is due to a fact that Doctrine\_EventListener already has empty skeletons for all these methods.
-
-Sometimes it may not be possible to define a listener that extends Doctrine\_EventListener (you might have a listener that inherits some other base class). In this case you can make it implement Doctrine\_EventListener\_Interface.
-
-\begin{verbatim}
-class MyListener implements Doctrine_EventListener_Interface
-{
- // notice: all listener methods must be defined here
- // (otherwise PHP throws fatal error)
-
- public function preExec(Doctrine_Event $event)
- { }
- public function postExec(Doctrine_Event $event)
- { }
-
- // ...
-}
-\end{verbatim}
-
-The third way of creating a listener is a very elegant one. You can make a class that implements Doctrine\_Overloadable. This interface has only one method: \_\_call(), which can be used for catching *all* the events.
-
-\begin{verbatim}
-class MyDebugger implements Doctrine_Overloadable
-{
- public function __call($methodName, $args)
- {
- print $methodName . ' called !';
- }
-}
-\end{verbatim}
-
-\subsection{Attaching listeners}
-You can attach the listeners to a connection with setListener().
-
-\begin{verbatim}
-$conn->setListener(new MyDebugger());
-\end{verbatim}
-
-If you need to use multiple listeners you can use addListener().
-
-\begin{verbatim}
-$conn->addListener(new MyDebugger());
-$conn->addListener(new MyLogger());
-\end{verbatim}
-
-\subsection{preConnect, postConnect}
-\subsection{Transaction listeners}
-\begin{tabular}{|l|l|l|}
-\hline
-Methods & Listens & Params\\
-\hline
-preTransactionBegin() & Doctrine\_Transaction::beginTransaction() & \\
-\hline
-postTransactionBegin() & Doctrine\_Transaction::beginTransaction() & \\
-\hline
-preTransactionRollback() & Doctrine\_Transaction::rollback() & \\
-\hline
-postTransactionRollback() & Doctrine\_Transaction::rollback() & \\
-\hline
-preTransactionCommit() & Doctrine\_Transaction::commit() & \\
-\hline
-postTransactionCommit() & Doctrine\_Transaction::commit() & \\
-\hline
-preCreateSavepoint() & Doctrine\_Transaction::createSavepoint() & savepoint\\
-\hline
-postCreateSavepoint() & Doctrine\_Transaction::createSavepoint() & savepoint\\
-\hline
-preRollbackSavepoint() & Doctrine\_Transaction::rollbackSavepoint() & savepoint\\
-\hline
-postRollbackSavepoint() & Doctrine\_Transaction::rollbackSavepoint() & savepoint\\
-\hline
-preReleaseSavepoint() & Doctrine\_Transaction::releaseSavepoint() & savepoint\\
-\hline
-postReleaseSavepoint() & Doctrine\_Transaction::releaseSavepoint() & savepoint\\
-\hline
-\end{tabular}
-\subsection{Query execution listeners}
-\begin{tabular}{|l|l|l|}
-\hline
-Methods & Listens & Params\\
-\hline
-prePrepare() & Doctrine\_Connection::prepare() & query\\
-\hline
-postPrepare() & Doctrine\_Connection::prepare() & query\\
-\hline
-preExec() & Doctrine\_Connection::exec() & query\\
-\hline
-postExec() & Doctrine\_Connection::exec() & query, rows\\
-\hline
-preStmtExecute() & Doctrine\_Connection\_Statement::execute() & query\\
-\hline
-postStmtExecute() & Doctrine\_Connection\_Statement::execute() & query\\
-\hline
-preExecute() & Doctrine\_Connection::execute() * & query\\
-\hline
-postExecute() & Doctrine\_Connection::execute() * & query\\
-\hline
-preFetch() & Doctrine\_Connection::fetch() & query, data\\
-\hline
-postFetch() & Doctrine\_Connection::fetch() & query, data\\
-\hline
-preFetchAll() & Doctrine\_Connection::fetchAll() & query, data\\
-\hline
-postFetchAll() & Doctrine\_Connection::fetchAll() & query, data\\
-\hline
-\end{tabular}
-\begin{itemize}
-\item{preExecute() and postExecute() only get invoked when Doctrine\_Connection::execute() is called without prepared statement parameters. Otherwise Doctrine\_Connection::execute() invokes prePrepare, postPrepare, preStmtExecute and postStmtExecute.}
-\end{itemize}
-\section{Query listeners}
-\subsection{preHydrate, postHydrate}
-\subsection{preBuildQuery, postBuildQuery}
-\section{Record listeners}
-Doctrine\_Record provides listeners very similar to Doctrine\_Connection. You can set the listeners at global, connection and record(=table) level.
-
-Here is a list of all availible listener methods:
-
-\begin{tabular}{|l|l|}
-\hline
-Methods & Listens\\
-\hline
-preSave() & Doctrine\_Record::save()\\
-\hline
-postSave() & Doctrine\_Record::save()\\
-\hline
-preUpdate() & Doctrine\_Record::save() when the record state is DIRTY\\
-\hline
-postUpdate() & Doctrine\_Record::save() when the record state is DIRTY\\
-\hline
-preInsert() & Doctrine\_Record::save() when the record state is TDIRTY\\
-\hline
-postInsert() & Doctrine\_Record::save() when the record state is TDIRTY\\
-\hline
-preDelete() & Doctrine\_Record::delete()\\
-\hline
-postDelete() & Doctrine\_Record::delete()\\
-\hline
-preValidate() & Doctrine\_Validator::validate()\\
-\hline
-postValidate() & Doctrine\_Validator::validate()\\
-\hline
-\end{tabular}
-Just like with connection listeners there are three ways of defining a record listener: by extending Doctrine\_Record\_Listener, by implement Doctrine\_Record\_Listener\_Interface or by implementing Doctrine\_Overloadable. In the following we'll create a global level listener by implementing Doctrine\_Overloadable:
-
-\begin{verbatim}
-class Logger extends Doctrine_Overloadable
-{
- public function __call($m, $a)
- {
- print 'catched event ' . $m;
-
- // do some logging here...
- }
-}
-\end{verbatim}
-
-Attaching the listener to manager is easy:
-
-\begin{verbatim}
-$manager->addRecordListener(new Logger());
-\end{verbatim}
-
-Note that by adding a manager level listener it affects on all connections and all tables / records within these connections. In the following we create a connection level listener:
-
-\begin{verbatim}
-class Debugger extends Doctrine_Record_Listener
-{
- public function preInsert(Doctrine_Event $event)
- {
- print 'inserting a record ...';
- }
- public function preUpdate(Doctrine_Event $event)
- {
- print 'updating a record...';
- }
-}
-\end{verbatim}
-
-Attaching the listener to a connection is as easy as:
-
-\begin{verbatim}
-$conn->addRecordListener(new Debugger());
-\end{verbatim}
-
-Many times you want the listeners to be table specific so that they only apply on the actions on that given table. Here is an example:
-
-\begin{verbatim}
-class Debugger extends Doctrine_Record_Listener
-{
- public function postDelete(Doctrine_Event $event)
- {
- print 'deleted ' . $event->getInvoker()->id;
- }
-}
-\end{verbatim}
-
-Attaching this listener to given table can be done as follows:
-
-\begin{verbatim}
-class MyRecord extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- // some definitions
- }
-
- public function setUp()
- {
- $this->addListener(new Debugger());
- }
-}
-\end{verbatim}
-
-\section{Record hooks}
-\begin{tabular}{|l|l|}
-\hline
-Methods & Listens\\
-\hline
-preSave() & Doctrine\_Record::save()\\
-\hline
-postSave() & Doctrine\_Record::save()\\
-\hline
-preUpdate() & Doctrine\_Record::save() when the record state is DIRTY\\
-\hline
-postUpdate() & Doctrine\_Record::save() when the record state is DIRTY\\
-\hline
-preInsert() & Doctrine\_Record::save() when the record state is TDIRTY\\
-\hline
-postInsert() & Doctrine\_Record::save() when the record state is TDIRTY\\
-\hline
-preDelete() & Doctrine\_Record::delete()\\
-\hline
-postDelete() & Doctrine\_Record::delete()\\
-\hline
-preValidate() & Doctrine\_Validator::validate()\\
-\hline
-postValidate() & Doctrine\_Validator::validate()\\
-\hline
-\end{tabular}
-Example 1. Using insert and update hooks
-
-\begin{verbatim}
-class Blog extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('title', 'string', 200);
- $this->hasColumn('content', 'string');
- $this->hasColumn('created', 'date');
- $this->hasColumn('updated', 'date');
- }
- public function preInsert()
- {
- $this->created = date('Y-m-d', time());
- }
- public function preUpdate()
- {
- $this->updated = date('Y-m-d', time());
- }
-}
-\end{verbatim}
-
-\section{Listening events}
-\section{Chaining listeners}
-All different event listeners in Doctrine allow chaining. This means that more than one listener can be attached for listening the same methods. The following example attaches two listeners for given connection:
-
-\begin{verbatim}
-// here Debugger and Logger both inherit Doctrine_EventListener
-
-$conn->addListener(new Debugger());
-$conn->addListener(new Logger());
-\end{verbatim}
-
-\section{The Event object}
-\subsection{Getting the invoker}
-You can get the object that invoked the event by calling getInvoker():
-
-\begin{verbatim}
-class MyListener extends Doctrine_EventListener
-{
- public function preExec(Doctrine_Event $event)
- {
- $event->getInvoker(); // Doctrine_Connection
- }
-}
-\end{verbatim}
-
-\subsection{Event codes}
-Doctrine\_Event uses constants as event codes. Above is the list of all availible event constants:
-
-\begin{itemize}
-\item{Doctrine\_Event::CONN\_QUERY}
-\item{Doctrine\_Event::CONN\_EXEC}
-\item{Doctrine\_Event::CONN\_PREPARE}
-\item{Doctrine\_Event::CONN\_CONNECT}
-\item{Doctrine\_Event::STMT\_EXECUTE}
-\item{Doctrine\_Event::STMT\_FETCH}
-\item{Doctrine\_Event::STMT\_FETCHALL}
-\end{itemize}
-\begin{verbatim}
-class MyListener extends Doctrine_EventListener
-{
- public function preExec(Doctrine_Event $event)
- {
- $event->getCode(); // Doctrine_Event::CONN_EXEC
- }
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{Doctrine\_Event::TX\_BEGIN}
-\item{Doctrine\_Event::TX\_COMMIT}
-\item{Doctrine\_Event::TX\_ROLLBACK}
-\item{Doctrine\_Event::SAVEPOINT\_CREATE}
-\item{Doctrine\_Event::SAVEPOINT\_ROLLBACK}
-\item{Doctrine\_Event::SAVEPOINT\_COMMIT}
-\item{Doctrine\_Event::RECORD\_DELETE}
-\item{Doctrine\_Event::RECORD\_SAVE}
-\item{Doctrine\_Event::RECORD\_UPDATE}
-\item{Doctrine\_Event::RECORD\_INSERT}
-\item{Doctrine\_Event::RECORD\_SERIALIZE}
-\item{Doctrine\_Event::RECORD\_UNSERIALIZE}
-\end{itemize}
-\begin{verbatim}
-class MyRecord extends Doctrine_Record
-{
- public function preUpdate(Doctrine_Event $event)
- {
- $event->getCode(); // Doctrine_Event::RECORD_UPDATE
- }
-}
-\end{verbatim}
-
-\subsection{getInvoker()}
-The method getInvoker() returns the object that invoked the given event. For example for event Doctrine\_Event::CONN\_QUERY the invoker is a Doctrine\_Connection object. Example:
-
-\begin{verbatim}
-class MyRecord extends Doctrine_Record
-{
- public function preUpdate(Doctrine_Event $event)
- {
- $event->getInvoker(); // Object(MyRecord)
- }
-}
-\end{verbatim}
-
-\subsection{skipOperation()}
-Doctrine\_Event provides many methods for altering the execution of the listened method as well as for altering the behaviour of the listener chain.
-
-For some reason you may want to skip the execution of the listened method. It can be done as follows (note that preExec could be any listener method):
-
-\begin{verbatim}
-class MyListener extends Doctrine_EventListener
-{
- public function preExec(Doctrine_Event $event)
- {
- // some business logic, then:
-
- $event->skipOperation();
- }
-}
-\end{verbatim}
-
-\subsection{skipNextListener()}
-When using a chain of listeners you might want to skip the execution of the next listener. It can be achieved as follows:
-
-\begin{verbatim}
-class MyListener extends Doctrine_EventListener
-{
- public function preExec(Doctrine_Event $event)
- {
- // some business logic, then:
-
- $event->skipNextListener();
- }
-}
-\end{verbatim}
-
-\chapter{Plugins}
-\section{Validators}
-\subsection{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 \texttt{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 \texttt{Doctrine\_Manager} (see the code below).
-
-Once you enabled validation, you'll get a bunch of validations automatically:
-
-\begin{itemize}
-\item{\textbf{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).}
-\item{\textbf{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.}
-\end{itemize}
-\begin{verbatim}
-// turning on validation
-Doctrine_Manager::getInstance()->setAttribute(Doctrine::ATTR_VLD, true);
-\end{verbatim}
-
-\subsection{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 \texttt{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:
-
-\begin{itemize}
-\item{You can write the validator on your own.}
-\item{You can propose your need for a new validator to a Doctrine developer.}
-\item{You can use validation hooks.}
-\end{itemize}
-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:
-
-\begin{itemize}
-\item{\texttt{validate()} (Executed every time the record gets validated)}
-\item{\texttt{validateOnInsert()} (Executed when the record is new and gets validated)}
-\item{\texttt{validateOnUpdate()} (Executed when the record is not new and gets validated)}
-\end{itemize}
-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 \texttt{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:
-
-\begin{verbatim}
-class User extends Doctrine_Record {
- public function setUp() {
- $this->ownsOne("Email","User.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(, )
- $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"));
- }
-}
-\end{verbatim}
-
-\subsection{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.
-
-\subsubsection{Implicit validation}
-Whenever a record is going to be saved to the persistent data store (i.e. through calling \texttt{\$record->save()}) the full validation procedure is executed. If errors occur during that process an exception of the type \texttt{Doctrine\_Validator\_Exception} will be thrown. You can catch that exception and analyze the errors by using the instance method \texttt{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 \texttt{Doctrine\_Record::getErrorStack()}. Each error stack is an instance of the class \texttt{Doctrine\_Validator\_ErrorStack}. The error stack provides an easy to use interface to inspect the errors.
-
-\subsubsection{Explicit validation}
-You can explicitly trigger the validation for any record at any time. For this purpose Doctrine\_Record provides the instance method \texttt{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 \texttt{Doctrine\_Record::getErrorStack()}.
-
-The following code snippet shows an example of handling implicit validation which caused a \texttt{Doctrine\_Validator\_Exception}.
-
-\begin{verbatim}
-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;
- }
- }
-}
-\end{verbatim}
-
-\subsection{List of predefined validators}
-Here is a list of predefined validators. You cannot use these names for your custom validators.
-
-\begin{tabular}{|l|l|l|}
-\hline
-name & arguments & task\\
-\hline
-email & & Check if value is valid email.\\
-\hline
-notblank & & Check if value is not blank.\\
-\hline
-notnull & & Check if value is not null.\\
-\hline
-country & & Check if valid is valid country code.\\
-\hline
-ip & & Checks if value is valid IP (internet protocol) address.\\
-\hline
-htmlcolor & & Checks if value is valid html color.\\
-\hline
-nospace & & Check if value has no space chars.\\
-\hline
-range & [min,max] & Checks if value is in range specified by arguments.\\
-\hline
-unique & & Checks if value is unique in its database table.\\
-\hline
-regexp & [expression] & Check if valie matches a given regexp.\\
-\hline
-\end{tabular}
-\section{View}
-\subsection{Introduction}
-Database views can greatly increase the performance of complex queries. You can think of them as cached queries. \texttt{Doctrine\_View} provides integration between database views and DQL queries.
-
-\subsection{Managing views}
-\begin{verbatim}
-$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();
-\end{verbatim}
-
-\subsection{Using views}
-\begin{verbatim}
-$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();
-\end{verbatim}
-
-\section{Profiler}
-\subsection{Introduction}
-\texttt{Doctrine\_Connection\_Profiler} is an eventlistener for \texttt{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.
-
-\texttt{Doctrine\_Connection\_Profiler} can be enabled by adding it as an eventlistener for \texttt{Doctrine\_Connection}.
-
-\begin{verbatim}
-$conn = Doctrine_Manager::connection($dsn);
-
-$profiler = new Doctrine_Connection_Profiler();
-
-$conn->setListener($profiler);
-\end{verbatim}
-
-\subsection{Basic usage}
-Perhaps some of your pages is loading slowly. The following shows how to build a complete profiler report from the connection:
-
-\begin{verbatim}
-$time = 0;
-foreach($profiler as $event){
- $time += $event->getElapsedSecs();
- echo $event->getName() . " " . sprintf("%f", $event->getElapsedSecs()) . "
\n";
- echo $event->getQuery() . "
\n";
- $params = $event->getParams();
- if(!empty($params)){
- var_dump($params);
- }
-}
-echo "Total time: " . $time . "
\n";
-\end{verbatim}
-
-\subsection{Advanced usage}
-\section{Locking Manager}
-\subsection{Introduction}
-[\textbf{Note}: The term 'Transaction' doesnt refer to database transactions here but to the general meaning of this term]
-
-[\textbf{Note}: This component is in \textbf{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.
-
-\textbf{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.
-
-\textbf{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.
-
-\subsection{Examples}
-The following code snippet demonstrates the use of Doctrine's pessimistic offline locking capabilities.
-
-At the page where the lock is requested\ldots
-
-\begin{verbatim}
-// 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
-}
-\end{verbatim}
-
-At the page where the transaction finishes\ldots
-
-\begin{verbatim}
-// 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
-}
-\end{verbatim}
-
-\subsection{Planned}
-\begin{itemize}
-\item{Possibility to release locks of a specific Record type (i.e. releasing all locks on 'User' objects).}
-\end{itemize}
-\subsection{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.
-
-\subsection{Maintainer}
-Roman Borschel - romanb at \#doctrine (freenode)
-
-Don't hesitate to contact me if you have questions, ideas, ect.
-
-\section{Connection Profiler}
-\section{AuditLog and versioning}
-Doctrine\_AuditLog provides a full versioning solution. Lets say we have a NewsItem class that we want to be versioned. This functionality can be applied by simply adding \$this->actAs('Versionable') into your record setup.
-
-\begin{verbatim}
-class NewsItem extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('title', 'string', 200);
- $this->hasColumn('content', 'string');
- // the versioning plugin needs version column
- $this->hasColumn('version', 'integer');
- }
-
- public function setUp()
- {
- $this->actAs('Versionable');
- }
-}
-\end{verbatim}
-
-Now when we have defined this record to be versionable, Doctrine does internally the following things:
-
-\begin{itemize}
-\item{It creates a class called NewsItemVersion on-the-fly, the table this record is pointing at is news\_item\_version}
-\item{Everytime a NewsItem object is deleted / updated the previous version is stored into news\_item\_version}
-\item{Everytime a NewsItem object is updated its version number is increased.}
-\end{itemize}
-\subsection{Using versioning}
-\begin{verbatim}
-$newsItem = new NewsItem();
-$newsItem->title = 'No news is good news';
-$newsItem->content = 'All quiet on the western front';
-
-$newsItem->save();
-$newsItem->version; // 1
-
-$newsItem->title = 'A different title';
-$newsItem->save();
-$newsItem->version; // 2
-\end{verbatim}
-
-\subsection{Reverting changes}
-Doctrine\_Record provides a method called revert() which can be used for reverting to specified version. Internally Doctrine queries the version table and fetches the data for given version. If the given version is not found a Doctrine\_Record\_Exception is being thrown.
-
-\begin{verbatim}
-$newsItem->revert(1);
-
-$newsItem->title; // No news is good news
-\end{verbatim}
-
-\subsection{Advanced usage}
-There are many options for the versioning plugin. Sometimes you may want to use other version column than 'version'. This can be achieved by giving the options parameter to actAs() method.
-
-\begin{verbatim}
-class NewsItem extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('title', 'string', 200);
- $this->hasColumn('content', 'string');
- // the versioning plugin needs version column
- $this->hasColumn('news_version', 'integer');
- }
-
- public function setUp()
- {
- $this->actAs('Versionable', array('versionColumn' => 'news_version'));
- }
-}
-\end{verbatim}
-
-You can also control the name of the versioning record and the name of the version table with option attributes 'className' and 'tableName'.
-
-\section{Hook}
-\subsection{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. \texttt{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.
-
-\subsection{Building queries}
-\subsection{List of parsers}
-\section{Soft-delete}
-Soft-delete is a very simple plugin for achieving the following behaviour: when a record is deleted its not removed from database. Usually the record contains some special field like 'deleted' which tells the state of the record (deleted or alive).
-
-The following code snippet shows what you need in order to achieve this kind of behaviour. Notice how we define two event hooks: preDelete and postDelete. Also notice how the preDelete hook skips the actual delete-operation with skipOperation() call. For more info about the event hooks see the Event listener section.
-
-\begin{verbatim}
-class SoftDeleteTest extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('name', 'string', null, array('primary' => true));
- $this->hasColumn('deleted', 'boolean', 1);
- }
- public function preDelete($event)
- {
- $event->skipOperation();
- }
- public function postDelete($event)
- {
- $this->deleted = true;
- $this->save();
- }
-}
-\end{verbatim}
-
-Now lets put the plugin in action:
-
-\begin{verbatim}
-// save a new record
-$record = new SoftDeleteTest();
-$record->name = 'new record';
-$record->save();
-
-$record->delete();
-var_dump($record->deleted); // true
-\end{verbatim}
-
-\chapter{Searching}
-\section{Introduction}
-Searching is a huge topic, hence an entire chapter has been devoted to a plugin called Doctrine\_Search. Doctrine\_Search is a fulltext indexing and searching tool similar to Apache Lucene.
-
-Consider we have a class called NewsItem with the following definition:
-
-\begin{verbatim}
-class NewsItem extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('title', 'string', 200);
- $this->hasColumn('content', 'string');
- }
-}
-\end{verbatim}
-
-Now lets say we have an application where users are allowed to search for different news items, an obvious way to implement this would be building a form and based on that form build DQL queries such as:
-
-\begin{verbatim}
-SELECT n.* FROM NewsItem n WHERE n.title LIKE ? OR n.content LIKE ?
-\end{verbatim}
-
-As the application grows these kind of queries become very slow. For example\newline
-when using the previous query with parameters '\%framework\%' and '\%framework\%'\newline
-(this would be equivalent of 'find all news items whose title or content\newline
-contains word 'framework') the database would have to traverse through each row in the table, which would naturally be very very slow.
-
-Doctrine solves this with its search component and inverse indexes. First lets alter our definition a bit:
-
-\begin{verbatim}
-class NewsItem extends Doctrine_Record
-{
- public function setTableDefinition()
- {
- $this->hasColumn('title', 'string', 200);
- $this->hasColumn('content', 'string');
- }
- public function setUp()
- {
- $this->loadTemplate('Doctrine_Search_Template',
- array('fields' => array('title', 'content')));
- }
-}
-\end{verbatim}
-
-Here we tell Doctrine that NewsItem class uses Doctrine\_Search\_Template and fields title and content are marked as fulltext indexed fields. This means that everytime a NewsItem is added or updated Doctrine will:
-
-1. Update the inverse search index or\newline
-2. Add new pending entry to the inverse search index (its efficient to update the inverse search index in batches)
-
-\section{Index structure}
-The structure of the inverse index Doctrine uses is the following:
-
-[ (string) keyword] [ (string) field ] [ (integer) position ] [ (mixed) [foreign\_keys] ]
-
-\begin{itemize}
-\item{\textbf{keyword} is the keyword in the text that can be searched for}
-\item{\textbf{field} is the field where the keyword was found}
-\item{\textbf{position} is the position where the keyword was found}
-\item{\textbf{[foreign\_keys]} either one or multiple fields depending on the owner component (here NewsItem)}
-\end{itemize}
-In the NewsItem example the [foreign\_keys] would simply contain one field newsitem\_id with foreign key references to NewsItem(id) and with onDelete => CASCADE constraint.
-
-\section{Index building}
-\chapter{Database abstraction}
-\section{Modules}
-\section{Export}
-\subsection{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":\newline
-events(id, name, datetime);\newline
-people(id, name);\newline
-event\_participants(event\_id, person\_id);
-
-\subsection{Creating a database}
-\begin{verbatim}
-$conn->export->createDatabase('events_db');
-\end{verbatim}
-
-\subsection{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:
-
-\begin{verbatim}
-$definition = array (
- 'id' => array (
- 'type' => 'integer',
- 'unsigned' => 1,
- 'notnull' => 1,
- 'default' => 0,
- ),
- 'name' => array (
- 'type' => 'text',
- 'length' => 255
- ),
- 'datetime' => array (
- 'type' => 'timestamp'
- )
-);
-
-$conn->export->createTable('events', $definition);
-\end{verbatim}
-
-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.
-
-\begin{tabular}{|l|l|l|l|l|l|}
-\hline
-Datatype & length & default & not null & unsigned & autoincrement\\
-\hline
-text & x & x & x & & \\
-\hline
-boolean & & x & x & & \\
-\hline
-integer & x & x & x & x & x\\
-\hline
-decimal & & x & x & & \\
-\hline
-float & & x & x & & \\
-\hline
-timestamp & & x & x & & \\
-\hline
-time & & x & x & & \\
-\hline
-date & & x & x & & \\
-\hline
-clob & x & & x & & \\
-\hline
-blob & x & & x & & \\
-\hline
-\end{tabular}
-Creating the people table:
-
-\begin{verbatim}
-$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' => 'text',
- 'length' => 255
- )
-);
-$conn->export->createTable('people', $definition, $options);
-\end{verbatim}
-
-\subsection{Creating foreign keys}
-Creating the event\_participants table with a foreign key:
-
-\begin{verbatim}
-$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);
-\end{verbatim}
-
-Now lets say we want to add foreign key on person\_id too. This can be achieved as follows:
-
-\begin{verbatim}
-$definition = array('local' => 'person_id',
- 'foreign' => 'id'
- 'foreignTable' => 'people'
- 'onDelete' => 'CASCADE'))
-
-$conn->export->createForeignKey('event_participants', $definition);
-\end{verbatim}
-
-\subsection{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()
-
-\begin{verbatim}
-$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)
-\end{verbatim}
-
-Doctrine\_Export::alterTable() takes two parameters:
-
-\begin{description}
-\item[string \textsl{\$name}] {name of the table that is intended to be changed.}
-\item[array \textsl{\$changes}] {associative array that contains the details of each type of change that is intended to be performed.}
-\end{description}
-
-The types of changes that are currently supported are defined as follows:
-
-\begin{itemize}
-\item{\textsl{name}}
-\end{itemize}
-New name for the table.
-
-\begin{itemize}
-\item{\textsl{add}}
-\end{itemize}
-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.
-
-\begin{itemize}
-\item{\textsl{remove}}
-\end{itemize}
-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.
-
-\begin{itemize}
-\item{\textsl{rename}}
-\end{itemize}
-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.
-
-\begin{itemize}
-\item{\textsl{change}}
-\end{itemize}
-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.
-
-\begin{verbatim}
-$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' => 'text',
- 'length' => 20
- )
- )
- ),
- 'rename' => array(
- 'sex' => array(
- 'name' => 'gender',
- 'definition' => array(
- 'type' => 'text',
- 'length' => 1,
- 'default' => 'M'
- )
- )
- )
-
- );
-
-$dbh = new PDO('dsn','username','pw');
-$conn = Doctrine_Manager::getInstance()->openConnection($dbh);
-
-$conn->export->alterTable('mytable', $a);
-\end{verbatim}
-
-\subsection{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.
-
-\begin{verbatim}
-$definition = array(
- 'fields' => array(
- 'datetime' => array()
- )
-);
-$conn->export->createIndex('events', 'event_timestamp', $definition);
-\end{verbatim}
-
-\subsection{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.
-
-\begin{verbatim}
-// 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');
-\end{verbatim}
-
-\section{Import}
-\subsection{Introduction}
-To see what's in the database, you can use the list*() family of functions in the Import module.
-
-\begin{itemize}
-\item{listDatabases()}
-\end{itemize}
-\begin{itemize}
-\item{listFunctions()}
-\end{itemize}
-\begin{itemize}
-\item{listSequences(): takes optional database name as a parameter. If not supplied, the currently selected database is assumed.}
-\end{itemize}
-\begin{itemize}
-\item{listTableConstraints(): takes a table name}
-\end{itemize}
-\begin{itemize}
-\item{listTableFields(): takes a table name}
-\end{itemize}
-\begin{itemize}
-\item{listTableIndexes(): takes a table name}
-\end{itemize}
-\begin{itemize}
-\item{listTables(): takes an optional database name}
-\end{itemize}
-\begin{itemize}
-\item{listTableTriggers(): takes a table name}
-\end{itemize}
-\begin{itemize}
-\item{listTableViews(): takes a table name}
-\end{itemize}
-\begin{itemize}
-\item{listUsers()}
-\end{itemize}
-\begin{itemize}
-\item{listViews(): takes an optional database name}
-\end{itemize}
-\subsection{Listing databases}
-\begin{verbatim}
-$dbs = $conn->import->listDatabases();
-print_r($dbs);
-\end{verbatim}
-
-\subsection{Listing sequences}
-\begin{verbatim}
-$seqs = $conn->export->listSequences('events_db');
-print_r($seqs);
-\end{verbatim}
-
-\subsection{Listing constraints}
-\begin{verbatim}
-$cons = $conn->export->listTableConstraints('event_participants');
-\end{verbatim}
-
-\subsection{Listing table fields}
-\begin{verbatim}
-$fields = $conn->export->listTableFields('events');
-print_r($fields);
-/*
-prints:
-Array
-(
- [0] => id
- [1] => name
- [2] => datetime
-)
-*/
-\end{verbatim}
-
-\subsection{Listing table indices}
-\begin{verbatim}
-$idx = $conn->export->listTableIndexes('events');
-print_r($idx);
-/*
-prints:
-Array
-(
- [0] => event_timestamp
-)
-*/
-\end{verbatim}
-
-\subsection{Listing tables}
-\begin{verbatim}
-$tables = $conn->export->listTables();
-print_r($tables);
-/*
-prints:
-Array
-(
- [0] => event_participants
- [1] => events
- [2] => people
-)
-*/
-\end{verbatim}
-
-\subsection{Listing views}
-\begin{verbatim}
-// 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->export->exec($sql);
-$sql = "CREATE VIEW last_ten_events AS SELECT * FROM events ORDER BY id DESC LIMIT 0,10";
-$conn->export->exec($sql);
-// list views
-$views = $conn->export->listViews();
-print_r($views);
-/*
-prints:
-Array
-(
- [0] => last_ten_events
- [1] => names_only
-)
-*/
-\end{verbatim}
-
-\section{DataDict}
-\subsection{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:\newline
-1. getPortableDeclaration(), which is used for converting native RDBMS type declaration to portable Doctrine declaration\newline
-2. getNativeDeclaration(), which is used for converting portable Doctrine declaration to driver specific type declaration
-
-\subsection{Getting portable declaration}
-\begin{verbatim}
-$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
- );
-*/
-\end{verbatim}
-
-\subsection{Getting native declaration}
-\begin{verbatim}
-$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)
-\end{verbatim}
-
-\section{Drivers}
-\subsection{Mysql}
-\subsubsection{Setting table type}
-\begin{verbatim}
-$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));
-\end{verbatim}
-
-\chapter{Improving Performance}
-\section{Introduction}
-Performance is a very important aspect of all medium to large sized applications. Doctrine is a large\newline
-abstraction library that provides a database abstraction layer as well as object-relational mapping.\newline
-While this provides a lot of benefits like portability and ease of development it's inevitable that this\newline
-leads to drawbacks in terms of performance. This chapter tries to help you to get the best performance out of Doctrine.
-
-\section{Fetch only what you need}
-Maybe the most important rule is to only fetch the data you actually need. This may sound trivial but laziness or lack of knowledge about the possibilities that are available often lead to a lot of unnecessary overhead.
-
-Take a look at this example:
-
-\begin{verbatim}
-$record = $table->find($id);
-\end{verbatim}
-
-How often do you find yourself writing code like that? It's convenient but it's very often not what you need. The example above will pull all columns of the record out of the database and populate the newly created object with that data. This not only means unnecessary network traffic but also means that Doctrine has to populate data into objects that is never used. I'm pretty sure you all know why
-
-\begin{verbatim}
-SELECT * FROM ...
-\end{verbatim}
-
- is bad in any application and this is also true when using Doctine. In fact it's even worse when using Doctrine because populating objects with data that is not needed is a waste of time.
-
-Another important rule that belongs in this category is: \textbf{Only fetch objects when you really need them}. Until recently this statement would make no sense at all but one of the recent additions to Doctrine is the ability to fetch "array graphs" instead of object graphs. At first glance this may sound strange because why use an object-relational mapper in the first place then? Take a second to think about it. PHP is by nature a prodecural language that has been enhanced with a lot of features for decent OOP. Arrays are still the most efficient data structures you can use in PHP. Objects have the most value when they're used to accomplish complex business logic. It's a waste of resources when data gets wrapped in costly object structures when you have no benefit of that. Take a look at the following pseudo-code that fetches all comments with some related data for an article, passing them to the view for display afterwards:
-
-\begin{verbatim}
-$comments = $query->select("c.id, ...")->from("Comment c")
- ->leftJoin("c.foo f")
- ->leftJoin("f.bar b")
- ->where("c.article_id = ?")
- ->execute(array(1));
-$view->comments = $comments;
-\end{verbatim}
-
- Can you think of any benefit of having objects in the view instead of arrays? You're not going to execute business logic in the view, are you? One parameter can save you a lot of unnecessary processing:
-
-\begin{verbatim}
-... ->execute(array(1), Doctrine::FETCH_ARRAY);
-\end{verbatim}
-
- This will return a bunch of nested php arrays. It could look something like this, assuming we fetched some comments:
-
-\begin{verbatim}
-array(5) (
- [0] => array(
- 'title' => 'Title1',
- 'message' => 'Hello there! I like donuts!',
- 'author' => array(
- 'first_name' => 'Bart',
- 'last_name' => 'Simpson'
- )
- ),
- [1] => array(
- 'title' => 'Title2',
- 'message' => 'Hullo!',
- 'author' => array(
- 'first_name' => 'Homer',
- 'last_name' => 'Simpson'
- )
- ),
- ...
-)
-\end{verbatim}
-
- Here 'author' is a related component of a 'comment' and thus results in a sub-array. If you always use the array syntax for accessing data, then the switch to array fetching requires nothing more than adding the additional parameter. The following code works regardless of the fetching style:
-
-\begin{verbatim}
-foreach ($comments as $comment) {
- echo $comment['title'] . '
';
- echo $comment['message'] . '
';
- echo $comment['author']['first_name'] . ' - ' . $comment['author']['last_name'] . '
';
-}
-\end{verbatim}
-
- \textbf{Array fetching is the best choice whenever you need data read-only like passing it to the view for display. And from my experience, most of the time when you fetch a large amount of data it's only for display purposes. And these are exactly the cases where you get the best performance payoff when fetching arrays instead of objects.}
-
-\section{Bundle your class files}
-When using Doctrine or any other large OO library or framework the number of files that need to be included on a regular HTTP request rises significantly. 50-100 includes per request are not uncommon. This has a significant performance impact because it results in a lot of disk operations. While this is generally no issue in a dev environment, it's not suited for production. The recommended way to handle this problem is to bundle the most-used classes of your libraries into a single file for production, stripping out any unnecessary whitespaces, linebreaks and comments. This way you get a significant performance improvement even without a bytecode cache (see next section). The best way to create such a bundle is probably as part of an automated build process i.e. with Phing.
-
-\section{Use a bytecode cache}
-A bytecode cache like APC will cache the bytecode that is generated by php prior to executing it. That means that the parsing of a file and the creation of the bytecode happens only once and not on every request. This is especially useful when using large libraries and/or frameworks. Together with file bundling for production this should give you a significant performance improvement. To get the most out of a bytecode cache you should contact the manual pages since most of these caches have a lot of configuration options which you can tweak to optimize the cache to your needs.
-
-\section{Other tips}
-\textbf{Helping the DQL parser}\newline
-There are two possible ways when it comes to using DQL. The first one is writing the plain DQL queries and passing them to Doctrine\_Connection::query(\$dql). The second one is to use a Doctrine\_Query object and its fluent interface. The latter should be preferred for all but very simple queries. The reason is that using the Doctrine\_Query object and it's methods makes the life of the DQL parser a little bit easier. It reduces the amount of query parsing that needs to be done and is therefore faster.
-
-\textbf{Efficient relation handling}\newline
-When you want to add a relation between two components you should \textbf{NOT} do something like the following:
-
-\begin{verbatim}
-// Assuming a many-many between role - user
-$user->roles[] = $newRole;
-\end{verbatim}
-
- This will load all roles of the user from the database if they're not yet loaded! Just to add one new link! Do this instead:
-
-\begin{verbatim}
-// Assuming a many-many between role - user, where UserRoleXref is the cross-reference table
-$ref = new UserRoleXref();
-$ref->role_id = $role_id;
-$ref->user_id = $user_id;
-$ref->save();
-\end{verbatim}
-
-\chapter{Technology}
-\section{Architecture}
-Doctrine is divided into 3 main packages:
-
-\begin{itemize}
-\item{Doctrine CORE}
-\begin{itemize}
-\item{Doctrine}
-\item{Doctrine\_Manager}
-\item{Doctrine\_Connection}
-\item{Doctrine\_Compiler}
-\item{Doctrine\_Exception}
-\item{Doctrine\_Formatter}
-\item{Doctrine\_Object}
-\item{Doctrine\_Null}
-\item{Doctrine\_Event}
-\item{Doctrine\_Overloadable}
-\item{Doctrine\_Configurable}
-\item{Doctrine\_EventListener}
-\end{itemize}
-\end{itemize}
-\begin{itemize}
-\item{Doctrine DBAL}
-\begin{itemize}
-\item{Doctrine\_Expression\_Driver}
-\item{Doctrine\_Export}
-\item{Doctrine\_Import}
-\item{Doctrine\_Sequence}
-\item{Doctrine\_Transaction}
-\item{Doctrine\_DataDict}
-\end{itemize}
-\end{itemize}
-Doctrine DBAL is also divided into driver packages.
-
-\begin{itemize}
-\item{Doctrine ORM}
-\begin{itemize}
-\item{Doctrine\_Record}
-\item{Doctrine\_Table}
-\item{Doctrine\_Relation}
-\item{Doctrine\_Expression}
-\item{Doctrine\_Query}
-\item{Doctrine\_RawSql}
-\item{Doctrine\_Collection}
-\item{Doctrine\_Tokenizer}
-\end{itemize}
-\end{itemize}
-There are also plugins for Doctrine:
-
-\begin{itemize}
-\item{Doctrine\_Validator}
-\item{Doctrine\_Hook}
-\item{Doctrine\_View}
-\item{Doctrine\_Tree + Doctrine\_Node}
-\end{itemize}
-\section{Design patterns used}
-GoF (Gang of Four) design patterns used:
-
-\begin{itemize}
-\item{Singleton\footnote{http://www.dofactory.com/Patterns/PatternSingleton.aspx}, for forcing only one instance of \texttt{Doctrine\_Manager}}
-\item{Composite\footnote{http://www.dofactory.com/Patterns/PatternComposite.aspx}, for leveled configuration}
-\item{Factory\footnote{http://www.dofactory.com/Patterns/PatternFactory.aspx}, for connection driver loading and many other things}
-\item{Observer\footnote{http://www.dofactory.com/Patterns/PatternObserver.aspx}, for event listening}
-\item{Flyweight\footnote{http://www.dofactory.com/Patterns/PatternFlyweight.aspx}, for efficient usage of validators}
-\item{Iterator\footnote{http://www.dofactory.com/Patterns/PatternFlyweight.aspx}, for iterating through components (Tables, Connections, Records etc.)}
-\item{State\footnote{http://www.dofactory.com/Patterns/PatternState.aspx}, for state-wise connections}
-\item{Strategy\footnote{http://www.dofactory.com/Patterns/PatternStrategy.aspx}, for algorithm strategies}
-\end{itemize}
-Enterprise application design patterns used:
-
-\begin{itemize}
-\item{Active Record\footnote{http://www.martinfowler.com/eaaCatalog/activeRecord.html}, Doctrine is an implementation of this pattern}
-\item{UnitOfWork\footnote{http://www.martinfowler.com/eaaCatalog/unitOfWork.html}, for maintaining a list of objects affected in a transaction}
-\item{Identity Field\footnote{http://www.martinfowler.com/eaaCatalog/identityField.html}, for maintaining the identity between record and database row}
-\item{Metadata Mapping\footnote{http://www.martinfowler.com/eaaCatalog/metadataMapping.html}, for Doctrine DataDict}
-\item{Dependent Mapping\footnote{http://www.martinfowler.com/eaaCatalog/dependentMapping.html}, for mapping in general, since all records extend \texttt{Doctrine\_Record} which performs all mappings}
-\item{Foreign Key Mapping\footnote{http://www.martinfowler.com/eaaCatalog/foreignKeyMapping.html}, for one-to-one, one-to-many and many-to-one relationships}
-\item{Association Table Mapping\footnote{http://www.martinfowler.com/eaaCatalog/associationTableMapping.html}, for association table mapping (most commonly many-to-many relationships)}
-\item{Lazy Load\footnote{http://www.martinfowler.com/eaaCatalog/lazyLoad.html}, for lazy loading of objects and object properties}
-\item{Query Object\footnote{http://www.martinfowler.com/eaaCatalog/queryObject.html}, DQL API is actually an extension to the basic idea of Query Object pattern}
-\end{itemize}
-\section{Speed}
-\begin{description}
-\item[\textbf{Lazy initialization}] {For collection elements}
-\item[\textbf{Subselect fetching}] {Doctrine knows how to fetch collections efficiently using a subselect.}
-\item[\textbf{Executing SQL statements later, when needed}] {The connection never issues an INSERT or UPDATE until it is actually needed. So if an exception occurs and you need to abort the transaction, some statements will never actually be issued. Furthermore, this keeps lock times in the database as short as possible (from the late UPDATE to the transaction end).}
-\item[\textbf{Join fetching}] {Doctrine knows how to fetch complex object graphs using joins and subselects}
-\item[\textbf{Multiple collection fetching strategies}] {Doctrine has multiple collection fetching strategies for performance tuning.}
-\item[\textbf{Dynamic mixing of fetching strategies}] {Fetching strategies can be mixed and for example users can be fetched in a batch collection while users' phonenumbers are loaded in offset collection using only one query.}
-\item[\textbf{Driver specific optimizations}] {Doctrine knows things like bulk-insert on mysql}
-\item[\textbf{Transactional single-shot delete}] {Doctrine knows how to gather all the primary keys of the pending objects in delete list and performs only one sql delete statement per table.}
-\item[\textbf{Updating only the modified columns.}] {Doctrine always knows which columns have been changed.}
-\item[\textbf{Never inserting/updating unmodified objects.}] {Doctrine knows if the the state of the record has changed.}
-\item[\textbf{PDO for database abstraction}] {PDO is by far the fastest availible database abstraction layer for php.}
-\end{description}
-
-\section{Internal optimizations}
-\chapter{Exceptions and warnings}
-\section{Manager exceptions}
-Doctrine\_Manager\_Exception is thrown if something failed at the connection management
-
-\begin{verbatim}
-try {
- $manager->getConnection('unknown');
-} catch (Doctrine_Manager_Exception) {
- // catch errors
-}
-\end{verbatim}
-
-\section{Relation exceptions}
-Relation exceptions are being thrown if something failed during the relation parsing.
-
-\section{Connection exceptions}
-Connection exceptions are being thrown if something failed at the database level. Doctrine offers fully portable database error handling. This means that whether you are using sqlite or some other database you can always get portable error code and message for the occurred error.
-
-\begin{verbatim}
-try {
- $conn->execute('SELECT * FROM unknowntable');
-} catch (Doctrine_Connection_Exception $e) {
- print 'Code : ' . $e->getPortableCode();
- print 'Message : ' . $e->getPortableMessage();
-}
-\end{verbatim}
-
-\section{Query exceptions}
-thrown if DQL parsing fails somehow
-
-\chapter{Real world examples}
-\section{User management system}
-\section{Forum application}
-\begin{verbatim}
-class Forum_Category extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn("root_category_id", "integer", 10);
- $this->hasColumn("parent_category_id", "integer", 10);
- $this->hasColumn("name", "string", 50);
- $this->hasColumn("description", "string", 99999);
- }
- public function setUp() {
- $this->hasMany("Forum_Category as Subcategory", "Subcategory.parent_category_id");
- $this->hasOne("Forum_Category as Rootcategory", "Forum_Category.root_category_id");
- }
-}
-class Forum_Board extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn("category_id", "integer", 10);
- $this->hasColumn("name", "string", 100);
- $this->hasColumn("description", "string", 5000);
- }
- public function setUp() {
- $this->hasOne("Forum_Category as Category", "Forum_Board.category_id");
- $this->ownsMany("Forum_Thread as Threads", "Forum_Thread.board_id");
- }
-}
-
-class Forum_Entry extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn("author", "string", 50);
- $this->hasColumn("topic", "string", 100);
- $this->hasColumn("message", "string", 99999);
- $this->hasColumn("parent_entry_id", "integer", 10);
- $this->hasColumn("thread_id", "integer", 10);
- $this->hasColumn("date", "integer", 10);
- }
- public function setUp() {
- $this->hasOne("Forum_Entry as Parent", "Forum_Entry.parent_entry_id");
- $this->hasOne("Forum_Thread as Thread", "Forum_Entry.thread_id");
- }
-}
-
-class Forum_Thread extends Doctrine_Record {
- public function setTableDefinition() {
- $this->hasColumn("board_id", "integer", 10);
- $this->hasColumn("updated", "integer", 10);
- $this->hasColumn("closed", "integer", 1);
- }
- public function setUp() {
- $this->hasOne("Forum_Board as Board", "Forum_Thread.board_id");
- $this->ownsMany("Forum_Entry as Entries", "Forum_Entry.thread_id");
- }
-}
-\end{verbatim}
-
-\section{Album lister}
-\chapter{Coding standards}
-\section{Overview}
-\subsection{Scope}
-\subsection{Goals}
-\section{PHP File Formatting}
-\subsection{General}
-For files that contain only PHP code, the closing tag ("\texttt{?>}") 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 \texttt{\_\_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.
-
-\subsection{Indentation}
-Use an indent of 4 spaces, with no tabs.
-
-\subsection{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.
-
-\subsection{Line termination}
-\begin{itemize}
-\item{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.}
-\item{Do not use carriage returns (CR) like Macintosh computers (0x0D).}
-\item{Do not use the carriage return/linefeed combination (CRLF) as Windows computers (0x0D, 0x0A).}
-\end{itemize}
-\section{Naming Conventions}
-\subsection{Classes}
-\begin{itemize}
-\item{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.}
-\item{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".}
-\item{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.}
-\end{itemize}
-\subsection{Interfaces}
-\begin{itemize}
-\item{Interface classes must follow the same conventions as other classes (see above), however must end with the word "\texttt{Interface}" (unless the interface is approved not to contain it such as \texttt{Doctrine\_Overloadable}). Some examples:}
-\begin{itemize}
-\item{\texttt{Doctrine\_Db\_EventListener\_Interface}}
-\item{\texttt{Doctrine\_EventListener\_Interface}}
-\end{itemize}
-\end{itemize}
-\subsection{Filenames}
-\begin{itemize}
-\item{For all other files, only alphanumeric characters, underscores, and the dash character ("-") are permitted. Spaces are prohibited.}
-\item{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:}
-\begin{itemize}
-\item{\texttt{Doctrine/Db.php}}
-\item{\texttt{Doctrine/Connection/Transaction.php}}
-\end{itemize}
-\item{File names must follow the mapping to class names described above.}
-\end{itemize}
-\subsection{Functions and methods}
-\begin{itemize}
-\item{Function names may only contain alphanumeric characters. Underscores are not permitted. Numbers are permitted in function names but are discouraged.}
-\item{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.}
-\item{Verbosity is encouraged. Function names should be as verbose as is practical to enhance the understandability of code.}
-\item{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 \texttt{Doctrine\_Record}, it should populate the get / set namespace as little as possible.}
-\item{Functions in the global scope ("floating functions") are NOT permmitted. All static functions should be wrapped in a static class.}
-\end{itemize}
-\subsection{Variables}
-All variables must satisfy the following conditions:
-
-\begin{itemize}
-\item{Variable names may only contain alphanumeric characters. Underscores are not permitted. Numbers are permitted in variable names but are discouraged.}
-\item{Variable names must always start with a lowercase letter and follow the "camelCaps" capitalization convention.}
-\item{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.}
-\item{Within the framework certain generic object variables should always use the following names:}
-\end{itemize}
-\begin{tabular}{|l|l|}
-\hline
-Object type & Variable name\\
-\hline
-\texttt{Doctrine\_Connection} & \texttt{\$conn}\\
-\hline
-\texttt{Doctrine\_Collection} & \texttt{\$coll}\\
-\hline
-\texttt{Doctrine\_Manager} & \texttt{\$manager}\\
-\hline
-\texttt{Doctrine\_Query} & \texttt{\$query}\\
-\hline
-\texttt{Doctrine\_Db} & \texttt{\$db}\\
-\hline
-\end{tabular}
-\begin{itemize}
-\item{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.}
-\end{itemize}
-\subsection{Constants}
-Following rules must apply to all constants used within Doctrine framework:
-
-\begin{itemize}
-\item{Constants may contain both alphanumeric characters and the underscore.}
-\item{Constants must always have all letters capitalized.}
-\item{For readablity reasons, words in constant names must be separated by underscore characters. For example, \texttt{ATTR\_EXC\_LOGGING} is permitted but \texttt{ATTR\_EXCLOGGING} is not.}
-\item{Constants must be defined as class members by using the "const" construct. Defining constants in the global scope with "define" is NOT permitted.}
-\end{itemize}
-\begin{verbatim}
-class Doctrine_SomeClass
-{
- const MY_CONSTANT = 'something';
-}
-print Doctrine_SomeClass::MY_CONSTANT;
-\end{verbatim}
-
-\subsection{Record columns}
-\begin{itemize}
-\item{All record columns must be in lowercase}
-\item{Usage of \_ is encouraged for columns that consist of more than one word}
-\end{itemize}
-\begin{verbatim}
-class User
-{
- public function setTableDefinition()
- {
- $this->hasColumn('home_address', 'string');
- }
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{Foreign key fields must be in format [tablename]\_[column]}
-\end{itemize}
-\begin{verbatim}
-class Phonenumber
-{
- public function setTableDefinition()
- {
- // this field is a foreign key that points to user(id)
- $this->hasColumn('user_id', 'integer');
- }
-}
-\end{verbatim}
-
-\section{Coding Style}
-\subsection{PHP code demarcation}
-\begin{itemize}
-\item{PHP code must always be delimited by the full-form, standard PHP tags}
-\item{Short tags are never allowed. For files containing only PHP code, the closing tag must always be omitted}
-\end{itemize}
-\subsection{Strings}
-\begin{itemize}
-\item{When a string is literal (contains no variable substitutions), the apostrophe or "single quote" must always used to demarcate the string:}
-\end{itemize}
-\begin{verbatim}
-// literal string
-$string = 'something';
-\end{verbatim}
-
-\begin{itemize}
-\item{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:}
-\end{itemize}
-\begin{verbatim}
-// string contains apostrophes
-$sql = "SELECT id, name FROM people WHERE name = 'Fred' OR name = 'Susan'";
-\end{verbatim}
-
-\begin{itemize}
-\item{Variable substitution is permitted using the following form:}
-\end{itemize}
-\begin{verbatim}
-// variable substitution
-$greeting = "Hello $name, welcome back!";
-\end{verbatim}
-
-\begin{itemize}
-\item{Strings may be concatenated using the "." operator. A space must always be added before and after the "." operator to improve readability:}
-\end{itemize}
-\begin{verbatim}
-// concatenation
-$framework = 'Doctrine' . ' ORM ' . 'Framework';
-\end{verbatim}
-
-\begin{itemize}
-\item{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:}
-\end{itemize}
-\begin{verbatim}
-// concatenation line breaking
-$sql = "SELECT id, name FROM user "
- . "WHERE name = ? "
- . "ORDER BY name ASC";
-\end{verbatim}
-
-\subsection{Arrays}
-\begin{itemize}
-\item{Negative numbers are not permitted as indices.}
-\item{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.}
-\item{When declaring indexed arrays with the array construct, a trailing space must be added after each comma delimiter to improve readability.}
-\item{It is also permitted to declare multiline indexed arrays using the "array" construct. In this case, each successive line must be padded with spaces.}
-\item{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:}
-\end{itemize}
-\begin{verbatim}
-$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');
-\end{verbatim}
-
-\subsection{Classes}
-\begin{itemize}
-\item{Classes must be named by following the naming conventions.}
-\item{The brace is always written next line after the class name (or interface declaration).}
-\item{Every class must have a documentation block that conforms to the PHPDocumentor standard.}
-\item{Any code within a class must be indented four spaces.}
-\item{Only one class is permitted per PHP file.}
-\item{Placing additional code in a class file is NOT permitted.}
-\end{itemize}
-This is an example of an acceptable class declaration:
-
-\begin{verbatim}
-/**
- * Documentation here
- */
-class Doctrine_SampleClass
-{
- // entire content of class
- // must be indented four spaces
-}
-\end{verbatim}
-
-\subsection{Functions and methods}
-\begin{itemize}
-\item{Methods must be named by following the naming conventions.}
-\item{Methods must always declare their visibility by using one of the private, protected, or public constructs.}
-\item{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.}
-\item{Functions in the global scope are strongly discouraged.}
-\item{This is an example of an acceptable function declaration in a class:}
-\end{itemize}
-\begin{verbatim}
-/**
- * Documentation Block Here
- */
-class Foo
-{
- /**
- * Documentation Block Here
- */
- public function bar()
- {
- // entire content of function
- // must be indented four spaces
- }
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{Passing by-reference is permitted in the function declaration only:}
-\end{itemize}
-\begin{verbatim}
-/**
- * Documentation Block Here
- */
-class Foo
-{
- /**
- * Documentation Block Here
- */
- public function bar(&$baz)
- {
- }
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{Call-time pass by-reference is prohibited.}
-\item{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.}
-\end{itemize}
-\begin{verbatim}
-/**
- * Documentation Block Here
- */
-class Foo
-{
- /**
- * WRONG
- */
- public function bar() {
- return($this->bar);
- }
- /**
- * RIGHT
- */
- public function bar()
- {
- return $this->bar;
- }
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{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:}
-\end{itemize}
-\begin{verbatim}
-threeArguments(1, 2, 3);
-\end{verbatim}
-
-\begin{itemize}
-\item{Call-time pass by-reference is prohibited. See the function declarations section for the proper way to pass function arguments by-reference.}
-\item{For functions whose arguments permitted arrays, the function call may include the \texttt{array} construct and can be split into multiple lines to improve readability. In these cases, the standards for writing arrays still apply:}
-\end{itemize}
-\begin{verbatim}
-threeArguments(array(1, 2, 3), 2, 3);
-
-threeArguments(array(1, 2, 3, 'Framework',
- 'Doctrine', 56.44, 500), 2, 3);
-\end{verbatim}
-
-\subsection{Control statements}
-\begin{itemize}
-\item{Control statements based on the \texttt{if} and \texttt{elseif} constructs must have a single space before the opening parenthesis of the conditional, and a single space after the closing parenthesis.}
-\item{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.}
-\item{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.}
-\end{itemize}
-\begin{verbatim}
-if ($foo != 2) {
- $foo = 2;
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{For \texttt{if} statements that include \texttt{elseif} or \texttt{else}, the formatting must be as in these examples:}
-\end{itemize}
-\begin{verbatim}
-if ($foo != 1) {
- $foo = 1;
-} else {
- $foo = 3;
-}
-if ($foo != 2) {
- $foo = 2;
-} elseif ($foo == 1) {
- $foo = 3;
-} else {
- $foo = 11;
-}
-\end{verbatim}
-
-When ! operand is being used it must use the following formatting:
-
-\begin{verbatim}
-if ( ! $foo) {
-
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{Control statements written with the \texttt{switch} construct must have a single space before the opening parenthesis of the conditional statement, and also a single space after the closing parenthesis.}
-\item{All content within the \texttt{switch} statement must be indented four spaces. Content under each \texttt{case} statement must be indented an additional four spaces but the breaks must be at the same indentation level as the \texttt{case} statements.}
-\end{itemize}
-\begin{verbatim}
-switch ($case) {
- case 1:
- case 2:
- break;
- case 3:
- break;
- default:
- break;
-}
-\end{verbatim}
-
-\begin{itemize}
-\item{The construct default may never be omitted from a switch statement.}
-\end{itemize}
-\subsection{Inline documentation}
-Documentation Format:
-
-\begin{itemize}
-\item{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/\footnote{http://phpdoc.org/}}
-\end{itemize}
-Methods:
-
-\begin{itemize}
-\item{Every method, must have a docblock that contains at a minimum:}
-\begin{itemize}
-\item{A description of the function}
-\item{All of the arguments}
-\item{All of the possible return values}
-\end{itemize}
-\item{It is not necessary to use the \texttt{@access} tag because the access level is already known from the \texttt{public}, \texttt{private}, or \texttt{protected} construct used to declare the function.}
-\item{If a function/method may throw an exception, use \texttt{@throws}:}
-\begin{itemize}
-\item{\texttt{@throws exceptionclass [description]}}
-\end{itemize}
-\end{itemize}
-\section{Testing}
-\subsection{Writing tests}
-\subsubsection{Classes}
-\begin{itemize}
-\item{All test classes should be referring to a class or specific testing aspect of some class.}
-\begin{itemize}
-\item{For example \texttt{Doctrine\_Record\_TestCase} is a valid name since its referring to class named \texttt{Doctrine\_Record}.}
-\item{\texttt{Doctrine\_Record\_State\_TestCase} is also a valid name since its referring to testing the state aspect of the \texttt{Doctrine\_Record} class.}
-\item{However something like \texttt{Doctrine\_PrimaryKey\_TestCase} is not valid since its way too generic.}
-\end{itemize}
-\item{Every class should have atleast one \texttt{TestCase} equivalent}
-\item{All testcase classes should inherit \texttt{Doctrine\_UnitTestCase}}
-\end{itemize}
-\subsubsection{Methods}
-\begin{itemize}
-\item{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.}
-\begin{itemize}
-\item{For example \texttt{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.}
-\end{itemize}
-\end{itemize}
-NOTE: Commonly used testing method naming convention \texttt{TestCase::test[methodName]} is \textbf{not} allowed in Doctrine. So in this case \texttt{Doctrine\_Export\_Pgsql\_TestCase::testCreateTable()} would not be allowed!
-
-\begin{itemize}
-\item{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.}
-\end{itemize}
-\subsubsection{Assertions}
-\begin{itemize}
-\item{There should never be assertions within any loops and rarely within functions.}
-\end{itemize}
-\end{document}
-\end{document}