Создание word документа с помощью библиотеки phpWord

3 марта 2022 г.

В данной статье хочу показать небольшой пример работы с библиотекой PHPWord. Логика работы примерно следующая: есть элемент инфоблока с некоторыми полями, в том числе, привязка к пользователю, где хранятся все его данные. При создании или обновлении элемента на лету создается word документ, в котором указывается фио сотрудника или возможно какие-то другие данные. Этот документ сохраняется в базу и присваивается этому же элементу как значение свойства типа «файл». После чего этот файл можно скачать.

Для подключения данной библиотеки, нам понадобиться composer. Для Bitrix composer подключается довольно просто. В папке /bitrix, есть файл «composer-bx.json». Переименовываем его в composer.json и в консоле,перейдя в эту папку, запускаем установщик composer`а

composer install

После чего в данном разделе появляется папка «vendor», куда и будут установлены необходимые библиотеки. Добавим зависимость «"phpoffice/phpword": "v0.16."» и установим все пакеты. Актуальную версию смотрите на официальном сайте или github разработчиков. Для подключения автозагрузки классов* в init.php пропишем следующую конструкцию:

if($_SERVER["DOCUMENT_ROOT"].'/bitrix/vendor/autoload.php') {
    include_once ($_SERVER["DOCUMENT_ROOT"].'/bitrix/vendor/autoload.php');
}

Также, если нужно, можем использовать автозагрузчик от Bitrix, в котором можем использовать свои собственные классы.

CModule::AddAutoloadClasses(
    «», // не указываем имя модуля
    array(
        // ключ - имя класса, значение - путь относительно корня сайта к файлу с классом
        'MyHelper' => '/local/php_interface/MyHelper/helpers.php',        
    )
);
AddEventHandler("iblock", "OnAfterIBlockElementAdd", 'MyHelper::DocCreator');  

В данном примере мы подключили класс MyHelper и повешали событие, которое после добавления элемента инфоблока вызовит статический метод DocCreator этого класса.

В файле /local/php_interface/MyHelper/helpers.php

class DocChange{
 public static function DocCreator(&$arFields) {
      if($arFields["PROPERTY_VALUES"]) { // условие на инфоблок или что угодно  
       self::CreateAfterDoc($arFields);  
       }
     }
}

В публичном статическом методе проверяем, что обновляется элемент из нужного инфоблока и данные не пустые. Далее вызываем обычный метод для формирования самого .doc документа.

public function CreateAfterDoc($incomArray) { 
 $phpWord = new \PhpOffice\PhpWord\PhpWord(); // создаем экземпляр класса
 // параметры которые будут указаны при создании документа
 $phpWord->setDefaultFontName("Times New Roman"); // устанавливаем шрифт
 $phpWord->setDefaultFontSize(12); // устанавливаем шрифт
 $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language(\PhpOffice\PhpWord\Style\Language::EN_GB)); // устанавливаем язык документа
 $phpWord->getSettings()->setHideSpellingErrors(true);

 // параметры документа
 $properties = $phpWord->getDocInfo();
 $properties->setCreator('');
 $properties->setCompany('MyCompany ');
 $properties->setTitle('My title');
 $properties->setDescription('My description');
 $properties->setCategory('My category');
 $properties->setLastModifiedBy('My name');
 $properties->setCreated(mktime(0, 0, 0, 3, 12, 2020));
 $properties->setModified(mktime(0, 0, 0, 3, 14, 2020));
 $properties->setSubject('My subject');
 $properties->setKeywords('my, key, word'); 
 $sectionStyle = []; // стили для раздела
$section = $phpWord->addSection($sectionStyle); // добавление раздела с добавлением стилей
}

Мы создали документ и задали какие-то начальные параметры. Но для того, что бы начать что-то рисовать, нужно создать отдельную секцию и передать массив со стилями (цвета, шрифт, отступы, и.т.д.).

Добавление текста происходит примерно так:

$text = 'Отчет о выполнении плана работ';
$section->addText(htmlspecialchars($text), ['bold'=>true, 'size' => 14], ["alignment" => 'center']);

Добавление таблиц, так:

// стили для таблиц
$table_styles_1 = [
'borderColor' => '000000', 
 'borderSize'  => 1,
 'cellMargin'  => 0,
 'cellSpacing ' => 0
];
$table_styles_2 = [
 'borderColor' => '000000',  
 'borderTopSize'  => 1,
  'cellMargin'  => 0, 
  'cellSpacing ' => 0 
];
$cellStyleTopBorder = [
 'borderTopColor' => "000000",
 'borderBottomColor' => "ffffff", 
 'borderRightColor' => "ffffff", 
 'borderLeftColor' => "ffffff", 
 'borderSize'  => 1,
 'cellMargin'  => 0, 
  'cellSpacing ' => 0
];

// добавляем стили для таблицы. Задаем идентификатор настроек со стилями, сами стили и стили для первой строки. Параметры идут по порядку.
$firstRowStyle = array('bgColor' => '66BBFF'); // стиль для первой строки
$phpWord->addTableStyle('first_table', $table_styles_1, []);
$phpWord->addTableStyle('second_table', $table_styles_2, []);
Тут мы создали два стиля с идентификаторами ‘first_table’ и ‘'second_table’
Далее задаем параметры для строк и ячеек:
$cellColSpan = array('gridSpan' => 2, 'valign' => 'center');
$cellHCentered = array('alignment' => \PhpOffice\PhpWord\SimpleType\Jc::CENTER);
$cellVCentered = array('valign' => 'center');
И наконец, в нашей секции обрисовываем таблицу и передаем в конструктор идентификатор стилей.
$table = $section->addTable("first_table");

Переменная $table имеет доступ к методам для создания строк и ячеек таблиц. Укажем все стили и параметры которые мы задали для них.

$table->addRow([\PhpOffice\PhpWord\Shared\Converter::pixelToTwip(1200)]); 
 $cell2 = $table->addCell(\PhpOffice\PhpWord\Shared\Converter::pixelToTwip(600), $cellColSpan);
 $textrun2 = $cell2->addTextRun($cellHCentered);
 $textrun2->addText('ФИО работника');     

 $cell2 = $table->addCell(\PhpOffice\PhpWord\Shared\Converter::pixelToTwip(600), $cellColSpan);
 $textrun2 = $cell2->addTextRun($cellHCentered);
 $textrun2->addText($fullName);

$table->addRow([\PhpOffice\PhpWord\Shared\Converter::pixelToTwip(1200)]); 
 $cell2 = $table->addCell(\PhpOffice\PhpWord\Shared\Converter::pixelToTwip(600), $cellColSpan);
 $textrun2 = $cell2->addTextRun($cellHCentered);
 $textrun2->addText('Должность');     

$cell2 = $table->addCell(\PhpOffice\PhpWord\Shared\Converter::pixelToTwip(600), $cellColSpan);
 $textrun2 = $cell2->addTextRun($cellHCentered);   
 $textrun2->addText($userFields["FIELDS"]["UF_POSITION"]);
Методы вполне говорящие. 
 $table->addRow() - создает строку таблицы
 $table->addCell() - создает ячейку в таблице. Причем сколько раз указан этот метод, столько ячеек и будет создано.

В документации указаны полные списки параметров для создании той или иной сущности. Я лишь хочу привести пример использования. В результате, должно получиться нечто подобное пример таблицы 1

С помощью метода $section->addTextBreak(1); можно выставлять разрывы между секциями.

Все данные берутся из свойств элемента . По-этому событие нужно вешать после того, как элемент сохраниться в базе данных, что бы были доступны все значения свойств.

Если нужен эффект ознакомления, то можно использовать такую же таблицу, только цвет для соответствующих границ указать белым как фон самого документа.

$cellStyleTopBorder = [
     'borderTopColor' => "000000",
     'borderBottomColor' => "ffffff", 
     'borderRightColor' => "ffffff", 
     'borderLeftColor' => "ffffff", 
     'borderSize'  => 1,
     'cellMargin'  => 0, 
        'cellSpacing ' => 0
    ];

пример таблицы 1

Так же можно добавить несколько строк для свободного текста с помощью $section->addLine(['weight' => 1, 'width' => 450, 'height' => 0]);

Что бы сохранить получившийся документ и привязать его к соответствующему свойству вашего элемента, сделаем следующие шаги:

$pathToDOC = $_SERVER["DOCUMENT_ROOT"]."/upload/hr_docs/".$incomArray["CODE"];
\Bitrix\Main\IO\Directory::createDirectory($pathToDOC);

// сохраняем документ, с помощью библиотеки. Доступен
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
$objWriter->save($pathToDOC.'/'.$incomArray["CODE"].'.docx');

// затем сохраняем его методами Bitrix, что бы файл попал в базу и проиндексировался, присваиваем значение свойству и удаляем тот что был создан с помощью библиотеки. 
$arFile = CFile::MakeFileArray($pathToDOC.'/'.$incomArray["CODE"].'.docx');
$arFile["MODULE_ID"] = "iblock";
$arFile["del"] = "Y";
CIBlockElement::SetPropertyValueCode($incomArray["ID"], "DOCUMENT_SECOND", ["86" => ["VALUE" => $arFile]]);
\Bitrix\Main\IO\Directory::deleteDirectory($pathToDOC); // удаление директории куда записали исходный файл

Немного странный подход, но по-другому сделать не получилось. В моем случае, все документы делались по ajax без перезагрузки страниц.

В результате кодовая база получается довольно большая, в основном из-за больших массивов со стилями и громоздких параметров при создании элементов таблиц. Библиотека PHPWord содержит возможность создавать все элементы word, и я показал наиболее сложные, на мой взгляд, элементы - таблицы.

Комментарии(0)