четверг, 18 февраля 2010 г.

Word, Excel, реестр - для PHP доступно всё!


PHP — работа с приложениями Microsoft Office


Язык PHP, в конце прошлого года вышел на третье место, по популярности, среди других средств программирования. Это не удивительно — эффективность его использования для различных задач общеизвестна.
Сам php в сознании разработчиков и системных администраторов, почти повсеместно рассматривается как инструмент из мира unix-подобных систем. Между тем, он уже довольно давно совершенно комфортно себя чувствует на Windows платформе. О использовании его на IIS 7, в качестве fastCGI обработчика, я уже писал. Теперь я хочу рассмотреть возможность, которая так-же очень давно доступна php программистам, но  по какой-то причине, остаётся для многих из них тайной. Я имею ввиду работу с объектами  пакета приложений Microsoft Office.




Что такое COM объекты и COM технология?
 

COM (Component Object Model — Объектная Модель Компонентов) – технология от компании Microsoft, заключающаяся в создании  программного обеспечения на основе  различных взаимодействующих компонентов, каждый из которых может, доступен для использования многими программами и процессами. 
В современных версиях Windows COM используется очень широко. На основе COM были созданы технологии: Microsoft OLE Automation, ActiveX, и другие.
Чтобы лучше понять эту технологию, можно попробовать самому написать простейший COM-компонент. Используя VisulBasic, создадим проект ActiveX dll, с именем test_com. Создадим в нём класс HelloCOM.phpcom Весь код, который следует написать:


Public Function hello() As String
    hello = "Hello COM!"
End Function

Скомпилируем dll и зарегистрируем её в системе, посредством regsvr32.exe.
Теперь пишем PHP приложение, использующее этот компонент:

<?php
$obj = new COM("HelloCOM.phpcom ");
$output=$obj->hello();
echo $output;
?>

Если после запуска скрипта в браузер будет выведено «Hello COM!» можно вносить в резюме сведения о знакомстве с COM технологией.
Теперь уже ничего не страшно. Вперёд!

Хеллоу Ворд!


Итак, приступим. Сначала попробуем сгенерировать простейший документ Word. Текст программы будет выглядеть так:

<?php
$word = new COM("word.application");
$word->Visible = 0;
$word->Documents->Add();
$word->Selection->Font->Name = 'Times New Roman';
$word->Selection->Font->Size = 32;
$word->Selection->TypeText("Превед!");
$word->Documents[1]->SaveAs("preved.doc");
$word->quit();
isset($word);

?>

Запускаем это скрипт, и в папке для сохранения по умолчанию, получаем сгенерированный документ Word (рис.1).




Рис. 1
Здравствуй Word!

Тут всё просто – сначала мы создаём новый COM объект – экземпляр word.application. Затем делаем его невидимым (иначе он появиться на экране сервера),  создаём новый документ, устанавливаем  параметры шрифта и пишем текст.
 Последние команды (quit(),isset()) обязательны, иначе процесс WINWORD.EXE  так и будет висеть в памяти. Из этого следует, что ели ваше  приложение, завершилось аварийно (что при отладке вполне нормальное явление), эти процессы следует завершить в ручную, через диспетчер задач.
Вполне вероятно, что при запуски скрипта никакого документа создано не будет, а скрипт завершиться с сообщением типа:
Uncaught exception 'com_exception' with message 'Failed to create COM object `word.application': Отказано в доступе
Это значит, что у сервера нет соответствующих  прав доступа на этот объект, и решается следующим образом:
Идём в Панель управления=>Администрование=>Службы компонентов (или выполним команду dcomcnfg). Мы попадём в консоль Службы компонентов (рис. 2), где выберем Службы компонентов=>Мой компьютер=>Настройка DCOM. 



Рис. 2 
Консоль службы компонентов

Тут выберем нужное приложение (в моём случае это Документ Microsoft Office Word 97 – 2003, название может отличаться) и настроим (на вкладке «Безопасность», в свойствах приложения) права (рис 3).

 

Рис. 3
Настраиваем права доступа

Ещё один нюанс – на вкладке «Удостоверение», в поле «Какую учетную запись использовать для запуска этого приложения?» ставим отметку на пункте «Текущий пользователь».
Теперь всё должно работать.
Можно ли напечатать что-то сложнее? Запросто. Изменяем наш код:



<?php
$word = new COM("word.application");
$word->Visible = 0;
$word->Documents->Add();
$word->Selection->Font->Name = 'Times New Roman';
$word->Selection->Font->Size = 32;
$word->Selection->TypeText("Ура!");
$word->Selection->TypeParagraph;
$word->Selection->TypeParagraph;
$word->Selection->Font->Size = 12;
$word->Selection->TypeText("Создаём документ MSWord посредством PHP. ");
$word->Selection->Font->Color=255255;
$word->Selection->TypeText("Разноцветный документ");
$word->Selection->TypeParagraph;
$word->Documents[1]->SaveAs("test.doc");
unset($word);

