Fixed the tutorial, it was a mess! Now its explaining everything step by step and all bugs are removed. Changed introduction and configuration to use the simpler Setup helper class
This commit is contained in:
parent
82419813df
commit
5fc0ede5bf
@ -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
|
||||
|
||||
<?php
|
||||
// test.php
|
||||
|
||||
require '/path/to/libraries/Doctrine/Common/ClassLoader.php';
|
||||
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine', '/path/to/libraries');
|
||||
$classLoader->register(); // register on SPL autoload stack
|
||||
require 'Doctrine/ORM/Tools/Setup.php';
|
||||
|
||||
Doctrine\ORM\Tools\Setup::registerAutoloadPEAR();
|
||||
|
||||
Tarball Download
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// test.php
|
||||
require 'Doctrine/ORM/Tools/Setup.php';
|
||||
|
||||
$lib = "/path/to/doctrine2-orm/lib";
|
||||
Doctrine\ORM\Tools\Setup::registerAutoloadDirectory($lib);
|
||||
|
||||
Git
|
||||
^^^
|
||||
@ -65,41 +77,18 @@ packages through ``git submodule update --init``
|
||||
|
||||
<?php
|
||||
// test.php
|
||||
require 'Doctrine/ORM/Tools/Setup.php';
|
||||
|
||||
$lib = '/path/to/doctrine2-orm/lib/';
|
||||
require $lib . 'vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php';
|
||||
|
||||
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine\Common', $lib . 'vendor/doctrine-common/lib');
|
||||
$classLoader->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
|
||||
|
||||
<?php
|
||||
// PEAR or Tarball setup
|
||||
$classloader = new \Doctrine\Common\ClassLoader('Symfony', '/path/to/libraries/Doctrine');
|
||||
$classloader->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
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
$paths = array("/path/to/entities-or-mapping-files");
|
||||
$isDevMode = false;
|
||||
|
||||
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
|
||||
$em = EntityManager::create($dbParams, $config);
|
||||
|
||||
// or if you prefer yaml or xml
|
||||
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
|
||||
|
||||
These setup commands make several assumptions:
|
||||
|
||||
- If `$devMode` is true always use an ``ArrayCache`` and set ``setAutoGenerateProxyClasses(true)``.
|
||||
- If `$devMode` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211) unless `$cache` is passed as fourth argument.
|
||||
- If `$devMode` is false, set ``setAutoGenerateProxyClasses(false)``
|
||||
- If third argument `$proxyDir` is not set, use the systems temporary directory.
|
||||
|
||||
Configuration Options
|
||||
---------------------
|
||||
|
@ -23,7 +23,7 @@ Doctrine does not support to set the default values in columns through the "DEFA
|
||||
This is not necessary however, you can just use your class properties as default values. These are then used
|
||||
upon insert:
|
||||
|
||||
.. code-block::
|
||||
.. code-block:: php
|
||||
|
||||
class User
|
||||
{
|
||||
@ -47,7 +47,7 @@ or adding entities to a collection twice. You have to check for both conditions
|
||||
in the code before calling ``$em->flush()`` if you know that unique constraint failures
|
||||
can occur.
|
||||
|
||||
In `Symfony 2<http://www.symfony.com>`_ for example there is a Unique Entity Validator
|
||||
In `Symfony 2 <http://www.symfony.com>`_ 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 <inheritance-mapping>`_ for
|
||||
See the documentation chapter on :doc:`inheritance mapping <inheritance-mapping>` for
|
||||
the details.
|
||||
|
||||
Why does Doctrine not create proxy objects for my inheritance hierachy?
|
||||
|
@ -136,9 +136,9 @@ the ``ClassLoader`` with the following code.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
require 'Doctrine/Common/ClassLoader.php';
|
||||
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
|
||||
$classLoader->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
|
||||
|
@ -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 <https://github.com/doctrine/doctrine2-orm-tutorial>`_.
|
||||
|
||||
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
|
||||
|
||||
<?php
|
||||
class Bug
|
||||
{
|
||||
public $id;
|
||||
public $description;
|
||||
public $created;
|
||||
public $status;
|
||||
public $products = array();
|
||||
public $reporter;
|
||||
public $engineer;
|
||||
protected $id;
|
||||
protected $description;
|
||||
protected $created;
|
||||
protected $status;
|
||||
}
|
||||
class Product
|
||||
{
|
||||
public $id;
|
||||
public $name;
|
||||
protected $id;
|
||||
protected $name;
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->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
|
||||
|
||||
<?php
|
||||
// entities/Bug.php
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
class Bug
|
||||
{
|
||||
public $products = null;
|
||||
protected $products = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->products = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// entities/User.php
|
||||
class User
|
||||
{
|
||||
public $reportedBugs = null;
|
||||
public $assignedBugs = null;
|
||||
protected $reportedBugs = null;
|
||||
protected $assignedBugs = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -224,10 +285,11 @@ the bi-directional reference:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// entities/Bug.php
|
||||
class Bug
|
||||
{
|
||||
private $engineer;
|
||||
private $reporter;
|
||||
protected $engineer;
|
||||
protected $reporter;
|
||||
|
||||
public function setEngineer($engineer)
|
||||
{
|
||||
@ -251,10 +313,15 @@ the bi-directional reference:
|
||||
return $this->reporter;
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// entities/User.php
|
||||
class User
|
||||
{
|
||||
private $reportedBugs = null;
|
||||
private $assignedBugs = null;
|
||||
protected $reportedBugs = null;
|
||||
protected $assignedBugs = null;
|
||||
|
||||
public function addReportedBug($bug)
|
||||
{
|
||||
@ -270,6 +337,9 @@ the bi-directional reference:
|
||||
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.
|
||||
This approach is my personal preference, you can choose whatever
|
||||
method to make this work.
|
||||
|
||||
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
|
||||
@ -300,9 +370,10 @@ the database that points from from Bugs to Products.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// entities/Bug.php
|
||||
class Bug
|
||||
{
|
||||
private $products = null;
|
||||
protected $products = null;
|
||||
|
||||
public function assignToProduct($product)
|
||||
{
|
||||
@ -366,35 +437,38 @@ the most simple one:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// entities/Product.php
|
||||
/**
|
||||
* @Entity @Table(name="products")
|
||||
*/
|
||||
class Product
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
public $id;
|
||||
protected $id;
|
||||
/** @Column(type="string") */
|
||||
public $name;
|
||||
protected $name;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<!-- config/xml/Product.dcm.xml -->
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Product" table="products">
|
||||
<id name="id" type="integer" column="product_id">
|
||||
<id name="id" type="integer">
|
||||
<generator strategy="AUTO" />
|
||||
</id>
|
||||
|
||||
<field name="name" column="product_name" type="string" />
|
||||
<field name="name" type="string" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. 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
|
||||
|
||||
<?php
|
||||
// entities/Bug.php
|
||||
/**
|
||||
* @Entity @Table(name="bugs")
|
||||
*/
|
||||
@ -430,38 +505,39 @@ We then go on specifying the definition of a Bug:
|
||||
/**
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
*/
|
||||
public $id;
|
||||
protected $id;
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
public $description;
|
||||
protected $description;
|
||||
/**
|
||||
* @Column(type="datetime")
|
||||
*/
|
||||
public $created;
|
||||
protected $created;
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
public $status;
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="User", inversedBy="assignedBugs")
|
||||
*/
|
||||
private $engineer;
|
||||
protected $engineer;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="User", inversedBy="reportedBugs")
|
||||
*/
|
||||
private $reporter;
|
||||
protected $reporter;
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="Product")
|
||||
*/
|
||||
private $products;
|
||||
protected $products;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<!-- config/xml/Bug.dcm.xml -->
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
@ -485,6 +561,7 @@ We then go on specifying the definition of a Bug:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# config/yaml/Bug.dcm.yml
|
||||
Bug:
|
||||
type: entity
|
||||
table: bugs
|
||||
@ -547,6 +624,7 @@ The last missing definition is that of the User entity:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// entities/User.php
|
||||
/**
|
||||
* @Entity @Table(name="users")
|
||||
*/
|
||||
@ -556,13 +634,13 @@ The last missing definition is that of the User entity:
|
||||
* @Id @GeneratedValue @Column(type="integer")
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Bug", mappedBy="reporter")
|
||||
@ -578,12 +656,13 @@ The last missing definition is that of the User entity:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<!-- config/xml/User.dcm.xml -->
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="User" name="users">
|
||||
<entity name="User" table="users">
|
||||
<id name="id" type="integer">
|
||||
<generator strategy="AUTO" />
|
||||
</id>
|
||||
@ -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
|
||||
|
||||
<?php
|
||||
// Setup Autoloader (1)
|
||||
// bootstrap_doctrine.php
|
||||
|
||||
// See :doc:`Configuration <../reference/configuration>` 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 <http://www.doctrine-project.org/documentation/manual/2_0/en/dbal>`_.
|
||||
|
||||
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
|
||||
|
||||
<?php
|
||||
// examples, use your own logic to determine this:
|
||||
$isDevMode = ($_SERVER['HTTP_HOST'] == 'localhost');
|
||||
$isDevMode = ($_ENV['APPLICATION_ENV'] == 'development');
|
||||
|
||||
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.
|
||||
factory method.
|
||||
|
||||
We also have to create a general bootstrap file for our application:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// boostrap.php
|
||||
|
||||
require_once "entities/User.php";
|
||||
require_once "entities/Product.php";
|
||||
require_once "entities/Bug.php";
|
||||
|
||||
if (!class_exists("Doctrine\Common\Version", false)) {
|
||||
require_once "bootstrap_doctrine.php";
|
||||
}
|
||||
|
||||
Generating the Database Schema
|
||||
------------------------------
|
||||
@ -736,6 +805,9 @@ doctrine command. Its a fairly simple file:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// cli-config.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||
'em' => 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
|
||||
|
||||
<?php
|
||||
$newUsername = "beberlei";
|
||||
// create_user.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$newUsername = $argv[1];
|
||||
|
||||
$user = new User();
|
||||
$user->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
|
||||
|
||||
<?php
|
||||
$newProductName = "My Product";
|
||||
// create_product.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$newProductName = $argv[1];
|
||||
|
||||
$product = new Product();
|
||||
$product->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
|
||||
|
||||
<?php
|
||||
// create_bug.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theReporterId = $argv[1];
|
||||
$theDefaultEngineerId = $argv[1];
|
||||
$productIds = explode(",", $argv[3]);
|
||||
|
||||
$reporter = $entityManager->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
|
||||
|
||||
<?php
|
||||
// list_bugs.php
|
||||
require_once "bootstrap.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);
|
||||
@ -974,6 +1084,9 @@ can rewrite our code:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// list_bugs_array.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ".
|
||||
"JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC";
|
||||
$query = $entityManager->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
|
||||
|
||||
<?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:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
echo "Bug: ".$bug->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
|
||||
|
||||
<?php
|
||||
// show_bug.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theBugId = $argv[1];
|
||||
|
||||
$bug = $entityManager->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
|
||||
|
||||
<?php
|
||||
// dashboard.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theUserId = $argv[1];
|
||||
|
||||
$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";
|
||||
"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";
|
||||
}
|
||||
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
|
||||
|
||||
<?php
|
||||
// products.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ".
|
||||
"JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id";
|
||||
$productBugs = $entityManager->createQuery($dql)->getScalarResult();
|
||||
@ -1132,6 +1265,24 @@ should be able to close a bug. This looks like:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// entities/Bug.php
|
||||
|
||||
class Bug
|
||||
{
|
||||
public function close()
|
||||
{
|
||||
$this->status = "CLOSE";
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// close_bug.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theBugId = $argv[1];
|
||||
|
||||
$bug = $entityManager->find("Bug", (int)$theBugId);
|
||||
$bug->close();
|
||||
|
||||
@ -1194,6 +1345,7 @@ the previoiusly discussed query functionality in it:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// repositories/BugRepository.php
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
@ -1236,6 +1388,8 @@ the previoiusly discussed query functionality in it:
|
||||
}
|
||||
}
|
||||
|
||||
Dont forget to add a `require_once` call for this class to the bootstrap.php
|
||||
|
||||
To be able to use this query logic through ``$this->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">
|
||||
|
||||
<entity name="Bug" table="bugs" entity-repository="BugRepository">
|
||||
<entity name="Bug" table="bugs" repository-class="BugRepository">
|
||||
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. 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
|
||||
|
||||
<?php
|
||||
// list_bugs_repository.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$bugs = $entityManager->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";
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user