gremo
gremo

Reputation: 48899

In Doctrine, how to ignore specific column from update schema command?

I have my mapped entity named Product, with just two columns: id and name.

If I add another column manually, i.e. stock_qty directly with an SQL statement, the schema update tool will remote it, of course.

How can I prevent Doctrine remove my custom columns, not mapped to my entity?

Related issues: https://github.com/doctrine/doctrine2/issues/6434

Upvotes: 3

Views: 4463

Answers (3)

Brewal
Brewal

Reputation: 8189

I had some success using the postGenerateSchema with Symfony 6.3 without using deprecated or internal doctrine code.

You can see the full gist here.

This works by manually adding the ignored fields and indexes to the schema, so the migration and validation commands don't try to add them.

Upvotes: 1

Henry
Henry

Reputation: 7881

Jannes Botis's answer was very helpful. I had some extra issues with an index and foreign key being removed even though the column was ignored. Also, the down migration continued to try to re-add the column in question.

What follows is my expanded solution, based on the other answer.

<?php
namespace App\Listener\Doctrine;

use Doctrine\DBAL\Event\SchemaAlterTableAddColumnEventArgs;
use Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs;

class DoctrineListener
{
    protected $ignoreFields;

    public function __construct($doctrineIgnoreFields)
    {
        $this->ignoreFields = $doctrineIgnoreFields;
    }

    public function onSchemaAlterTableAddColumn(SchemaAlterTableAddColumnEventArgs $args)
    {
        $tableDiff = $args->getTableDiff();
        if (in_array($tableDiff->name, array_keys($this->ignoreFields))) {
            $tableName = $tableDiff->name;
            $tableColumn = $args->getColumn();
            if (in_array($tableColumn->getName(), $this->ignoreFields[$tableName])) {
                $columnName = $tableColumn->getName();

                if ($tableColumn->getName() == $columnName) {
                    $tableDiff = $args->getTableDiff();
                    unset($tableDiff->addedColumns[$columnName]);
                    foreach ($args->getTableDiff()->addedIndexes as $index => $addedIndex) {
                        foreach ($addedIndex->getColumns() as $column) {
                            if ($column === $columnName) {
                                unset($tableDiff->addedIndexes[$index]);
                                $args->preventDefault();
                            }
                        }
                    }
                    foreach ($args->getTableDiff()->addedForeignKeys as $index => $foreignKey) {
                        foreach ($foreignKey->getColumns() as $column) {
                            if ($column === $columnName) {
                                unset($tableDiff->addedForeignKeys[$index]);
                                $args->preventDefault();
                            }
                        }
                    }
                }
            }
        }
    }

    public function onSchemaAlterTableRemoveColumn(SchemaAlterTableRemoveColumnEventArgs $args)
    {
        $tableDiff = $args->getTableDiff();

        if (in_array($tableDiff->name, array_keys($this->ignoreFields))) {

            $tableName = $tableDiff->name;
            $tableColumn = $args->getColumn();

            if(in_array($tableColumn->getName(), $this->ignoreFields[$tableName])) {
                $columnName = $tableColumn->getName();
                $args->preventDefault();

                unset($tableDiff->removedColumns[$columnName]);
                foreach ($args->getTableDiff()->removedIndexes as $index => $removedIndex) {
                    foreach ($removedIndex->getColumns() as $column) {
                        if ($column === $columnName) {
                            unset($tableDiff->removedIndexes[$index]);
                            $args->preventDefault();
                        }
                    }
                }
                foreach ($args->getTableDiff()->removedForeignKeys as $index => $removedForeignKey) {
                    foreach ($removedForeignKey->getColumns() as $column) {
                        if ($column === $columnName) {
                            unset($tableDiff->removedForeignKeys[$index]);
                            $args->preventDefault();
                        }
                    }
                }
            }
        }
    }
}

Configuration:

services:
    # default configuration for services in *this* file
    _defaults:
        ...
        bind:
            $doctrineIgnoreFields:
                'table_a':
                    - 'column_a'
                    - 'column_b'
                'table_b':
                    - 'column_c'
                    - 'column_d'

             

    App\Listener\Doctrine\DoctrineListener:
        tags:
            - { name: doctrine.event_listener, event: onSchemaAlterTableAddColumn }
            - { name: doctrine.event_listener, event: onSchemaAlterTableRemoveColumn }

This solution was tested on symfony 4.4

Upvotes: 0

Jannes Botis
Jannes Botis

Reputation: 11242

You can define either a onSchemaColumnDefinition or a onSchemaAlterTableRemoveColumn event and ignore these columns.

Using the onSchemaAlterTableRemoveColumn Event

In your bundle, create eg. src/AppBundle/EventListener/MyEventListener.php:

namespace AppBundle\EventListener;

use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs;
use Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs;

class MyEventListener
{
    public function onSchemaAlterTableRemoveColumn(SchemaAlterTableRemoveColumnEventArgs $args)
    {
        $tableColumn = $args->getColumn();
        $table = $args->getTableDiff();
        if($table->name == 'tableName' && $tableColumn->getName() == 'columnName') {
            $args->preventDefault();
        }
    }
}

Then, register your event listener in your services:

services:
    AppBundle\EventListener\MyEventListener:
    tags:
        - { name: doctrine.event_listener, event: onSchemaAlterTableRemoveColumn }

Similar, you could do this:

Using the onSchemaColumnDefinition Event

In your bundle, create eg. src/AppBundle/EventListener/MyEventListener.php:

namespace AppBundle\EventListener;

use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs;
use Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs;

class MyEventListener
{
    public function onSchemaColumnDefinition(SchemaColumnDefinitionEventArgs $args)
    {
        $tableColumn = $args->getTableColumn();
        $table = $args->getTable();
        if($table == 'tableName' && $tableColumn['Field'] == 'columnName') {
            $args->preventDefault();
        }
    }
}

Then, register your event listener in your services.yml:

services:
    AppBundle\EventListener\MyEventListener:
    tags:
        - { name: doctrine.event_listener, event: onSchemaColumnDefinition }

When running:

php bin/console doctrine:schema:update --force

should ignore the specified column(s). ("tableName"."columnName" in this case will not be deleted)

References:

Doctrine schema events

Doctrine Event Listeners

Upvotes: 2

Related Questions