- most key features implemented
- alpha stage
- Issue Tracker
- have the register-system-node-types command provide api conform node type definition as well to support other implementations
- write documentation
- expand test suite
- translations
- provide a method to get a detached translated document so the relations can be translated automatically
- php >= 5.3
- libxml version >= 2.7.0 (due to a bug in libxml http://bugs.php.net/bug.php?id=36501)
- phpunit >= 3.6 and wget (if you want to run the tests)
If you use the Doctrine PHPCR ODM Symfony Bundle, please look into the Tutorial to install the DoctrinePHPCRBundle. This documentation explains how to use PHPCR ODM outside of symfony, which requires some manual initialization.
git clone git://github.com/doctrine/phpcr-odm.git
cd phpcr-odm
git submodule update --init --recursive
PHPCR ODM uses the PHP Content Repository API for storage. You need to install one of the available providers:
Jackalope with the Jackrabbit backend is the only PHPCR implementation that is enough feature complete for the PHPCR ODM.
You will also need to check out Jackalope Jackrabbit transport and the related dependencies.
Follow Running Jackrabbit Server from the Jackalope wiki.
Jackalope with the DBAL backend is partially working and can use any RDBMS support by Doctrine DBAL.
You will also need to check out Jackalope Doctrine DBAL transport and the related dependencies.
Midgard2 is a PHPCR provider that provides most of the functionality needed for PHPCR ODM, and can persist your content in typical relational databases like SQLite and MySQL. Midgard2 only needs a PHP extension to run. On typical Linux setups getting the extension is as easy as:
$ sudo apt-get install php5-midgard2
The console provides a bunch of useful commands:
# in the phpcr-odm root directoy
cp cli-config.php.dist cli-config.php
# edit the file and adjust if needed - the defaults expect all submodules in place
Now running php bin/phpcr
will show you a list of the available commands.
php bin/phpcr help <cmd>
displays additional information for that command.
PHPCR ODM uses a custom node type to track meta information without interfering with your content. We provide a command that makes it trivial to register this type and the phpcr namespace.
php bin/phpcr doctrine:phpcr:register-system-node-types
This examples shows how to run the tests for jackrabbit. You can run the tests for the other backends (doctrine_dbal, midgard_mysql, midgard_sqlite) by replacing jackrabbit with the same commands. Just replace jackrabbit with the name of the backend you want to run.
-
Make sure you have installed the submodules
-
Run this command to download jackrabbit and launch it (requires wget)
./tests/travis_jackrabbit.sh
-
Run the tests:
phpunit -c tests/phpunit_jackrabbit.xml.dist
For an inspiration for the autoloading, have a look at cli-config.php.dist
.
You need to make sure that the following paths are autoloaded (all paths relative to the phpcr-odm root directory):
'Doctrine\ODM' => 'lib',
'Doctrine\Common' => 'lib/vendor/doctrine-common/lib',
'Symfony\Component\Console' => 'lib/vendor/jackalope/lib/phpcr-utils/lib/vendor',
'Symfony' => 'lib/vendor,
'PHPCR\Util' => 'lib/vendor/jackalope/lib/phpcr-utils/src',
'PHPCR' => 'lib/vendor/jackalope/lib/phpcr/src',
'Jackalope' => 'lib/vendor/jackalope/src',
'Midgard\PHPCR' => 'lib/vendor/Midgard/PHPCR/src',
'Doctrine\DBAL' => 'lib/vendor/jackalope/lib/vendor/doctrine-dbal',
You can choose between the drivers for annotations, xml and yml configuration files:
<?php
// Annotation driver
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$driver = new \Doctrine\ODM\PHPCR\Mapping\Driver\AnnotationDriver($reader, array('/path/to/your/document/classes'));
// Xml driver
$driver = new \Doctrine\ODM\PHPCR\Mapping\Driver\XmlDriver(array('/path/to/your/mapping/files'));
// Yaml driver
$driver = new \Doctrine\ODM\PHPCR\Mapping\Driver\YamlDriver(array('/path/to/your/mapping/files'));
With the Jackrabbit provider, the PHPCR ODM connection can be configured with:
<?php
$repository = \Jackalope\RepositoryFactoryJackrabbit::getRepository(
array('jackalope.jackrabbit_uri' => 'http://localhost:8080/server'));
$credentials = new \PHPCR\SimpleCredentials('user', 'pass');
$session = $repository->login($credentials, 'your_workspace');
With Midgard2, the connection configuration (using MySQL as an example) would be something like:
<?php
$repository = \Midgard\PHPCR\RepositoryFactory::getRepository(
array(
'midgard2.configuration.db.type' => 'MySQL',
'midgard2.configuration.db.name' => 'phpcr',
'midgard2.configuration.db.host' => 'localhost',
'midgard2.configuration.db.username' => 'midgard',
'midgard2.configuration.db.password' => 'midgard',
'midgard2.configuration.blobdir' => '/some/path/for/blobs',
'midgard2.configuration.db.init' => true
)
);
$credentials = new \PHPCR\SimpleCredentials('admin', 'password');
$session = $repository->login($credentials, 'your_workspace');
Note that the midgard2.configuration.db.init
setting should only be used the
first time you connect to the Midgard2 repository. After that the database is
ready and this setting should be removed for better performance.
<?php
$config = new \Doctrine\ODM\PHPCR\Configuration();
$config->setMetadataDriverImpl($driver);
$dm = new \Doctrine\ODM\PHPCR\DocumentManager($session, $config);
Now you are ready to use the PHPCR ODM
<?php
// fetching a document by PHPCR path (id in PHPCR ODM lingo)
$user = $dm->getRepository('Namespace\For\Document\User')->find('/bob');
//or let the odm find the document class for you
$user = $dm->find('/bob');
// create a new document
$newUser = new \Namespace\For\Document\User();
$newUser->username = 'Timmy';
$newUser->email = '[email protected]';
$newUser->path = '/timmy';
// make the document manager know this document
// this will create the node in phpcr but not read the fields or commit
// the changes yet.
$dm->persist($newUser);
// store all changes, insertions, etc. with the storage backend
$dm->flush();
// move/rename a document in the tree
$dm->move($newUser, '/tommy');
$dm->flush();
// make sure there is no documents left thinking $newUser at the old path
$dm->clear();
// run a query
$qb = $dm->createQueryBuilder();
// SELECT * FROM nt:unstructured WHERE name NOT IS NULL
$factory = $qb->getQOMFactory();
$qb->select($factory->selector('nt:unstructured'))
->where($factory->propertyExistance('name'))
->setFirstResult(10)
->setMaxResults(10)
->execute();
$result = $dm->getDocumentsByQuery($qb->getQuery());
foreach ($result as $document) {
echo $document->getId();
}
// remove a document - and all documents in paths under that one!
$dm->remove($newUser);
$dm->flush();
You write your own document classes that will be mapped to and from the phpcr database by doctrine. The documents are usually simple
<?php
namespace Acme\SampleBundle\Document;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
/**
* @PHPCRODM\Document
*/
class MyDocument
{
/**
* @PHPCRODM\Id()
*/
public $path;
/**
* @PHPCRODM\String()
*/
public $title;
/**
* @PHPCRODM\String()
*/
public $content;
}
Note that there are basic Document classes for the standard PHPCR node types
nt:file
, nt:folder
and nt:resource
. See lib/Doctrine/ODM/PHPCR/Document/
Every document needs an id
. This is used to later retrieve the document
from storage again. The id is the path in the content repository to the node
storing the document.
It is possible to choose the generator strategy. Currently, there are 3 strategies available:
- With the default "assigned id" you need to assign a path to your id field and have to make sure yourself that the parent exists.
- The "parent and name" strategy determines the path from the @ParentDocument and the @Nodename fields. This is the most failsave strategy.
- The repository strategy lets your custom repository determine an id so you can implement any logic you might need.
This is the default but very unsafe strategy. You need to manually assign the path to the id field. A document is not allowed to have no parent, so you need to make sure that the parent of that path already exists. (It can be a plain PHPCR node not representing any PHPCR-ODM document, though.)
<?php
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
/**
* @PHPCRODM\Document
*/
class Document
{
/** @PHPCRODM\Id */
public $id;
}
$doc = new Document();
$doc->id = '/test';
This strategy uses the @Nodename (desired name of this node) and @ParentDocument (PHPCR-ODM document that is the parent). The id is generated as the id of the parent concatenated with '/' and the Nodename.
If you supply a ParentDocument annotation, the strategy is automatically set to parent. This strategy will check the parent and the name and will fall back to the assigned id if either is missing.
<?php
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
/**
* @PHPCRODM\Document
*/
class Document
{
/** @PHPCRODM\ParentDocument */
public $parent;
/** @PHPCRODM\Nodename */
public $nodename;
}
$doc = new Document();
$doc->parent = $dm->find('/test');
$doc->nodename = 'mynode';
// => /test/mynode
If you need custom logic to determine the id, you can explicitly set the strategy to "repository". You need to define the repositoryClass which will handle the task of generating the id from the information in the document. This gives you full control how you want to build the path.
<?php
namespace Acme\SampleBundle\Document;
use Doctrine\ODM\PHPCR\Id\RepositoryIdInterface;
use Doctrine\ODM\PHPCR\DocumentRepository as BaseDocumentRepository;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
/**
* @PHPCRODM\Document(repositoryClass="Acme\SampleBundle\DocumentRepository")
*/
class Document
{
/** @PHPCRODM\Id(strategy="repository") */
public $id;
/** @PHPCRODM\String(name="title") */
public $title;
}
class DocumentRepository extends BaseDocumentRepository implements RepositoryIdInterface
{
/**
* Generate a document id
*
* @param object $document
* @return string
*/
public function generateId($document)
{
return '/functional/'.$document->title;
}
}
Id: | Read only except on new documents with the assigned id strategy. The PHPCR path to this node. (see above). For new nodes not using the default strategy, it is populated during the persist() operation. |
Uuid: | Read only (generated on flush). The unique id of this node. (only allowed if node is referenceable). |
Node: | The PHPCR\NodeInterface instance for direct access. This is populated as soon as you register the document with the manager using persist(). |
Nodename: | Read only except for new documents with the parent and name strategy. For new nodes with other id strategies, it is populated during the persist() operation. The name of the PHPCR node (this is the part of the path after the last '/' in the id). |
ParentDocument: | Read only except for new documents with the parent and name strategy. The parent document of this document. If the repository knows the document class, the document will be of that type, otherwise Doctrine\ODM\PHPCR\Document\Generic is used. |
Child(name=x): | Map the child with name x to this field. If name is not specified, the name of the annotated varialbe is used. |
Children(filter=x): | Map the collection of children to this field. Filter is optional and works like the parameter in PHPCR Node::getNodes() |
ReferenceOne(targetDocument="myDocument", strategy="weak"): (*) | Refers a document of the type myDocument. The default is a weak reference. By optionaly specifying strategy="hard" you get a hard reference. Finally with strategy="path" it will simply store the path to the node, but automatically dereference. It is optional to specify the targetDocument, you can reference any document type. However using strategy="path" will be faster if a targetDocument is set. |
ReferenceMany(targetDocument="myDocument", weak="weak"): (*) | Same as ReferenceOne except that you can refer many documents with the same document and reference type. If you dont't specify targetDocument you can reference documents of mixed types in the same property. This type of collection will always be lazy loaded regardless of the strategy chosen. |
Referrers(filter="x", referenceType=null): | Read only, the inverse of the Reference field. This field is a collection of all documents that refer this document. The ``filter`` is optional. If set, it is used as parameter ``name`` for Node::getReferences() or Node::getWeakReferences(). You can also specify an optional referenceType, weak or hard, to only get documents that have either a weak or a hard reference to this document. If you specify null then all documents with weak or hard references are fetched, which is also the default behavior. |
Locale: | Indentifies the field that will be used to store the current locale of the document. This annotation is required for translatable documents. |
VersionName: | Read only, only populated for detached documents returned by findVersionByName. Stores the version name this document represents. Otherwise its ignored. |
VersionCreated: | Read only, only populated for detached documents returned by findVersionByName. Stores the DateTime object when this version was created with the checkin() operation. Otherwise its ignored. |
String, Binary, Long (alias Int), Decimal, Double (alias Float), Date, Boolean, Name, Path, Uri |
Map node properties to the document. See PHPCR\PropertyType for details about the types. |
(*) Note that creating new references with the help of the ReferenceOne/ReferenceMany annotations is only possible if your PHPCR implementation supports programmatically setting the uuid property at node creation.
In the parenthesis after the type, you can specify some additional information like the name of the PHPCR property to store the value in.
name | The property name to use for storing this field. If not specified, defaults to the php variable name. |
multivalue | Set multivalue=true to mark this property as multivalue. It then contains a numerically indexed array of values instead of just one value. For more complex data structures, use child nodes. |
translated | Set translated=true to mark this property as being translated. See below. |
<?php
/**
* @PHPCRODM\String(name="categories", multivalue=true)
*/
private $cat;
PHPCR-ODM supports multilingual documents so that you can mark properties as translatable and then make the document manager automatically store the translations.
To use translatable documents you need to use several annotations and some bootstrapping code. Your document annotation must specify a translator type.
<?php
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
/**
* @PHPCRODM\Document(translator="attribute")
*/
class Article
{
/** @PHPCRODM\Id */
public $id;
/**
* The language this document currently is in
* @PHPCRODM\Locale
*/
public $locale;
/**
* Untranslated property
* @PHPCRODM\Date
*/
public $publishDate;
/**
* Translated property
* @PHPCRODM\String(translated=true)
*/
public $topic;
/**
* Language specific image
* @PHPCRODM\Binary(translated=true)
*/
public $image;
}
Note that translation always happens on a document level, not on individual fields. With the above document, there is no way to store a new translation for the topic without generating a copy of the image (unless you remove the translated=true from image, but then the image is no longer translated for any language).
A translation strategy needs to be selected by adding the translator
parameter to the @Document annotation.
The translation strategy is responsible to actually persist the translated properties.
There are two default translation strategies implemented:
- attribute - will store the translations in attributes of the node containing the translatable properties
- child - will store the translations in a child node of the node containing the translatable properties
It is possible to implement other strategies to persist the translations, see below.
You may want to implement your own translation strategy to persist the translatable properties of a node. For example if you want all the translations to be stored in a separate branch of you content repository.
To do so you need to implement the Doctrine\ODM\PHPCR\Translation\TranslationStrategy\TranslationStrategyInterface
.
Then you have to register your translation strategy with the document manager during the bootstrap.
<?php
class MyTranslationStrategy implements Doctrine\ODM\PHPCR\Translation\TranslationStrategy\TranslationStrategyInterface
{
// ...
}
$dm = new \Doctrine\ODM\PHPCR\DocumentManager($session, $config);
$dm->setTranslationStrategy('my_strategy_name', new MyTranslationStrategy());
After registering your new translation strategy you can use it in the @Document annotation:
<?php
/**
* @PHPCRODM\Document(translator="my_strategy_name")
*/
class Article
{
// ...
}
The language chooser strategy provides the default language and a list of languages to be used as language fallback order to find the best available translation.
On reading, PHPCR-ODM tries to find a translation with each of the languages in that list and throws a not found exception if none of the languages exists.
The default language chooser strategy (Doctrine\ODM\PHPCR\Translation\LocaleChooser\LocaleChooser
) returns
a configurable list of languages based on the requested language. On instantiation, you specify
the default locale. This should be your application default locale. It is used to get the default locale order
which usually should not vary based on the current locale.
Based on the request or whatever criteria you have, you can use setLocale to have the document manager load
your document in the right language.
When you bootstrap the document manager, you need to set the language chooser strategy if you have any translatable documents:
<?php
$localePrefs = array(
'en' => array('en', 'de', 'fr'), // When EN is requested try to get a translation first in EN, then DE and finally FR
'fr' => array('fr', 'de', 'en'), // When FR is requested try to get a translation first in FR, then DE and finally EN
'it' => array('fr', 'de', 'en'), // When IT is requested try to get a translation first in FR, then DE and finally EN
);
$dm = new \Doctrine\ODM\PHPCR\DocumentManager($session, $config);
$dm->setLocaleChooserStrategy(new LocaleChooser($localePrefs, 'en'));
You can write your own strategy by implementing Doctrine\ODM\PHPCR\Translation\LocaleChooser\LocaleChooserInterface
.
This is useful to determine the default language based on some logic, or provide fallback orders based on user preferences.
If you annotate a field with this annotation, the current locale of the document is tracked there. It is populated when finding the document, updated when you call bindTranslation and also taken into account when you flush the document.
<?php
/**
* @PHPCRODM\Locale
*/
public $locale;
A property is set as translatable adding the translatable
parameter to the field definition annontation.
<?php
/** @PHPCRODM\String(translated=true) */
public $topic;
You can set any type of property as translatable.
Having at least one property marked as translatable will make the whole document translatable and thus forces you to have a @Locale field (see above).
Please note that internally, the translatable properties will be persisted by the translator strategy, not directly by the document manager.
For now, Child, Children, Parent, ReferenceMany, ReferenceOne and Referrers will all fall back to the default language. The reason for this is that there can be only one tracked instance of a document per session. (Otherwise what should happen if both copies where modified?...).
For more details, see the wiki page and the TODO at the top if this README.
Please refer to the phpDoc of the following functions:
For reading:
- DocumentManager::find (uses the default locale)
- DocumentManager::findTranslation (allows you to specify which locale to load)
- DocumentManager::getLocalesFor (get the available locales of a document)
For writing:
- DocumentManager::persist (save document in language based on @Locale or default language)
- DocumentManager::persitTranslation (save document with explicit language context)
<?php
// bootstrap the DocumentManager as required (see above)
$localePrefs = array(
'en' => array('en', 'fr'),
'fr' => array('fr', 'en'),
);
$dm = new \Doctrine\ODM\PHPCR\DocumentManager($session, $config);
$dm->setLocaleChooserStrategy(new LocaleChooser($localePrefs, 'en'));
// then to use translations:
$doc = new Article();
$doc->id = '/my_test_node';
$doc->author = 'John Doe';
$doc->topic = 'An interesting subject';
$doc->text = 'Lorem ipsum...';
// Persist the document in English
$dm->persist($doc);
$dm->bindTranslation($doc, 'en');
// Change the content and persist the document in French
$doc->topic = 'Un sujet intéressant';
$dm->bindTranslation($doc, 'fr');
// locale is updated automatically if there is such an annotation
echo $doc->locale; // fr
// Flush to write the changes to the phpcr backend
$dm->flush();
// Get the document in default language (English if you bootstrapped as in the example)
$doc = $dm->find('Doctrine\Tests\Models\Translation\Article', '/my_test_node');
// Get the document in French
$doc = $dm->findTranslation('Doctrine\Tests\Models\Translation\Article', '/my_test_node', 'fr');
$doc->title = 'nouveau';
$dm->flush(); // french is updated as the language is tracked by the dm
The provided translation strategies will report a translation as not existing if any of the fields declared in the document is not existing. This is a feature because you want to know when you try to load an incomplete document. But we are currently missing a concept how to do update the content to still be compatible when document annotations are changed. The solution could look similar to the ORM migrations concept.
PHPCR-ODM natively supports versioning documents, using the power of the PHPCR Version features. Before you try this out, make sure your implementation supports the versioning features. PHPCR-ODM does not replicate the complete PHPCR Version API (VersionManager, VersionHistory and Version). For the full power, you need to access the PHPCR session and interact with the VersionManager directly. PHPCR-ODM provides simple methods for the common operations.
There are 2 levels: simpleVersionable and (full) versionable. Simple versioning consists of a linear verison history and the checkin/checkout possibility. Checking in a node creates a new version and makes the node readonly. You need to check it out again to write to it (or just do a checkpoint to do both in one call). Full versioning additionally has non-linear versioning (which the PHPCR-ODM does not provide any helper methods for) and version labels (which we plan to support once Jackalope supports them). For each node, you can add labels to version, but one label string may only occur once per version history (meaning if you want to label another version, you need to remove the label from the first version before you add the label).
Version names are generated by PHPCR and can not be controlled by the client application. There is no concept of commit messages for PHPCR. We decided to not build something like that into the core of the ODM versioning system to avoid unnecessary overhead if the user does not need it. It is however doable with having a field on your document that you set to your commit message and flush before calling checkin().
For more background, read the Versioning section in the PHPCR Tutorial and refer to the specification JCR 2.0, Chapter 15.
For the PHPCR-ODM layer, the following applies: Contrary to translations, getting an old version does not change the document representing the current version. An old version can't be modified and can't be persisted. (Except with the special restoreVersion and removeVersion methods.) What you get is a detached instance of the document which is ignored by flush and can not be persisted.
Please refer to the phpDoc of the following functions:
Read version information:
- DocumentManager::find (returns the current version of the document)
- DocumentManager::getAllLinearVersions (returns information about existing versions)
- DocumentManager::findVersionByName (returns a detached read-only document representing a version)
Modify the version history:
- DocumentManager::checkin (create new version of a flushed document and make it readonly)
- DocumentManager::checkout (make a document that was checked in writable again)
- DocumentManager::checkpoint (create a new version without making the document read-only, aka checkin followed by checkout)
- DocumentManager::restoreVersion (restore the document to an old version)
- DocumentManager::removeVersion (completely remove an old version from the history)
<?php
$article = new Article();
$article->id = '/test';
$article->topic = 'Test';
$dm->persist($article);
$dm->flush();
// generate a version snapshot of the document as currently stored
$dm->checkpoint($article);
$article->topic = 'Newvalue';
$dm->flush();
// get the version information
$versioninfos = $dm->getAllLinearVersions($article);
$firstVersion = reset($versioninfos);
// and use it to find the snapshot of an old version
$oldVersion = $dm->findVersionByName(null, $article->id, $firstVersion['name']);
echo $oldVersion->topic; // "Test"
// find the head version
$article = $dm->find('/test');
echo $article->topic; // "Newvalue"
// restore the head to the old version
$dm->restoreVersion($oldVersion);
// the article document is refreshed
echo $article->topic; // "Test"
// create a second version to demo removing a version
$article->topic = 'Newvalue';
$dm->flush();
$dm->checkpoint($article);
// remove the old version from the history (not allowed for the last version)
$dm->removeVersion($oldVersion);
To be able to use the versioning methods of the DocumentManager, you need to specify the versionable attribute in your @Document annotation. You can choose between "full" and "simple" versionable.
If you only use the methods the DocumentManager offers, "simple" is enough. This will allow you to create a linear version history. The full versionable corresponds to the PHPCR mix:versionable that allows to branch versions. If you need that, you will need to access PHPCR directly for some operations.
<?php
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
/**
* @PHPCRODM\Document(versionable="full")
*/
class Article
{
... // properties as normal
}
Note that all fields of a document are automatically versioned, you can not exclude anything from being versioned. Referenced documents are not versioned, but it is stored to which document the reference pointed at this time. Children and parents are not versioned. (Actually children could be versioned if you are using a PHCPR node types that specifies to cascade versioning. This feature however is untested with PHPCR-ODM, if you have feedback please tell us.)
You can track some information about old versions in PHPCR-ODM. The VersionName tracks the code that PHPCR assigned the version you created, VersionCreated the timestamp when the version was created.
Be aware that there are two things:
- The document that is versionable. This is the document and you can
take snapshots of this document with the
checkin()
/checkpoint()
methods. - The frozen document that represents an old version of your document. You get this document with the findVersionByName method. It is read-only. The document class you use needs not be the same. You can define a version document that is the same as your base document, but all fields are read only and you use the VersionName and VersionCreated annotations on it. It also does not need the versionable document attribute. (You do not create versions of old versions, you only create versions of the main document.)
<?php
/** @PHPCRODM\VersionName */
public $versionName;
/** @PHPCRODM\VersionCreated */
public $versionCreated;
You can use @PHPCRODM\PostLoad and friends to have doctrine call a method without parameters on your entity.
You can also define event listeners on the DocumentManager with
$dm->getEventManager()->addEventListener(array(<events>), listenerclass);
Your class needs event name methods for the events. They get a parameter of type
Doctrine\Common\EventArgs.
See also http://www.doctrine-project.org/docs/orm/2.0/en/reference/events.html
- preRemove - occurs before a document is removed from the storage
- postRemove - occurs after the document has been successfully removed
- prePersist - occurs before a new document is created in storage
- postPersist - occurs after a document has been created in storage. generated ids will be available in this state.
- preUpdate - occurs before an existing document is updated in storage, during the flush operation
- postUpdate - occurs after an existing document has successfully been updated in storage
- postLoad - occurs after the document has been loaded from storage
- Explain Configuration class in more detail
- Proxy classes: Either configuration with setAutoGenerateProxyClasses(true) or make sure you generate proxies. proxies are used when you have references, children and so on to not load the whole PHPCR repository.