Files
structural_pages/docs/PLUGIN_SYSTEM_PLAN.md
Quintino A. G. Souza 88b9605408 Rename module site_structure → structural_pages and vocabulary site_section → site_sections
Renames the module from site_structure to structural_pages and pluralizes
the taxonomy vocabulary machine name from site_section to site_sections,
updating all config, PHP, translations, and documentation references
while preserving field_site_section, clears_site_section, and $site_section_id.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 08:10:32 -03:00

7.5 KiB

Plano: Sistema de Plugins ParentEntityHandler

Problema

O módulo structural_pages tem dependências hardcoded para diferentes tipos de entidade pai (taxonomy_term, user, group, node) espalhadas em múltiplos arquivos:

  • StructuralPagesSettingsForm.php:46-51 - Array hardcoded $supportedEntityTypes
  • structural_pages.module:55-59 - in_array($parent_entity_type, ['user', 'group'])
  • StructuralPagesMenuBlock.php:14 - use Drupal\group\Entity\GroupInterface; (causa erro sem o módulo group)
  • StructuralPagesMenuBlock.php:274-290 - Verificações instanceof para cada tipo
  • SectionBreadcrumbBuilder.php:82-100 - Switch statement para tipos de contexto

Consequência: Adicionar um novo tipo de entidade requer modificar múltiplos arquivos, e o import do GroupInterface causa erro quando o módulo group não está instalado.

Solução

Implementar um sistema de plugins Drupal (Attribute-based para D10+) onde cada handler define:

  • Tipo de entidade que gerencia
  • Módulo provedor (para dependências opcionais)
  • Como detectar a entidade na rota
  • Como construir breadcrumbs
  • Se deve limpar field_site_section
  • Campo de ordenação

Nova Estrutura de Arquivos

structural_pages/
├── src/
│   ├── Attribute/
│   │   └── ParentEntityHandler.php           # NOVO
│   ├── ParentEntityHandler/
│   │   ├── ParentEntityHandlerInterface.php  # NOVO
│   │   ├── ParentEntityHandlerManagerInterface.php  # NOVO
│   │   ├── ParentEntityHandlerBase.php       # NOVO
│   │   └── ParentEntityHandlerManager.php    # NOVO
│   ├── Plugin/
│   │   ├── Block/
│   │   │   └── StructuralPagesMenuBlock.php    # REFATORAR
│   │   └── ParentEntityHandler/              # NOVO
│   │       ├── TaxonomyTermHandler.php       # NOVO
│   │       ├── UserHandler.php               # NOVO
│   │       └── NodeHandler.php               # NOVO
│   ├── Breadcrumb/
│   │   └── SectionBreadcrumbBuilder.php      # REFATORAR
│   └── Form/
│       └── StructuralPagesSettingsForm.php     # REFATORAR
├── modules/
│   └── structural_pages_group/                 # NOVO (submódulo)
│       ├── structural_pages_group.info.yml
│       └── src/Plugin/ParentEntityHandler/
│           └── GroupHandler.php
├── structural_pages.module                     # REFATORAR
└── structural_pages.services.yml               # ATUALIZAR

Componentes Principais

1. Attribute (src/Attribute/ParentEntityHandler.php)

#[\Attribute(\Attribute::TARGET_CLASS)]
class ParentEntityHandler extends Plugin {
  public function __construct(
    public readonly string $id,
    public readonly TranslatableMarkup $label,
    public readonly string $entity_type_id,
    public readonly ?string $provider_module = NULL,
    public readonly bool $clears_site_section = FALSE,
    public readonly string $sort_field = 'title',
    public readonly ?string $route_parameter = NULL,
    public readonly array $bundle_restrictions = [],
    public readonly int $weight = 0,
  ) {}
}

2. Interface (src/ParentEntityHandler/ParentEntityHandlerInterface.php)

Métodos principais:

  • getEntityTypeId(): string
  • isAvailable(): bool
  • clearsSiteSection(): bool
  • getSortField(): string
  • getEntityFromRoute(RouteMatchInterface): ?EntityInterface
  • handlesEntity(EntityInterface): bool
  • buildBreadcrumb(Breadcrumb, EntityInterface): void
  • getSiteSectionId(EntityInterface): int|string|null