?>

Запускаем скрипт и любуемся результатом (рис 4).


Рис. 4 
Осваиваем форматирование

Ну что ж, уже чуть интересней. В принципе мы можем  создавать документы любой сложности? Только вот как? Откуда брать все эти команды и свойства типа Document-> Selection->Font->Color?
На этот вопрос ответить просто – нужно изучить VBA и MS Office API. Сложнее ответить на другой вопрос – где взять php программиста, который захочет всё это изучать?
К счастью в самом MS Office API есть прекрасные встроенные инструменты, для облегчения этого процесса.
Самый прстой  способ получить необходимые команды – раскрыть новый документ MS Office, затем, через меню  Сервис =>Запись макроса. Присвоим макросу какое ни будь оригинальное, красивое имя (например, Макрос1) и нажмём «Начать запись». После этого набираем  требуемый текст, творим, средствами MSOffice нужное форматирование, и по окончании этого действа останавливаем запись. Теперь идём в меню Сервис=>Макросы, и выбираем пункт «Изменить» для свежезаписанного макроса и изучаемого код.
Правда для работы с Microsoft Office 2007  и готовящемуся к выходу Microsoft Office 2010 пройдется выполнить несколько больше телодвижений.  Для того чтобы получить там эту возможность, следует сначала нажать на кнопку «Office», в левом верхнем углу рабочей области, и там выбрать пункт  «Параметры Word» (рис 5). 


Рис. 5 
Становимся разработчиками (наконец-то!)

В Появившимся окне, в разделе «Основные», в группе «Основные параметры работы с Word» отметить пункт «Показывать вкладку "Разработчик" на ленте». После этого в верхнем меню появиться искомый пункт.
Теперь посмотрим на код макроса:


Sub Макрос1()
'
' Макрос1  Макрос
'
'
    Selection.TypeText Text:="Ура!"
    Selection.MoveLeft Unit:=wdCharacter, Count:=4, Extend:=wdExtend
    Selection.Font.Size = 36
    Selection.MoveRight Unit:=wdCharacter, Count:=1
    Selection.Font.Size = 12
    Selection.TypeParagraph
    Selection.TypeParagraph
    Windows("test.docx").Activate
    Windows("Докяяяяумент3").Activate
    Selection.TypeText Text:="Саздаём документ "
    Application.Keyboard (1033)
    Selection.TypeText Text:="MSOffice"
    Application.Keyboard (1049)
    Selection.TypeText Text:=", посредством "
    Application.Keyboard (1033)
    Selection.TypeText Text:="PHP"
    Application.Keyboard (1049)
    Selection.TypeText Text:=". "
    Selection.Font.Color = 5287936
    Selection.TypeText Text:="Разноцветный документ."
    ActiveDocument.SaveAs FileName:="Ура.docx", FileFormat:= _
        wdFormatXMLDocument, LockComments:=False, Password:="", AddToRecentFiles _
        :=True, WritePassword:="", ReadOnlyRecommended:=False, EmbedTrueTypeFonts _
        :=False, SaveNativePictureFormat:=False, SaveFormsData:=False, _
        SaveAsAOCELetter:=False
End Sub

Можно из этого листинга извлечь необходимые свойства и методы MS Word? Мне, по крайней мере, это удалось.
Впрочем, есть и более правильный путь.  В том же подменю «Сервис», или на вкладке «Разработчик» для MS Office 2007, выбираем пункт “Редактор Visual Basic”. Там раскрываем View=>ObjectBrouser  (рис 6). Здесь можно получить сведения обо всех объектах, их методах и свойствах. 


Рис. 6

Теперь, вооружившись новыми знаниями, сделаем  что ни будь по сложнее. Ну, хотя бы создадим элементарную таблицу:
<?php
$word = new COM("word.application");
$word->Visible = 0;
$doc=$word->Documents->Add();
$word->Selection->ParagraphFormat->Alignment=1;
$word->Selection->TypeText("Ведомость");
$word->Selection->TypeParagraph;
$table= $doc->Tables->Add($word->Selection->Range,5,5);
$word->Selection->TypeText("Имя");
$word->Selection->MoveRight();
$word->Selection->TypeText("Должность");
$word->Selection->MoveRight();
$word->Selection->TypeText("Оклад");
$word->Selection->MoveRight();
$word->Selection->TypeText("Премия");
$word->Selection->MoveRight();
$word->Selection->TypeText("Что нибудь ещё");
$word->Selection->MoveRight();
$word->Selection->TypeText("Иванов Иван");
$word->Selection->MoveRight();
$word->Selection->TypeText("Программист");
$word->Selection->MoveRight();
$word->Selection->TypeText("120 000");
$word->Selection->MoveRight();
$word->Selection->TypeText("over 9000");
$word->Selection->MoveRight();
$word->Selection->TypeText("ещё");
$word->Documents[1]->SaveAs("test2.doc");
$word->quit();
unset($word);

