FuelPHPでphpwordを使ってワードファイル(docx)を出力できるようにした時のメモ

2014 年 4 月 21 日PC・IT

Pocket

FuelPHPとPHPWordのイメージ
【追記:2014/4/21】
以下で利用しているphpwordよりも新しいphpwordパッケージが見つかりました。そちらの方を利用する方が幸せになれると思いますので、コチラのエントリ:FuelPHPでphpoffice/phpwordを利用するメモ | Lunarian's Blogをご参照下さい。

職場で、簡単な業務用のアプリをFuelPHPで作っています。業務用アプリということで、文章を出力する機能が必要となり、PDF出力を利用していました。これについては、FuelPHP用にfuelpdfというパッケージ(fuel-packages/fuel-pdf · GitHub)があり、実装は簡単です。PDF出力ライブラリとしてTCPDFが利用されており、以前利用していたので簡単に実装できました。

ただ、どうしても取引先がMicrosoft Wordのファイルが良いという要望があり、こっちもそのためにWordを起動して手動でドキュメントを作る手間が馬鹿に出来なかったので、phpwordというPHPでMicrosoft Wordファイルを出力するPHPのライブラリをfuelPHPで利用できるようにしたまで奮闘した成果メモを記しておきます。

FuelPHPのパッケージへの理解、autoloadへの理解、フォルダ構成への理解などが不十分で結構とまどいましたが、とても簡単にできました。

phpwordの概要

生成できるのはWord2007形式

phpwordは、オープンソースのライブラリで、PHPでMicrosoft wordのファイルを出力するためのものです。
wordのファイルは、2003までの形式(*.doc)と2007移行の形式(*.docx)で中身が異なり、phpwordで出力できるのは、2007移行の形式(*.docx)になります。
なぜか2007移行の形式(*.docx)を毛嫌いしている人がたまに見受けられるのですが、Microsoft Office 2003は、XPのサポート終了と同時にサポートを終了してたりしますし、phpから2003までのword形式(バイナリファイル)を出力するのはかなり大変なので、そこはあきらめてほしいです。

生成方法は2通り

phpwordは、2つの方法でwordファイルを出力できます。
1つ目は、ワードファイルをテンプレートとして別途用意し、それを読み込んで編集(値を入れ替える)形で出力する方法。
2つ目は、一からワードファイルを生成する方法です。専用のオブジェクトを生成し、そのオブジェクトに一つ一つパーツを入れていき、最後に一つのワードファイルに生成します。

両方法のメリット・デメリット

1つ目は、ワードファイルをワードで編集して作っておくことが出来るなどのメリットがありますが、値を入れ替えるための変数の入力時などに若干面倒があります。また、プログラムの変更時にPHPとワードファイルと2つのソースを編集する必要があります。

2つ目の方法は、TCPDFなどでのやり方と同じなので慣れている人には楽です。またPHPで柔軟にドキュメント内容を変更できます。

私は2つ目の方法で主に利用するのですが、パスワード付きのドキュメントを取引先に指定されているので、その場合には、パスワードが付いたワードファイルのテンプレートを読み込むという形で1つめの方法で生成しています。(パスワードは固定なのでこの点ではそんなに手間はありません。セキュリティをそこまで厳密にする必要が無くて助かりました。phpwordには一からパスワード付きのファイルを生成する機能は無いようなので。)

phpwordの問題点:日本語への対応

問題として日本語への対応がされていないことが上げられます。
与えられた値に対して、PHP関数の「utf8_encode()」を適用してしまい、似非utf8でエンコードされてしまい文字化けする問題があります。これを手動で外す必要があります。
utf8_encode()という関数の名称から問題なさそうに思えるのですが、どうもちゃんとしたutf8にエンコードしてくれるわけではないとのことです。

fuelPHPへの導入方法その1− composerをつかう

(こちらの方法は、日本語対応で問題があるため、私は使っていません。)

