phpeventsdoctrinesymfony4

Doctrine Add column onSchemaCreateTable Event


I have some ManyToMany table relations.

I want to add a new column "created_at" automatically.

Following Symfony and Doctrine Documentation, i tired this :

app/config/services_dev.yaml

[...]
    App\Event\Listener\ManyToManyListener:
        tags:
            - { name: doctrine.event_listener, event: onSchemaCreateTable }

app/src/Event/Listener/ManyToManyListener.php


[.....]

class ManyToManyListener implements EventSubscriber
{
    public function getSubscribedEvents()
    {
        return ['onSchemaCreateTable'];
    }

    public function onSchemaCreateTable(SchemaCreateTableEventArgs  $event)
    {
        $columns = $event->getTable()->getColumns();

        if (count($columns) <= 2 && !array_key_exists("created_at", $columns)) {
            $tableName = $event->getTable()->getName();
            $sql = "ALTER TABLE $tableName ADD COLUMN created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP;";
            $event->addSql($sql);
              //dump($sql);
        }
    }
}

I can dump my SQL code inside, it works.

I also tried this code (inside the if statement)

$event->getTable()->addColumn(
                "created_at",
                "datetime",
                ["default" => "CURRENT_TIMESTAMP"]
            );

This never execute the SQL statement. For example, while php bin/console doctrine:schema:update --dump-sql, I can't see my query.


Solution

  • I went through doctrine code and I understood that it was impossible to do what I wanted with those event because Event are dispatched AFTER the code path through columns, for example in Doctrine\DBAL\Platforms\AbstractPlatform

        if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) {
            $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this);
            $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs);
    
            if ($eventArgs->isDefaultPrevented()) {
                return array_merge($eventArgs->getSql(), $columnSql);
            }
        }
    

    So the only way with this event would have been to preventDefault and create the SQL myself.


    Looking through code, I found a better way : I modify the schema generated by the Doctrine\ORM\Tools\SchemaTool->getSchemaFromMetadata

    Not sure it's the best way but it perfectly works.

        App\EventSubscriber\ManyToManyListener:
            tags:
                - { name: doctrine.event_listener, event: postGenerateSchema }
    
    class SchemaGeneratorListener
    {
        public function postGenerateSchema(GenerateSchemaEventArgs $event)
        {
            $tables = $event->getSchema()->getTables();
    
            foreach ($tables as $table) {
                $columns = $table->getColumns();
                if (count($columns) <= 2 && !array_key_exists("created_at", $columns)) {
                    $event->getSchema()->getTable($table->getName())->addColumn(
                        "created_at",
                        'datetime',
                        ["default" => "CURRENT_TIMESTAMP"]
                    );
                }
            }
        }
    
    }