?>

Конечно, данный код не является шедевром VBA программирования, для полноценной разработки с MS Office пучиться всё равно придется, но, тем не менее, это уже работает (рис 7).



Рис. 7
Хватит, по деньгам?

Теперь мы можем генерировать документы формата Word –счета, отчёты, объяснительные. Это уже хорошо, но вот незадача – бухгалтеру менеджеру нужны отчёты в виде электронных таблиц. Не проблема – буде генерировать таблицы Excel.
Хеллоу Эксель!
На самом деле тут всё так же несложно. Простейшая генерация документа будет выглядеть так:

<?php
$excel = new COM("excel.application");
$excel->Visible = 0;
$wkbook = $excel->Workbooks->Add();
$sheet = $wkbook->Worksheets(1);
$sheet->activate;
$cell = $sheet->Cells(1,1);
$cell->Activate;
$cell->value = 'Hello!';
$wkbook->SaveAs("hello.xls");

$wkyook->Close(false);
$excel->Workbooks->Close();
$excel->Quit();
isset($excel);

?>

Перед запуском скрипта, возможно следует  установить права в Консоли службы компонентов на приложение Microsoft Excel Application, аналогично тому, как мы делали с Word. Результат на рис. 8.


Рис. 8
Работаем с Excel

Тут, после создания объекта мы создаём новую рабочею книгу  (Workbook),  затем выбираем и активируем нужную вкладку (Worksheet) и ячейку таблицы (Cell). После этого меняем значение ячейки.
Можем мы сделать что-то по сложнее? Я надеюсь, ответ очевиден – можем. Мне только хотелось продемонстрировать  работу с уже имеющимся документом, так как это наиболее приближено к практике.
Сначала создадим шаблон документа (рис 9). Сохраним его под именем template.xlsx. Теперь из скрипта заполним его. Прежде всего, сгенерируем исходные данные (в реальной задачи они будут поступать из любого внешнего источника – базы данных, AD, 1С и т д.) и укажем имя, под которым будем сохранять документ:

$names=array('Иванов','Петров','Бешков');
$places=array('Администрация','Бухгалтерия','Служба безопастности');
$solary=array('3000','4500','9010');
$file="C:\\store\save”.date('Y-m-d').".xls";

Теперь создадим COM объект и откроем сохранённый шаблон:

$excel = new COM("excel.application");
$excel->Visible = 0;
$excel->DisplayAlerts = 0;
$wkbook = $excel->Workbooks->Open("C:\\www\\template.xls");

Команда DisplayAlerts нужна для подавления вывода блокирующих сообщений. Не забывайте, что генерация документа на деле будет проходить на удалённом сервере и кнопку «OK» в предупреждении, например, о сохранение в другом формате вы нажать не сможете.
Далее всё просто:


$sheet = $wkbook->Worksheets(1);
$sheet->activate;
$vofset=4;
$hofset=1;
for($i=0;$i
$cell = $sheet->Cells($vofset+$i,$hofset+1);
$cell->Activate;
$cell->value = $names[$i];

$cell = $sheet->Cells($vofset+$i,$hofset+2);
$cell->Activate;
$cell->value = $places[$i];

$cell = $sheet->Cells($vofset+$i,$hofset+3);
$cell->Activate;
$cell->value = $solary[$i];
}
print $file;
$wkbook->SaveAs($file);
$wkbook->Close(false);
$excel->Workbooks->Close();
$excel->Quit();
isset($excel);
?>

Результат на рис .

А дальше?

Что ещё мы сможем делать, используя COM? Теоретически – работать с любым объектом, имеющим COM интерфейс. На практике мы ограничены правами, под которыми запускает процессы наш веб-сервер и рядом системных ограничений.
Впрочем, работать с остальными приложениями Microsoft Office мы, в большинстве случаев можем. Вот так можно запустить презентацию в Powerpoint:

$ppoint->Presentations->Add() or die ("Could not create presentation");
$slide=$ppoint->Presentations[1]->Slides->Add(1,1);
$name = $slide->Name();
echo "Hello ”. $sname "!”;
$ppoint->Presentations[1]->SaveAs("c:/InetPub/www/test.ppt");
$ppoint->Presentations[1]->Close();
$ppoint->Quit();
unset($ppoint);
?>

