commit
0a2b5b8efd
@ -66,17 +66,17 @@ Bug Tracker domain model from the
|
||||
documentation. Reading their documentation we can extract the
|
||||
requirements:
|
||||
|
||||
- A Bugs has a description, creation date, status, reporter and
|
||||
- A Bug has a description, creation date, status, reporter and
|
||||
engineer
|
||||
- A bug can occur 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.
|
||||
- A Bug can occur on different Products (platforms)
|
||||
- A Product has a name.
|
||||
- Bug reporters 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.
|
||||
|
||||
Setup Project
|
||||
Project Setup
|
||||
-------------
|
||||
|
||||
Create a new empty folder for this tutorial project, for example
|
||||
@ -170,9 +170,9 @@ factory method.
|
||||
Generating the Database Schema
|
||||
------------------------------
|
||||
|
||||
Now that we have defined the Metadata Mappings and bootstrapped the
|
||||
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
|
||||
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.
|
||||
|
||||
@ -223,9 +223,8 @@ which can even be used without the Doctrine ORM package.
|
||||
Starting with the Product
|
||||
-------------------------
|
||||
|
||||
We start with the Product entity requirements, because it is the most simple one
|
||||
to get started. Create a ``src/Product.php`` file and put the ``Product``
|
||||
entity definition in there:
|
||||
We start with the simplest entity, the Product. Create a ``src/Product.php`` file to contain the ``Product``
|
||||
entity definition:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@ -258,10 +257,16 @@ entity definition in there:
|
||||
}
|
||||
}
|
||||
|
||||
Note how the properties have getter and setter methods defined except
|
||||
``$id``. To access data from entities Doctrine 2 uses the Reflection API, so it
|
||||
is possible for Doctrine to access the value of ``$id``. You don't have to
|
||||
take Doctrine into account when designing access to the state of your objects.
|
||||
Note that all fields are set to protected (not public) with a
|
||||
mutator (getter and setter) defined for every field except $id.
|
||||
The use of mutators allows Doctrine to hook into calls which
|
||||
manipulate the entities in ways that it could not if you just
|
||||
directly set the values with ``entity#field = foo;``
|
||||
|
||||
The id field has no setter since, generally speaking, your code
|
||||
should not set this value since it represents a database id value.
|
||||
(Note that Doctrine itself can still set the value using the
|
||||
Reflection API instead of a defined setter function)
|
||||
|
||||
The next step for persistence with Doctrine is to describe the
|
||||
structure of the ``Product`` entity to Doctrine using a metadata
|
||||
@ -325,15 +330,15 @@ References in the text will be made to the XML mapping.
|
||||
type: string
|
||||
|
||||
The top-level ``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
|
||||
the class and table-name. The primitive type ``Product#name`` is
|
||||
defined as a ``field`` attribute. The ``id`` property is defined with
|
||||
the ``id`` tag, this has a ``generator`` tag nested inside which
|
||||
defines that the primary key generation mechanism automatically
|
||||
uses the database platforms native id generation strategy, for
|
||||
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.
|
||||
case of PostgreSql and Oracle).
|
||||
|
||||
You have to update the database now, because we have a first Entity now:
|
||||
Now that we have defined our first entity, lets update the database:
|
||||
|
||||
::
|
||||
|
||||
@ -360,7 +365,7 @@ Now create a new script that will insert products into the database:
|
||||
|
||||
echo "Created Product with ID " . $product->getId() . "\n";
|
||||
|
||||
Call this script from the command line to see how new products are created:
|
||||
Call this script from the command-line to see how new products are created:
|
||||
|
||||
::
|
||||
|
||||
@ -382,7 +387,7 @@ Doctrine follows the UnitOfWork pattern which additionally detects all entities
|
||||
that were fetched and have changed during the request. You don't have to keep track of
|
||||
entities yourself, when Doctrine already knows about them.
|
||||
|
||||
As a next step we want to fetch a list of all the products. Let's create a
|
||||
As a next step we want to fetch a list of all the Products. Let's create a
|
||||
new script for this:
|
||||
|
||||
.. code-block:: php
|
||||
@ -399,7 +404,7 @@ new script for this:
|
||||
}
|
||||
|
||||
The ``EntityManager#getRepository()`` method can create a finder object (called
|
||||
repository) for every entity. It is provided by Doctrine and contains some
|
||||
a repository) for every entity. It is provided by Doctrine and contains some
|
||||
finder methods such as ``findAll()``.
|
||||
|
||||
Let's continue with displaying the name of a product based on its ID:
|
||||
@ -558,12 +563,12 @@ We continue with the bug tracker domain, by creating the missing classes
|
||||
|
||||
All of the properties discussed so far are simple string and integer values,
|
||||
for example the id fields of the entities, their names, description, status and
|
||||
change dates. With just the scalar values this model cannot describe the dynamics that we want. We
|
||||
want to model references between entities.
|
||||
change dates. Next we will model the dynamic relationships between the entities
|
||||
by defining the references between entities.
|
||||
|
||||
References between objects are foreign keys in the database. You never have to
|
||||
work with the foreign keys directly, only with objects that represent the
|
||||
foreign key through their own identity.
|
||||
(and never should) work with the foreign keys directly, only with the objects
|
||||
that represent the foreign key through their own identity.
|
||||
|
||||
For every foreign key you either have a Doctrine ManyToOne or OneToOne
|
||||
association. On the inverse sides of these foreign keys you can have
|
||||
@ -732,20 +737,20 @@ 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
|
||||
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
|
||||
``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()``
|
||||
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
|
||||
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
|
||||
@ -781,8 +786,8 @@ the database that points from Bugs to Products.
|
||||
}
|
||||
|
||||
We are now finished with the domain model given the requirements.
|
||||
Now we continue adding metadata mappings for the ``User`` and ``Bug``
|
||||
as we did for the ``Product`` before:
|
||||
Lets add metadata mappings for the ``User`` and ``Bug`` as we did for
|
||||
the ``Product`` before:
|
||||
|
||||
.. configuration-block::
|
||||
.. code-block:: php
|
||||
@ -884,11 +889,9 @@ as we did for the ``Product`` before:
|
||||
|
||||
|
||||
Here 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.
|
||||
For the "created" field we have used the ``datetime`` type,
|
||||
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``
|
||||
@ -902,14 +905,10 @@ side of the relationship. We will see in the next example that the ``inversed-by
|
||||
attribute has a counterpart ``mapped-by`` which makes that
|
||||
the inverse side.
|
||||
|
||||
The last missing property is the ``Bug::$products`` collection. It
|
||||
holds all products where the specific bug is occurring in. Again
|
||||
The last definition is for the ``Bug#products`` collection. It
|
||||
holds all products where the specific bug occurs. Again
|
||||
you have to define the ``target-entity`` and ``field`` attributes
|
||||
on the ``many-to-many`` tag. Furthermore you have to specify 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.
|
||||
on the ``many-to-many`` tag.
|
||||
|
||||
The last missing definition is that of the User entity:
|
||||
|
||||
@ -1132,7 +1131,7 @@ The console output of this script is then:
|
||||
|
||||
.. note::
|
||||
|
||||
**Dql is not Sql**
|
||||
**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
|
||||
@ -1145,6 +1144,7 @@ The console output of this script is then:
|
||||
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,
|
||||
@ -1154,30 +1154,31 @@ The console output of this script is then:
|
||||
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.
|
||||
Instead of handwriting DQL you can use the ``QueryBuilder`` retrieved
|
||||
by calling ``$entityManager->createQueryBuilder()``. There are more
|
||||
details about this in the relevant part of the documentation.
|
||||
|
||||
|
||||
As a last resort you can still 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.
|
||||
|
||||
|
||||
Array Hydration of the Bug List
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In the previous use-case we retrieved the result as their
|
||||
In the previous use-case we retrieved the results as their
|
||||
respective object instances. We are not limited to retrieving
|
||||
objects only from Doctrine however. For a simple list view like the
|
||||
previous one we only need read access to our entities and can
|
||||
switch the hydration from objects to simple PHP arrays instead.
|
||||
This can obviously yield considerable performance benefits for
|
||||
read-only requests.
|
||||
|
||||
Hydration can be an expensive process so only retrieving what you need can
|
||||
yield considerable performance benefits for read-only requests.
|
||||
|
||||
Implementing the same list view as before using array hydration we
|
||||
can rewrite our code:
|
||||
@ -1231,7 +1232,7 @@ write scenarios:
|
||||
echo "Bug: ".$bug->getDescription()."\n";
|
||||
echo "Engineer: ".$bug->getEngineer()->getName()."\n";
|
||||
|
||||
The output of the engineers name is fetched from the database! What is happening?
|
||||
The output of the engineer’s name is fetched from the database! What is happening?
|
||||
|
||||
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
|
||||
@ -1277,6 +1278,14 @@ The call prints:
|
||||
Bug: Something does not work!
|
||||
Engineer: beberlei
|
||||
|
||||
.. warning::
|
||||
|
||||
Lazy loading additional data can be very convenient but the additional
|
||||
queries create an overhead. If you know that certain fields will always
|
||||
(or usually) be required by the query then you will get better performance
|
||||
by explicitly retrieving them all in the first query.
|
||||
|
||||
|
||||
Dashboard of the User
|
||||
---------------------
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user