architect/_archive/2025-11-13-before-restructure/platform-projects/projects/cifra/archive/2025-11-10-restructure-v2/DRUPAL_COMPLETE.md

Архитектура Drupal: Полный анализ

Дата: 2025-11-10
Версия Drupal: 10.x


КРАТКИЙ ОТВЕТ

Drupal - архитектурный шедевр! Вот что делает его уникальным:

7 ключевых концепций:

  1. Entity API - всё является сущностью (Users, Nodes, Comments)
  2. Plugin System - самая мощная система плагинов в PHP
  3. Configuration Management - конфигурация как код (YAML)
  4. Field API - динамические поля для любой сущности
  5. Views - SQL конструктор без кода (GUI → запросы)
  6. Hooks/Events - расширяемость на всех уровнях
  7. Service Container - Dependency Injection (Symfony)

Что взять для ЦИФРА:
- ✅ Entity API (всё - entity с единым интерфейсом)
- ✅ Plugin System (расширяемость через аннотации)
- ✅ Configuration as Code (YAML export/import)
- ✅ Field API (attach поля к любой entity)
- ✅ DI Container (чистая архитектура)


Часть 1: ENTITY API

Философия: "Everything is an Entity"

В Drupal всё контент - это Entity:
- Users (пользователи)
- Nodes (статьи, страницы)
- Comments (комментарии)
- Media (медиа файлы)
- Taxonomy Terms (категории)
- Custom entities (ваши сущности)

Единый интерфейс для всех:

// Одинаковый API для всех entity
$user = User::load(1);
$node = Node::load(1);
$comment = Comment::load(1);

// Одинаковые методы
$user->save();
$node->delete();
$comment->label();

Типы Entity:

1. Content Entity (контент с полями)
- Хранятся в БД
- Поддерживают поля (Field API)
- Переводы (multilingual)
- Ревизии (versioning)

2. Config Entity (конфигурация)
- Хранятся в YAML
- Экспортируются/импортируются
- Нет переводов
- Нет ревизий

Пример: создание своей Entity

<?php
namespace Drupal\mymodule\Entity;

use Drupal\Core\Entity\ContentEntityBase;

/**
 * @ContentEntityType(
 *   id = "product",
 *   label = @Translation("Product"),
 *   base_table = "product",
 *   entity_keys = {
 *     "id" = "id",
 *     "label" = "name"
 *   }
 * )
 */
class Product extends ContentEntityBase {

  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    $fields = parent::baseFieldDefinitions($entity_type);

    // Name field
    $fields['name'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Name'))
      ->setRequired(TRUE);

    // Price field  
    $fields['price'] = BaseFieldDefinition::create('integer')
      ->setLabel(t('Price'))
      ->setDefaultValue(0);

    return $fields;
  }
}

Результат:
- ✅ Таблица product создаётся автоматически
- ✅ CRUD API доступен сразу
- ✅ Можно добавлять поля через Field API
- ✅ Forms генерируются автоматически

Handlers (обработчики):

/**
 * @ContentEntityType(
 *   handlers = {
 *     "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage",
 *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
 *     "form" = {
 *       "default" = "Drupal\mymodule\Form\ProductForm",
 *       "edit" = "Drupal\mymodule\Form\ProductForm",
 *       "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm"
 *     },
 *     "route_provider" = {
 *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider"
 *     }
 *   }
 * )
 */

Handlers - это Strategy Pattern:
- storage - как хранить (SQL, NoSQL, API)
- view_builder - как отображать
- form - как редактировать
- route_provider - какие маршруты создать


Часть 2: FIELD API

Концепция: поля независимы от entity

Field API - система динамических полей.

Ключевая идея:

Entity ← Attach Fields

Любая entity может иметь любые поля без изменения кода.

Типы полей:

// Все встроенные типы полей
- text (текст)
- integer (целое число)
- decimal (десятичное)
- boolean (да/нет)
- email (email)
- datetime (дата/время)
- entity_reference (ссылка на другую entity)
- image (изображение)
- file (файл)
- list (выпадающий список)
- link (ссылка)

Добавление поля через GUI:

/admin/structure/types/manage/article/fields
   Add field
   Выбираете тип (text, image, reference)
   Настраиваете
   Save

Поле сразу доступно!

Добавление поля программно:

use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;

// 1. Field Storage (определение поля)
$field_storage = FieldStorageConfig::create([
  'field_name' => 'field_price',
  'entity_type' => 'node',
  'type' => 'integer',
  'cardinality' => 1,
]);
$field_storage->save();

