class ViewsEntitySchemaSubscriber

Same name in other branches
  1. 9 core/modules/views/src/EventSubscriber/ViewsEntitySchemaSubscriber.php \Drupal\views\EventSubscriber\ViewsEntitySchemaSubscriber
  2. 8.9.x core/modules/views/src/EventSubscriber/ViewsEntitySchemaSubscriber.php \Drupal\views\EventSubscriber\ViewsEntitySchemaSubscriber
  3. 11.x core/modules/views/src/EventSubscriber/ViewsEntitySchemaSubscriber.php \Drupal\views\EventSubscriber\ViewsEntitySchemaSubscriber

Reacts to changes on entity types to update all views entities.

Hierarchy

Expanded class hierarchy of ViewsEntitySchemaSubscriber

1 string reference to 'ViewsEntitySchemaSubscriber'
views.services.yml in core/modules/views/views.services.yml
core/modules/views/views.services.yml
1 service uses ViewsEntitySchemaSubscriber
views.entity_schema_subscriber in core/modules/views/views.services.yml
Drupal\views\EventSubscriber\ViewsEntitySchemaSubscriber

File

core/modules/views/src/EventSubscriber/ViewsEntitySchemaSubscriber.php, line 18

Namespace

Drupal\views\EventSubscriber
View source
class ViewsEntitySchemaSubscriber implements EntityTypeListenerInterface, EventSubscriberInterface {
    use EntityTypeEventSubscriberTrait;
    
    /**
     * Indicates that a base table got renamed.
     */
    const BASE_TABLE_RENAME = 0;
    
    /**
     * Indicates that a data table got renamed.
     */
    const DATA_TABLE_RENAME = 1;
    
    /**
     * Indicates that a data table got added.
     */
    const DATA_TABLE_ADDITION = 2;
    
    /**
     * Indicates that a data table got removed.
     */
    const DATA_TABLE_REMOVAL = 3;
    
    /**
     * Indicates that a revision table got renamed.
     */
    const REVISION_TABLE_RENAME = 4;
    
    /**
     * Indicates that a revision table got added.
     */
    const REVISION_TABLE_ADDITION = 5;
    
    /**
     * Indicates that a revision table got removed.
     */
    const REVISION_TABLE_REMOVAL = 6;
    
    /**
     * Indicates that a revision data table got renamed.
     */
    const REVISION_DATA_TABLE_RENAME = 7;
    
    /**
     * Indicates that a revision data table got added.
     */
    const REVISION_DATA_TABLE_ADDITION = 8;
    
    /**
     * Indicates that a revision data table got removed.
     */
    const REVISION_DATA_TABLE_REMOVAL = 9;
    
    /**
     * The entity type manager.
     *
     * @var \Drupal\Core\Entity\EntityTypeManagerInterface
     */
    protected $entityTypeManager;
    
    /**
     * The default logger service.
     *
     * @var \Psr\Log\LoggerInterface
     */
    protected $logger;
    
    /**
     * Array of views that need to be saved, indexed by view name.
     *
     * @var \Drupal\views\ViewEntityInterface[]
     */
    protected $viewsToSave = [];
    
    /**
     * Constructs a ViewsEntitySchemaSubscriber.
     *
     * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
     *   The entity type manager.
     * @param \Psr\Log\LoggerInterface $logger
     *   A logger instance.
     */
    public function __construct(EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger) {
        $this->entityTypeManager = $entity_type_manager;
        $this->logger = $logger;
    }
    
    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents() : array {
        return static::getEntityTypeEvents();
    }
    