fuelPHPは、バージョン1.6からパッケージ管理にcomposerが利用されています。(composer公式サイト:Composer
Packagistに、phpwordがありますので、composerで導入することができます。

composer.json(composer.pharと同一フォルダにある)のrequire部分に、下記記載を追記。

"phpword/phpword": "dev-master"

イメージ:
composer.jsonに追記する

あとは、ターミナル(黒い画面)で

composer.phar update

ターミナル上でcomposer.phar updateする

で導入されます。phpwordは、ルートのvendorファイル下に配置され、class PHPWordにパスが通ります(という言い方が正しいかよくわからないけど、オートロードされるということ)。従って、普通に「new ¥PHPWord」でインスタンス生成で、利用できます。

この方法がオフィシャルな感じで一番正しい導入方法と思いますが、上述の日本語対応の問題を手動で修正しても「composer.phar update」時に新バージョンで上書きされてしまい、そのたびに修正しなければいけないのでは無いかと思います。(masterブランチしかないので)

そこで以下の方法でパッケージ化(というほどでもないけど)しました。

ちなみに、phpwordをforkしてutf8_encode()部分を消して、packagistに登録して、それでcomposerで導入という方法もありますが、めんどいのと、いろいろとアレなので私はやりません。

FuelPHPへの導入方法その2− packageにしてみる

fuelwordというそれっぽい名前でパッケージ化してみました。
なお、導入方法1と両方やった場合でどういつ名称だとcomposer導入の方が優先されるかと思います。

パッケージの構成と位置

fuel
├app
├core
├vendor
└packages
  └fuelword
    ├classes
     └fuelword.php
    ├config
    ├bootstrap.php
    └vendor
      └phpword(ダウンロードしてここに入れる)
        ├Examples
        ├PHPWord
        ├template
        └PHPWord.php

このような感じになります。fuelフォルダ下のpackagesフォルダ下に「fuelword」というフォルダを作成し、その下にclassesフォルダ、configフォルダ、vendorフォルダを作成。vendorフォルダ下に、PHPWordのページの「download」からphpword本体をダウンロード・解凍し、配置。

fuelwordフォルダの直下の「bootstrap.php」と、fuelword/classesフォルダの下に「fuelword.php」を自作します。私は以下の内容で自作しました。(下記は、フォルダ名、ファイル名が上記の通りであることを前提の内容)

<?php

// Add namespace, necessary if you want the autoloader to be able to find classes
Autoloader::add_namespace('Fuelword', __DIR__.'/classes/');

// Add as core namespace
Autoloader::add_core_namespace('Fuelword');

// Add as core namespace (classes are aliased to global, thus useable without namespace prefix)
// Set the second argument to true to prefix and be able to overwrite core classes
Autoloader::add_core_namespace('Fuelword', true);

// And add the classes, this is useful for:
// - optimization: no path searching is necessary
// - it's required to be able to use as a core namespace
// - if you want to break the autoloader's path search rules
Autoloader::add_classes(array(
	'Fuelword\\Fuelword' => __DIR__.'/classes/fuelword.php',
	'PHPWord' => __DIR__.'/vendor/phpword/PHPWord.php'
));
<?php

namespace Fuelword;

class Fuelword {

	protected $vendor_path = '';

	protected function __construct() {

		$this->vendor_path = PKGPATH . 'fuelword' . DS . 'vendor' . DS . 'phpword'. DS;
		require_once($this->vendor_path.'PHPWord.php');
	}


	public static function forge($template = null) {

		//$phpword = $this->phpword;

		if($template == null) {
			return new \PHPWord();
		}
		else {

			$filepath = PKGPATH . 'fuelword' . DS . 'vendor' . DS . 'phpword'. DS . 'template' . DS . $template;

			if(file_exists($filepath)) {

				$phpword = new \PHPWord();
				$phpword = $phpword->loadTemplate($filepath);

				return $phpword;
			}

			else {
				return new \PHPWord();
			}
		}

	}


	public static function createSection($instance = null) {

		if($instance != null) {
			$PHPWord = $instance->createSection();
		}
		else {
			$PHPWord = new \PHPWord();
			$PHPWord->createSection();
		}

		return $PHPWord;
	}

	public static function createWriter($instance, $ver) {

		return \PHPWord_IOFactory::createWriter($instance, $ver);

	}

}


ライセンスがどうなってるのかなど確認してないので問題があったら消します。

内容としては、たいしたことはしてません。他にスマートな方法がある気がしますが、時間が無いのでこれで済ませました。

実際に使ってみる

テンプレートから作成パターン

※テンプレートファイル(*.docx)は自分で用意

下記の参考リンクにも役立つ情報有りなので見てね。

	public function action_word_create() {

		$this->template = false;

		Package::load('Fuelword');

		//package/fuelword/classes/fuelword.phpのforge functionでパスを指定している
		$fuelword = Fuelword\Fuelword::forge('template.docx');

		$contents = 'test';

		$fuelword->setValue('contents', $contents);

		$dir = dirname(dirname(dirname(__FILE__))) . '/tmp/';
		$fuelword->save($dir.'test.docx');
	}

1から作成パターン

	//Wordファイルテスト
	public function action_word_create() {

		$this->template = false;

		Package::load('Fuelword');
		$fuelword = Fuelword\Fuelword::forge();
		//Debug::dump($fuelword);

		//数値単位は、twips [ 1twips  1/1440inch  ]
		// 1440twips = 1 inch = 2.54cm

		// 567 twips = 1cm

		// A4サイズ 210mm×297mm | (およそ)8.26inch × 11.68inch | 11,894 twips × 16,819 twips

		$section = Fuelword\Fuelword::createSection($fuelword);
		//Debug::dump($test);

		//フォントスタイルの設定
		$default_font_style = array();

		//行スタイルの設定
		$right_paragraph_style = array(
				'align' => 'right',
				'spaceBefore' => false,
				'spaceAfter' => false,
				'spacing' => 1.3 //行間
		);

		$center_paragraph_style = array(
				'align' => 'center',
				'spaceBefore' => false,
				'spaceAfter' => false,
				'spacing' => 1.3 //行間
		);

		//描画
		$section->addText('テキスト追加テスト', $default_font_style, $right_paragraph_style);
		$section->addTextBreak();
		$section->addTextBreak();

		$section->addText('テキスト追加できるかな?', $default_font_style, $center_paragraph_style);
		$section->addTextBreak();


		$dir = dirname(dirname(dirname(__FILE__))) . '/tmp/';

		//新規出力
		$objWriter = Fuelword\Fuelword::createWriter($fuelword, 'Word2007');
		$objWriter->save($dir.'testfile.docx');

		//Debug::dump($objWriter);


	}

↓こんな感じになります。
生成したワードファイル

参考リンク

似たようなエントリ

2014 年 4 月 21 日PC・IT