Так – работать с Access:

$conn = new COM("ADODB.Connection");
$conn->open('DRIVER={Microsoft Access Driver (*.mdb)}; DBQ=' . realpath("relative_path/db.mdb"));
$sql="SELECT Name, SName FROM clients";
$result = $conn->execute($sql);
$conn->Close();
unset($conn);
var_dump($result);

C Outlook всё несколько сложнее. До Outlook 2003 можно было отправить письмо вот таким простым кодом:

$Outlook = new COM("Outlook.Application");
$Item = $ Outlook ->CreateItem(olMailItem);
$a=$Item->Recipients->Add("suckhov@mics.ru");
$Item->Subject="Hi";
$Item->Body="Hello!”
$Item->Display();
$Item->Send();


К сожалению, в современных версиях Outlook, скриптом отправить почту без подтверждения не представляется возможным (и это наверно правильно). Но не надо забывать, что Outlook это в первую очередь информационный менеджер, полноценный органайзер, и все его функции, кроме почтовых нам теперь доступны.

Впрочем, офисом наши возможности не ограниваются. Работать можно с любым доступным windows приложение, имеющее COM интерфейс.
Мне, честно говоря, не приходит в голову причина, по которой может понадобиться запускать на сервере браузер, но сделать это определённо можно:

$browser = new COM("InternetExplorer.Application");
$browser->Visible = true;
$browser->Navigate("http://mysite.com");

Да что там приложения, смело лезем в реестр (если у нас есть на это права):

$folder="RegisteredApplications";
$key="WinRar";
$WShell = new COM("WScript.Shell");
$registry = "HKEY_LOCAL_MACHINE\SOFTWARE\\" . $folder . "\\" . $key;

$result = $WshShell->RegRead($registry)

Ну и, наконец, действительно полезное применение COM расширения. Что оно делает, думаю, разберётесь сами:


$mp = new COM("WMPlayer.OCX");
$mp->cdromcollection->item(0)->eject();


Конечно, возможности PHP по работе с COM объектами  сейчас применяются довольно мало. Это Понятно – PHP это всё-таки, в основном язык веб программирования, и применяется он в основном на Unix платформах. Но это положение вещей меняется, и вполне вероятно, у же в скором времени, кун-фу, описанная в этой заметке будет широко востребовано. Впрочем, и сейчас COM функции можно эффективно применять на внутреннем сайте компании. В Любом случае важно знать, что такая возможность есть.

Ресурсы:


8 комментариев:

  1. Настроила все точно также как в статье, однако выдается ошибка -
    Failed to create COM object `excel.application': Процесс сервера не может быть запущен, так как указана неправильная идентификация. Проверьте правильность указания имени пользователя и пароля.

    Что это может быть?

    ОтветитьУдалить
  2. А настройку прав DCOM вы делали? Судя по всему, не хватет прав. Но ещё смущает запрос паороля. Машина в домене?

    ОтветитьУдалить
  3. Благодарю за отличную статью! Весомо помогло!

    ОтветитьУдалить
  4. я человек начинающий,может покажется глупо,но у меня не выходит создать проект activx dll,подскажите как это сделать в visual studio?
    лучше пошагово)

    ОтветитьУдалить
  5. C классом COM для Word Excel PP Access понятно (проверил - работает). А вот как таблицу (с заранее неизвестными размерами) скопировать, например, из Excel в Word.

    Экспериментально нашел, как определить границы таблицы:
    $xls -> Range("A1") -> Select; // Выбираем 1ю ячейку
    $xls -> Selection -> End(2) -> End(4) -> Select; // Выбираем последнюю ячейку
    $Addr = $xls -> Selection -> Address;
    $Row = $xls -> Selection -> Row;
    $Col = $xls -> Selection -> Column;

    Читаю содержимое:
    $range=$xls->Range("A1:$Addr");
    $result = $range -> Value;

    Соответсвенно создаю таблицу в ворде:
    $table= $doc->Tables->Add($word->Selection->Range,$Row,$Col);
    $table->Select();

    А как в нее записать?
    $word->Selection->TypeText( $result ); - не катит - "несоответствие типов".

    ОтветитьУдалить
  6. Вообще, где бы найти описание функций COM (для "Excel.Application", "Word.Application" и т.д.) для php5 ?
    На MSDN есть описание - но все, понятно, для VB.
    Приходится экспериментировать - тратится много времени.
    Времени жалко.

    ОтветитьУдалить
  7. В "Службы компонентов" нет ни чего связанного с office, в чём может быть проблема?

    ОтветитьУдалить
  8. Что делать если в службе компонентов нет word и excel. Хотя на компьютере установлен офис 2007?

    ОтветитьУдалить