// 2. Field Instance (привязка к bundle)
$field = FieldConfig::create([
  'field_storage' => $field_storage,
  'bundle' => 'product',
  'label' => 'Price',
  'required' => TRUE,
  'default_value' => [['value' => 0]],
]);
$field->save();

Field Storage vs Field Instance:

Field Storage (хранилище)
├─ Определяет тип данных
├─ Определяет кардинальность (сколько значений)
└─ Общее для всех bundles

Field Instance (экземпляр)
├─ Привязан к конкретному bundle (content type)
├─ Настройки отображения
└─ Настройки формы

Использование полей:

// Получение значения
$node = Node::load(1);
$price = $node->get('field_price')->value;
$image_url = $node->get('field_image')->entity->url();

// Установка значения
$node->set('field_price', 1000);
$node->save();

// Мультизначные поля
$tags = $node->get('field_tags')->referencedEntities();
foreach ($tags as $term) {
  echo $term->label();
}

Часть 3: PLUGIN SYSTEM

Самая мощная система плагинов в PHP!

Plugin - класс, который выполняет конкретную задачу.

Типы плагинов в Drupal:

- Block (блоки)
- Field Widget (виджеты для редактирования полей)
- Field Formatter (форматирование отображения)
- Field Type (типы полей)
- Filter (текстовые фильтры)
- Image Effect (эффекты для изображений)
- Menu Link (ссылки меню)
- Views (плагины для Views)
- ...и сотни других

Plugin Discovery (обнаружение плагинов):

Annotation-based (через аннотации):

<?php
namespace Drupal\mymodule\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a 'Hello' Block.
 *
 * @Block(
 *   id = "hello_block",
 *   admin_label = @Translation("Hello block"),
 *   category = @Translation("Custom")
 * )
 */
class HelloBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    return [
      '#markup' => $this->t('Hello, World!'),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state) {
    $form['message'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Message'),
      '#default_value' => $this->configuration['message'] ?? '',
    ];
    return $form;
  }
}

Результат:
- ✅ Блок автоматически обнаруживается
- ✅ Доступен в /admin/structure/block
- ✅ Можно разместить в любой регион темы
- ✅ Настраиваемый через форму

Plugin Manager (менеджер плагинов):

// Получение всех блоков
$block_manager = \Drupal::service('plugin.manager.block');
$blocks = $block_manager->getDefinitions();

// Создание экземпляра плагина
$block_plugin = $block_manager->createInstance('hello_block', [
  'message' => 'Custom message'
]);

// Рендеринг
$build = $block_plugin->build();

Кастомный Plugin Type:

// 1. Создать аннотацию
namespace Drupal\mymodule\Annotation;

/**
 * @Annotation
 */
class PaymentGateway extends Plugin {
  public $id;
  public $label;
}

// 2. Создать интерфейс
interface PaymentGatewayInterface {
  public function charge($amount);
}

// 3. Создать менеджер
class PaymentGatewayManager extends DefaultPluginManager {
  public function __construct() {
    parent::__construct(
      'Plugin/PaymentGateway',  // Директория
      \Drupal::service('container.namespaces'),
      \Drupal::service('module_handler'),
      'Drupal\mymodule\Plugin\PaymentGatewayInterface',
      'Drupal\mymodule\Annotation\PaymentGateway'
    );
  }
}

// 4. Создать плагин
/**
 * @PaymentGateway(
 *   id = "stripe",
 *   label = @Translation("Stripe")
 * )
 */
class StripeGateway implements PaymentGatewayInterface {
  public function charge($amount) {
    // Stripe API call
  }
}

Часть 4: CONFIGURATION MANAGEMENT

Концепция: Configuration as Code

Config Management - экспорт всех настроек в YAML файлы.

Что экспортируется:

- Content types (типы контента)
- Fields (поля)
- Views (представления)
- Blocks (блоки)
- Menus (меню)
- Permissions (права доступа)
- Text formats (форматы текста)
- Image styles (стили изображений)
- ...всё!

Пример config файла:

# config/sync/node.type.article.yml
langcode: en
status: true
dependencies: {  }
name: Article
type: article
description: 'Use articles for time-sensitive content like news, press releases or blog posts.'
help: ''
new_revision: true
preview_mode: 1
display_submitted: true

Workflow:

# 1. Экспорт конфигурации
drush config:export

