diff --git a/docs/en/cookbook/resolve-target-document-listener.rst b/docs/en/cookbook/resolve-target-document-listener.rst index 1a304d39b..9bff06d13 100644 --- a/docs/en/cookbook/resolve-target-document-listener.rst +++ b/docs/en/cookbook/resolve-target-document-listener.rst @@ -1,26 +1,20 @@ Keeping Your Modules Independent ================================ -One of the goals of using modules is to create discrete 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 MongoDB ODM includes a utility called -``ResolveTargetDocumentListener``, that functions by intercepting certain calls -inside Doctrine and rewriting ``targetDocument`` parameters in your metadata -mapping at runtime. This allows your bundle to use an interface or abstract -class in its mappings while still allowing the mapping to resolve to a concrete -document class at runtime. It will also rewrite class names when no mapping -metadata has been found for the original class name. - -This functionality allows you to define relationships between different -documents without creating hard dependencies. +If you work with independent modules, you may encounter the problem of creating +relationships between objects in different modules. This is problematic because +it creates a dependency between the modules. This can be resolved by using +interfaces or abstract classes to define the relationships between the objects +and then using the ``ResolveTargetDocumentListener``. This event listener will +intercept certain calls inside Doctrine and rewrite ``targetDocument`` +parameters in your metadata mapping at runtime. It will also rewrite class names +when no mapping metadata has been found for the original class name. Background ---------- -In the following example, we have an `InvoiceModule` that provides invoicing -functionality, and a `CustomerModule` that contains customer management tools. +In the following example, we have an ``InvoiceModule`` that 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; however, we'd like to use them together in our application. @@ -32,79 +26,94 @@ with a real class that implements that interface. Configuration ------------- -We're going to use the following basic documents (which are incomplete -for brevity) to explain how to set up and use the -``ResolveTargetDocumentListener``. +We're going to use the following basic documents to explain how to set up and +use the ``ResolveTargetDocumentListener``. -A Customer document: +A ``Customer`` class in the ``CustomerModule``. This class will be extended in +the application: .. code-block:: php name; + } + } + +Next, we need to configure a ``ResolveTargetDocumentListener`` to resolve to the +``Customer`` class of the application when an instance of +``InvoiceSubjectInterface`` from ``InvoiceModule`` is expected. This must be +done in the bootstrap code of your application. This is usually done before the +instantiation of the ``DocumentManager``: .. code-block:: php @@ -115,7 +124,7 @@ you cannot be guaranteed that the targetDocument resolution will occur reliably: // Adds a target-document class $rtdl->addResolveTargetDocument( \Acme\InvoiceModule\Model\InvoiceSubjectInterface::class, - \Acme\CustomerModule\Document\Customer::class, + \App\Document\Customer::class, [] ); @@ -125,9 +134,39 @@ you cannot be guaranteed that the targetDocument resolution will occur reliably: // Create the document manager as you normally would $dm = \Doctrine\ODM\MongoDB\DocumentManager::create(null, $config, $evm); +With this configuration, you can create an ``Invoice`` document and set the +``subject`` property to a ``Customer`` document. When the invoice is retrieved +from the database, the ``subject`` property will be an instance of +``Customer``. + +.. code-block:: php + + name = 'Example Customer'; + $invoice = new Invoice(); + $invoice->subject = $customer; + + $dm->persist($customer); + $dm->persist($invoice); + $dm->flush(); + $dm->clear(); + + // Retrieve the invoice from the database + $invoice = $dm->find(Invoice::class, $invoice->id); + + // The subject property will be an instance of Customer + echo $invoice->subject->getName(); + + Final Thoughts -------------- -With ``ResolveTargetDocumentListener``, we are able to decouple our bundles so +With ``ResolveTargetDocumentListener``, we are able to decouple our modules so that they are usable by themselves and easier to maintain independently, while -still being able to define relationships between different objects. +still being able to define relationships between different objects across +modules. diff --git a/tests/Documentation/ResolveTargetDocument/Customer.php b/tests/Documentation/ResolveTargetDocument/Customer.php new file mode 100644 index 000000000..3d41a168a --- /dev/null +++ b/tests/Documentation/ResolveTargetDocument/Customer.php @@ -0,0 +1,18 @@ +name; + } +} diff --git a/tests/Documentation/ResolveTargetDocument/CustomerModule/Customer.php b/tests/Documentation/ResolveTargetDocument/CustomerModule/Customer.php new file mode 100644 index 000000000..e2d8f64d3 --- /dev/null +++ b/tests/Documentation/ResolveTargetDocument/CustomerModule/Customer.php @@ -0,0 +1,19 @@ +dm->getEventManager(); + $rtdl = new ResolveTargetDocumentListener(); + + // Adds a target-document class + $rtdl->addResolveTargetDocument( + InvoiceSubjectInterface::class, + Customer::class, + [], + ); + + $evm->addEventSubscriber($rtdl); + + $customer = new Customer(); + $customer->name = 'Example Customer'; + $invoice = new Invoice(); + $invoice->subject = $customer; + + $this->dm->persist($customer); + $this->dm->persist($invoice); + $this->dm->flush(); + $this->dm->clear(); + + $invoice = $this->dm->find(Invoice::class, $invoice->id); + $this->assertInstanceOf(Customer::class, $invoice->subject); + $this->assertSame('Example Customer', $invoice->subject->name); + } +}