diff --git a/en/reference/configuration.rst b/en/reference/configuration.rst index 27a54c6fe..178ff9bb4 100644 --- a/en/reference/configuration.rst +++ b/en/reference/configuration.rst @@ -43,17 +43,29 @@ different types of Doctrine Installations: the following code in. Something like a ``test.php`` file. -PEAR or Tarball Download -^^^^^^^^^^^^^^^^^^^^^^^^ +PEAR +^^^^ .. code-block:: php register(); // register on SPL autoload stack + require 'Doctrine/ORM/Tools/Setup.php'; + + Doctrine\ORM\Tools\Setup::registerAutoloadPEAR(); + +Tarball Download +^^^^^^^^^^^^^^^^ + +.. code-block:: php + + register(); - - $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\DBAL', $lib . 'vendor/doctrine-dbal/lib'); - $classLoader->register(); - - $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\ORM', $lib); - $classLoader->register(); + $lib = '/path/to/doctrine2-orm-root'; + Doctrine\ORM\Tools\Setup::registerAutoloadGit($lib); + Additional Symfony Components ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you don't use Doctrine2 in combination with Symfony2 you have to -register an additional namespace to be able to use the Doctrine-CLI -Tool or the YAML Mapping driver: - -.. code-block:: php - - register(); - - // Git Setup - $classloader = new \Doctrine\Common\ClassLoader('Symfony', $lib . 'vendor/'); - $classloader->register(); - -For best class loading performance it is recommended that you keep -your include\_path short, ideally it should only contain the path -to the PEAR libraries, and any other class libraries should be -registered with their full base path. +All three autoloading setups described above also take care of +the autoloading of the Symfony Console and YAML component, +which are optional dependencies for Doctrine 2. Obtaining an EntityManager ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -108,9 +97,10 @@ Once you have prepared the class loading, you acquire an *EntityManager* instance. The EntityManager class is the primary access point to ORM functionality provided by Doctrine. -A simple configuration of the EntityManager requires a +The configuration of the EntityManager requires a ``Doctrine\ORM\Configuration`` instance as well as some database -connection parameters: +connection parameters. This example shows all the potential +steps of configuration. .. code-block:: php @@ -161,6 +151,35 @@ connection parameters: fast in-memory cache storage that you can use for the metadata and query caches as seen in the previous code snippet. +Configuration Shortcuts +~~~~~~~~~~~~~~~~~~~~~~~ + +The above example is a complete setup of the required options for Doctrine. +You can have this step of your code much simpler and use one of the predefined +setup methods: + +.. code-block:: php + + flush()`` if you know that unique constraint failures can occur. -In `Symfony 2`_ for example there is a Unique Entity Validator +In `Symfony 2 `_ for example there is a Unique Entity Validator to achieve this task. For collections you can check with ``$collection->contains($entity)`` if an entity is already @@ -128,7 +128,7 @@ Can I use Inheritance with Doctrine 2? Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2. -See the documentation chapter on :doc:`inheritance mapping `_ for +See the documentation chapter on :doc:`inheritance mapping ` for the details. Why does Doctrine not create proxy objects for my inheritance hierachy? diff --git a/en/reference/introduction.rst b/en/reference/introduction.rst index e832766c8..fcc8c4917 100644 --- a/en/reference/introduction.rst +++ b/en/reference/introduction.rst @@ -136,9 +136,9 @@ the ``ClassLoader`` with the following code. .. code-block:: php register(); + require 'Doctrine/ORM/Tools/Setup.php'; + + Doctrine\ORM\Tools\Setup\registerAutoloadPEAR(); The packages are installed in to your shared PEAR PHP code folder in a folder named ``Doctrine``. You also get a nice command line diff --git a/en/tutorials/getting-started-xml-edition.rst b/en/tutorials/getting-started-xml-edition.rst index 24ea4ab67..ef509d710 100644 --- a/en/tutorials/getting-started-xml-edition.rst +++ b/en/tutorials/getting-started-xml-edition.rst @@ -12,6 +12,10 @@ belief that there are considerable benefits for object-oriented programming if persistence and entities are kept perfectly separated. +.. note:: + + The code of this tutorial is `available on Github `_. + What are Entities? ------------------ @@ -55,54 +59,105 @@ requirements to be: copy-paste the examples here, it is not production ready without the additional comments and knowledge this tutorial teaches. +Setup Project +------------- + +Make sure you get Doctrine from PEAR by calling the following commands +on your CLI: + +:: + + $ pear channel-discover pear.doctrine-project.org + $ pear install doctrine/DoctrineORM --all-deps + +This should install the packages DoctrineCommon, DoctrineDBAL, DoctrineORM, +SymfonyConsole and SymfonyYAML. + +Now create a new directory for this tutorial project: + +:: + + $ mkdir project + $ cd project + +You can prepare the directory structure so that in the end it looks like: + +:: + + project + |-- config + | |-- xml + | `-- yaml + `-- entities A first prototype ----------------- A first simplified design for this domain model might look like the -following set of classes: +following set of classes. Put them into `entities/Bug.php`, +`entities/Product.php` and `entities/User.php` respectively. .. code-block:: php id; + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } } class User { - public $id; - public $name; - public $reportedBugs = array(); - public $assignedBugs = array(); + protected $id; + public $name; // public for educational purpose, see below + + public function getId() + { + return $this->id; + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } } .. 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. + Properties should never be public when working when using Doctrine. + This will lead to bugs with the way lazy loading works in Doctrine. - -Because we will focus on the mapping aspect, no effort is being -made to encapsulate the business logic in this example. All -persistable 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. +You see that all properties have getters and setters except `$id`. +Doctrine 2 uses Reflection to access the values in all your entities properties, so it +is possible to set the `$id` value for Doctrine, however not from +your application code. The use of reflection by Doctrine allows you +to completely encapsulate state and state changes in your entities. Many of the fields are single scalar values, for example the 3 ID fields of the entities, their names, description, status and change @@ -111,13 +166,14 @@ 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 +We will soon add references between objects in this domain +model. The semantics are discussed case by case 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. +domain model. You never have to work with the foreign keys, only +with objects that represent the foreign key through their own identity. If you think this through carefully you realize Doctrine 2 will load up the complete database in memory if you access one object. @@ -137,22 +193,27 @@ with the assumptions about related collections: .. code-block:: php products = new ArrayCollection(); } } - + +.. code-block:: php + + reporter; } } + +.. code-block:: php + + - + - + .. code-block:: yaml + # config/yaml/Product.dcm.yml Product: type: entity table: products @@ -422,6 +496,7 @@ We then go on specifying the definition of a Bug: .. code-block:: php - + @@ -597,6 +676,7 @@ The last missing definition is that of the User entity: .. code-block:: yaml + # config/xml/User.dcm.yml User: type: entity table: users @@ -639,86 +719,75 @@ step: .. code-block:: php ` for up to date autoloading details. + use Doctrine\ORM\Tools\Setup; + + require_once "Doctrine/ORM/Tools/Setup.php"; + Setup::registerAutoloadPEAR(); + + // Create a simple "default" Doctrine ORM configuration for XML Mapping + $isDevMode = true; + $config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode); + // or if you prefer yaml or annotations + //$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/entities"), $isDevMode); + //$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode); - $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/xml"); - //$driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings/yml"); - //$driverImpl = $config->newDefaultAnnotationDriver(__DIR__."/entities"); - $config->setMetadataDriverImpl($driverImpl); - - // Caching Configuration (5) - if (APPLICATION_ENV == "development") { - $cache = new \Doctrine\Common\Cache\ArrayCache(); - } else { - $cache = new \Doctrine\Common\Cache\ApcCache(); - } - $config->setMetadataCacheImpl($cache); - $config->setQueryCacheImpl($cache); - - // database configuration parameters (6) + // database configuration parameters $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); + // obtaining the entity manager + $entityManager = \Doctrine\ORM\EntityManager::create($conn, $config); -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 ``ClassLoader`` 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 first block sets up the autoloading capabilities of Doctrine. +We assume here that you have installed Doctrine using PEAR. +See :doc:`Configuration <../reference/configuration>` for more details +on other installation procedures. 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 -:doc:`Configuration section of the manual <../reference/configuration>`. +Configuration object using the Setup helper. It assumes a bunch +of defaults that you don't have to bother about for now. You can +read up on the configuration details in the +:doc:`reference chapter on configuration <../reference/configuration>`. -The Proxy Configuration is a required block for your application, -you have to specify 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 specify which namespace they will reside under as -well 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 +The third 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 `_. +You should make sure to make it configurable if Doctrine should run +in dev or production mode using the `$devMode` variable. You can +use an environment variable for example, hook into your frameworks configuration +or check for the HTTP_HOST of your devsystem (localhost for example) + +.. code-block:: php + + new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager) )); @@ -745,8 +817,8 @@ Doctrine command-line tool: :: - doctrine@my-desktop> cd myproject/ - doctrine@my-desktop> doctrine orm:schema-tool:create + $ cd project/ + $ doctrine orm:schema-tool:create .. note:: @@ -766,14 +838,14 @@ either re-create the database: :: - doctrine@my-desktop> doctrine orm:schema-tool:drop --force - doctrine@my-desktop> doctrine orm:schema-tool:create + $ doctrine orm:schema-tool:drop --force + $ doctrine orm:schema-tool:create Or use the update functionality: :: - doctrine@my-desktop> doctrine orm:schema-tool:update --force + $ doctrine orm:schema-tool:update --force The updating of databases uses a Diff Algorithm for a given Database Schema, a cornerstone of the ``Doctrine\DBAL`` package, @@ -783,47 +855,68 @@ its not available in SQLite since it does not support ALTER TABLE. Writing Entities into the Database ---------------------------------- +.. note:: + + This tutorial assumes you call all the example scripts from the CLI. + Having created the schema we can now start and save entities in the database. For starters we need a create user use-case: .. code-block:: php name = $newUsername; + $user->setName($newUsername); $entityManager->persist($user); $entityManager->flush(); + echo "Created User with ID " . $user->getId() . "\n"; + Products can also be created: .. code-block:: php name = $newProductName; + $product->setName($newProductName); $entityManager->persist($product); $entityManager->flush(); + echo "Created Product with ID " . $product->getId() . "\n"; + +Now call: + +.. + + $ php create_user.php beberlei + $ php create_product.php MyProduct + So what is happening in those two snippets? In both examples the -class creation is pretty standard, the interesting bits are the +code that works on User and Product is pretty standard OOP. 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 +does not act on this command, 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 +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 flush is called. -Using this approach the write-performance is significantly faster +single transaction, which is executed when flush is called. +Using this approach the write-performance is significantly better than in a scenario where updates are done for each entity in isolation. In more complex scenarios than the previous two, you are free to request updates on many different entities and all flush @@ -834,10 +927,7 @@ retrieval from the database automatically when the flush operation is called, 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. This comparison to find dirty entities that need -updating is using a very efficient algorithm that has almost no -additional memory overhead and can even save you computing power by -only updating those database columns that really changed. +respectively. We are now getting to the "Create a New Bug" requirement and the code for this scenario may look like this: @@ -845,13 +935,24 @@ code for this scenario may look like this: .. code-block:: php find("User", $theReporterId); $engineer = $entityManager->find("User", $theDefaultEngineerId); + if (!$reporter || !$engineer) { + echo "No reporter and/or engineer found for the input.\n"; + exit(1); + } $bug = new Bug(); - $bug->description = "Something does not work!"; - $bug->created = new DateTime("now"); - $bug->status = "NEW"; + $bug->setDescription("Something does not work!"); + $bug->setCreated(new DateTime("now")); + $bug->setStatus("OPEN"); foreach ($productIds AS $productId) { $product = $entityManager->find("Product", $productId); @@ -864,7 +965,13 @@ code for this scenario may look like this: $entityManager->persist($bug); $entityManager->flush(); - echo "Your new Bug Id: ".$bug->id."\n"; + echo "Your new Bug Id: ".$bug->getId()."\n"; + +Since we only have one user and product, probably with the ID of 1, we can call this script with: + +.. + + php create_bug.php 1 1 1 This is the first contact with the read API of the EntityManager, showing that a call to ``EntityManager#find($name, $id)`` returns a @@ -892,6 +999,9 @@ the first read-only use-case: .. code-block:: php createQuery($dql); @@ -974,6 +1084,9 @@ can rewrite our code: .. code-block:: php createQuery($dql); @@ -1004,22 +1117,25 @@ 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: -.. code-block:: php - - find("Bug", (int)$theBugId); - However we will soon see another problem with our entities using this approach. Try displaying the engineer's name: .. code-block:: php description."\n"; + // show_bug.php + require_once "bootstrap.php"; + + $theBugId = $argv[1]; + + $bug = $entityManager->find("Bug", (int)$theBugId); + + echo "Bug: ".$bug->getDescription()."\n"; + // Accessing our special public $name property here on purpose: 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 persistence code of +The output of the engineers name is null! What is happening? +It worked in the previous example, so it can't be a problem with the persistence code of Doctrine. What is it then? You walked in the public property trap. Since we only retrieved the bug by primary key both the engineer @@ -1063,13 +1179,23 @@ protected and add getters and setters to get a working example: .. code-block:: php find("Bug", (int)$theBugId); + echo "Bug: ".$bug->getDescription()."\n"; echo "Engineer: ".$bug->getEngineer()->getName()."\n"; - - /** + +Now prints: + +:: + + $ php show_bug.php 1 Bug: Something does not work! Engineer: beberlei - */ Being required to use private or protected properties Doctrine 2 actually enforces you to encapsulate your objects according to @@ -1086,20 +1212,24 @@ and usage of bound parameters: .. code-block:: php createQuery($dql) ->setParameter(1, $theUserId) ->setMaxResults(15) ->getResult(); - foreach ($myBugs AS $bug) { - echo $bug->getDescription()."\n"; - } + echo "You have created or assigned to " . count($myBugs) . " open bugs:\n\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. + foreach ($myBugs AS $bug) { + echo $bug->getId() . " - " . $bug->getDescription()."\n"; + } Number of Bugs -------------- @@ -1115,6 +1245,9 @@ grouped by product: .. code-block:: php createQuery($dql)->getScalarResult(); @@ -1132,6 +1265,24 @@ should be able to close a bug. This looks like: .. code-block:: php status = "CLOSE"; + } + } + +.. code-block:: php + + find("Bug", (int)$theBugId); $bug->close(); @@ -1194,6 +1345,7 @@ the previoiusly discussed query functionality in it: .. code-block:: php getEntityManager()->getRepository('Bug')`` we have to adjust the metadata slightly. @@ -1260,14 +1414,14 @@ we have to adjust the metadata slightly. xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> - + .. code-block:: yaml - Product: + Bug: type: entity repositoryClass: BugRepository @@ -1277,14 +1431,17 @@ As an example here is the code of the first use case "List of Bugs": .. code-block:: php getRepository('Bug')->getRecentBugs(); 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"; + echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n"; + echo " Reported by: ".$bug->getReporter()->getName()."\n"; + echo " Assigned to: ".$bug->getEngineer()->getName()."\n"; foreach($bug->getProducts() AS $product) { - echo " Platform: ".$product->name."\n"; + echo " Platform: ".$product->getName()."\n"; } echo "\n"; }