# Создаётся: config/sync/*.yml
# - node.type.article.yml
# - field.field.node.article.body.yml
# - ...

# 2. Commit в Git
git add config/sync/
git commit -m "Added article content type"

# 3. Deploy на другой сервер
git pull

# 4. Импорт конфигурации
drush config:import

# Все настройки применяются!

Преимущества:

  1. Version Control - конфигурация в Git
  2. Code Review - видно что изменилось
  3. Deploy - одна команда для применения
  4. Rollback - откат через Git
  5. Sync - одинаковые настройки на всех серверах

Configuration vs Content:

Configuration (настройки):
- Типы контента
- Поля
- Views
- Блоки
→ Хранится в YAML
→ Экспортируется/импортируется

Content (данные):
- Статьи
- Пользователи
- Комментарии
→ Хранится в БД
→ Не экспортируется в config

Часть 5: VIEWS (Query Builder)

Концепция: SQL без кода

Views - модуль для построения запросов через GUI.

Что можно создать:

- Списки контента
- Таблицы
- Карты (с модулем)
- Календари
- Галереи
- RSS ленты
- REST API endpoints
- GraphQL endpoints

Архитектура Views:

Display (отображение)
├─ Page (страница /products)
├─ Block (блок для размещения)
├─ Feed (RSS)
└─ REST Export (JSON API)

Query (запрос)
├─ Fields (какие поля показывать)
├─ Filters (условия WHERE)
├─ Sort (сортировка)
├─ Relationships (JOIN)
└─ Contextual Filters (параметры URL)

Output (вывод)
├─ Format (таблица, список, сетка)
├─ Pager (пагинация)
└─ Headers/Footers (доп. контент)

Пример: создание Views через GUI

1. /admin/structure/views/add
2. View name: Products
3. Show: Content of type Product
4. Create a Page: /products
5. Display: Table
6. Fields:
   - Title
   - Price
   - Image
7. Filters:
   - Published: Yes
   - Type: Product
8. Sort by: Created (desc)
9. Save

 Страница /products работает!
 SQL запрос сгенерирован автоматически

Views экспорт (configuration):

# config/sync/views.view.products.yml
id: products
label: Products
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
display:
  default:
    display_plugin: default
    id: default
    display_title: Master
    position: 0
    display_options:
      fields:
        title:
          id: title
          table: node_field_data
          field: title
          label: ''
        field_price:
          id: field_price
          table: node__field_price
          field: field_price
          label: Price
      filters:
        status:
          value: '1'
          expose:
            operator: false
  page_1:
    display_plugin: page
    id: page_1
    display_title: Page
    position: 1
    display_options:
      path: products

Программный доступ к Views:

// Выполнение Views программно
$view = Views::getView('products');
$view->setDisplay('page_1');
$view->execute();

$results = $view->result;
foreach ($results as $row) {
  $node = $row->_entity;
  echo $node->label();
}

Часть 6: SERVICE CONTAINER (Dependency Injection)

Концепция: чистая архитектура через DI

Drupal использует Symfony Service Container.

Регистрация сервиса:

# mymodule.services.yml
services:
  mymodule.product_manager:
    class: Drupal\mymodule\ProductManager
    arguments: ['@entity_type.manager', '@database']

  mymodule.email_notifier:
    class: Drupal\mymodule\EmailNotifier
    arguments: ['@plugin.manager.mail']

Использование сервиса:

// В контроллере/плагине
class ProductController extends ControllerBase {

  protected $productManager;

  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('mymodule.product_manager')
    );
  }

  public function __construct(ProductManager $product_manager) {
    $this->productManager = $product_manager;
  }

  public function list() {
    $products = $this->productManager->getAll();
    return ['#theme' => 'product_list', '#products' => $products];
  }
}

Dependency Injection через контейнер:

// Плохо (жёсткая зависимость)
class ProductManager {
  public function send() {
    $mailer = \Drupal::service('plugin.manager.mail');
    $mailer->mail(...);
  }
}

// Хорошо (через DI)
class ProductManager {
  protected $mailer;

  public function __construct(MailManagerInterface $mailer) {
    $this->mailer = $mailer;
  }

  public function send() {
    $this->mailer->mail(...);
  }
}

Часть 7: HOOKS & EVENTS

Hooks (старый способ)

// mymodule.module

/**
 * Implements hook_node_presave().
 */
function mymodule_node_presave(NodeInterface $node) {
  // Выполняется перед сохранением ноды
  if ($node->bundle() == 'product') {
    $node->set('field_updated', time());
  }
}