3. Manager (src/ParentEntityHandler/ParentEntityHandlerManager.php)

Métodos principais:

  • getAvailableHandlers(): array - Retorna handlers com módulos instalados
  • getHandlerForEntityType(string): ?ParentEntityHandlerInterface
  • getSupportedEntityTypes(): array - Para o formulário de configurações
  • clearsSiteSection(string): bool
  • getSortField(string): string
  • getEntityFromRoute(RouteMatchInterface): ?EntityInterface
  • buildBreadcrumbForEntity(Breadcrumb, EntityInterface): bool

4. Handlers Built-in

Handler entity_type_id clears_site_section sort_field bundle_restrictions
TaxonomyTermHandler taxonomy_term false name ['site_sections']
UserHandler user true name []
NodeHandler node false title ['content_page', 'section_page']

5. Submódulo structural_pages_group

# structural_pages_group.info.yml
name: 'Structural Pages Group Integration'
type: module
dependencies:
  - structural_pages:structural_pages
  - group:group

GroupHandler: entity_type_id: group, clears_site_section: true, sort_field: label


Refatorações

structural_pages.module (linhas 53-66)

Antes:

if (in_array($parent_entity_type, ['user', 'group'])) {
  $entity->set('field_site_section', NULL);
  return;
}

Depois:

$handler_manager = \Drupal::service('plugin.manager.parent_entity_handler');
if ($handler_manager->clearsSiteSection($parent_entity_type)) {
  $entity->set('field_site_section', NULL);
  return;
}

StructuralPagesMenuBlock.php

  • Remover use Drupal\group\Entity\GroupInterface;
  • Injetar ParentEntityHandlerManagerInterface
  • Substituir getAncestorFromRoute():
protected function getAncestorFromRoute(): ?EntityInterface {
  return $this->handlerManager->getEntityFromRoute($this->routeMatch);
}

SectionBreadcrumbBuilder.php

  • Injetar ParentEntityHandlerManagerInterface
  • Substituir switch statement:
if ($context) {
  $this->handlerManager->buildBreadcrumbForEntity($breadcrumb, $context['entity']);
}
  • Remover métodos addUserBreadcrumb() e addGroupBreadcrumb()

StructuralPagesSettingsForm.php

  • Injetar ParentEntityHandlerManagerInterface
  • Substituir $supportedEntityTypes:
$supportedEntityTypes = $this->handlerManager->getSupportedEntityTypes();
  • Substituir match statement de sort_field:
$sort_field = $this->handlerManager->getSortField($entity_type);

structural_pages.services.yml

services:
  plugin.manager.parent_entity_handler:
    class: Drupal\structural_pages\ParentEntityHandler\ParentEntityHandlerManager
    arguments:
      - '@container.namespaces'
      - '@cache.discovery'
      - '@module_handler'

  structural_pages.breadcrumb.section:
    class: Drupal\structural_pages\Breadcrumb\SectionBreadcrumbBuilder
    arguments:
      - '@entity_type.manager'
      - '@plugin.manager.parent_entity_handler'
    tags:
      - { name: breadcrumb_builder, priority: 100 }

Verificação

  1. Sem módulo group instalado:

    • O módulo structural_pages deve funcionar normalmente
    • Formulário de configurações não deve mostrar opção "Groups"
    • Nenhum erro de classe não encontrada
  2. Com submódulo structural_pages_group ativado:

    • Opção "Groups" aparece no formulário de configurações
    • Groups funcionam como entidades pai
    • Breadcrumbs mostram nome do grupo
  3. Extensibilidade:

    • Criar handler de teste em módulo custom
    • Verificar que é descoberto automaticamente pelo manager
  4. Cache:

    • Limpar cache após mudanças (drush cr)
    • Verificar que handlers são cacheados corretamente

Compatibilidade

  • Configuração existente (allowed_parent_targets) continua funcionando
  • Nenhuma alteração no schema de configuração necessária
  • Backwards compatible - apenas mudança interna de implementação