Dynamically add mapping to Doctirne2 using annotations
This blog post will attempt to describe how to use @Annotations to dynamically
add mapping information to your model.
In our case we are adding a @DataInheritance flag to many classes:
/**
* @orm:Entity
* @orm:Table(name="page")
* @ylly:DataInheritance()
*/
class Page
We want this annotation to simply add two fields to the entity, _dataParent and
_dataChildren.
We are coming from the context of Symfony2 but that shouldnt matter too much.
Annotations
Annotations are a convenient way of specifying metadata on classes. The Doctrine
annotation reader works by interpreting each @Foo annotation as a class, and
if the class exists it is loaded as an annotation, if not, nothing happens.
So we define a simple class as follows::
<?php
namespace Our/Namespace/Doctrine/Mapping/Annotations;
use Doctrine/Common/Annotations/Annotation;
class DataInheritance extends Annotation
{
}
And thats it! If you want to add properties to your annotation you can do so by adding public properties to the class, have a look at::
Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php
and checkout all the doctrine annotation definitions.
So now we can add an annotation to our class by specifying the FQN::
<?php
/**
* @Entity
* ..
* @Our\Namespace\Doctrine\Mapping\Annotations\DataInheritance
*/
class Foo
{
...
}
Writing the entire class name including namespace each time is a pain and prone to error so Doctrine provides us with an alias feature, explained in the next part which allows us to specify an alias instead of the FQN so our annotation can then take the form of:
<?php
/**
* @Entity
* ..
* @ylly:DataInheritance
*/
class Foo
{
...
}
Events
The second thing we need to do is to create an event that will read the annotation and apply the new mappings.
Doctrine provides access to many events in the Entities lifecycle such as postPersist, preUpdate, etc. But the one we are interested in is LoadClassMetadata. This event is executed when Doctrine loads the metadata for the class, so we can listen to this event and then add our own mapping information.
The event will be called by each registered entity.
Our event class looks as follows::
<?php
namespace Ylly\CmsBundle\Inheritance\Doctrine;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\Common\Annotations\AnnotationReader;
class DataInheritanceEvent
{
const ANNOTATION_NS = 'Ylly\CmsBundle\Inheritance\Doctrine\Mapping\\';
const ANNOTATION_DATA_INHERITANCE = 'DataInheritance';
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
// annotation reader gets the annotations for the class
$reader = new AnnotationReader;
$reader->setAutoloadAnnotations(true);
$reader->setAnnotationNamespaceAlias(self::ANNOTATION_NS, 'ylly');
// the $metadata is all the mapping info for this class
$metadata = $eventArgs->getClassMetadata();
// the annotation reader accepts a ReflectionClass, which can be
// obtained from the $metadata
$class = $metadata->getReflectionClass();
$annotations = $reader->getClassAnnotations($class);
if (array_key_exists(self::ANNOTATION_NS.self::ANNOTATION_DATA_INHERITANCE, $annotations)) {
$metadata->mapManyToOne(array(
'targetEntity' => $class->getName(),
'fieldName' => '_dataParent',
'joinColumns' => array(array('name' => '_data_parent_id')),
'inversedBy' => '_dataChildren',
));
$metadata->mapOneToMany(array(
'targetEntity' => $class->getName(),
'fieldName' => '_dataChildren',
'mappedBy' => '_dataParent',
));
}
}
So first of all we scan the class for annotations using the AnnotationReader, we set the namespace
alias for ylly. Then we check to see if our annotation exists in the array of annotations, which
looks like this for a class with the ylly:DataInheritance annotation:
array
'Ylly\CmsBundle\Inheritance\Doctrine\Mapping\DataInheritance' =>
object(Ylly\CmsBundle\Inheritance\Doctrine\Mapping\DataInheritance)[380]
public 'value' => null
Having identified a class with our annotation we then use the ClassMetadata API to
add some mapping information.
Registering the event
For the event to work it must be registered with the Doctrine2 Event Manager. In Symfony2
this can be easily accomplished by using your Bundles boot method, if you dont use
Symfony2 its pretty much the same:
class CmsBundle extends Bundle
{
public function boot()
{
// get the doctrine 2 entity manager
$em = $this->container->get('doctrine.orm.default_entity_manager');
// get the event manager
$evm = $em->getEventManager();
// create and then add our event!
$inheritableEntityEvent = new InheritableEntityEvent();
$evm->addEventListener(Events::loadClassMetadata, $inheritableEntityEvent);
}
}
And thats it
So basically its not too hard to utilize @Annotations in your own code, but you
have more work to do if you want to support XML and YAML.
Comments
Post new comment
Tags
- DropBox
- XMPP
- android
- apache
- archos
- audacious
- awesome
- bash
- bootstrap
- bristol
- diagramming
- doctrine
- doctrine2
- git
- gloucester
- graphs
- gt540
- jack
- javascript
- manchester
- mapdroyd
- markdown
- mongodb
- paris
- php
- profiling
- projectm
- running
- scripting
- sed
- software design
- ssh
- symfony
- symfony2
- thonon-les-bains
- trainer
- travel
- twig
- ubnutu
- vim
- weymouth
- workflow
- xdebug
- xml
- ylly
- yprox
10 Latest Items
-
08
Maytrainer [Velo] paris - compiègne 153.00km / 05:48:32 / 00:02:16mpkm Fois.
-
06
Maytrainer [Velo] Vincennes Hippodrome 1hr 34.34km / 01:00:00 / 00:01:44mpkm Solo effort. Did interfals (sprinting from zebra crossing to hairpin turn).
-
05
Maytrainer [Run] Diderot > Pnt. Alx III > Rue de Charonne 13.96km / 01:08:40 / 00:04:55mpkm Good to run in the rain. Lots of traffic.
-
04
Maytrainer [Velo] Vincennes Hippodrome 1hr 32.20km / 01:00:00 / 00:01:51mpkm Rode apace a peloton, but tried not to get in the draft. Sprinted for a few minutes on every lap.
-
03
Maytrainer [Run] Dumas > P. Auguste > Belleville > Prc. de Villette > Pt. de Lilas > Pt. Vincennces 14.48km / 01:07:00 / 00:04:37mpkm Kept up a reasonably good pace. No problems from calf muscle as was the case yesterday.
-
02
Maytrainer [Run] Dumas > Diderot > Trocadero > Basitlle 14.48km / 01:10:00 / 00:04:49mpkm Experienced pain in the back of my calf and stopped a few times towards the end.
-
01
Maytrainer [Velo] Dumas > Rambouillet 123.31km / 05:06:00 / 00:02:28mpkm Paris Rambouillet. Sunny day. Ate a bakery pizza, tarte au pomme and drank a cola then lay down on a bench in the châte...
-
29
Aprtrainer [Run] 14 - 15 miles 23.34km / 01:50:00 / 00:04:42mpkm Run down the Rue Charonne, down Boulevard Henri IV, round le Ile St-Louis, along the Seine, crossing the Pont d'lén...
-
28
Aprtrainer [Velo] Vincennes Hippodrome 1hr 34.12km / 01:00:00 / 00:01:45mpkm Actually 45 minutes. Rain stopped play. Also boredom. "Sprinted" up the second half of the upside at each circ...
-
27
Aprtrainer [Run] 54 minutes 11.50km / 00:54:00 / 00:04:41mpkm Ran down and around.
