Using a Custom Document Class Mapper with PHPCR-ODM

The default document class mapper of PHPCR-ODM uses the attribute phpcr:class to store and retrieve the document class of a node. When accessing an existing PHPCR repository, you might need different logic to decide on the class.

You can extend the DocumentClassMapper or implement DocumentClassMapperInterface from scratch. The important methods are getClassName that needs to find the class name and writeMetadata that needs to make sure the class of a newly stored document can be determined when loading it again.

An example mapper from the symfony cmf sandbox (magnolia_integration branch):

namespace Sandbox\MagnoliaBundle\Document;

use Doctrine\ODM\PHPCR\DocumentClassMapper;
use Doctrine\ODM\PHPCR\DocumentManager;
use Doctrine\ODM\PHPCR\Document\Generic;

use PHPCR\NodeInterface;
use PHPCR\PropertyType;

class MagnoliaDocumentClassMapper extends DocumentClassMapper
{
    public function __construct(
        /**
         * @var array<string, string> map from mgnl:template values to document class names
         */
        private array $templateMap
    ) {
    }

    /**
     * Determine the class name from a given node
     *
     * @throws \RuntimeException if no class name could be determined
     */
    public function getClassName(DocumentManager $dm, NodeInterface $node, string $className = null): string
    {
        $className = parent::getClassName($dm, $node, $className);
        if (Generic::class === $className) {
            if ($node->hasNode('MetaData')) {
                $metaData = $node->getNode('MetaData');
                if ($metaData->hasProperty('mgnl:template')) {
                    if (isset($this->templateMap[$metaData->getPropertyValue('mgnl:template')])) {
                        return $this->templateMap[$metaData->getPropertyValue('mgnl:template')];
                    }
                }
            }
        }

        return $className;
    }
}

Then adjust your bootstrap code to use the custom mapper:

/* prepare the doctrine configuration */
$config = new \Doctrine\ODM\PHPCR\Configuration();
$map = [
    'standard-templating-kit:pages/stkSection' => \Sandbox\MagnoliaBundle\Document\Section::class,
];
$mapper = new MagnoliaDocumentClassMapper($map);
$config->setDocumentClassMapper($mapper);

$documentManager = \Doctrine\ODM\PHPCR\DocumentManager::create($session, $config);

...

Symfony integration

If you are running on Symfony, you do not instantiate PHPCR-ODM manually. Instead, you adjust the configuration in your service definition.

Here we overwrite the doctrine.odm_configuration service to call setDocumentClassMapper on it. This will make it use this mapper instead of instantiating the default one. An example from the symfony cmf sandbox (magnolia_integration branch):

  • YAML
    1# if you want to overwrite default configuration, otherwise use a # custom name and specify in odm configuration block doctrine.odm_configuration: class: %doctrine_phpcr.odm.configuration.class% calls: - [ setDocumentClassMapper, [@sandbox_magnolia.odm_mapper] ] sandbox_magnolia.odm_mapper: class: "Sandbox\MagnoliaBundle\Document\MagnoliaDocumentClassMapper" arguments: - 'standard-templating-kit:pages/stkSection': 'Sandbox\MagnoliaBundle\Document\Section'
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  • XML
    1<service id="doctrine.odm_configuration" class="%doctrine_phpcr.odm.configuration.class%"> <call method="setDocumentClassMapper"> <argument type="service" id="sandbox_magnolia.odm_mapper" /> </call> </service> <service id="sandbox_magnolia.odm_mapper" class="Sandbox\MagnoliaBundle\Document\MagnoliaDocumentClassMapper"> <argument type="collection"> <argument type="standard-templating-kit:pages/stkSection">Sandbox\MagnoliaBundle\Document\Section</argument> </argument> </service>
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  • PHP
    1use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Sandbox\MagnoliaBundle\Document\MagnoliaDocumentClassMapper; use Sandbox\MagnoliaBundle\Document\Section; $container ->register('doctrine.odm_configuration', '%doctrine_phpcr.odm.configuration.class%') ->addMethodCall('setDocumentClassMapper', [ new Reference('sandbox_magnolia.odm_mapper'), ]) ; $container ->setDefinition('sandbox_amgnolia.odm_mapper', new Definition( MagnoliaDocumentClassMapper::class, [ [ 'standard-templating-kit:pages/stkSection' => Section::class, ], ], ));
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20