    /**
     * {@inheritdoc}
     */
    public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
        $changes = [];
        // We implement a specific logic for table updates, which is bound to the
        // default sql content entity storage.
        if (!$this->entityTypeManager
            ->getStorage($entity_type->id()) instanceof SqlContentEntityStorage) {
            return;
        }
        if ($entity_type->getBaseTable() != $original->getBaseTable()) {
            $changes[] = static::BASE_TABLE_RENAME;
        }
        $revision_add = $entity_type->isRevisionable() && !$original->isRevisionable();
        $revision_remove = !$entity_type->isRevisionable() && $original->isRevisionable();
        $translation_add = $entity_type->isTranslatable() && !$original->isTranslatable();
        $translation_remove = !$entity_type->isTranslatable() && $original->isTranslatable();
        if ($revision_add) {
            $changes[] = static::REVISION_TABLE_ADDITION;
        }
        elseif ($revision_remove) {
            $changes[] = static::REVISION_TABLE_REMOVAL;
        }
        elseif ($entity_type->isRevisionable() && $entity_type->getRevisionTable() != $original->getRevisionTable()) {
            $changes[] = static::REVISION_TABLE_RENAME;
        }
        if ($translation_add) {
            $changes[] = static::DATA_TABLE_ADDITION;
        }
        elseif ($translation_remove) {
            $changes[] = static::DATA_TABLE_REMOVAL;
        }
        elseif ($entity_type->isTranslatable() && $entity_type->getDataTable() != $original->getDataTable()) {
            $changes[] = static::DATA_TABLE_RENAME;
        }
        if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) {
            if ($revision_add || $translation_add) {
                $changes[] = static::REVISION_DATA_TABLE_ADDITION;
            }
            elseif ($entity_type->getRevisionDataTable() != $original->getRevisionDataTable()) {
                $changes[] = static::REVISION_DATA_TABLE_RENAME;
            }
        }
        elseif ($original->isRevisionable() && $original->isTranslatable() && ($revision_remove || $translation_remove)) {
            $changes[] = static::REVISION_DATA_TABLE_REMOVAL;
        }
        // Stop here if no changes are needed.
        if (empty($changes)) {
            return;
        }
        
        /** @var \Drupal\views\Entity\View[] $all_views */
        $all_views = $this->entityTypeManager
            ->getStorage('view')
            ->loadMultiple(NULL);
        foreach ($changes as $change) {
            switch ($change) {
                case static::BASE_TABLE_RENAME:
                    $this->baseTableRename($all_views, $entity_type->id(), $original->getBaseTable(), $entity_type->getBaseTable());
                    break;
                case static::DATA_TABLE_RENAME:
                    $this->dataTableRename($all_views, $entity_type->id(), $original->getDataTable(), $entity_type->getDataTable());
                    break;
                case static::DATA_TABLE_ADDITION:
                    $this->dataTableAddition($all_views, $entity_type, $entity_type->getDataTable(), $entity_type->getBaseTable());
                    break;
                case static::DATA_TABLE_REMOVAL:
                    $this->dataTableRemoval($all_views, $entity_type->id(), $original->getDataTable(), $entity_type->getBaseTable());
                    break;
                case static::REVISION_TABLE_RENAME:
                    $this->baseTableRename($all_views, $entity_type->id(), $original->getRevisionTable(), $entity_type->getRevisionTable());
                    break;
                case static::REVISION_TABLE_ADDITION:
                    // If we add revision support we don't have to do anything.
                    break;
                case static::REVISION_TABLE_REMOVAL:
                    $this->revisionRemoval($all_views, $original);
                    break;
                case static::REVISION_DATA_TABLE_RENAME:
                    $this->dataTableRename($all_views, $entity_type->id(), $original->getRevisionDataTable(), $entity_type->getRevisionDataTable());
                    break;
                case static::REVISION_DATA_TABLE_ADDITION:
                    $this->dataTableAddition($all_views, $entity_type, $entity_type->getRevisionDataTable(), $entity_type->getRevisionTable());
                    break;
                case static::REVISION_DATA_TABLE_REMOVAL:
                    $this->dataTableRemoval($all_views, $entity_type->id(), $original->getRevisionDataTable(), $entity_type->getRevisionTable());
                    break;
            }
        }
        foreach ($this->viewsToSave as $view) {
            try {
                // All changes done to the views here can be trusted and this might be
                // called during updates, when it is not safe to rely on configuration
                // containing valid schema. Trust the data and disable schema validation
                // and casting.
                $view->trustData()
                    ->save();
            } catch (\Exception $e) {
                // In case the view could not be saved, log an error message that the
                // view needs to be updated manually instead of failing the entire
                // entity update process.
                $this->logger
                    ->critical("The %view_id view could not be updated automatically while processing an entity schema update for the %entity_type_id entity type.", [
                    '%view_id' => $view->id(),
                    '%entity_type_id' => $entity_type->id(),
                ]);
            }
        }
        $this->viewsToSave = [];
    }
    
    /**
     * {@inheritdoc}
     */
    public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
        $tables = [
            $entity_type->getBaseTable(),
            $entity_type->getDataTable(),
            $entity_type->getRevisionTable(),
            $entity_type->getRevisionDataTable(),
        ];
        $all_views = $this->entityTypeManager
            ->getStorage('view')
            ->loadMultiple(NULL);
        
        /** @var \Drupal\views\Entity\View $view */
        foreach ($all_views as $view) {
            // First check just the base table.
            if (in_array($view->get('base_table'), $tables)) {
                $view->disable();
                $view->save();
            }
        }
    }
    
    /**
     * Applies a callable onto all handlers of all passed in views.
     *
     * @param \Drupal\views\Entity\View[] $all_views
     *   All views entities.
     * @param callable $process
     *   A callable which retrieves a handler config array.
     */
    protected function processHandlers(array $all_views, callable $process) {
        foreach ($all_views as $view) {
            foreach (array_keys($view->get('display')) as $display_id) {
                $display =& $view->getDisplay($display_id);
                foreach (Views::getHandlerTypes() as $handler_type) {
                    $handler_type = $handler_type['plural'];
                    if (!isset($display['display_options'][$handler_type])) {
                        continue;
                    }
                    foreach ($display['display_options'][$handler_type] as $id => &$handler_config) {
                        $process($handler_config, $view);
                        if ($handler_config === NULL) {
                            unset($display['display_options'][$handler_type][$id]);
                        }
                    }
                }
            }
        }
    }
    
    /**
     * Updates views if a base table is renamed.
     *
     * @param \Drupal\views\Entity\View[] $all_views
     *   All views.
     * @param string $entity_type_id
     *   The entity type ID.
     * @param string $old_base_table
     *   The old base table name.
     * @param string $new_base_table
     *   The new base table name.
     */
    protected function baseTableRename($all_views, $entity_type_id, $old_base_table, $new_base_table) {
        foreach ($all_views as $view) {
            if ($view->get('base_table') == $old_base_table) {
                $view->set('base_table', $new_base_table);
                $this->viewsToSave[$view->id()] = $view;
            }
        }
        $this->processHandlers($all_views, function (&$handler_config, ViewEntityInterface $view) use ($entity_type_id, $old_base_table, $new_base_table) {
            if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id && $handler_config['table'] == $old_base_table) {
                $handler_config['table'] = $new_base_table;
                $this->viewsToSave[$view->id()] = $view;
            }
        });
    }
    
    /**
     * Updates views if a data table is renamed.
     *
     * @param \Drupal\views\Entity\View[] $all_views
     *   All views.
     * @param string $entity_type_id
     *   The entity type ID.
     * @param string $old_data_table
     *   The old data table name.
     * @param string $new_data_table
     *   The new data table name.
     */
    protected function dataTableRename($all_views, $entity_type_id, $old_data_table, $new_data_table) {
        foreach ($all_views as $view) {
            if ($view->get('base_table') == $old_data_table) {
                $view->set('base_table', $new_data_table);
                $this->viewsToSave[$view->id()] = $view;
            }
        }
        $this->processHandlers($all_views, function (&$handler_config, ViewEntityInterface $view) use ($entity_type_id, $old_data_table, $new_data_table) {
            if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id && $handler_config['table'] == $old_data_table) {
                $handler_config['table'] = $new_data_table;
                $this->viewsToSave[$view->id()] = $view;
            }
        });
    }
    
    /**
     * Updates views if a data table is added.
     *
     * @param \Drupal\views\Entity\View[] $all_views
     *   All views.
     * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
     *   The entity type.
     * @param string $new_data_table
     *   The new data table.
     * @param string $base_table
     *   The base table.
     */
    protected function dataTableAddition($all_views, EntityTypeInterface $entity_type, $new_data_table, $base_table) {
        
        /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
        $entity_type_id = $entity_type->id();
        $storage = $this->entityTypeManager
            ->getStorage($entity_type_id);
        $storage->setEntityType($entity_type);
        $table_mapping = $storage->getTableMapping();
        $data_table_fields = $table_mapping->getFieldNames($new_data_table);
        $base_table_fields = $table_mapping->getFieldNames($base_table);
        $data_table = $new_data_table;
        $this->processHandlers($all_views, function (&$handler_config, ViewEntityInterface $view) use ($entity_type_id, $base_table, $data_table, $base_table_fields, $data_table_fields) {
            if (isset($handler_config['entity_type']) && isset($handler_config['entity_field']) && $handler_config['entity_type'] == $entity_type_id) {
                // Move all fields which just exists on the data table.
                if ($handler_config['table'] == $base_table && in_array($handler_config['entity_field'], $data_table_fields) && !in_array($handler_config['entity_field'], $base_table_fields)) {
                    $handler_config['table'] = $data_table;
                    $this->viewsToSave[$view->id()] = $view;
                }
            }
        });
    }
    
    /**
     * Updates views if a data table is removed.
     *
     * @param \Drupal\views\Entity\View[] $all_views
     *   All views.
     * @param string $entity_type_id
     *   The entity type ID.
     * @param string $old_data_table
     *   The name of the previous existing data table.
     * @param string $base_table
     *   The name of the base table.
     */
    protected function dataTableRemoval($all_views, $entity_type_id, $old_data_table, $base_table) {
        // We move back the data table back to the base table.
        $this->processHandlers($all_views, function (&$handler_config, ViewEntityInterface $view) use ($entity_type_id, $old_data_table, $base_table) {
            if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id) {
                if ($handler_config['table'] == $old_data_table) {
                    $handler_config['table'] = $base_table;
                    $this->viewsToSave[$view->id()] = $view;
                }
            }
        });
    }
    
    /**
     * Updates views if revision support is removed.
     *
     * @param \Drupal\views\Entity\View[] $all_views
     *   All views.
     * @param \Drupal\Core\Entity\EntityTypeInterface $original
     *   The origin entity type.
     */
    protected function revisionRemoval($all_views, EntityTypeInterface $original) {
        $revision_base_table = $original->getRevisionTable();
        $revision_data_table = $original->getRevisionDataTable();
        foreach ($all_views as $view) {
            if (in_array($view->get('base_table'), [
                $revision_base_table,
                $revision_data_table,
            ])) {
                // Let's disable the views as we no longer support revisions.
                $view->setStatus(FALSE);
                $this->viewsToSave[$view->id()] = $view;
            }
            // For any kind of field, let's rely on the broken handler functionality.
        }
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
EntityTypeEventSubscriberTrait::getEntityTypeEvents public static function Gets the subscribed events.
EntityTypeEventSubscriberTrait::onEntityTypeCreate public function 3
EntityTypeEventSubscriberTrait::onEntityTypeEvent public function Listener method for any entity type definition event.
EntityTypeEventSubscriberTrait::onFieldableEntityTypeCreate public function 2
EntityTypeEventSubscriberTrait::onFieldableEntityTypeUpdate public function 2
ViewsEntitySchemaSubscriber::$entityTypeManager protected property The entity type manager.
ViewsEntitySchemaSubscriber::$logger protected property The default logger service.
ViewsEntitySchemaSubscriber::$viewsToSave protected property Array of views that need to be saved, indexed by view name.
ViewsEntitySchemaSubscriber::baseTableRename protected function Updates views if a base table is renamed.
ViewsEntitySchemaSubscriber::BASE_TABLE_RENAME constant Indicates that a base table got renamed.
ViewsEntitySchemaSubscriber::dataTableAddition protected function Updates views if a data table is added.
ViewsEntitySchemaSubscriber::dataTableRemoval protected function Updates views if a data table is removed.
ViewsEntitySchemaSubscriber::dataTableRename protected function Updates views if a data table is renamed.
ViewsEntitySchemaSubscriber::DATA_TABLE_ADDITION constant Indicates that a data table got added.
ViewsEntitySchemaSubscriber::DATA_TABLE_REMOVAL constant Indicates that a data table got removed.
ViewsEntitySchemaSubscriber::DATA_TABLE_RENAME constant Indicates that a data table got renamed.
ViewsEntitySchemaSubscriber::getSubscribedEvents public static function
ViewsEntitySchemaSubscriber::onEntityTypeDelete public function Overrides EntityTypeEventSubscriberTrait::onEntityTypeDelete
ViewsEntitySchemaSubscriber::onEntityTypeUpdate public function Overrides EntityTypeEventSubscriberTrait::onEntityTypeUpdate
ViewsEntitySchemaSubscriber::processHandlers protected function Applies a callable onto all handlers of all passed in views.
ViewsEntitySchemaSubscriber::revisionRemoval protected function Updates views if revision support is removed.
ViewsEntitySchemaSubscriber::REVISION_DATA_TABLE_ADDITION constant Indicates that a revision data table got added.
ViewsEntitySchemaSubscriber::REVISION_DATA_TABLE_REMOVAL constant Indicates that a revision data table got removed.
ViewsEntitySchemaSubscriber::REVISION_DATA_TABLE_RENAME constant Indicates that a revision data table got renamed.
ViewsEntitySchemaSubscriber::REVISION_TABLE_ADDITION constant Indicates that a revision table got added.
ViewsEntitySchemaSubscriber::REVISION_TABLE_REMOVAL constant Indicates that a revision table got removed.
ViewsEntitySchemaSubscriber::REVISION_TABLE_RENAME constant Indicates that a revision table got renamed.
ViewsEntitySchemaSubscriber::__construct public function Constructs a ViewsEntitySchemaSubscriber.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.