Merge pull request #101 from merk/rtel
Initial ResolveTargetEntityListener cookbook entry
This commit is contained in:
commit
1f2050f20d
136
en/cookbook/resolve-target-entity-listener.rst
Normal file
136
en/cookbook/resolve-target-entity-listener.rst
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
Keeping your Modules independent
|
||||||
|
=================================
|
||||||
|
|
||||||
|
One of the goals of using modules is to create discreet units of functionality
|
||||||
|
that do not have many (if any) dependencies, allowing you to use that
|
||||||
|
functionality in other applications without including unnecessary items.
|
||||||
|
|
||||||
|
Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``,
|
||||||
|
that functions by intercepting certain calls inside Doctrine and rewrite
|
||||||
|
targetEntity parameters in your metadata mapping at runtime. It means that
|
||||||
|
in your bundle you are able to use an interface or abstract class in your
|
||||||
|
mappings and expect correct mapping to a concrete entity at runtime.
|
||||||
|
|
||||||
|
This functionality allows you to define relationships between different entities
|
||||||
|
but not making them hard dependencies.
|
||||||
|
|
||||||
|
Background
|
||||||
|
----------
|
||||||
|
|
||||||
|
In the following example, the situation is we have an `InvoiceModule`
|
||||||
|
which provides invoicing functionality, and a `CustomerModule` that
|
||||||
|
contains customer management tools. We want to keep these separated,
|
||||||
|
because they can be used in other systems without each other, but for
|
||||||
|
our application we want to use them together.
|
||||||
|
|
||||||
|
In this case, we have an ``Invoice`` entity with a relationship to a
|
||||||
|
non-existant object, an ``InvoiceSubjectInterface``. The goal is to get
|
||||||
|
the ``ResolveTargetEntityListener`` to replace any mention of the interface
|
||||||
|
with a real object that implements that interface.
|
||||||
|
|
||||||
|
Set up
|
||||||
|
------
|
||||||
|
|
||||||
|
We're going to use the following basic entities (which are incomplete
|
||||||
|
for brevity) to explain how to set up and use the RTEL.
|
||||||
|
|
||||||
|
A Customer entity
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
// src/Acme/AppModule/Entity/Customer.php
|
||||||
|
|
||||||
|
namespace Acme\AppModule\Entity;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Acme\CustomerModule\Entity\Customer as BaseCustomer;
|
||||||
|
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table(name="customer")
|
||||||
|
*/
|
||||||
|
class Customer extends BaseCustomer implements InvoiceSubjectInterface
|
||||||
|
{
|
||||||
|
// In our example, any methods defined in the InvoiceSubjectInterface
|
||||||
|
// are already implemented in the BaseCustomer
|
||||||
|
}
|
||||||
|
|
||||||
|
An Invoice entity
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
// src/Acme/InvoiceModule/Entity/Invoice.php
|
||||||
|
|
||||||
|
namespace Acme\InvoiceModule\Entity;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping AS ORM;
|
||||||
|
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an Invoice.
|
||||||
|
*
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table(name="invoice")
|
||||||
|
*/
|
||||||
|
class Invoice
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity="Acme\InvoiceModule\Model\InvoiceSubjectInterface")
|
||||||
|
* @var InvoiceSubjectInterface
|
||||||
|
*/
|
||||||
|
protected $subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
An InvoiceSubjectInterface
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
// src/Acme/InvoiceModule/Model/InvoiceSubjectInterface.php
|
||||||
|
|
||||||
|
namespace Acme\InvoiceModule\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface that the invoice Subject object should implement.
|
||||||
|
* In most circumstances, only a single object should implement
|
||||||
|
* this interface as the ResolveTargetEntityListener can only
|
||||||
|
* change the target to a single object.
|
||||||
|
*/
|
||||||
|
interface InvoiceSubjectInterface
|
||||||
|
{
|
||||||
|
// List any additional methods that your InvoiceModule
|
||||||
|
// will need to access on the subject so that you can
|
||||||
|
// be sure that you have access to those methods.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
Next, we need to configure the listener. Add this to the area you set up Doctrine. You
|
||||||
|
must set this up in the way outlined below, otherwise you can not be guaranteed that
|
||||||
|
the targetEntity resolution will occur reliably::
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$evm = new \Doctrine\Common\EventManager;
|
||||||
|
|
||||||
|
$rtel = new \Doctrine\ORM\Tools\ResolveTargetEntityListener;
|
||||||
|
$rtel->addResolveTargetEntity('Acme\\InvoiceModule\\Model\\InvoiceSubjectInterface',
|
||||||
|
'Acme\\CustomerModule\\Entity\\Customer', array());
|
||||||
|
|
||||||
|
// Add the ResolveTargetEntityListener
|
||||||
|
$evm->addEventSubscriber($rtel);
|
||||||
|
|
||||||
|
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
|
||||||
|
|
||||||
|
Final Thoughts
|
||||||
|
--------------
|
||||||
|
|
||||||
|
With the ``ResolveTargetEntityListener``, we are able to decouple our
|
||||||
|
bundles, keeping them usable by themselves, but still being able to
|
||||||
|
define relationships between different objects. By using this method,
|
||||||
|
I've found my bundles end up being easier to maintain independently.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user