Схема работы генератора документов и основные объекты
Модуль Генератор документов documentgenerator отделяет структуру документа от данных, которыми она заполняется. В генерации участвуют несколько объектов:
-
шаблон задает исходное тело и поля,
-
провайдер получает значения из объекта приложения,
-
документ объединяет шаблон и данные.
После обработки модуль сохраняет результат через файловое хранилище, выбранное в настройках модуля. При необходимости отправляет файл на преобразование в производные форматы.
Разделение объектов по назначению помогает выбрать нужный API и точку расширения. Например, шаблон отвечает за форму документа, провайдер — за источник данных, а хранилище — за работу с файлами.
Как связаны объекты
Генерация проходит от сохраненного шаблона к файлу готового документа. Между ними модуль сопоставляет поля шаблона с данными, получает исходные значения и преобразует их с учетом контекста.
Driver
├── классы Document, VirtualDocument и Template
├── DataProviderManager
├── UserPermissions
└── Storage по умолчанию
Registry\DataProvider Registry\Body Registry\Storage
│ │ │
└────────────────────┴─────────────────┘
│
v
Template
├── сведения о шаблоне
├── исходный файл
├── класс Body
└── поля и настройки
│
v
Document ──────── пользователь и флаг проверки доступа
Template ──────── регион ────────> Context ────────> форматы дат и имен
│
├── DataProvider
│ └── DataProviderManager
│ └── Value
│
v
Body с подставленными значениями
│
v
Storage
├── исходный файл документа
├── PDF
└── изображение
Шаблон и документ хранят сведения о себе отдельно от содержимого файлов. Класс Body обрабатывает содержимое соответствующего формата, а реализация интерфейса Storage сохраняет и возвращает файл.
Класс Driver находится уровнем выше отдельного документа. Статический метод Driver::getInstance() возвращает единственный экземпляр Driver. Через этот объект можно получить менеджер провайдеров, классы объектов модуля, объект прав пользователя и хранилище по умолчанию.
Как выбрать объект для задачи
Классы модуля отвечают за разные этапы процесса генерации. Для работы с объектами модуля используйте:
-
Используйте
Driver, чтобы получить классы объектов модуля, менеджер провайдеров, объект проверки прав или хранилище по умолчанию. -
Используйте
Template, чтобы изменить форму документа, набор полей или настройки их представления. -
Используйте
Document, чтобы создать, получить или актуализировать конкретный документ.
Для расширения возможностей модуля:
-
Создайте
DataProvider, если шаблон должен получать данные из нового типа исходного объекта. -
Создайте класс
Valueи укажите его вTYPEописания поля, если данные требуют нового способа форматирования. -
Создайте класс
Body, если модуль должен обрабатывать новый формат шаблона. -
Создайте реализацию
Storage, если файлы нужно сохранять и получать другим способом.
Класс Document координирует работу остальных объектов: передает получение данных провайдерам, обработку содержимого — телу, а работу с файлами — хранилищу.
Шаблон и документ
Шаблон и документ связаны, но описывают разные состояния. Шаблон — повторно используемая форма, а документ — результат применения этой формы к конкретному объекту и контексту.
Шаблон
Объект Template объединяет сведения о шаблоне и ссылку на исходный файл. В записи шаблона указаны тип тела, класс провайдера данных, настройки полей и параметры использования шаблона.
Исходный файл хранится отдельно от записи шаблона. Поэтому изменение записи шаблона и замена его файла — разные операции, которые нужно согласовывать через API модуля. Прямая запись в хранилище данных не обновляет сведения о связанном файле в шаблоне.
Документ
Объект Document представляет один сгенерированный документ. Он связывает шаблон с исходным объектом, дополнительными значениями, идентификатором пользователя и сохраненными файлами.
Документ использует настройки шаблона как исходную конфигурацию. Дополнительные значения относятся к конкретной генерации и могут уточнять данные, которые вернул провайдер. После обработки запись документа хранит ссылки на файлы результата, поэтому последующее изменение шаблона не обновляет автоматически уже созданный документ.
Получение и подготовка данных
Провайдеры отделяют предметную модель приложения от формата шаблона. Формат шаблона не зависит от того, из какого ORM-класса, сервиса или связанного объекта получено значение.
DataProvider
Базовый класс DataProvider задает общий контракт источника данных. Конкретный провайдер описывает доступные поля, получает их значения и при необходимости возвращает вложенный провайдер для связанного объекта.
Описание поля служит картой для генератора. По нему модуль определяет, как получить значение, является ли оно множественным, какой тип значения использовать и можно ли продолжить разрешение составного плейсхолдера через другой провайдер.
DataProviderManager
Класс DataProviderManager координирует работу провайдеров. Он сопоставляет поле шаблона с описанием поля, проходит по вложенным связям и передает найденное значение на преобразование.
Менеджер нужен, когда поле нельзя получить одним обращением к корневому провайдеру. Например, при разборе составного плейсхолдера менеджер переходит от исходного объекта к связанному, а затем получает значение его поля. Для множественной связи менеджер сохраняет несколько значений до этапа форматирования.
Value
Классы Value приводят исходные данные к виду, который можно поместить в тело документа. Тип значения отделяет получение данных от их представления: провайдер возвращает предметное значение, а Value применяет правила форматирования поддерживаемого типа.
Не форматируйте значение внутри провайдера, если формат зависит от шаблона или региональных настроек. Провайдер должен возвращать данные, а класс значения — готовить их для документа.
Контекст генерации
Одинаковый шаблон и исходный объект могут дать разное представление данных в зависимости от контекста. Перед обработкой Context::createFromDocument() собирает его из документа, шаблона и контекста приложения.
-
Идентификатор пользователя и флаг проверки доступа берутся из
Document. Провайдер проверяет доступ пользователя, только если проверка включена для документа. -
Регион берется из свойства
Template::REGION. -
По региону модуль выбирает настройки форматирования дат и имен. Если настройки для региона не найдены, используются региональные настройки текущего контекста приложения.
Контекст действует во время получения и подготовки значений. Изменение пользователя или региона после генерации не преобразует сохраненный файл автоматически. Для нового представления документ нужно актуализировать.
Тело документа и файлы
Класс Body работает с содержимым конкретного формата. Он получает исходное тело шаблона, находит поля, подставляет подготовленные значения и формирует содержимое готового документа.
Хранилище сохраняет и возвращает содержимое файлов. Реализация интерфейса Storage не должна разбирать плейсхолдеры или форматировать значения, а класс Body не должен определять место постоянного хранения файла.
запись Template ──> идентификатор исходного файла
│
v
Storage
│
v
Body
│ обработка полей
v
запись Document ──> идентификатор файла результата
После обработки тела модуль сохраняет исходный файл документа. При необходимости для него можно запросить дополнительные представления — PDF и изображение.
TransformerManager отправляет задачи преобразования в PDF и JPG в очередь documentgenerator_create. После выполнения задачи обработчик сохраняет идентификаторы файлов в документе и вызывает событие о завершении преобразования onDocumentTransformationComplete. Поэтому успешная постановка задачи в очередь не означает, что PDF_ID и IMAGE_ID уже заполнены.
Связи записей и файлов
Записи шаблона и документа ссылаются на записи файлов модуля. Запись файла хранит класс реализации Storage и внутренний идентификатор содержимого в этом хранилище.
|
Объект |
Сохраняемая связь |
Когда меняется |
|
Шаблон |
|
При загрузке или замене тела шаблона |
|
Документ |
|
При создании или актуализации документа |
|
Документ |
|
После успешной обработки и записи тела |
|
Документ |
|
В обработчике обратного вызова после завершения задачи преобразования |
|
Файл модуля |
Класс хранилища и идентификатор содержимого внутри него |
При сохранении через выбранную реализацию |
Жизненный цикл документа
Жизненный цикл документа состоит из следующих этапов:
-
Выбор шаблона.
-
Создание
Documentи передача исходного объекта. -
Получение полей через
DataProviderManager. -
Подготовка значений и обработка
Body. -
Сохранение записи документа и исходного файла результата.
После сохранения документ можно преобразовать в PDF или изображение либо актуализировать при изменении данных. Созданный объект Document еще не означает, что файлы результата сформированы.
Создание и обработка
Сначала код выбирает шаблон и создает объект Document. Затем документ получает исходный объект, дополнительные значения и идентификатор пользователя. Во время обработки DataProviderManager получает контекст, находит значения полей и подготавливает их, а тело заменяет плейсхолдеры.
До сохранения проверяйте результат обработки. Ошибка получения обязательного значения, неподдерживаемый тип тела или недоступный файл шаблона не должны приводить к использованию неполного результата.
Сохранение
Для нового документа вызывайте публичный метод Document::getFile(). Он обрабатывает тело, создает файл и добавляет запись документа. Результат метода содержит файл или ошибки выполнения.
Файл и запись документа сохраняются последовательно, а не как одно неделимое действие. Если сохранить запись документа не удалось, модуль не удаляет уже созданный файл автоматически. Всегда проверяйте объект Result, который вернул getFile().
Повторный вызов создания может создать дубликат документа. Чтобы избежать дублей:
-
сохраняйте идентификатор документа или другой ключ связи с исходным объектом,
-
перед созданием проверяйте, существует ли связанный документ.
Преобразование
Преобразование создает дополнительное представление уже обработанного документа. PDF или изображение не заменяют исходный файл. Модуль Конвертер файлов transformer выполняет задачу асинхронно, поэтому проверяйте PDF_ID и IMAGE_ID после завершения преобразования или обрабатывайте событие onDocumentTransformationComplete.
Актуализация
Актуализация повторно получает данные исходного объекта и обновляет содержимое существующего документа. Идентификатор документа и его связь с шаблоном сохраняются, а значения и файлы результата могут измениться.
Перед актуализацией учитывайте изменения шаблона, дополнительных значений и региона. Если внешняя система запускает обновление несколько раз, защищайте обработчик от параллельной актуализации одного документа.
У класса Document нет публичного метода удаления. Не удаляйте файл напрямую через Storage. Запись документа продолжит ссылаться на отсутствующий файл.
Реестры и точки расширения
Реестры провайдеров, тел и хранилищ не принадлежат Driver. Они используются для добавления реализаций соответствующих объектов.
Реестры связывают тип из настроек с PHP-классом, который реализует нужное поведение. Модуль содержит отдельные реестры для провайдеров данных, тел документов и хранилищ.
тип в настройках
│
v
реестр реализаций
│
v
PHP-класс DataProvider, Body или Storage
Выберите реестр по объекту, поведение которого нужно расширить:
-
провайдер — когда нужен новый источник или новая структура данных,
-
тело — когда нужен новый формат шаблона и алгоритм замены полей,
-
хранилище — когда нужно изменить постоянное размещение и получение файлов.
Для классов Value отдельного реестра нет. Чтобы добавить способ представления данных, укажите класс-наследник Bitrix\DocumentGenerator\Value в ключе TYPE описания поля. DataProviderManager создаст этот объект при подготовке значения.
Регистрируйте только тот объект, поведение которого нужно изменить. Например, для нового источника данных добавьте провайдер, не заменяя обработку тела и хранение файлов.
Границы API
Архитектурная карта показывает классы, которые участвуют в генерации, но не делает каждый внутренний объект точкой входа. Для работы приложения используйте методы классов Driver, Template, Document и события реестров onGetDataProviderList, onGetBodyTypeList и onGetStorageTypeList. ORM-модели предназначены для внутренней работы модуля. Не обращайтесь к ним напрямую, если тот же сценарий выполняет объект верхнего уровня.
Согласованность и побочные эффекты
Генерация затрагивает данные приложения и файловое хранилище. Получение данных, сохранение документа и преобразование файла выполняются как отдельные операции. Код приложения должен отдельно обрабатывать ошибку каждого этапа.
Во время обработки документа модуль выполняет несколько действий:
-
обработка тела читает данные через провайдеры и может обращаться к связанным объектам,
-
сохранение сначала создает файл результата, а затем добавляет или обновляет запись документа,
-
преобразование отправляет задачу в очередь, а обработчик обратного вызова позднее добавляет производные файлы,
-
актуализация заменяет ранее сформированное содержимое,
-
удаление очищает связи и файлы документа,
-
обработчики событий могут запускать дополнительный код приложения.
Если обработчик события запускает повторную генерацию того же документа, сначала проверяйте признак обработки или идентификатор документа. Эта проверка остановит рекурсивный вызов.
Связанные материалы
-
Введение и базовые концепции — назначение модуля и первый сценарий генерации.
-
Шаблоны, поля и форматирование — подготовка структуры и плейсхолдеров документа.
-
Провайдеры данных — получение значений из исходного объекта.
-
Документы — жизненный цикл конкретного документа и его файлов.