/**
 * Implements hook_form_alter().
 */
function mymodule_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if ($form_id == 'node_product_form') {
    // Изменение формы продукта
    $form['title']['#description'] = t('Enter product name');
  }
}

Events (современный способ)

// Event Subscriber
namespace Drupal\mymodule\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ProductEventSubscriber implements EventSubscriberInterface {

  public static function getSubscribedEvents() {
    $events['kernel.request'][] = ['onRequest'];
    return $events;
  }

  public function onRequest($event) {
    // Обработка события
  }
}

Часть 8: ЧТО ВЗЯТЬ ДЛЯ ПЛАТФОРМЫ ЦИФРА

1. Entity API подход

Концепция: Всё - entity с единым интерфейсом.

Для ЦИФРА:

from abc import ABC, abstractmethod

class BaseEntity(ABC):
    """Базовый класс для всех entities"""

    @abstractmethod
    def id(self):
        pass

    @abstractmethod
    def label(self):
        pass

    def save(self):
        """Сохранение entity"""
        pass

    def delete(self):
        """Удаление entity"""
        pass

# Все entities наследуют BaseEntity
class User(BaseEntity):
    pass

class Product(BaseEntity):
    pass

class Order(BaseEntity):
    pass

# Единый API для всех
user = User.load(1)
product = Product.load(1)

user.save()
product.delete()

2. Field API (динамические поля)

Концепция: Поля независимы от entity.

Для ЦИФРА:

# Attach поле к любой entity
from cidl.fields import StringField, IntegerField

# Entity может иметь любые поля
User.attach_field(StringField(name='bio'))
Product.attach_field(IntegerField(name='stock'))

# Поля добавляются без изменения кода entity

3. Plugin System

Концепция: Расширяемость через плагины с аннотациями.

Для ЦИФРА:

# Декоратор для регистрации плагина
@plugin(
    id="stripe_payment",
    label="Stripe Payment Gateway",
    category="Payment"
)
class StripePaymentGateway(BasePaymentGateway):
    def charge(self, amount):
        # Implementation
        pass

# Автоматическое обнаружение и регистрация
plugin_manager.discover_plugins()
stripe = plugin_manager.create_instance('stripe_payment')

4. Configuration as Code

Концепция: Конфигурация в YAML, экспорт/импорт.

Для ЦИФРА:

# config/entity_types/product.yml
entity:
  name: Product
  label: Product
  base_table: products

fields:
  - name: name
    type: string
    required: true
  - name: price
    type: integer
    default: 0

api:
  endpoints:
    list: /api/products
    create: /api/products
# Экспорт конфигурации
$ cifra config:export

# Импорт
$ cifra config:import

5. Service Container (DI)

Концепция: Dependency Injection через контейнер.

Для ЦИФРА:

# services.yml
services:
  product_manager:
    class: ProductManager
    arguments: ['@database', '@email_sender']

# Использование
class ProductController:
    def __init__(self, product_manager: ProductManager):
        self.product_manager = product_manager

    def list_products(self):
        return self.product_manager.get_all()

ИТОГО

Архитектурные принципы Drupal:

  1. Everything is an Entity - единый API
  2. Fields are attachable - динамические поля
  3. Plugins are discoverable - автообнаружение через аннотации
  4. Configuration is code - YAML экспорт/импорт
  5. Dependencies are injected - Service Container
  6. Hooks are everywhere - расширяемость на всех уровнях

Что берём для ПЛАТФОРМЫ ЦИФРА:

Entity API (BaseEntity)
+ Field API (attach fields)
+ Plugin System (@plugin decorator)
+ Configuration Management (YAML)
+ Service Container (DI)
+ Event System (hooks)
= Архитектура ЦИФРА

Сравнение с другими фреймворками:

Концепция Drupal Django Strapi ЦИФРА
Entity API ✅ Лучший 🟡 Models ✅ Content Types ✅ Берём
Field API ✅ Лучший ❌ Нет ✅ Есть ✅ Берём
Plugins ✅ Лучший 🟡 Apps ✅ Plugins ✅ Берём
Config as Code ✅ Да ❌ Нет 🟡 Частично ✅ Берём
DI Container ✅ Symfony ❌ Нет ❌ Нет ✅ Берём

Drupal - лучший источник архитектурных паттернов!


Версия: 1.0.0
Статус: Анализ завершён
Следующий шаг: Применить концепции Drupal к архитектуре ЦИФРА