1
0
mirror of synced 2025-02-02 21:41:45 +03:00

Updating the Getting Started guide's documentation

This commit is contained in:
NateC 2016-02-24 17:13:25 -06:00 committed by Marco Pivetta
parent 9cfdf1ef81
commit b446afd937
No known key found for this signature in database
GPG Key ID: 4167D3337FD9D629

View File

@ -14,7 +14,7 @@ Guide Assumptions
-----------------
This guide is designed for beginners that haven't worked with Doctrine ORM
before. There are some prerequesites for the tutorial that have to be
before. There are some prerequisites for the tutorial that have to be
installed:
- PHP (latest stable version)
@ -118,8 +118,8 @@ Add the following directories:
Obtaining the EntityManager
---------------------------
Doctrine's public interface is the EntityManager, it provides the
access point to the complete lifecycle management of your entities
Doctrine's public interface is through the ``EntityManager``. This class
provides access points to the complete lifecycle management for 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
@ -150,8 +150,8 @@ step:
// obtaining the entity manager
$entityManager = EntityManager::create($conn, $config);
The first require statement sets up the autoloading capabilities of Doctrine
using the Composer autoload.
The require_once statement sets up the class autoloading for Doctrine and
its dependencies using Composer's autoloader.
The second block consists of the instantiation of the ORM
``Configuration`` object using the Setup helper. It assumes a bunch
@ -159,8 +159,8 @@ 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 third block shows the configuration options required to connect
to a database, in my case a file-based sqlite database. All the
The third block shows the configuration options required to connect to
a database. In this case, we'll use 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://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/>`_.
@ -170,15 +170,10 @@ factory method.
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 command-line tool to work a cli-config.php file has to be
present in the project root directory, where you will execute the
doctrine command. Its a fairly simple file:
Doctrine has a command-line interface that allows you to access the SchemaTool,
a component that can generate a relational database schema based entirely on the
defined entity classes and their metadata. For this tool to work, a
cli-config.php file must exist in the project root directory:
.. code-block:: php
@ -188,40 +183,38 @@ doctrine command. Its a fairly simple file:
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
You can then change into your project directory and call the
Doctrine command-line tool:
Change into your project directory and call the Doctrine command-line tool:
::
$ cd project/
$ vendor/bin/doctrine orm:schema-tool:create
At this point no entity metadata exists in `src` so you will see a message like
"No Metadata Classes to process." Don't worry, we'll create a Product entity and
corresponding metadata in the next section.
Since we haven't added any entity metadata in `src` yet, you'll see a message
stating "No Metadata Classes to process." In the next section, we'll create a
Product entity along with the corresponding metadata, and run this command again.
You should be aware that during the development process you'll periodically need
to update your database schema to be in sync with your Entities metadata.
You can easily recreate the database:
Note that as you modify your entities' metadata during the development process,
you'll need to update your database schema to stay in sync with the metadata.
You can rasily recreate the database using the following commands:
::
$ vendor/bin/doctrine orm:schema-tool:drop --force
$ vendor/bin/doctrine orm:schema-tool:create
Or use the update functionality:
Or you can use the update functionality:
::
$ vendor/bin/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,
Database Schema. This is a cornerstone of the ``Doctrine\DBAL`` package,
which can even be used without the Doctrine ORM package.
Starting with the Product
-------------------------
Starting with the Product Entity
--------------------------------
We start with the simplest entity, the Product. Create a ``src/Product.php`` file to contain the ``Product``
entity definition:
@ -257,9 +250,9 @@ entity definition:
}
}
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
When creating entity classes, all of the fields should be protected or private
(not public), with getter and setter methods for each one (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;``
@ -274,9 +267,10 @@ 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 configured using a XML, YAML or Docblock Annotations.
This Getting Started Guide will show the mappings for all Mapping Drivers.
References in the text will be made to the XML mapping.
Metadata for an Entity can be configured using DocBlock annotations directly
in the Entity class itself, or in an external XML or YAML file. This Getting
Started guide will demonstrate metadata mappings using all three methods,
but you only need to choose one.
.. configuration-block::
@ -332,27 +326,28 @@ References in the text will be made to the XML mapping.
The top-level ``entity`` definition tag specifies information about
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
example AUTO INCREMENT in the case of MySql or Sequences in the
the ``id`` tag. It has a ``generator`` tag nested inside, which
specifies that the primary key generation mechanism should automatically
use the database platform's native id generation strategy (for
example, AUTO INCREMENT in the case of MySql, or Sequences in the
case of PostgreSql and Oracle).
Now that we have defined our first entity, let's update the database:
Now that we have defined our first entity and its metadata,
let's update the database schema:
::
$ vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
Specifying both flags ``--force`` and ``--dump-sql`` prints and executes the DDL
statements.
Specifying both flags ``--force`` and ``--dump-sql`` will cause the DDL
statements to be executed and then printed to the screen.
Now create a new script that will insert products into the database:
Now, we'll create a new script to insert products into the database:
.. code-block:: php
<?php
// create_product.php
// create_product.php <name>
require_once "bootstrap.php";
$newProductName = $argv[1];
@ -372,22 +367,19 @@ Call this script from the command-line to see how new products are created:
$ php create_product.php ORM
$ php create_product.php DBAL
What is happening here? Using the ``Product`` is pretty standard OOP.
What is happening here? Using the ``Product`` class is pretty standard OOP.
The interesting bits are the use of the ``EntityManager`` service. To
notify the EntityManager that a new entity should be inserted into the database
you have to call ``persist()``. To initiate a transaction to actually perform
the insertion, You have to explicitly call ``flush()`` on the ``EntityManager``.
notify the EntityManager that a new entity should be inserted into the database,
you have to call ``persist()``. To initiate a transaction to actually *perform*
the insertion, you have to explicitly call ``flush()`` on the ``EntityManager``.
This distinction between persist and flush is allows to aggregate all writes
(INSERT, UPDATE, DELETE) into one 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.
This distinction between persist and flush is what allows the aggregation of
all database writes (INSERT, UPDATE, DELETE) into one single transaction, which
is executed when ``flush()`` is called. Using this approach, the write-performance
is significantly better than in a scenario in which writes are performed on
each entity in isolation.
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
Next, we'll fetch a list of all the Products in the database. Let's create a
new script for this:
.. code-block:: php
@ -404,10 +396,10 @@ new script for this:
}
The ``EntityManager#getRepository()`` method can create a finder object (called
a repository) for every entity. It is provided by Doctrine and contains some
finder methods such as ``findAll()``.
a repository) for every type of entity. It is provided by Doctrine and contains
some finder methods like ``findAll()``.
Let's continue with displaying the name of a product based on its ID:
Let's continue by creating a script to display the name of a product based on its ID:
.. code-block:: php
@ -425,9 +417,13 @@ Let's continue with displaying the name of a product based on its ID:
echo sprintf("-%s\n", $product->getName());
Updating a product name demonstrates the functionality UnitOfWork of pattern
discussed before. We only need to find a product entity and all changes to its
properties are written to the database:
Next we'll update a product's name, given its id. This simple example will
help demonstrate Doctrine's implementation of the UnitOfWork pattern. Doctrine
keeps track of all the entities that were retrieved from the Entity Manager,
and can detect when any of those entities' properties have been modified.
As a result, rather than needing to call ``persist($entity)`` for each individual
entity whose properties were changed, a single call to ``flush()`` at the end of a
request is sufficient to update the database for all of the modified entities.
.. code-block:: php
@ -455,9 +451,8 @@ product name changed by calling the ``show_product.php`` script.
Adding Bug and User Entities
----------------------------
We continue with the bug tracker domain, by creating the missing classes
``Bug`` and ``User`` and putting them into ``src/Bug.php`` and
``src/User.php`` respectively.
We continue with the bug tracker example by creating the ``Bug`` and ``User``
classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
.. code-block:: php
@ -561,14 +556,15 @@ 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. Next we will model the dynamic relationships between the entities
by defining the references between entities.
All of the properties we've seen so far are of simple types (integer, string,
and datetime). But now, we'll add properties that will store objects of
specific *entity types* in order to model the relationships between different
entities.
References between objects are foreign keys in the database. You never have to
(and never should) work with the foreign keys directly, only with the objects
that represent the foreign key through their own identity.
At the database level, relationships between entities are represented by foreign
keys. But with Doctrine, you'll never have to (and never should) work with
the foreign keys directly. You should only work with objects that represent
foreign keys through their own identities.
For every foreign key you either have a Doctrine ManyToOne or OneToOne
association. On the inverse sides of these foreign keys you can have
@ -602,6 +598,7 @@ domain model to match the requirements:
<?php
// src/User.php
use Doctrine\Common\Collections\ArrayCollection;
class User
{
// ... (previous code)
@ -616,12 +613,13 @@ domain model to match the requirements:
}
}
You use Doctrine's ArrayCollections in your Doctrine models, rather
than plain PHP arrays, so that Doctrine can watch what happens with
them and act appropriately. Note that if you dump your entities,
you'll see a "PersistentCollection" in place of your ArrayCollection,
which is just an
internal Doctrine class with the same interface.
.. note::
Whenever an entity is created from the database, a ``Collection``
implementation of the type ``PersistentCollection`` will be injected into
your entity instead of an ``ArrayCollection``. This helps Doctrine ORM
understand the changes that have happened to the collection that are
noteworthy for persistence.
.. warning::
@ -644,24 +642,22 @@ 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.
- 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-one relation, the Many-side is the owning side by
default because it holds the foreign key. Accordingly, the One-side
is the inverse side by default.
- In a many-to-one relation, the One-side can only be the owning side if
the relation is implemented as a ManyToMany with a join table, and the
One-side is restricted to allow only UNIQUE values per database constraint.
- In a many-to-many relation, both sides can be the owning side of
the relation. However, in a bi-directional many-to-many relation,
only one side is allowed to be the owning side.
- Changes to Collections are saved or updated, when the entity on
the *owning* 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-to-many 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.
.. note::
@ -686,13 +682,13 @@ the bi-directional reference:
protected $engineer;
protected $reporter;
public function setEngineer($engineer)
public function setEngineer(User $engineer)
{
$engineer->assignedToBug($this);
$this->engineer = $engineer;
}
public function setReporter($reporter)
public function setReporter(User $reporter)
{
$reporter->addReportedBug($this);
$this->reporter = $reporter;
@ -717,15 +713,15 @@ the bi-directional reference:
{
// ... (previous code)
protected $reportedBugs = null;
protected $assignedBugs = null;
protected $reportedBugs;
protected $assignedBugs;
public function addReportedBug($bug)
public function addReportedBug(Bug $bug)
{
$this->reportedBugs[] = $bug;
}
public function assignedToBug($bug)
public function assignedToBug(Bug $bug)
{
$this->assignedBugs[] = $bug;
}
@ -741,7 +737,7 @@ 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
calling Doctrine for persistence would not update the Collections'
representation in the database.
Only using ``Bug#setEngineer()`` or ``Bug#setReporter()``
@ -749,7 +745,7 @@ correctly saves the relation information.
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
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
@ -758,7 +754,7 @@ 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 an uni-directional ManyToMany relation in
Bugs reference Products by a uni-directional ManyToMany relation in
the database that points from Bugs to Products.
.. code-block:: php
@ -771,7 +767,7 @@ the database that points from Bugs to Products.
protected $products = null;
public function assignToProduct($product)
public function assignToProduct(Product $product)
{
$this->products[] = $product;
}
@ -783,7 +779,7 @@ the database that points from Bugs to Products.
}
We are now finished with the domain model given the requirements.
Lets add metadata mappings for the ``User`` and ``Bug`` as we did for
Lets add metadata mappings for the ``Bug`` entity, as we did for
the ``Product`` before:
.. configuration-block::
@ -890,13 +886,13 @@ 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
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. Since
``reporter`` and ``engineer`` are on the owning side of a
bi-directional relation we also have to specify the ``inversed-by``
bi-directional relation, we also have to specify the ``inversed-by``
attribute. They have to point to the field names on the inverse
side of the relationship. We will see in the next example that the ``inversed-by``
attribute has a counterpart ``mapped-by`` which makes that
@ -907,7 +903,7 @@ 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.
The last missing definition is that of the User entity:
Finally, we'll add metadata mappings for the ``User`` entity.
.. configuration-block::
@ -934,13 +930,13 @@ The last missing definition is that of the User entity:
/**
* @OneToMany(targetEntity="Bug", mappedBy="reporter")
* @var Bug[]
* @var Bug[] An ArrayCollection of Bug objects.
**/
protected $reportedBugs = null;
/**
* @OneToMany(targetEntity="Bug", mappedBy="engineer")
* @var Bug[]
* @var Bug[] An ArrayCollection of Bug objects.
**/
protected $assignedBugs = null;
@ -996,10 +992,7 @@ 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.
Update your database running:
Update your database schema by running:
::
$ vendor/bin/doctrine orm:schema-tool:update --force
@ -1008,7 +1001,8 @@ Update your database running:
Implementing more Requirements
------------------------------
For starters we need to create user entities:
So far, we've seen the most basic features of the metadata definition language.
To explore additional functionality, let's first create new ``User`` entities:
.. code-block:: php
@ -1032,23 +1026,22 @@ Now call:
$ php create_user.php beberlei
We now have the data to create a bug and the code for this scenario may look
like this:
We now have the necessary data to create a new Bug entity:
.. code-block:: php
<?php
// create_bug.php
// create_bug.php <reporter-id> <engineer-id> <product-ids>
require_once "bootstrap.php";
$theReporterId = $argv[1];
$theDefaultEngineerId = $argv[2];
$reporterId = $argv[1];
$engineerId = $argv[2];
$productIds = explode(",", $argv[3]);
$reporter = $entityManager->find("User", $theReporterId);
$engineer = $entityManager->find("User", $theDefaultEngineerId);
$reporter = $entityManager->find("User", $reporterId);
$engineer = $entityManager->find("User", $engineerId);
if (!$reporter || !$engineer) {
echo "No reporter and/or engineer found for the input.\n";
echo "No reporter and/or engineer found for the given id(s).\n";
exit(1);
}
@ -1070,22 +1063,17 @@ like this:
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:
Since we only have one user and product, probably with the ID of 1, we can
call this script as follows:
::
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
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 relationship when flush is
called and relate them in the database appropriately.
See how simple it is to relate a Bug, Reporter, Engineer and Products?
Also recall that thanks to the UnitOfWork pattern, Doctrine will detect
these relations and update all of the modified entities in the database
automatically when ``flush()`` is called.
Queries for Application Use-Cases
---------------------------------
@ -1094,7 +1082,7 @@ List of Bugs
~~~~~~~~~~~~
Using the previous examples we can fill up the database quite a
bit, however we now need to discuss how to query the underlying
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:
@ -1225,7 +1213,7 @@ write scenarios:
.. code-block:: php
<?php
// show_bug.php
// show_bug.php <id>
require_once "bootstrap.php";
$theBugId = $argv[1];
@ -1300,7 +1288,7 @@ and usage of bound parameters:
.. code-block:: php
<?php
// dashboard.php
// dashboard.php <user-id>
require_once "bootstrap.php";
$theUserId = $argv[1];
@ -1366,7 +1354,7 @@ should be able to close a bug. This looks like:
.. code-block:: php
<?php
// close_bug.php
// close_bug.php <bug-id>
require_once "bootstrap.php";
$theBugId = $argv[1];