Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configurable dynamic Doctrine database entities in Symfony 2

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?
like image 601
in7l Avatar asked Mar 08 '16 13:03

in7l


1 Answers

Is it possible to have fully configurable dynamic Doctrine entities?

Doctrine generates proxy classes for you entities. That means that doctrine generates PHP code with class, which extends your Entity class and overrides the methods - puts some custom logic and then calls the parent method.

So, I think that the only way to make this really happen is to generate the PHP code for entities in your code. That is, every time entity is created in your website, you should generate PHP file with that entity, then run migrations.

Am I on the right track with my approach? If not, could you suggest an alternative?

I don't think that you should use Doctrine ORM at all in this case, at least in the way you're trying to do that.

Generally, ORM is used for easier/more manageable programming. That is, you can set relations, use lazy-loading, unit of work (change entity properties and then just flush) etc. If your entities are generated dynamically, what features will you use at all? Developer will not write code for these entities, because, as you've said, there is no way to know what fields it will have.

You haven't provided concrete use-case - why do you want to do that in the first place. But I imagine that it could be really done in some easier way.

If users can store any structure at all, should you use MySQL at all? ElasticSearch or similar solutions could be really much better in such cases.

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?

As I've mentioned - yes. Unless you would want to override or replace some of Doctrine code, but I imagine it could be lots of it (proxy classes etc.)

like image 56
Marius Balčytis Avatar answered Oct 03 '22 03:10

Marius Balčytis