1
0
mirror of synced 2025-01-18 22:41:43 +03:00

Merge pull request #734 from caponica/patch-1

Cleaned up documentation
This commit is contained in:
Benjamin Eberlei 2014-01-02 15:00:11 -08:00
commit 0a2b5b8efd

View File

@ -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 engineers 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
---------------------