cloud-castle / document
Универсальная работа с документами для PHP 8.1+: единая объектная модель, парсинг и создание документов, сохранение в 10 форматах (Markdown, HTML, DOCX, ODT, RTF, PDF, EPUB, JSON, XML, TXT) и конвертация между любыми из них. Безопасно по умолчанию: защита от XXE, zip-бомб и HTML-инъекций.
Requires
- php: >=8.1
Requires (Dev)
- 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-17 16:02:16 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, zip, zlib
(входят в типовую поставку PHP).
Быстрый старт
<?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.
Поддерживаемые форматы
| Формат | Чтение | Запись | Особенности |
|---|---|---|---|
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 версиях аналогов.
Функциональность
| Возможность | 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 |
| Без сетевых обращений при разборе | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 | 🟡 | ✅ | document и ещё 5 |
| Zero-dependency ядро (только PSR/cloud-castle) | ✅ | — | — | ✅ | — | — | — | — | document, parsedown |
| Дозапись и правка открытых документов | ✅ | 🟡 | — | — | — | — | — | — | document |
| Шаблоны «${имя}» с подстановкой значений | ✅ | ✅ | — | — | — | — | — | — | document, phpword |
| Страницы: доступ по номеру, правка, чтение из файлов | ✅ | 🟡 | — | — | — | — | — | 🟡 | document |
| Настройки страницы: поля, интервалы, нумерация (Word-дефолты) | ✅ | ✅ | — | — | — | 🟡 | ✅ | — | document, phpword, mpdf |
| Каскад стилей: слово › абзац, ячейка › строка › таблица | ✅ | 🟡 | — | — | — | ✅ | ✅ | — | document, dompdf, mpdf |
| Водяной знак | ✅ | 🟡 | — | — | — | — | ✅ | — | document, mpdf |
| Итого (из 38) | 38 | 20.5 | 7.5 | 6 | 4 | 9.5 | 14.5 | 6 | document |
Производительность
Замеры: идентичный документ (60 разделов: заголовок, абзац со стилями, список, таблица), лучший из 7 прогонов, отдельный процесс на каждый замер.
| Сценарий | 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 |
Потребление памяти
| Сценарий | 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 |
Безопасность
| Свойство безопасности | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|---|---|---|---|---|---|---|---|---|
| Защита от XXE по умолчанию | ✅ | ✅ | — | — | — | 🟡 | 🟡 | — | document, phpword |
| Защита от zip-бомб | ✅ | — | — | — | — | — | — | — | document |
| Санитизация HTML по белому списку | ✅ | — | 🟡 | 🟡 | — | — | — | — | document |
| Запрет опасных схем URL | ✅ | — | ✅ | 🟡 | — | 🟡 | 🟡 | — | document, commonmark |
| Без сетевых обращений при разборе | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 | 🟡 | ✅ | document и ещё 5 |
| Итого (из 5) | 5 | 2 | 2.5 | 2 | 1 | 1.5 | 1.5 | 1 | document |
Соответствие стандартам и практикам безопасности composer-пакетов (CWE-классы защит, OWASP ASVS, политика раскрытия, строгие типы):
| Стандарт / практика безопасности | 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 и ещё 5 |
| 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 | 5.5 | 6 | 3.5 | 4.5 | 3.5 | 3.5 | 2.5 | document |
Качество кода
| Практика качества | 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 |
Честно о пакете
Сильные стороны
- Единственный 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. - 308 тестов, покрытие 96,7 %, 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 — эта задача сюда
не входит.
Документация
- Полное оглавление документации — страница на каждую возможность.
- Архитектура и жизненный цикл — слои и mermaid-диаграммы.
- CHANGELOG · Политика безопасности · Как внести вклад · Поддержка
Разработка
composer install
composer check # полный конвейер: lint → psalm → phpstan → phpmd → phpcs
# → rector → deptrac → тесты → память → утечки
# → производительность → пороги покрытия
composer test # только тесты
composer fix # автопоправки стиля
Лицензия
MIT.