cloud-castle / document
Универсальная работа с документами для PHP 8.1+: единая объектная модель, парсинг и создание документов, сохранение в 10 форматах (Markdown, HTML, DOCX, ODT, RTF, PDF, EPUB, JSON, XML, TXT) и конвертация между любыми из них. Безопасно по умолчанию: защита от XXE, zip-бомб и HTML-инъекций.
Requires
- php: >=8.1
- ext-dom: *
- ext-libxml: *
- ext-mbstring: *
- ext-zlib: *
- cloud-castle/archive: ^1.1
- cloud-castle/file-system: ^1.1
- cloud-castle/serialize: ^1.2
Requires (Dev)
- ext-zip: *
- deptrac/deptrac: ^3.0 || ^4.0
- ergebnis/composer-normalize: ^2.45
- friendsofphp/php-cs-fixer: ^3.75
- icanhazstring/composer-unused: ^0.9
- infection/infection: ^0.29 || ^0.33
- php-parallel-lint/php-parallel-lint: ^1.4
- phpmd/phpmd: ^2.15
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^1.12 || ^2.1
- phpstan/phpstan-deprecation-rules: ^1.2 || ^2.0
- phpstan/phpstan-phpunit: ^1.4 || ^2.0
- phpstan/phpstan-strict-rules: ^1.6 || ^2.0
- phpunit/phpunit: ^10.5 || ^11.5
- psalm/plugin-phpunit: ^0.19 || ^0.20
- rector/rector: ^1.2 || ^2.0
- roave/security-advisories: dev-latest
- squizlabs/php_codesniffer: ^3.12 || ^4.0
- vimeo/psalm: ^6.0
- webmozart/assert: ^1.11
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-28 08:24:01 UTC
README
🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano
CloudCastle Document
Универсальная работа с документами на чистом PHP 8.1+: единая объектная модель, чтение и запись десяти форматов — Markdown, HTML5+CSS, TXT, DOCX, ODT, RTF, PDF, EPUB, JSON и XML — и конвертация «любой → любой» одной строкой. Собственные писатель и читатель PDF (TrueType, оглавление, закладки, колонтитулы), инструментарий уровня Word (таблицы с объединением ячеек, сноски, списки всех видов, изображения, стили) и безопасность по умолчанию: XXE, zip-бомбы, path traversal и опасные схемы URL отклоняются без настройки.
Установка
composer require cloud-castle/document
Требуется PHP 8.1+ с расширениями dom, libxml, mbstring, zlib
(входят в типовую поставку PHP). Контейнеры DOCX, ODT и EPUB читает и
пишет cloud-castle/archive на чистом PHP, поэтому расширение zip
не нужно.
Быстрый старт
<?php
use CloudCastle\Document\Document;
// Конвертация «любой → любой» одной строкой.
Document::convert('отчёт.docx', 'отчёт.pdf');
// Чтение любого поддерживаемого формата в объектную модель.
$document = Document::open('статья.md');
echo $document->plainText();
// Создание документа и сохранение в несколько форматов.
$document = Document::create()
->title('Квартальный отчёт')
->author('Отдел аналитики')
->header('Компания — {PAGE} из {PAGES}')
->addTableOfContents('Содержание')
->addHeading('Итоги квартала')
->addParagraph('Выручка выросла на 12 % по сравнению с прошлым кварталом.')
->addList(['рост розницы', 'запуск двух регионов', 'снижение оттока'])
->addTable([
['Показатель', 'Значение'],
['Выручка', '84,3 млн'],
['Маржа', '31 %'],
]);
$document->save('отчёт.pdf');
$document->save('отчёт.docx');
$document->save('отчёт.html');
Дозапись и правка существующих документов
Открытый документ — обычная модель: дополняйте его, вставляйте и заменяйте блоки, правьте текст и сохраняйте в тот же или другой формат.
use CloudCastle\Document\Document;
use CloudCastle\Document\Element\Paragraph;
// Дозапись: открыть, добавить содержимое, сохранить.
$document = Document::open('отчёт.docx')
->addHeading('Дополнение от 17.09')
->addParagraph('Абзац, дописанный программно.');
$document->save('отчёт.docx');
// Точечные правки по позициям блоков.
$document->insertAt(0, Paragraph::of('Преамбула сверху'));
$document->replaceAt(2, Paragraph::of('Полностью новый третий блок'));
$document->removeAt(5);
// Исправление текста по всему документу (метаданные, таблицы,
// колонтитулы и сноски включительно; адреса ссылок не трогаются).
$fixed = $document->replaceText('ООО «Ромашка»', 'АО «Ромашка»');
// Шаблоны: подстановка значений в «${имя}» по всему документу —
// абзацы, таблицы, колонтитулы, водяной знак, метаданные; адреса
// ссылок и атрибуты оформления не затрагиваются.
$contract = Document::open('шаблон-договора.docx')->fillTemplate([
'номер' => '42-Б',
'дата' => '17.09.2026',
]);
$contract->save('договор-42Б.pdf');
replaceText() и fillTemplate() возвращают новый документ — исходный
остаётся нетронутым; методы insertAt()/replaceAt()/removeAt() меняют
документ на месте и удобны в цепочках.
Страницы
Документ можно наполнять двумя способами. Сплошным потоком — как в
«Быстром старте»: тогда разбивка на страницы выполняется автоматически
при выводе в постраничные форматы (PDF, печатные DOCX/ODT). Либо явно
постранично: addPage() открывает страницу, каждый метод add*()
принимает элемент и возвращает объект страницы Page, поэтому страница
собирается цепочкой; следующий вызов addPage() завершает её разрывом.
use CloudCastle\Document\Document;
$document = Document::create()
->title('Годовой отчёт') // мета-информация задаётся на документе
->author('Отдел аналитики');
$document->addPage() // страница 1
->addHeading('Введение')
->addParagraph('Цели и рамки отчёта.')
->addPage() // страница 2
->addHeading('Итоги')
->addTable([['Показатель', 'Значение'], ['Выручка', '84,3 млн']])
->save('отчёт.pdf'); // страницы PDF совпадают с явными
$page = $document->page(); // текущая (последняя) страница
$page->blocks(); // блоки только этой страницы
$page->plainText(); // текст только этой страницы
$page->document(); // возврат к документу из цепочки
Страницы доступны по номеру и редактируются на месте: добавление идёт в конец именно этой страницы, позиционные правки — в её локальных индексах, после чего документ сохраняется в любой из форматов.
$document->pageCount(); // количество страниц
$document->pages(); // список объектов Page
$first = $document->page(1); // страница по номеру (с единицы)
$first->replaceAt(1, Paragraph::of('новый текст')) // правка внутри страницы
->insertAt(0, Paragraph::of('преамбула'))
->removeAt(2)
->addParagraph('дописано в конец первой страницы')
->save('после-правки.pdf'); // сохранение всего документа
Страницы при чтении тоже восстанавливаются: Document::open() любого
формата с постраничной структурой (DOCX, ODT, RTF, PDF, EPUB-главы,
HTML c page-break, TXT c \f, Markdown, JSON/XML) даёт документ, в
котором page(n) возвращает страницу со всеми её таблицами,
изображениями и текстом.
Параметры страницы настраиваются на документе или странице; без явных значений действуют настройки нового документа MS Word (поля 2/1,5/2/3 см, A4):
$document->margins(56.7, 42.5, 56.7, 85.0) // верх/право/низ/лево, pt
->lineSpacing(1.15) // межстрочный, как в Word
->paragraphSpacing(8.0) // интервал после абзаца
->numberPages('стр. {PAGE} из {PAGES}'); // нумерация в подвале
Каскад стилей
Каждый элемент — объект со своими атрибутами оформления: стиль слова накладывается поверх стиля абзаца, настройки ячейки приоритетнее строки и таблицы:
use CloudCastle\Document\Element\{Paragraph, TextRun, Table, TableRow, TableCell};
use CloudCastle\Document\Style\TextStyle;
new Paragraph(
[new TextRun('важное', TextStyle::regular()->withColor('#cc0000'))],
style: TextStyle::regular()->withBold()->withFontSize(13.0),
); // оба слова жирные 13pt, выделенное — красное
new Table(
[new TableRow(
[new TableCell([Paragraph::of('своя рамка')], borderWidth: 2.0, borderColor: '#cc0000')],
background: '#eef4ff', // фон строки — на все её ячейки
)],
borderWidth: 0.75, // сетка таблицы
borderColor: '#3355aa',
);
Каскад действует в HTML, DOCX, ODT, RTF и PDF и без потерь сохраняется в JSON/XML.
Диаграммы и списки задач
Диаграмма — блок документа с данными, а не картинка: PDF рисует её векторно, HTML и EPUB — встроенным SVG, DOCX, ODT и RTF — фигурами, Markdown и TXT — таблицей значений. Обратное чтение возвращает тот же объект, поэтому диаграмма переживает конвертацию в обе стороны.
use CloudCastle\Document\Document;
use CloudCastle\Document\Element\{Chart, ChartSeries, ChartType, ListBlock, ListItem};
Document::create()
->add(Chart::bars(['Янв' => 120, 'Фев' => 245, 'Мар' => 178], 'Выручка'))
->add(new Chart(
ChartType::Line,
[new ChartSeries('План', [100.0, 140.0, 180.0]), new ChartSeries('Факт', [120.0, 245.0, 178.0])],
['Янв', 'Фев', 'Мар'],
'План и факт',
))
->add(new ListBlock([
ListItem::task('Собрать требования', checked: true),
ListItem::task('Проверить конвертацию'),
]))
->save('отчёт.docx');
Списки задач (ListItem::task()) пишутся флажками: - [x] в Markdown,
<input type="checkbox"> в HTML, символом ☒/☐ в DOCX, ODT и RTF,
нарисованной рамкой с галочкой в PDF — и читаются обратно как задачи
во всех этих форматах.
Блок-схемы mermaid из Markdown разбираются в объект Diagram (узлы,
связи, направление) и рисуются вектором в PDF, SVG в HTML и EPUB,
фигурами в DOCX, ODT и RTF; Markdown получает обратно исходный блок
## Изображения: локальные, встроенные и по ссылке
Картинки из разметки попадают в модель тремя путями: `data:`-URI,
относительная ссылка на соседний файл (`assets/banner.svg`) и обычный
сетевой адрес (`https://img.shields.io/…`). Во всех случаях в документ
встраиваются сами данные, поэтому значок или логотип виден и в PDF, и в
DOCX, и в RTF, а не заменяется подписью.
Векторные картинки остаются векторными: в PDF они рисуются примитивами,
в DOCX и RTF — метафайлом EMF, в HTML, EPUB и ODT сохраняется исходный
SVG. Обратное чтение возвращает именно SVG, а не его растровую копию.
Загрузка по сети ограничена: только схемы `http` и `https`, только
публичные адреса (`localhost`, `10.0.0.0/8`, `169.254.0.0/16` и прочие
внутренние диапазоны отклоняются до запроса и на каждом редиректе),
не более 8 МиБ и 8 секунд на файл, повторный адрес берётся из кэша.
```php
use CloudCastle\Document\Support\RemoteImagePolicy;
RemoteImagePolicy::disable(); // не ходить в сеть совсем
RemoteImagePolicy::limit(2 * 1024 * 1024, 3); // 2 МиБ и 3 секунды на файл
RemoteImagePolicy::allowHosts(['img.shields.io']); // только доверенные хосты
```
## Перенос вёрстки PDF в офисные форматы
PDF хранит не абзацы, а координаты строк, поэтому при переводе в DOCX,
ODT и RTF вёрстка собирается заново по геометрии оригинала:
- строка ставится по базовой линии исходника: расстояние до неё
считается от подъёма шрифта и высоты строки, как это делают редакторы;
- разбиение повторяется по переносам оригинала, а не пересчитывается
по ширине, где расходятся кернинг и ширины цифр;
- начертания документа едут в пакет под своим именем: подмножеству из
PDF восстанавливаются таблицы `cmap` и `name`, а имя присваивается
только тому шрифту, которому хватает глифов на весь текст;
- линейки-разделители, плашки за словами и границы колонок страницы
переносятся как оформление, а не теряются;
- текстовая рамка не выходит за край листа: иначе редактор сдвигает
её целиком и строка уезжает со своего места.
## Правка PDF без потери вёрстки
Документ, прочитанный из PDF, хранит координаты каждого блока, шрифты
файла и его графику. Поэтому правка не пересобирает страницу заново:
- пересохранение без изменений отдаёт исходные байты;
- дозапись добавляет объекты, не трогая оригинальные страницы;
- замена текста и вставка блоков перерисовывают только текстовый слой,
а рамки, печати, подложки и фон остаются из исходного файла;
- текст рисуется встроенными шрифтами оригинала, а строка, не влезающая
в исходную ширину, сжимается по горизонтали, а не переносится.
```php
$document = Document::open('договор.pdf')->replaceText('ООО «Ромашка»', 'АО «Ромашка»');
$document->save('договор-2.pdf'); // графика и разметка исходника на месте
$document->save('договор-2.docx'); // то же размещение в текстовых рамках
```
## Оформление страницы и таблиц
Лист документа несёт собственное оформление, и оно переживает
конвертацию: ровная заливка, градиент, водяной знак, поля и колонтитулы
читаются из исходного файла и выводятся всюду, где формат это позволяет.
```php
use CloudCastle\Document\Document;
use CloudCastle\Document\Element\{Paragraph, Table, TableBorders, TableRow};
use CloudCastle\Document\Style\TextStyle;
$document = Document::create()
->pageBackground('#fff8e7') // ровная заливка листа
->pageGradient('#fff8e7', '#4472c4', 45.0) // градиент: от, до, угол
->watermark('ЧЕРНОВИК');
$document->add(new Table(
[
TableRow::header(['Показатель', 'Значение']),
TableRow::of(['Выручка', '84,3 млн']),
],
sides: TableBorders::HeaderBottom, // только линия под шапкой
));
$document->addParagraph('Разрядка заголовка')
->add(Paragraph::of('в р а з р я д к у', style: TextStyle::regular()->withLetterSpacing(3.0)));
```
Набор линий таблицы (`TableBorders`) задаётся отдельно от толщины и
цвета: полная сетка, только горизонтали, линия под шапкой или вовсе без
линий. Шапка таблицы, помеченная заголовочной, повторяется на каждой её
странице в PDF, DOCX, ODT и RTF. Разрядка набора
(`TextStyle::withLetterSpacing()`) читается из PDF, DOCX, ODT, RTF и
HTML и выводится во все форматы, где она выразима.
## Поддерживаемые форматы
| Формат | Чтение | Запись | Особенности |
|---|:---:|:---:|---|
| Markdown (`md`) | ✅ | ✅ | CommonMark-подмножество, таблицы GFM, сноски, front matter |
| HTML5+CSS (`html`) | ✅ | ✅ | санитизация белым списком, инлайн-стили, колонтитулы |
| Текст (`txt`) | ✅ | ✅ | абзацы, страницы через form feed |
| DOCX (`docx`) | ✅ | ✅ | стили, списки, `gridSpan`/`vMerge`, сноски, колонтитулы, изображения |
| ODT (`odt`) | ✅ | ✅ | автостили, объединения ячеек, сноски, колонтитулы |
| RTF (`rtf`) | ✅ | ✅ | кодовые страницы, `\uN`, поля TOC/HYPERLINK, изображения |
| PDF (`pdf`) | ✅ | ✅ | собственный писатель и читатель, TrueType, оглавление, закладки |
| EPUB 3 (`epub`) | ✅ | ✅ | главы по spine, метаданные DC, изображения из архива |
| JSON (`json`) | ✅ | ✅ | родной lossless-формат, версия схемы, байт-в-байт |
| XML (`xml`) | ✅ | ✅ | родной lossless-формат, версия схемы, байт-в-байт |
## Сравнение с аналогами
Все таблицы генерируются автоматически (`php benchmarks/run.php`,
`php benchmarks/inventory.php`, `php tools/comparison-sync.php`) на
зафиксированных в `benchmarks/composer.lock` версиях аналогов.
### Функциональность
<!-- FEATURES:START -->
| Возможность | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| Чтение Markdown | ✅ | — | ✅ | ✅ | — | — | — | — | document, commonmark, parsedown |
| Чтение HTML | ✅ | 🟡 | — | — | ✅ | ✅ | ✅ | — | document и ещё 3 |
| Чтение DOCX | ✅ | ✅ | — | — | — | — | — | — | document, phpword |
| Чтение ODT | ✅ | 🟡 | — | — | — | — | — | — | document |
| Чтение RTF | ✅ | 🟡 | — | — | — | — | — | — | document |
| Чтение PDF | ✅ | — | — | — | — | — | — | ✅ | document, pdfparser |
| Чтение EPUB | ✅ | — | — | — | — | — | — | — | document |
| Запись Markdown | ✅ | — | — | — | ✅ | — | — | — | document, html-to-markdown |
| Запись HTML | ✅ | ✅ | ✅ | ✅ | — | — | — | — | document и ещё 3 |
| Запись DOCX | ✅ | ✅ | — | — | — | — | — | — | document, phpword |
| Запись ODT | ✅ | ✅ | — | — | — | — | — | — | document, phpword |
| Запись RTF | ✅ | ✅ | — | — | — | — | — | — | document, phpword |
| Запись PDF | ✅ | 🟡 | — | — | — | ✅ | ✅ | — | document, dompdf, mpdf |
| Запись EPUB | ✅ | — | — | — | — | — | — | — | document |
| Родной lossless-формат (JSON/XML) | ✅ | — | — | — | — | — | — | — | document |
| Конвертация «любой → любой» | ✅ | 🟡 | — | — | — | — | — | — | document |
| Объектная модель документа (AST) | ✅ | ✅ | ✅ | — | — | — | — | 🟡 | document, phpword, commonmark |
| Таблицы (colspan + rowspan) | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | — | document и ещё 3 |
| Извлечение изображений из PDF | ✅ | — | — | — | — | — | — | 🟡 | document |
| CSS-блочная модель (фон, рамка, отступы контейнеров) | ✅ | — | — | — | — | ✅ | ✅ | — | document, dompdf, mpdf |
| Изображения (встроенные и внешние) | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | 🟡 | document и ещё 3 |
| Сноски | ✅ | ✅ | 🟡 | — | — | — | ✅ | — | document, phpword, mpdf |
| Оглавление (генерация) | ✅ | ✅ | — | — | — | — | ✅ | — | document, phpword, mpdf |
| Колонтитулы с номерами страниц | ✅ | ✅ | — | — | — | 🟡 | ✅ | — | document, phpword, mpdf |
| Метаданные документа | ✅ | ✅ | 🟡 | — | — | 🟡 | ✅ | ✅ | document и ещё 3 |
| Кириллица в PDF без настройки | ✅ | — | — | — | — | 🟡 | ✅ | ✅ | document, mpdf, pdfparser |
| Защита от XXE по умолчанию | ✅ | ✅ | — | — | — | 🟡 | 🟡 | — | document, phpword |
| Защита от zip-бомб | ✅ | — | — | — | — | — | — | — | document |
| Санитизация HTML по белому списку | ✅ | — | 🟡 | 🟡 | — | — | — | — | document |
| Запрет опасных схем URL | ✅ | — | ✅ | 🟡 | — | 🟡 | 🟡 | — | document, commonmark |
| Загрузка изображений по ссылке с защитой от SSRF | ✅ | — | — | — | — | 🟡 | 🟡 | — | document |
| Zero-dependency ядро (только PSR/cloud-castle) | ✅ | — | — | ✅ | — | — | — | — | document, parsedown |
| Дозапись и правка открытых документов | ✅ | 🟡 | — | — | — | — | — | — | document |
| Шаблоны «${имя}» с подстановкой значений | ✅ | ✅ | — | — | — | — | — | — | document, phpword |
| Страницы: доступ по номеру, правка, чтение из файлов | ✅ | 🟡 | — | — | — | — | — | 🟡 | document |
| Настройки страницы: поля, интервалы, нумерация (Word-дефолты) | ✅ | ✅ | — | — | — | 🟡 | ✅ | — | document, phpword, mpdf |
| Каскад стилей: слово › абзац, ячейка › строка › таблица | ✅ | 🟡 | — | — | — | ✅ | ✅ | — | document, dompdf, mpdf |
| Водяной знак | ✅ | 🟡 | — | — | — | — | ✅ | — | document, mpdf |
| Градиентная заливка листа | ✅ | — | — | — | — | 🟡 | 🟡 | — | document |
| Разрядка набора (letter-spacing) | ✅ | 🟡 | — | — | — | 🟡 | 🟡 | — | document |
| Набор линий таблицы: сетка, горизонтали, линия под шапкой | ✅ | 🟡 | — | — | — | 🟡 | 🟡 | — | document |
| Повтор шапки таблицы на каждой странице | ✅ | ✅ | — | — | — | 🟡 | ✅ | — | document, phpword, mpdf |
| Работа с ZIP без расширения ext-zip | ✅ | — | — | — | — | — | — | — | document |
| **Итого (из 43)** | **43** | 21.5 | 6.5 | 5 | 3 | 11.5 | 17 | 5 | document |
<!-- FEATURES:END -->
### Производительность
Замеры: идентичный документ (60 разделов: заголовок, абзац со стилями,
список, таблица), лучший из 7 прогонов, отдельный процесс на каждый замер.
<!-- PERF:START -->
| Сценарий | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| Markdown → HTML | 2.7 мс | — | 23.2 мс | 3.4 мс | — | — | — | — | document |
| HTML → Markdown | 2.4 мс | — | — | — | 5.2 мс | — | — | — | document |
| Запись DOCX | 3.3 мс | 10.8 мс | — | — | — | — | — | — | document |
| Запись PDF | 70.6 мс | — | — | — | — | 247.3 мс | 129.3 мс | — | document |
| Чтение PDF | 24.8 мс | — | — | — | — | — | — | 39.5 мс | document |
<!-- PERF:END -->
### Потребление памяти
<!-- MEMORY:START -->
| Сценарий | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| Markdown → HTML | 0.0 МБ | — | 0.0 МБ | 0.0 МБ | — | — | — | — | document, commonmark, parsedown |
| HTML → Markdown | 0.0 МБ | — | — | — | 0.0 МБ | — | — | — | document, html-to-markdown |
| Запись DOCX | 0.0 МБ | 4.0 МБ | — | — | — | — | — | — | document |
| Запись PDF | 2.0 МБ | — | — | — | — | 2.0 МБ | 0.0 МБ | — | mpdf |
| Чтение PDF | 2.0 МБ | — | — | — | — | — | — | 8.0 МБ | document |
<!-- MEMORY:END -->
### Безопасность
<!-- SECURITY:START -->
| Свойство безопасности | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| Защита от XXE по умолчанию | ✅ | ✅ | — | — | — | 🟡 | 🟡 | — | document, phpword |
| Защита от zip-бомб | ✅ | — | — | — | — | — | — | — | document |
| Санитизация HTML по белому списку | ✅ | — | 🟡 | 🟡 | — | — | — | — | document |
| Запрет опасных схем URL | ✅ | — | ✅ | 🟡 | — | 🟡 | 🟡 | — | document, commonmark |
| Загрузка изображений по ссылке с защитой от SSRF | ✅ | — | — | — | — | 🟡 | 🟡 | — | document |
| **Итого (из 5)** | **5** | 1 | 1.5 | 1 | 0 | 1.5 | 1.5 | 0 | document |
<!-- SECURITY:END -->
Соответствие стандартам и практикам безопасности composer-пакетов
(CWE-классы защит, OWASP ASVS, политика раскрытия, строгие типы):
<!-- SECSTD:START -->
| Стандарт / практика безопасности | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| CWE-611: защита от XXE включена по умолчанию | ✅ | ✅ | — | — | — | 🟡 | 🟡 | — | document, phpword |
| CWE-409: лимиты распаковки архивов (zip-бомбы) | ✅ | — | — | — | — | — | — | — | document |
| CWE-22: контроль путей при распаковке (path traversal) | ✅ | 🟡 | — | — | — | — | — | — | document |
| CWE-918: загрузка по ссылке только на публичные адреса (SSRF) | ✅ | — | — | — | — | — | — | — | document |
| CWE-79: экранирование/санитизация HTML-вывода по умолчанию | ✅ | — | 🟡 | 🟡 | — | — | — | — | document |
| OWASP ASVS V5: валидация входа на границе, типизированные отказы | ✅ | 🟡 | ✅ | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | document, commonmark |
| Политика раскрытия уязвимостей (SECURITY.md) | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | — | document и ещё 5 |
| Без известных advisories в актуальной версии (composer audit) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | document и ещё 7 |
| declare(strict_types=1) во всех файлах пакета | ✅ | — | ✅ | — | ✅ | — | — | — | document, commonmark, html-to-markdown |
| Тесты безопасности в наборе пакета (XXE, бомбы, схемы URL) | ✅ | 🟡 | 🟡 | 🟡 | — | — | — | — | document |
| **Итого (из 10)** | **10** | 4.5 | 5 | 2.5 | 3.5 | 3 | 3 | 1.5 | document |
<!-- SECSTD:END -->
### Качество кода
<!-- QUALITYTABLE:START -->
| Практика качества | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| PHPStan (максимальный уровень + strict-rules) | ✅ | 🟡 | ✅ | — | 🟡 | — | 🟡 | 🟡 | document, commonmark |
| Psalm | ✅ | — | ✅ | — | — | — | — | — | document, commonmark |
| Мутационное тестирование (Infection) | ✅ | — | — | — | — | — | — | — | document |
| Архитектурные слои (Deptrac) | ✅ | — | — | — | — | — | — | — | document |
| Тесты утечек памяти в CI | ✅ | — | — | — | — | — | — | — | document |
| Тесты безопасности (XXE, бомбы, схемы URL) | ✅ | 🟡 | 🟡 | — | — | — | — | — | document |
| Контроль покрытия per-file в CI | ✅ | — | — | — | — | — | — | — | document |
| **Итого (из 7)** | **7** | 1 | 2.5 | 0 | 0.5 | 0 | 0.5 | 0.5 | document |
<!-- QUALITYTABLE:END -->
## Честно о пакете
**Сильные стороны**
- Единственный PHP-пакет с конвертацией «любой → любой» между десятью
форматами через одну объектную модель: остальные библиотеки закрывают
по одному-два направления.
- Быстрее аналогов в 4 из 5 сценариев (запись DOCX — в 8 раз, запись PDF —
в 2,8–6,8 раза, HTML → Markdown — в 2,4 раза) при меньшем потреблении
памяти (чтение PDF — 2 МБ против 8 МБ у smalot/pdfparser).
- Безопасность по умолчанию, подтверждённая тестами: XXE, zip-бомбы,
path traversal, опасные схемы URL, санитизация HTML; внешние изображения
никогда не скачиваются при разборе.
- Ядро без внешних зависимостей: только пакеты `cloud-castle/*`
(архивы, файловая система, сериализация) и стандартные расширения PHP;
ни `ext-zip`, ни офисный пакет на сервере не нужны.
- 2140 тестов, покрытие 99,4 % строк при пороге 99,2 %, PHPStan max +
strict, Psalm, PHPMD, PHPCS, Rector, Deptrac и мутационное
тестирование — без единого подавления.
**Слабые стороны — честно**
- Пакет моложе и менее распространён, чем аналоги с многолетней историей.
- Потребление памяти местами приносится в жертву производительности
и функциональности: полная объектная модель держит документ целиком
(генераторы и ленивые потоки сглаживают это — 2 МБ на чтение PDF
против 8 МБ у smalot/pdfparser, — но потоковой обработки «строка за
строкой» без модели пакет не предлагает).
## Где применять
| Сфера | Что брать | Почему |
| --- | --- | --- |
| Веб-сервисы конвертации файлов | `Document::convert()` | Один пакет вместо связки из трёх-четырёх: DOCX → PDF, MD → EPUB и любое другое направление |
| Генерация отчётов и договоров | `Document::create()` + `fillTemplate()` | Единый код на все форматы вывода; шаблоны «${имя}», колонтитулы, оглавление, водяные знаки |
| Приём документов от пользователей | `Document::fromString()` | Защита от XXE, zip-бомб, SSRF и опасных схем URL включена всегда |
| Полнотекстовый поиск и индексация | `plainText()` | Извлечение текста из десяти форматов, включая PDF — быстрее и экономнее smalot/pdfparser |
| Хранение документов в БД/очередях | форматы `json` / `xml` | Родная lossless-сериализация с версией схемы, байт-в-байт восстановление |
| Издательские конвейеры | `save('книга.epub')` | Главы, навигация и метаданные EPUB 3 из одной модели |
| Массовые правки существующих файлов | `replaceText()`, `insertAt()` | Точечное редактирование DOCX/ODT/RTF без офисного пакета на сервере |
| Документная HTML/CSS-вёрстка в PDF | элемент `Box` | Фоны, рамки и отступы контейнеров с корректным разрывом по страницам |
**Когда пакет не нужен.** Если требуется пиксельная эмуляция браузера
(float, flex, grid, `position`) в PDF — этого нет ни в одной PHP-библиотеке
в полном объёме; ближе всех mpdf и dompdf. Если нужно читать макросы,
диаграммы или историю правок DOCX — модель хранит содержимое и оформление,
а не OLE-объекты. Для распознавания сканов нужен OCR — эта задача сюда
не входит.
## Документация
- [Полное оглавление документации](doc/README.md) — страница на каждую возможность.
- [Диаграммы](wiki/docs/ru/features/Charts.md) · [Блок-схемы](wiki/docs/ru/features/Diagrams.md) ·
[Страницы](wiki/docs/ru/features/Pages.md) · [Шаблоны](wiki/docs/ru/features/Templates.md)
- [Архитектура и жизненный цикл](doc/architecture.md) — слои и mermaid-диаграммы.
- [CHANGELOG](CHANGELOG.md) · [Политика безопасности](SECURITY.md) ·
[Как внести вклад](CONTRIBUTING.md) · [Поддержка](SUPPORT.md)
## Разработка
```bash
composer install
composer check # полный конвейер: lint → psalm → phpstan → phpmd → phpcs
# → rector → deptrac → тесты → память → утечки
# → производительность → пороги покрытия
composer test # только тесты
composer fix # автопоправки стиля
```
## Лицензия
[MIT](LICENSE).
---
**🇷🇺 Русский** · [🇬🇧 English](docs/en/README.md) · [🇩🇪 Deutsch](docs/de/README.md) · [🇫🇷 Français](docs/fr/README.md) · [🇪🇸 Español](docs/es/README.md) · [🇮🇹 Italiano](docs/it/README.md)