Search by

cloud-castle / document

alex-4-17

Универсальная работа с документами для PHP 8.1+: единая объектная модель, парсинг и создание документов, сохранение в 10 форматах (Markdown, HTML, DOCX, ODT, RTF, PDF, EPUB, JSON, XML, TXT) и конвертация между любыми из них. Безопасно по умолчанию: защита от XXE, zip-бомб и HTML-инъекций.

v0.1.0 2026-09-17 05:36 UTC

This package is auto-updated.

Last update: 2026-09-17 16:02:16 UTC


README

🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano

CloudCastle Document

CloudCastle Document

Packagist Version PHP Version License Downloads Monthly Downloads Stars Advisories

Quality Publish Репозиторий Релиз Задачи Запросы слияния Вики Лента

PHPStan Psalm PHPMD PHPCS coverage Infection MSI OpenSSF

Универсальная работа с документами на чистом 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 версиях аналогов.

Функциональность

Возможностьdocumentphpwordcommonmarkparsedownhtml-to-markdowndompdfmpdfpdfparser🏆 Победитель
Чтение Markdowndocument, commonmark, parsedown
Чтение HTML🟡document и ещё 3
Чтение DOCXdocument, phpword
Чтение ODT🟡document
Чтение RTF🟡document
Чтение PDFdocument, pdfparser
Чтение EPUBdocument
Запись Markdowndocument, html-to-markdown
Запись HTMLdocument и ещё 3
Запись DOCXdocument, phpword
Запись ODTdocument, phpword
Запись RTFdocument, phpword
Запись PDF🟡document, dompdf, mpdf
Запись EPUBdocument
Родной 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)3820.57.5649.514.56document

Производительность

Замеры: идентичный документ (60 разделов: заголовок, абзац со стилями, список, таблица), лучший из 7 прогонов, отдельный процесс на каждый замер.

Сценарийdocumentphpwordcommonmarkparsedownhtml-to-markdowndompdfmpdfpdfparser🏆 Победитель
Markdown → HTML2.7 мс23.2 мс3.4 мсdocument
HTML → Markdown2.4 мс5.2 мсdocument
Запись DOCX3.3 мс10.8 мсdocument
Запись PDF70.6 мс247.3 мс129.3 мсdocument
Чтение PDF24.8 мс39.5 мсdocument

Потребление памяти

Сценарийdocumentphpwordcommonmarkparsedownhtml-to-markdowndompdfmpdfpdfparser🏆 Победитель
Markdown → HTML0.0 МБ0.0 МБ0.0 МБdocument, commonmark, parsedown
HTML → Markdown0.0 МБ0.0 МБdocument, html-to-markdown
Запись DOCX0.0 МБ4.0 МБdocument
Запись PDF2.0 МБ2.0 МБ0.0 МБmpdf
Чтение PDF2.0 МБ8.0 МБdocument

Безопасность

Свойство безопасностиdocumentphpwordcommonmarkparsedownhtml-to-markdowndompdfmpdfpdfparser🏆 Победитель
Защита от XXE по умолчанию🟡🟡document, phpword
Защита от zip-бомбdocument
Санитизация HTML по белому списку🟡🟡document
Запрет опасных схем URL🟡🟡🟡document, commonmark
Без сетевых обращений при разборе🟡🟡document и ещё 5
Итого (из 5)522.5211.51.51document

Соответствие стандартам и практикам безопасности composer-пакетов (CWE-классы защит, OWASP ASVS, политика раскрытия, строгие типы):

Стандарт / практика безопасностиdocumentphpwordcommonmarkparsedownhtml-to-markdowndompdfmpdfpdfparser🏆 Победитель
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)105.563.54.53.53.52.5document

Качество кода

Практика качестваdocumentphpwordcommonmarkparsedownhtml-to-markdowndompdfmpdfpdfparser🏆 Победитель
PHPStan (максимальный уровень + strict-rules)🟡🟡🟡🟡document, commonmark
Psalmdocument, commonmark
Мутационное тестирование (Infection)document
Архитектурные слои (Deptrac)document
Тесты утечек памяти в CIdocument
Тесты безопасности (XXE, бомбы, схемы URL)🟡🟡document
Контроль покрытия per-file в CIdocument
Итого (из 7)712.500.500.50.5document

Честно о пакете

Сильные стороны

  • Единственный 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 — эта задача сюда не входит.

Документация

Разработка

composer install
composer check        # полный конвейер: lint → psalm → phpstan → phpmd → phpcs
                      #   → rector → deptrac → тесты → память → утечки
                      #   → производительность → пороги покрытия
composer test         # только тесты
composer fix          # автопоправки стиля

Лицензия

MIT.