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?
class UserObject::$fields,class UserField::$dataType, etc.