diff --git a/orm/cookbook/en.txt b/orm/cookbook/en.txt new file mode 100644 index 000000000..db1b17496 --- /dev/null +++ b/orm/cookbook/en.txt @@ -0,0 +1,8 @@ ++ Getting Started XML-Edition ++ Implementing ArrayAccess for domain objects ++ Implementing the NOTIFY changetracking policy ++ Validation of Entities ++ Implementing wakeup or clone ++ Integrating with CodeIgniter ++ DQL Custom Walkers ++ DQL User Defined Functions \ No newline at end of file diff --git a/orm/cookbook/en/dql-custom-walkers.txt b/orm/cookbook/en/dql-custom-walkers.txt new file mode 100644 index 000000000..48d4840a4 --- /dev/null +++ b/orm/cookbook/en/dql-custom-walkers.txt @@ -0,0 +1,174 @@ +# Extending DQL in Doctrine 2: Custom AST Walkers + +The Doctrine Query Language (DQL) is a propriotary sql-dialect that substitutes +tables and columns for Entity names and their fields. Using DQL you write a query +against the database using your entities. With the help of the metadata you +can write very concise, compact and powerful queries that are then translated +into SQL by the Doctrine ORM. + +In Doctrine 1 the DQL language was not implemented using a real parser. This +made modifications of the DQL by the user impossible. Doctrine 2 in constrast +has a real parser for the DQL language, which transforms the DQL statement +into an [Abstract Syntax Tree](http://en.wikipedia.org/wiki/Abstract_syntax_tree) +and generates the appropriate SQL statement for it. Since this process is +deterministic Doctrine heavily caches the SQL that is generated from any given DQL query, +which reduces the performance overhead of the parsing process to zero. + +You can modify the Abstract syntax tree by hooking into DQL parsing process +by adding a Custom Tree Walker. A walker is an interface that walks each +node of the Abstract syntax tree, thereby generating the SQL statement. + +There are two types of custom tree walkers that you can hook into the DQL parser: + +- An output walker. This one actually generates the SQL, and there is only ever one of them. We implemented the default SqlWalker implementation for it. +- A tree walker. There can be many tree walkers, they cannot generate the sql, however they can modify the AST before its rendered to sql. + +Now this is all awfully technical, so let me come to some use-cases fast +to keep you motivated. Using walker implementation you can for example: + +* Modify the AST to generate a Count Query to be used with a paginator for any given DQL query. +* Modify the Output Walker to generate vendor-specific SQL (instead of ANSI). +* Modify the AST to add additional where clauses for specific entities (example ACL, country-specific content...) +* Modify the Output walker to pretty print the SQL for debugging purposes. + +In this cookbook-entry I will show examples on the first two points. There +are probably much more use-cases. + +## Generic count query for pagination + +Say you have a blog and posts all with one category and one author. A query +for the front-page or any archive page might look something like: + + [sql] + SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... + +Now in this query the blog post is the root entity, meaning its the one that +is hydrated directly from the query and returned as an array of blog posts. +In contrast the comment and author are loaded for deeper use in the object tree. + +A pagination for this query would want to approximate the number of posts that +match the WHERE clause of this query to be able to predict the number of pages +to show to the user. A draft of the DQL query for pagination would look like: + + [sql] + SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... + +Now you could go and write each of these queries by hand, or you can use a tree +walker to modify the AST for you. Lets see how the API would look for this use-case: + + [php] + $pageNum = 1; + $query = $em->createQuery($dql); + $query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20); + + $totalResults = Paginate::count($query); + $results = $query->getResult(); + +The `Paginate::count(Query $query)` looks like: + + [php] + class Paginate + { + static public function count(Query $query) + { + /* @var $countQuery Query */ + $countQuery = clone $query; + + $countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker')); + $countQuery->setFirstResult(null)->setMaxResults(null); + + return $countQuery->getSingleScalarResult(); + } + } + +It clones the query, resets the limit clause first and max results and registers the `CountSqlWalker` +customer tree walker which will modify the AST to execute a count query. The walkers +implementation is: + + [php] + class CountSqlWalker extends TreeWalkerAdapter + { + /** + * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. + * + * @return string The SQL. + */ + public function walkSelectStatement(SelectStatement $AST) + { + $parent = null; + $parentName = null; + foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) { + if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) { + $parent = $qComp; + $parentName = $dqlAlias; + break; + } + } + + $pathExpression = new PathExpression( + PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, array( + $parent['metadata']->getSingleIdentifierFieldName()) + ); + $pathExpression->type = PathExpression::TYPE_STATE_FIELD; + + $AST->selectClause->selectExpressions = array( + new SelectExpression( + new AggregateExpression('count', $pathExpression, true), null + ) + ); + } + } + +This will delete any given select expressions and replace them with a distinct count +query for the root entities primary key. This will only work if your entity has +only one identifier field (composite keys won't work). + +## Modify the Output Walker to generate Vendor specific SQL + +Most RMDBS have vendor-specific features for optimizing select query +execution plans. You can write your own output walker to introduce certain +keywords using the Query Hint API. A query hint can be set via `Query::setHint($name, $value)` +as shown in the previous example with the `HINT_CUSTOM_TREE_WALKERS` query hint. + +We will implement a custom Output Walker that allows to specifiy the SQL_NO_CACHE +query hint. + + [php] + $dql = "SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ..."; + $query = $m->createQuery($dql); + $query->setQueryHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker'); + $query->setQueryHint("mysqlWalker.sqlNoCache", true); + $results = $query->getResult(); + +Our `MysqlWalker` will extend the default `SqlWalker`. We will modify the generation +of the SELECT clause, adding the SQL_NO_CACHE on those queries that need it: + + [php] + class MysqlWalker extends SqlWalker + { + /** + * Walks down a SelectClause AST node, thereby generating the appropriate SQL. + * + * @param $selectClause + * @return string The SQL. + */ + public function walkSelectClause($selectClause) + { + $sql = parent::walkSelectClause($selectClause); + + if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) { + if ($selectClause->isDistinct) { + $sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql); + } else { + $sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql); + } + } + + return $sql; + } + } + +Writing extensions to the Output Walker requires a very deep understanding +of the DQL Parser and Walkers, but may offer your huge benefits with using vendor specific +features. This would still allow you write DQL queries instead of NativeQueries +to make use of vendor specific features. diff --git a/orm/cookbook/en/dql-user-defined-functions.txt b/orm/cookbook/en/dql-user-defined-functions.txt new file mode 100644 index 000000000..d738617c4 --- /dev/null +++ b/orm/cookbook/en/dql-user-defined-functions.txt @@ -0,0 +1,198 @@ +By default DQL supports a limited subset of all the vendor-specific SQL functions +common between all the vendors. However in many cases once you have decided on a +specific database vendor, you will never change it during the life of your project. +This decision for a specific vendor potentially allows you to make use of powerful +SQL features that are unique to the vendor. + +> **Note** +> +> It is worth to mention that Doctrine 2 also allows you to handwrite your SQL instead of extending +> the DQL parser, which is sort of an advanced extension point. You can map arbitrary SQL to your +> objects and gain access to vendor specific functionalities using the `EntityManager#createNativeQuery()` API. + +The DQL Parser has hooks to register functions that can then be used in your DQL queries and transformed into SQL, +allowing to extend Doctrines Query capabilities to the vendors strength. This post explains the +Used-Defined Functions API (UDF) of the Dql Parser and shows some examples to give you +some hints how you would extend DQL. + +There are three types of functions in DQL, those that return a numerical value, +those that return a string and those that return a Date. Your custom method +has to be registered as either one of those. The return type information +is used by the DQL parser to check possible syntax errors during the parsing +process, for example using a string function return value in a math expression. + +## Registering your own DQL functions + +You can register your functions adding them to the ORM configuration: + + [php] + $config = new \Doctrine\ORM\Configuration(); + $config->addCustomStringFunction($name, $class); + $config->addCustomNumericFunction($name, $class); + $config->addCustomDatetimeFunction($name, $class); + + $em = EntityManager::create($dbParams, $config); + +The `$name` is the name the function will be referred to in the DQL query. `$class` is a +string of a class-name which has to extend `Doctrine\ORM\Query\Node\FunctionNode`. +This is a class that offers all the necessary API and methods to implement +a UDF. + +In this post we will implement some MySql specific Date calculation methods, +which are quite handy in my opinion: + +## Date Diff + +[Mysql's DateDiff function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff) +takes two dates as argument and calculates the difference in days with `date1-date2`. + +The DQL parser is a top-down recursive descent parser to generate the +Abstract-Syntax Tree (AST) and uses a TreeWalker approach to generate the appropriate +SQL from the AST. This makes reading the Parser/TreeWalker code managable +in a finite amount of time. + +The `FunctionNode` class I referred to earlier requires you to implement +two methods, one for the parsing process (obviously) called `parse` and +one for the TreeWalker process called `getSql()`. I show you the code for +the DateDiff method and discuss it step by step: + + [php] + /** + * DateDiffFunction ::= "DATEDIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" + */ + class DateDiff extends FunctionNode + { + // (1) + public $firstDateExpression = null; + public $secondDateExpression = null; + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); // (2) + $parser->match(Lexer::T_OPEN_PARENTHESIS); // (3) + $this->firstDateExpression = $parser->ArithmeticPrimary(); // (4) + $parser->match(Lexer::T_COMMA); // (5) + $this->secondDateExpression = $parser->ArithmeticPrimary(); // (6) + $parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3) + } + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'DATEDIFF(' . + $this->firstDateExpression->dispatch($sqlWalker) . ', ' . + $this->secondDateExpression->dispatch($sqlWalker) . + ')'; // (7) + } + } + +The Parsing process of the DATEDIFF function is going to find two expressions +the date1 and the date2 values, whose AST Node representations will be saved +in the variables of the DateDiff FunctionNode instance at (1). + +The parse() method has to cut the function call "DATEDIFF" and its argument +into pieces. Since the parser detects the function using a lookahead the +T_IDENTIFIER of the function name has to be taken from the stack (2), followed +by a detection of the arguments in (4)-(6). The opening and closing parenthesis +have to be detected also. This happens during the Parsing process and leads +to the generation of a DateDiff FunctionNode somewhere in the AST of the +dql statement. + +The `ArithmeticPrimary` method call is the most common denominator of valid +EBNF tokens taken from the [DQL EBNF grammer](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf) +that matches our requirements for valid input into the DateDiff Dql function. +Picking the right tokens for your methods is a tricky business, but the EBNF +grammer is pretty helpful finding it, as is looking at the Parser source code. + +Now in the TreeWalker process we have to pick up this node and generate SQL +from it, which apprently is quite easy looking at the code in (7). Since +we don't know which type of AST Node the first and second Date expression +are we are just dispatching them back to the SQL Walker to generate SQL from +and then wrap our DATEDIFF function call around this output. + +Now registering this DateDiff FunctionNode with the ORM using: + + [php] + $config = new \Doctrine\ORM\Configuration(); + $config->addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff'); + +We can do fancy stuff like: + + [sql] + SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7 + +## Date Add + +Often useful it the ability to do some simple date calculations in your DQL query +using [MySql's DATE_ADD function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add). + +I'll skip the bla and show the code for this function: + + [php] + /** + * DateAddFunction ::= + * "DATE_ADD" "(" ArithmeticPrimary ", INTERVAL" ArithmeticPrimary Identifier ")" + */ + class DateAdd extends FunctionNode + { + public $firstDateExpression = null; + public $intervalExpression = null; + public $unit = null; + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->firstDateExpression = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_COMMA); + $parser->match(Lexer::T_IDENTIFIER); + + $this->intervalExpression = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_IDENTIFIER); + + /* @var $lexer Lexer */ + $lexer = $parser->getLexer(); + $this->unit = $lexer->token['value']; + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'DATE_ADD(' . + $this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' . + $this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit . + ')'; + } + } + +The only difference compared to the DATEDIFF here is, we additionally need the `Lexer` to access +the value of the `T_IDENTIFIER` token for the Date Interval unit, for example the MONTH in: + + [sql] + SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created + +The above method now only supports the specification using `INTERVAL`, to also +allow a real date in DATE_ADD we need to add some decision logic to the parsing +process (makes up for a nice excercise). + +Now as you see, the Parsing process doesn't catch all the possible SQL errors, +here we don't match for all the valid inputs for the interval unit. +However where necessary we rely on the database vendors SQL parser to show us further errors +in the parsing process, for example if the Unit would not be one of the supported values +by MySql. + +## Conclusion + +Now that you all know how you can implement vendor specific SQL functionalities in DQL, +we would be excited to see user extensions that add vendor specific function packages, +for example more math functions, XML + GIS Support, Hashing functions and so on. + +For 2.0 we will come with the current set of functions, however for a future +version we will re-evaluate if we can abstract even more vendor sql functions +and extend the DQL languages scope. + +Code for this Extension to DQL and other Doctrine Extensions can be found +[in my Github DoctrineExtensions repository](http://github.com/beberlei/DoctrineExtensions). \ No newline at end of file diff --git a/orm/cookbook/en/getting-started-xml-edition.txt b/orm/cookbook/en/getting-started-xml-edition.txt new file mode 100644 index 000000000..b9a9632d1 --- /dev/null +++ b/orm/cookbook/en/getting-started-xml-edition.txt @@ -0,0 +1,704 @@ +Doctrine 2 is a project that aims to handle the persistence of the domain model in a non-interfering way. +The Data Mapper pattern is at the heart of this project, aiming for a complete separation of the domain/business logic +from the persistence in a relational database management system. The benefit of Doctrine for the programmer is the +possibility can focus soley on the business and worry about persistence only as a secondary task. This doesn't mean +persistence is not important to Doctrine 2, however it is our believe that there are considerable benefits for object-oriented +programming, if persistence and entities are kept perfectly seperated. + +## What are Entities? + +Entities are leightweight PHP Objects that don't need to extend any abstract base class or interface. +An entity class must not be final or contain final methods. Additionally it must not implement __clone +nor __wakeup or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone). + +An entity contains persistable properties. A persistable property is an instance variable of the entity +that contains the data which is persisted and retrieved by Doctrine's data mapping capabilities. + +## An Example Model: Bug Tracker + +For this Getting Started Guide for Doctrine we will implement the Bug Tracker domain model from the [Zend_Db_Table](http://framework.zend.com/manual/en/zend.db.table.html) +documentation. Reading that documentat we can extract the requirements to be: + +* A Bugs has a description, creation date, status, reporter and engineer +* A bug can occour on different products (platforms) +* Products have a name. +* Bug Reporter and Engineers are both Users of the System. +* A user can create new bugs. +* The assigned engineer can close a bug. +* A user can see all his reported or assigned bugs. +* Bugs can be paginated through a list-view. + +> **WARNING** +> +> This tutorial is incrementally building up your Doctrine 2 knowledge and even lets you make some mistakes, to +> show some common pitfalls in mapping Entities to a database. Don't blindly copy-paste the examples here, it +> is not production ready without the additional comments and knowledge this tutorial teaches. + +## A first prototype + +A first simplified design for this domain model might look like the following set of classes: + + [php] + class Bug + { + public $id; + public $description; + public $created; + public $status; + public $products = array(); + public $reporter; + public $engineer; + } + class Product + { + public $id; + public $name; + } + class User + { + public $id; + public $name; + public $reportedBugs = array(); + public $assignedBugs = array(); + } + +> **WARNING** +> +> This is only a prototype, please don't use public properties with Doctrine 2 at all, +> the "Queries for Application Use-Cases" section shows you why. In combination with proxies +> public properties can make up for pretty nasty bugs. + +Because we will focus on the mapping aspect, no effort is being made to encapsulate the business logic in this example. +All peristable properties are public in visibility. We will soon see that this is not the best solution in combination +with Doctrine 2, one restriction that actually forces you to encapsulate your properties. For persistence Doctrine 2 +actually uses Reflection to access the values in all your entities properties. + +Many of the fields are single scalar values, for example the 3 ID fields of the entities, their names, description, +status and change dates. Doctrine 2 can easily handle these single values as can any other ORM. From a point of our +domain model they are ready to be used right now and we will see at a later stage how they are mapped to the database. + +There are also several references between objects in this domain model, whose semantics are discussed case by case at this point +to explain how Doctrine handles them. In general each OneToOne or ManyToOne Relation in the Database is replaced by an +instance of the related object in the domain model. Each OneToMany or ManyToMany Relation is replaced by a collection +of instances in the domain model. + +If you think this through carefully you realize Doctrine 2 will load up the complete database in memory if you access +one object. However by default Doctrine generates Lazy Load proxies of entities or collections of all the relations +that haven't been explicitly retrieved from the database yet. + +To be able to use lazyload with collections, simple PHP arrays have to be replaced by a generic collection +interface Doctrine\Common\Collections\Collection which tries to act as array as much as possible using ArrayAccess, +IteratorAggregate and Countable interfaces. The class \Doctrine\Common\Collections\ArrayCollection is the most simple +implementation of this interface. + +Now that we know this, we have to clear up our domain model to cope with the assumptions about related collections: + + [php] + use Doctrine\Common\Collections\ArrayCollection; + + class Bug + { + public $products = null; + + public function __construct() + { + $this->products = new ArrayCollection(); + } + } + + class User + { + public $reportedBugs = null; + public $assignedBugs = null; + + public function __construct() + { + $this->reportedBugs = new ArrayCollection(); + $this->assignedBugs = new ArrayCollection(); + } + } + +Whenever an entity is recreated from the database, an Collection implementation of the type +Doctrine\ORM\PersistantCollection is injected into your entity instead of an array. Compared +to the ArrayCollection this implementation helps the Doctrine ORM understand the changes that +have happend to the collection which are noteworthy for persistence. + +> **Warning** +> Lazy load proxies always contain an instance of Doctrine's EntityManager and all its dependencies. Therefore a var_dump() +> will possibly dump a very large recursive structure which is impossible to render and read. You have to use +> `Doctrine\Common\Util\Debug::dump()` to restrict the dumping to a human readable level. Additionally you should be aware +> that dumping the EntityManager to a Browser may take several minutes, and the Debug::dump() method just ignores any +> occurences of it in Proxy instances. + +Because we only work with collections for the references we must be careful to implement a bidirectional reference in +the domain model. The concept of owning or inverse side of a relation is central to this notion and should always +be kept in mind. The following assumptions are made about relations and have to be followed to be able to work with Doctrine 2. +These assumptions are not unique to Doctrine 2 but are best practices in handling database relations and Object-Relational Mapping. + +* Changes to Collections are saved or updated, when the entity on the *ownin*g side of the collection is saved or updated. +* Saving an Entity at the inverse side of a relation never triggers a persist operation to changes to the collection. +* In a one-to-one relation the entity holding the foreign key of the related entity on its own database table is *always* the owning side of the relation. +* In a many-to-many relation, both sides can be the owning side of the relation. However in a bi-directional many-tomany relation only one is allowed to be. +* In a many-to-one relation the Many-side is the owning side by default, because it holds the foreign key. +* The OneToMany side of a relation is inverse by default, since the foreign key is saved on the Many side. A OneToMany relation can only be the owning side, if its implemented using a ManyToMany relation with join table and restricting the one side to allow only UNIQUE values per database constraint. + +> **Important** +> +> Consistency of bi-directional references on the inverse side of a relation have to be managed in userland application code. +> Doctrine cannot magically update your collections to be consistent. + +In the case of Users and Bugs we have references back and forth to the assigned and reported bugs from a user, +making this relation bi-directional. We have to change the code to ensure consistency of the bi-directional reference: + + [php] + class Bug + { + protected $engineer; + protected $reporter; + + public function setEngineer($engineer) + { + $engineer->assignedToBug($this); + $this->engineer = $engineer; + } + + public function setReporter($reporter) + { + $reporter->addReportedBug($this); + $this->reporter = $reporter; + } + + public function getEngineer() + { + return $this->engineer; + } + + public function getReporter() + { + return $this->reporter; + } + } + class User + { + public function addReportedBug($bug) + { + $this->reportedBugs[] = $bug; + } + + public function assignedToBug($bug) + { + $this->assignedBugs[] = $bug; + } + } + +I chose to name the inverse methods in past-tense, which should indicate that the actual assigning has already taken +place and the methods are only used for ensuring consistency of the references. You can see from `User::addReportedBug()` +and `User::assignedToBug()` that using this method in userland alone would not add the Bug to the collection of the owning +side in Bug::$reporter or Bug::$engineer. Using these methods and calling Doctrine for persistence would not update +the collections representation in the database. + +Only using `Bug::setEngineer()` or `Bug::setReporter()` correctly saves the relation information. We also set both collection +instance variables to protected, however with PHP 5.3's new features Doctrine is still able to use Reflection to set and get values +from protected and private properties. + +The `Bug::$reporter` and `Bug::$engineer` properties are Many-To-One relations, which point to a User. In a normalized +relational model the foreign key is saved on the Bug's table, hence in our object-relation model the Bug is at the owning +side of the relation. You should always make sure that the use-cases of your domain model should drive which side +is an inverse or owning one in your Doctrine mapping. In our example, whenever a new bug is saved or an engineer is assigned +to the bug, we don't want to update the User to persist the reference, but the Bug. +This is the case with the Bug being at the owning side of the relation. + +Bugs reference Products by a uni-directional ManyToMany relation in the database that points from from Bugs to Products. + + [php] + class Bug + { + protected $products = null; // Set protected for encapsulation + + public function assignToProduct($product) + { + $this->products[] = $product; + } + + public function getProducts() + { + return $this->products; + } + } + +We are now finished with the domain model given the requirements. From the simple model with public properties only +we had to do quite some work to get to a model where we encapsulated the references between the objects to make sure +we don't break its consistent state when using Doctrine. + +However up to now the assumptions Doctrine imposed on our business objects have not restricting us much in our domain +modelling capabilities. Actually we would have encapsulated access to all the properties anyways by using +object-oriented best-practices. + +## Metadata Mappings for our Entities + +Up to now we have only implemented our Entites as Data-Structures without actually telling Doctrine how to persist +them in the database. If perfect in-memory databases would exist, we could now finish the application using these entities +by implementing code to fullfil all the requirements. However the world isn't perfect and we have to persist our +entities in some storage to make sure we don't loose their state. Doctrine currently serves Relational Database Management Systems. +In the future we are thinking to support NoSQL vendors like CouchDb or MongoDb, however this is still far in the future. + +The next step for persistance with Doctrine is to describe the structure of our domain model entities to Doctrine +using a metadata language. The metadata language describes how entities, their properties and references should be +persisted and what constraints should be applied to them. + +Metadata for entities are loaded using a `Doctrine\ORM\Mapping\Driver\Driver` implementation and Doctrine 2 already comes +with XML, YAML and Annotations Drivers. In this Getting Started Guide I will use the XML Mapping Driver. I think XML +beats YAML because of schema validation, and my favorite IDE netbeans offers me auto-completion for the XML mapping files +which is awesome to work with and you don't have to look up all the different metadata mapping commands all the time. + +Since we haven't namespaced our three entities, we have to implement three mapping files called Bug.dcm.xml, +Product.dcm.xml and User.dcm.xml and put them into a distinct folder for mapping configurations. + +The first discussed definition will be for the Product, since it is the most simple one: + + [xml] + + + + + + + + + + + + +The toplevel `entity` definition tag specifies information about the class and table-name. The +primitive type `Product::$name` is defined as `field` attributes. The Id property is defined with the `id` tag. +The id has a `generator` tag nested inside which defines that the primary key generation mechanism +automatically uses the database platforms native id generation strategy, for example AUTO INCREMENT +in the case of MySql or Sequences in the case of PostgreSql and Oracle. + +We then go on specifying the definition of a Bug: + + [xml] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Here again we have the entity, id and primitive type definitions. +The column names are used from the Zend_Db_Table examples and have different names than the properties +on the Bug class. Additionally for the "created" field it is specified that it is of the Type "DATETIME", +which translates the YYYY-mm-dd HH:mm:ss Database format into a PHP DateTime instance and back. + +After the field definitions the two qualified references to the user entity are defined. They are created by +the `many-to-one` tag. The class name of the related entity has to be specified with the `target-entity` +attribute, which is enough information for the database mapper to access the foreign-table. The +`join-column` tags are used to specifiy how the foreign and referend columns are named, an information +Doctrine needs to construct joins between those two entities correctly. + +The last missing property is the `Bug::$products` collection. It holds all products where the specific +bug is occouring in. Again you have to define the `target-entity` and `field` attributes on the `many-to-many` +tag. Furthermore you have to specifiy the details of the many-to-many join-table and its foreign key columns. +The definition is rather complex, however relying on the XML auto-completion I got it working easily, although +I forget the schema details all the time. + +The last missing definition is that of the User entity: + + [xml] + + + + + + + + + + + + + + + + +Here are some new things to mention about the `one-to-many` tags. Remember that we discussed about +the inverse and owning side. Now both reportedBugs and assignedBugs are inverse relations, +which means the join details have already been defined on the owning side. Therefore we only +have to specify the property on the Bug class that holds the owning sides. + +This example has a fair overview of the most basic features of the metadata definition language. + +## Obtaining the EntityManager + +Doctrine's public interface is the EntityManager, it provides the access point to the complete +lifecycle management of your entities and transforms entities from and back to persistence. You +have to configure and create it to use your entities with Doctrine 2. I will show the configuration +steps and then discuss them step by step: + + [php] + // Setup Autoloader (1) + require '/path/to/lib/Doctrine/Common/ClassLoader.php'; + $loader = new Doctrine\Common\ClassLoader("Doctrine", '/path/to/Doctrine/trunk/lib/'); + $loader->register(); + + $config = new Doctrine\ORM\Configuration(); // (2) + + // Proxy Configuration (3) + $config->setProxyDir(__DIR__.'/lib/MyProject/Proxies'); + $config->setProxyNamespace('MyProject\Proxies'); + $config->setAutoGenerateProxyClasses((APPLICATION_ENV == "development")); + + // Mapping Configuration (4) + $driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings"); + $config->setMetadataDriverImpl($driverImpl); + + // Caching Configuration (5) + if (APPLICATION_ENV == "develoment") { + $cache = new \Doctrine\Common\Cache\ArayCache(); + } else { + $cache = new \Doctrine\Common\Cache\ApcCache(); + } + $config->setMetadataCacheImpl($cache); + $config->setQueryCacheImpl($cache); + + // database configuration parameters (6) + $conn = array( + 'driver' => 'pdo_sqlite', + 'path' => __DIR__ . '/db.sqlite', + ); + + // obtaining the entity manager (7) + $evm = new Doctrine\Common\EventManager() + $entityManager = \Doctrine\ORM\EntityManager::create($conn, $config, $evm); + +The first block sets up the autoloading capabilities of Doctrine. I am registering the Doctrine +namespace to the given path. To add your own namespace you can instantiate another `CloassLoader` +with different namespace and path arguments. There is no requirement to use the Doctrine `ClassLoader` +for your autoloading needs, you can use whatever suits you best. + +The second block contains of the instantiation of the ORM Configuration object. Besides the +configuration shown in the next blocks there are several others with are all explained +in the [Configuration section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options). + +The Proxy Configuration is a required block for your application, you have to specifiy where +Doctrine writes the PHP code for Proxy Generation. Proxies are children of your entities generated +by Doctrine to allow for type-safe lazy loading. We will see in a later chapter how exactly this works. +Besides the path to the proxies we also specifiy which namespace they will reside under aswell as +a flag `autoGenerateProxyClasses` indicating that proxies should be re-generated on each request, +which is recommended for development. In production this should be prevented at all costs, +the proxy class generation can be quite costly. + +The fourth block contains the mapping driver details. We will use XML Mapping in this example, so +we configure the `XmlDriver` instance with a path to mappings configuration folder where we put +the Bug.dcm.xml, Product.dcm.xml and User.dcm.xml. + +In the 5th block the caching configuration is set. In production we use caching only on a per request-basis +using the ArrayCache. In production it is literally required to use Apc, Memcache or XCache to get the full +speed out of Doctrine. Internally Doctrine uses caching heavily for the Metadata and DQL Query Language +so make sure you use a caching mechanism. + +The 6th block shows the configuration options required to connect to a database, in my case a file-based +sqlite database. All the configuration options for all the shipped drivers are given in the [DBAL Configuration +section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/dbal). + +The last block shows how the `EntityManager` is obtained from a factory method, Here we also pass +in an `EventManager` instance which is optional. However using the EventManager you can hook in to the lifecycle +of entities, which is a common use-case, so you know how to configure it already. + +## Generating the Database Schema + +Now that we have defined the Metadata Mappings and bootstrapped the EntityManager +we want to generate the relational database schema from it. +Doctrine has a Command-Line-Interface that allows you to access the SchemaTool, a component that generates +the required tables to work with the metadata. + +For the commandline tool to work a cli-config.php file has to be present in the project root directry, +where you will execute the doctrine command. Its a fairly simple file: + + [php] + $cliConfig = new Doctrine\Common\Cli\Configuration(); + $cliConfig->setAttribute('em', $entityManager); + +You can then use your favorite console tool to call: + + [console] + doctrine@my-desktop> cd myproject/ + doctrine@my-desktop> doctrine orm:schema-tool --create + +During the development you probably need to re-create the database several times when changing the Entity +metadata. You can then either re-create the database, or use the update functionality: + + [console] + doctrine@my-desktop> doctrine orm:schema-tool --re-create + doctrine@my-desktop> doctrine orm:schema-tool --update + +The updating of databases uses a Diff Algorithm for a given Database Schema, a cornerstone of the `Doctrine\DBAL` +package, which can even be used without the Doctrine ORM package. However its not available in SQLite since +it does not support ALTER TABLE. + +## Writing Entities into the Database + +Having created the schema we can now start and save entities in the database. For starters we need a create user use-case: + + [php] + $newUsername = "beberlei"; + + $user = new User(); + $user->name = $newUsername; + + $entityManager->persist($user); + $entityManager->flush(); + +Having a user, he can create products: + + [php] + $newProductName = "My Product"; + + $product = new Product(); + $product->name = $newProductName; + + $entityManager->persist($product); + $entityManager->flush(); + +So what is happening in those two snippets? In both examples the class creation is pretty standard, the interesting +bits are the communication with the `EntityManager`. To notify the EntityManager that a new entity should be inserted +into the database you have to call `persist()`. However the EntityManager does not act on this, its merely notified. +You have to explicitly call `flush()` to have the EntityManager write those two entities to the database. + +You might wonder why does this distinction between persist notification and flush exist? Doctrine 2 uses the +UnitOfWork pattern to aggregate all writes (INSERT, UDPATE, DELETE) into one single fast transaction, which +is executed when flushing. This way the write-performance is unbelievable fast. Also in more complex scenarios +than those two before you can request updates on many different entities and all flush them at once. + +Doctrine's UnitOfWork even detects entities that have been retrieved from the database and changed when calling +flush, so that you only have to keep track of those entities that are new or to be removed and pass them to +`EntityManager#persist()` and `EntityManager#remove()` respectively. + +We are now getting to the "Create a New Bug" requirement and the code for this scenario may look like this: + + [php] + $reporter = $entityManager->find("User", $theReporterId); + $engineer = $entityManager->find("User", $theDefaultEngineerId); + + $bug = new Bug(); + $bug->description = "Something does not work!"; + $bug->created = new DateTime("now"); + $bug->status = "NEW"; + + foreach ($productIds AS $productId) { + $product = $entityManager->find("Product", $productId); + $bug->assignToProduct($product); + } + + $bug->setReporter($reporter); + $bug->setEngineer($engineer); + + $entityManager->persist($bug); + $entityManager->flush(); + + echo "Your new Bug Id: ".$bug->id."\n"; + +This is the first contact with the read API of the EntityManager, showing that a call to +`EntityManager#find($name, $id)` returns a single instance of an entity queried by primary key. +Besides this we see the persist + flush pattern again to save the Bug into the database. + +See how simple relating Bug, Reporter, Engineer and Products is done by using the discussed methods +in the "A first prototype" section. The UnitOfWork will detect this relations when flush +is called and relate them in the database appropriately. + +## Queries for Application Use-Cases + +Using the previous examples we can fill up the database quite a bit, however we now need to discuss how to query the underlying +mapper for the required view representations. When opening the application, bugs can be paginated through a list-view, which is the first +read-only use-case: + + [php] + $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC"; + + $query = $entityManager->createQuery($dql); + $query->setMaxResults(30); + $bugs = $query->getResult(); + + foreach($bugs AS $bug) { + echo $bug->description." - ".$bug->created->format('d.m.Y')."\n"; + echo " Reported by: ".$bug->getReporter()->name."\n"; + echo " Assigned to: ".$bug->getEngineer()->name."\n"; + foreach($bug->getProducts() AS $product) { + echo " Platform: ".$product->name."\n"; + } + echo "\n"; + } + +The DQL Query in this example fetches the 30 most recent bugs with their respective engineer and reporter +in one single SQL statement. The console output of this script is then: + + Something does not work! - 02.04.2010 + Reported by: beberlei + Assigned to: beberlei + Platform: My Product + +> **NOTE** +> +> **Dql is not Sql** +> +> You may wonder why we start writing SQL at the beginning of this use-case. Don't we use an ORM to get rid +> of all the endless hand-writing of SQL? Doctrine introduces DQL which is best described +> as **object-query-language** and is a dialect of [OQL](http://en.wikipedia.org/wiki/Object_Query_Language) and +> similar to [HQL](http://www.hibernate.org) or [JPQL](http://en.wikipedia.org/wiki/Java_Persistence_Query_Language). +> It does not know the concept of columns and tables, but only those +> of Entity-Class and property. Using the Metadata we defined before it allows for very short distinctive +> and powerful queries. +> +> An important reason why DQL is favourable to the Query API of most ORMs is its similarity to SQL. The DQL language +> allows query constructs that most ORMs don't, GROUP BY even with HAVING, Subselects, Fetch-Joins of nested +> classes, mixed results with entities and scalar data such as COUNT() results and much more. Using +> DQL you should seldom come to the point where you want to throw your ORM into the dumpster, because it +> doesn't support some the more powerful SQL concepts. +> +> Besides handwriting DQL you can however also use the `QueryBuilder` retrieved by calling `$entityManager->createQueryBuilder()` +> which is a Query Object around the DQL language. +> +> As a last resort you can however also use Native SQL and a description of the result set to retrieve +> entities from the database. DQL boils down to a Native SQL statement and a `ResultSetMapping` instance itself. +> Using Native SQL you could even use stored procedures for data retrieval, or make use of advanced non-portable +> database queries like PostgreSql's recursive queries. + +The next Use-Case is displaying a Bug by primary key. This could be done using DQL as in the previous example with a where clause, +however there is a convenience method on the Entity Manager that handles loading by primary key, which we have already +seen in the write scenarios: + + [php] + $bug = $entityManager->find("Bug", (int)$theBugId); + +However we will soon see another problem with our entities using this approach. Try displaying the engineer's name: + + [php] + echo "Bug: ".$bug->description."\n"; + echo "Engineer: ".$bug->getEngineer()->name."\n"; + +It will be null! What is happening? It worked in the previous example, so it can't be a problem with the persistance +code of Doctrine. You walked in the public property trap. Since we only retrieved the bug by primary key both the +engineer and reporter are not immediately loaded from the database but are replaced by LazyLoading proxies. Sample +code of this proxy generated code can be found in the specified Proxy Directory, it looks like: + + [php] + namespace MyProject\Proxies; + + /** + * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. + */ + class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy + { + // .. lazy load code here + + public function addReportedBug($bug) + { + $this->_load(); + return parent::addReportedBug($bug); + } + + public function assignedToBug($bug) + { + $this->_load(); + return parent::assignedToBug($bug); + } + } + +See how upon each method call the proxy is lazily loaded from the database? Using public properties however +we never call a method and Doctrine has no way to hook into the PHP Engine to detect this access and trigger +the lazy load. We need to revise our entities, make all the properties private or protected and add getters +and setters to get a working example: + + [php] + echo "Bug: ".$bug->getDescription()."\n"; + echo "Engineer: ".$bug->getEngineer()->getName()."\n"; + + /** + Bug: Something does not work! + Engineer: beberlei + */ + +For the next use-case we want to retrieve the dashboard view, a list of all open bugs the user reported or +was assigned to. This will be achieved using DQL again, this time with some WHERE clauses and usage of bound parameters: + + [php] + $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ". + "WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC"; + + $myBugs = $entityManager->createQuery($dql) + ->setParameter(1, $theUserId) + ->setMaxResults(15) + ->getResult(); + + foreach ($myBugs AS $bug) { + echo $bug->getDescription()."\n"; + } + +That is it for the read-scenarios of this example, we will continue with the last missing bit, engineers +being able to close a bug. + +## Updating Entities + +There is a single use-case missing from the requirements, Engineers should be able to close a bug. This looks like: + + [php] + $bug = $entityManager->find("Bug", (int)$theBugId); + $bug->close(); + + $entityManager->flush(); + +When retrieving the Bug from the database it is inserted into the IdentityMap inside the UnitOfWork of Doctrine. +This means your Bug with exactly this id can only exist once during the whole request no matter how often you +call `EntityManager#find()`. It even detects entities that are hydrated using DQL and are already present in +the Identity Map. + +When flush is called the EntityManager loops over all the entities in the identity map and performs a comparison +between the values originally retrieved from the database and those values the entity currently has. If at +least one of these properties is different the entity is scheduled for an UPDATE against the database. +Only the changed columns are updated, which offers a pretty good performance improvement compared to updating +all the properties. + +This tutorial is over here, I hope you had fun. Additional content will be added to this tutorial +incrementally, topics will include: + + * Entity Repositories + * More on Association Mappings + * Lifecycle Events triggered in the UnitOfWork + * Ordering of Collections + +Additional details on all the topics discussed here can be found in the respective manual chapters. diff --git a/orm/cookbook/en/implementing-arrayaccess-for-domain-objects.txt b/orm/cookbook/en/implementing-arrayaccess-for-domain-objects.txt new file mode 100644 index 000000000..4f295420d --- /dev/null +++ b/orm/cookbook/en/implementing-arrayaccess-for-domain-objects.txt @@ -0,0 +1,89 @@ + +This recipe will show you how to implement ArrayAccess for your domain objects in order to allow more uniform access, for example in templates. In these examples we will implement ArrayAccess on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects. + +++ Option 1 + +In this implementation we will make use of PHPs highly dynamic nature to dynamically access properties of a subtype in a supertype at runtime. Note that this implementation has 2 main caveats: + +* It will not work with private fields +* It will not go through any getters/setters + +- + + [php] + abstract class DomainObject implements ArrayAccess + { + public function offsetExists($offset) { + return isset($this->$offset); + } + + public function offsetSet($offset, $value) { + $this->$offset = $value; + } + + public function offsetGet($offset) { + return $this->$offset; + } + + public function offsetUnset($offset) { + $this->$offset = null; + } + } + + +++ Option 2 + +In this implementation we will dynamically invoke getters/setters. Again we use PHPs dynamic nature to invoke methods on a subtype from a supertype at runtime. This implementation has the following caveats: + +* It relies on a naming convention +* The semantics of offsetExists can differ +* offsetUnset will not work with typehinted setters + +- + + [php] + abstract class DomainObject implements ArrayAccess + { + public function offsetExists($offset) { + // In this example we say that exists means it is not null + $value = $this->{"get$offset"}(); + return $value !== null; + } + + public function offsetSet($offset, $value) { + $this->{"set$offset"}($value); + } + + public function offsetGet($offset) { + return $this->{"get$offset"}(); + } + + public function offsetUnset($offset) { + $this->{"set$offset"}(null); + } + } + +++ Read-only + +You can slightly tweak option 1 or option 2 in order to make array access read-only. This will also circumvent some of the caveats of each option. Simply make offsetSet and offsetUnset throw an exception (i.e. BadMethodCallException). + + [php] + abstract class DomainObject implements ArrayAccess + { + public function offsetExists($offset) { + // option 1 or option 2 + } + + public function offsetSet($offset, $value) { + throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!"); + } + + public function offsetGet($offset) { + // option 1 or option 2 + } + + public function offsetUnset($offset) { + throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!"); + } + } + diff --git a/orm/cookbook/en/implementing-the-notify-changetracking-policy.txt b/orm/cookbook/en/implementing-the-notify-changetracking-policy.txt new file mode 100644 index 000000000..025375487 --- /dev/null +++ b/orm/cookbook/en/implementing-the-notify-changetracking-policy.txt @@ -0,0 +1,48 @@ + +The NOTIFY changetracking policy is the most effective changetracking policy provided by Doctrine but it requires some boilerplate code. This recipe will show you how this boilerplate code should look like. We will implement it on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects. + +++ Implementing NotifyPropertyChanged + +The NOTIFY policy is based on the assumption that the entities notify interested listeners of changes to their properties. For that purpose, a class that wants to use this policy needs to implement the `NotifyPropertyChanged` interface from the `Doctrine\Common` namespace. + + [php] + use Doctrine\Common\NotifyPropertyChanged, + Doctrine\Common\PropertyChangedListener; + + abstract class DomainObject implements NotifyPropertyChanged + { + private $_listeners = array(); + + public function addPropertyChangedListener(PropertyChangedListener $listener) { + $this->_listeners[] = $listener; + } + + /** Notifies listeners of a change. */ + protected function _onPropertyChanged($propName, $oldValue, $newValue) { + if ($this->_listeners) { + foreach ($this->_listeners as $listener) { + $listener->propertyChanged($this, $propName, $oldValue, $newValue); + } + } + } + } + +Then, in each property setter of concrete, derived domain classes, you need to invoke _onPropertyChanged as follows to notify listeners: + + [php] + // Mapping not shown, either in annotations, xml or yaml as usual + class MyEntity extends DomainObject + { + private $data; + // ... other fields as usual + + public function setData($data) { + if ($data != $this->data) { // check: is it actually modified? + $this->_onPropertyChanged('data', $this->data, $data); + $this->data = $data; + } + } + } + +The check whether the new value is different from the old one is not mandatory but recommended. That way you can avoid unnecessary updates and also have full control over when you consider a property changed. + diff --git a/orm/cookbook/en/implementing-wakeup-or-clone.txt b/orm/cookbook/en/implementing-wakeup-or-clone.txt new file mode 100644 index 000000000..efdefa7c3 --- /dev/null +++ b/orm/cookbook/en/implementing-wakeup-or-clone.txt @@ -0,0 +1,63 @@ + +As explained in the [restrictions for entity classes in the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities), +it is usually not allowed for an entity to implement `__wakeup` or `__clone`, because Doctrine +makes special use of them. However, it is quite easy to make use of these methods in a safe way +by guarding the custom wakeup or clone code with an entity identity check, as demonstrated in the following sections. + +++ Safely implementing __wakeup + +To safely implement `__wakeup`, simply enclose your implementation code in an identity check +as follows: + + [php] + class MyEntity + { + private $id; // This is the identifier of the entity. + //... + + public function __wakeup() + { + // If the entity has an identity, proceed as normal. + if ($this->id) { + // ... Your code here as normal ... + } + // otherwise do nothing, do NOT throw an exception! + } + + //... + } + +++ Safely implementing __clone + +Safely implementing `__clone` is pretty much the same: + + [php] + class MyEntity + { + private $id; // This is the identifier of the entity. + //... + + public function __clone() + { + // If the entity has an identity, proceed as normal. + if ($this->id) { + // ... Your code here as normal ... + } + // otherwise do nothing, do NOT throw an exception! + } + + //... + } + +++ Summary + +As you have seen, it is quite easy to safely make use of `__wakeup` and `__clone` in your entities +without adding any really Doctrine-specific or Doctrine-dependant code. + +These implementations are possible and safe because when Doctrine invokes these methods, +the entities never have an identity (yet). Furthermore, it is possibly a good idea to check +for the identity in your code anyway, since it's rarely the case that you want to unserialize +or clone an entity with no identity. + + + diff --git a/orm/cookbook/en/integrating-with-codeigniter.txt b/orm/cookbook/en/integrating-with-codeigniter.txt new file mode 100644 index 000000000..23165176a --- /dev/null +++ b/orm/cookbook/en/integrating-with-codeigniter.txt @@ -0,0 +1,104 @@ +This is recipe for using Doctrine 2 in your [CodeIgniter](http://www.codeigniter.com) framework. + +Here is how to set it up: + +Make a CodeIgniter library that is both a wrapper and a bootstrap for Doctrine 2. + +++ Setting up the file structure + +Here are the steps: + +* Add a php file to your system/application/libraries folder called Doctrine.php. This is going to be your wrapper/bootstrap for the D2 entity manager. +* Put the Doctrine folder (the one that contains Common, DBAL, and ORM) inside that same libraries folder. +* Your system/application/libraries folder now looks like this: + + system/applications/libraries + -Doctrine + -Doctrine.php + -index.html + +* If you want, open your config/autoload.php file and autoload your Doctrine library. + + [php] + $autoload['libraries'] = array('doctrine'); + +++ Creating your Doctrine CodeIgniter library + +Now, here is what your Doctrine.php file should look like. Customize it to your needs. + + [php] + use Doctrine\Common\ClassLoader, + Doctrine\ORM\Configuration, + Doctrine\ORM\EntityManager, + Doctrine\Common\Cache\ArrayCache, + Doctrine\DBAL\Logging\EchoSqlLogger; + + class Doctrine { + + public $em = null; + + public function __construct() + { + // load database configuration from CodeIgniter + require_once APPPATH.'config/database.php'; + + // Set up class loading. You could use different autoloaders, provided by your favorite framework, + // if you want to. + require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php'; + + $doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries'); + $doctrineClassLoader->register(); + $entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" )); + $entitiesClassLoader->register(); + $proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies'); + $proxiesClassLoader->register(); + + // Set up caches + $config = new Configuration; + $cache = new ArrayCache; + $config->setMetadataCacheImpl($cache); + $config->setQueryCacheImpl($cache); + + // Proxy configuration + $config->setProxyDir(APPPATH.'/models/proxies'); + $config->setProxyNamespace('Proxies'); + + // Set up logger + $logger = new EchoSqlLogger; + $config->setSqlLogger($logger); + + $config->setAutoGenerateProxyClasses( TRUE ); + + // Database connection information + $connectionOptions = array( + 'driver' => 'pdo_mysql', + 'user' => $db['default']['username'], + 'password' => $db['default']['password'], + 'host' => $db['default']['hostname'], + 'dbname' => $db['default']['database'] + ); + + // Create EntityManager + $this->em = EntityManager::create($connectionOptions, $config); + } + } + +Please note that this is a development configuration; for a production system you'll want to use a real caching system like APC, get rid of EchoSqlLogger, and turn off autoGenerateProxyClasses. + +For more details, consult the [Doctrine 2 Configuration documentation](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options). + +++ Now to use it + +Whenever you need a reference to the entity manager inside one of your controllers, views, or models you can do this: + + [php] + $em = $this->doctrine->em; + +That's all there is to it. Once you get the reference to your EntityManager do your Doctrine 2.0 voodoo as normal. + +Note: If you do not choose to autoload the Doctrine library, you will need to put this line before you get a reference to it: + + [php] + $this->load->library('doctrine'); + +Good luck! diff --git a/orm/cookbook/en/validation-of-entities.txt b/orm/cookbook/en/validation-of-entities.txt new file mode 100644 index 000000000..739a608d2 --- /dev/null +++ b/orm/cookbook/en/validation-of-entities.txt @@ -0,0 +1,114 @@ +Doctrine 2 does not ship with any internal validators, the reason being that +we think all the frameworks out there already ship with quite decents ones that can be integrated +into your Domain easily. What we offer are hooks to execute any kind of validation. + +> **Note** +> You don't need to validate your entities in the lifecycle events. Its only +> one of many options. Of course you can also perform validations in value setters +> or any other method of your entities that are used in your code. + +Entities can register lifecycle evnt methods with Doctrine that are called on +different occasions. For validation we would need to hook into the +events called before persisting and updating. Even though we don't support +validation out of the box, the implementation is even simpler than in Doctrine 1 +and you will get the additional benefit of being able to re-use your validation +in any other part of your domain. + +Say we have an `Order` with several `OrderLine` instances. We never want to +allow any customer to order for a larger sum than he is allowed to: + + [php] + class Order + { + public function assertCustomerAllowedBuying() + { + $orderLimit = $this->customer->getOrderLimit(); + + $amount = 0; + foreach ($this->orderLines AS $line) { + $amount += $line->getAmount(); + } + + if ($amount > $orderLimit) { + throw new CustomerOrderLimitExceededException(); + } + } + } + +Now this is some pretty important piece of business logic in your code, enforcing +it at any time is important so that customers with a unknown reputation don't +owe your business too much money. + +We can enforce this constraint in any of the metadata drivers. First Annotations: + + [php] + /** + * @Entity + * @HasLifecycleCallbacks + */ + class Order + { + /** + * @PrePersist @PreUpdate + */ + public function assertCustomerAllowedBuying() {} + } + +In XML Mappings: + + [xml] + + + + + + + + + +YAML needs some little change yet, to allow multiple lifecycle events for one method, +this will happen before Beta 1 though. + +Now validation is performed whenever you call `EntityManager#persist($order)` +or when you call `EntityManager#flush()` and an order is about to be updated. +Any Exception that happens in the lifecycle callbacks will be catched by the +EntityManager and the current transaction is rolled back. + +Of course you can do any type of primitive checks, not null, email-validation, string size, +integer and date ranges in your validation callbacks. + + [php] + class Order + { + /** + * @PrePersist @PreUpdate + */ + public function validate() + { + if (!($this->plannedShipDate instanceof DateTime)) { + throw new ValidateException(); + } + + if ($this->plannedShipDate->format('U') < time()) { + throw new ValidateException(); + } + + if ($this->customer == null) { + throw new OrderRequiresCustomerException(); + } + } + } + +What is nice about lifecycle events is, you can also re-use the methods at other places +in your domain, for example in combination with your form library. +Additionally there is no limitation in the number of methods you register +on one particular event, i.e. you can register multiple methods for validation in "PrePersist" +or "PreUpdate" or mix and share them in any combinations between those two events. + +There is no limit to what you can and can't validate in "PrePersist" and "PreUpdate" aslong as +you don't create new entity instances. This was already discussed in the previous +blog post on the Versionable extension, which requires another type of event called "onFlush". + +Further readings: + +* [Doctrine 2 Manual: Events](http://www.doctrine-project.org/documentation/manual/2_0/en/events#lifecycle-events) \ No newline at end of file