Configurable dynamic Doctrine database entities in Symfony 2

Viewed 3536

What I am trying to achieve

  • Users would be able to configure Doctrine entities through an HTML form on a website.
  • Users would be able to define new entities, as well as add and delete fields for existing entities. (Similar to Drupal's content types)
  • The Doctrine entities would get dynamic properties based on the configuration that the user supplied through the web UI.
  • Either the single DB table per Doctrine entity would be altered dynamically whenever an entity configuration changes; Or there could be multiple tables used per single entity (each new entity field would get its own table).

Done so far

I have been researching this for the past few days without much success but I stumbled across this answer which seems quite related to what I am trying to achieve.

I have registered and added the loadClassMetadata listener which maps the field foo:

// src/DynamicMappingTest/AdminBundle/EventListener/MappingListener.php

namespace DynamicMappingTest\AdminBundle\EventListener;

use Doctrine\ORM\Event\LoadClassMetadataEventArgs;

class MappingListener
{
    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
    {
        $classMetadata = $eventArgs->getClassMetadata();
        if ($classMetadata->getName() != 'DynamicMappingTest\\AdminBundle\\Entity\\CustomNode')
        {
            // Not the CustomNode test class. Do not alter the class metadata.
            return;
        }
        $table = $classMetadata->table;

        $oldName = $table['name'];      // ... or $classMetaData->getTableName()

        // your logic here ...

        $table['name'] = 'custom_node';

        $classMetadata->setPrimaryTable($table);

        $reflClass = $classMetadata->getReflectionClass();
        dump($reflClass);

        // ... or add a field-mapping like this

        $fieldMapping = array(
            'fieldName' => 'foo',
            'type' => 'string',
            'length' => 255
        );
        $classMetadata->mapField($fieldMapping);
    }
}

Now, this all works as long as I have the foo property declared in the DynamicMappingTest\AdminBundle\Entity\CustomNode class:

// src/DynamicMappingTest/AdminBundle/Entity/CustomNode.php

namespace DynamicMappingTest\AdminBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * CustomNode
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="DynamicMappingTest\AdminBundle\Entity\CustomNodeRepository")
 */
class CustomNode
{
    ...
    private $foo;
}

Problem

However, there is no way for me to know what properties the users will define for their custom entities. If I remove the foo property from the CustomNode class, the ReflectionClass that I get from the ClassMetadata will naturally not include the foo property and so I get the following exception whenever the mapField() in MappingListener is executed:

ReflectionException: Property DynamicMappingTest\AdminBundle\Entity\CustomNode::$foo does not exist
in vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php at line 80

77.         */
78.        public function getAccessibleProperty($class, $property)
79.        {
80.            $reflectionProperty = new ReflectionProperty($class, $property);
81.
82.            if ($reflectionProperty->isPublic()) {
83.                $reflectionProperty = new RuntimePublicReflectionProperty($class, $property);

Questions

  • Is it possible to have fully configurable dynamic Doctrine entities?
  • Am I on the right track with my approach? If not, could you suggest an alternative?
  • How could I have truly dynamic class properties? Or should I be generating new Doctrine entity PHP classes whenever the users change the entity configuration?
1 Answers
Related