PHPでPDF帳票を作成する

はじめに

こんにちは、イメージ・マジックのもあいです。

春になり、JリーグやMotoGP等のスポーツも今シーズンが開幕、UCLや欧州サッカーも終盤を迎えて情報を追ったり観戦したりするのが大変な状態です。インターネットが発達してこんなに情報が増えようとは思いもしなかった今日この頃です。 今回はPHPでPDF帳票を作成するために必要だったことをまとめてみました。

使用するライブラリ

FPDFmPDFwkhtmltopdf という選択肢から、mPDFを使用しました。選定した理由は扱いやすさとライブラリ自身がそれなりの頻度で更新されているからです。

インストール

composerでインストールできます。
composer require mpdf/mpdf
そのほかに日本語を表示させるのであれば日本語フォントが必要になります。

使い方

htmlとcssを用意する

mPDFはhtmlとcssでPDF帳票のレイアウトを作成します。htmlなのでポジションを調整するのが難しいと思われますが、cssのposition:absoluteが条件付きで使用することができます。

ドキュメントにも記載されているのですが、body要素の直下の要素にだけposition:absoluteが適用できます。これを使うことによりレイアウトを調整できます。単位もpxでは無くてptやmmが使えますので帳票レイアウトの調整はそれほぞ難しくないです。
下記のようなコードの場合aクラスとbクラスはposition:absoluteが適用できますが、cクラスは適用できません。
<body>
  <div class="a"></div>
  <div class="b">
    <div class="c"></div>
  </div>
</body>

htmlを取得する

弊社ではSymfony4を使用して開発していますので、htmlを取得する場合はコントローラのrenderViewメソッドでtwigとデータを渡すとhtmlが取得できます。

PDFを生成する

mPDFのインスタンスを生成するときに設定情報を配列で引き渡します。この時に用紙の大きさ/向き/マージンやフォントファイルの場所やフォントにkanする方法を引き渡します。 設定情報はだいたい下記の通りです。
$config = [
    'mode' => 'ja+aCJK',
    'format' => [297, 210],
    'dpi' => 200,
    'tempDir' => '/tmp/',
    'margin_left' => 0,
    'margin_right' => 0,
    'margin_top' => 0,
    'margin_bottom' => 0,
    'orientation' => 'P',
    'fontDir' => [
        '/path/to/fontDir/,
    ],
    'fontdata' => [
        'ipag' => [
            'R' => 'ipag.ttf',
        ],
        'ipagp' => [
            'R' => 'ipagp.ttf',
        ],
    ],
];
A4縦でDPIは200、フォントにIPAゴシックとIPAゴシックプロポーショナルを指定し、fontdataの配下のipagとipagpというキーはcssのfont-familyで指定することができます。

設定情報をmPDFのインスタンスに渡してWriteHTMLメソッドを呼び出してからOutputメソッドを呼び出すとPDFを標準出力に出力します。
Symfony4で標準出力の結果をクライアントに送信する場合はStreamedResponseクラスを使用することになります。
$mpdf = new \Mpdf\Mpdf($config);
$mpdf->SetDefaultFont('ipagp');
$mpdf->WriteHTML($html);
$mpdf->Output();

最後に

htmlで帳票を作成するに際に、位置がずれると問題になる帳票を作成した際に調査した結果です。

PHPコードのチューニング

はじめに

こんにちは、イメージ・マジックのもあいです。

最近はサッカーを見るときには2つのチームがどのように動こうとしているのかを観察することによって、より面白く観戦できるようになってきました。日本代表チームがアジアカップの決勝でどのような戦い方をするのかが楽しみではありますが、相手チームであるカタールにも気をつけてみるとさらに面白くなります。

今回はPHP(Symfony4)のパフォーマンスチューニングについての記事になります。

記事の前提
 OS: Ubuntu 18.04
 PHP:7.2.10
 nginx:1.14.0
 PhpStorm:2018.3.3
 ※全てのソフトウェアはaptコマンドでインストールしています

Xdebugのプロファイラ機能

PHPで関数/メソッドの処理時間を測定するのにXdebugをインストールします。

sudo apt install php-xdebug

php.iniを編集して有効にします。
/etc/php/7.2/fpm/conf.d/20-xdebug.ini は下記の通り

xdebug.remote_enable=On
xdebug.remote_autostart=On
xdebug.remote_host=192.168.33.1
xdebug.profiler_enable=On
xdebug.profiler_output_dir=/tmp
xdebug.profiler_enable_trigger = 1;
xdebug.max_nesting_level=1000
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_timeout=2000

ポイントとしては、xdebug.profiler_enableをOnにするのと、xdebug.profiler_output_dirに測定結果を出力するディレクトリを指定してphp-fpmをreloadします。測定が終わったら xdebug.profiler_enable を Offにしてphp-fpmをreloadします。常にOnにするのは処理が非常に重たくなるのでおすすめできません。

プロファイラを有効にして測定したいURLを実行すると下記のようなファイルが作成されます
-rwxrwxrwx 1 moai moai 42986908  1月 17 17:09 cachegrind.out.26276
-rwxrwxrwx 1 moai moai     2644  1月 17 17:09 cachegrind.out.26276.0c892b
この2つのファイルをPhpStormをインストールしているマシンにコピーします。

PhpStormでプロファイル結果を解析

PhpStormでプロファイル結果を読み込みます。

Tool → Analyze Xdebug Profiler Snapshot…

ファイル選択ダイアログが開かれるので、ファイルサイズの大きいファイルを選択します。
デフォルトで処理時間の多い順番に表示されますが、これだけだとわかりにくいので、Call Treeタブをクリックして実行順表示にしてから処理に時間がかかっているのをドリルダウンして探します。

遅い部分を探して比較を行うと下記のような状態にすることができます。
改修前
DlvryController->jsonが19657msかかっています。ここで全体の53%の処理時間がかかってル事がわかります。ローカルの仮想マシンで測定しているとはいえ遅すぎます。
jsonが何を行っているかというと、Symfony4のEntityをjsonに変換しています。フレームワークの処理で遅くなっているのであれば手の施しようが無いような気もしますが、よくよく調べるとクライアント側で使っていない情報も返却していました。送る情報が少なくなればそれなりに速くなるのでは無いかという課程のもと、返却する情報を必要最低限の情報に絞った改修を行いました。

プロファイル結果は下記の通り。
改修後
3603msになりました。4.5倍速くなったことになります。
ここに出てくる処理時間はプロファイルを行っているため実際よりも大分遅いですし、VirtualBox上の仮想マシンで実行していますので、本番環境よりもさらに遅くなっていますが、どこで遅くなったかを把握するにはちょうど良い事になります。

最後に

プロファイルを行うことによりどこが遅いのかが一目瞭然でわかったのと、使っていないデータまで送ると遅くなる事がはっきりしました。

開発をしていると、面倒だからといってデータ取得の便利メソッドを使ってデータを取得し、そのままクライアントに返却することをやってしまうのですが、そういう事をすると最後にしっぺ返しが返ってくる良い例でした。

Doctrine2でMySQLのレプリケーションを設定する

はじめに

こんにちは、イメージ・マジックのもあいです。
弊社のシステムで、工場の生産を管理する生産管理システムがあるのですが、このシステムのデータベースはレプリケーションを行っており、読み取り専用のスレーブが存在します。
Symfony 4.1で生産管理のWeb開発を行っているのですが、レプリケーションの設定で調査を行った結果を記載したいと思います。

Doctrine2について

Symfony 4.1で使用するORMなのですが、Symfony専用というわけではなく、汎用的に使えるORMです。Doctrineの意味を調べてみるとちょっとお堅い感じの意味が出てきたのにはびっくりしました。

マスター/スレーブ構成とは

MySQLでは大分昔のバージョンから使えますが、一つのマスターDBと複数のスレーブDBからなるシステムで、スレーブは基本的には書き込みができません。スレーブDBに参照クエリを集めることにより、マスターDBの負荷を分散させることができるため、システムのスループット向上が見込めます。

Doctrine2の設定

doctrine.ymlにSlave設定を追加するだけで、現在のバージョンでは使用することが可能です。
slavesの配下にslaveのDB設定を追加すれば、スレーブを増やすこともできます。
現在のDoctrine2(2.5)ではこれだけです。

        connections:
            default:
                driver: 'pdo_mysql'
                charset: utf8mb4
                default_table_options:
                    charset: utf8mb4
                    collate: utf8mb4_general_ci
                    row_format: dynamic
                dbname: 'dbname'
                user: 'db_user_name'
                password: 'db_user_password'
                host: 'db_master_host'
                port: 3306
                slaves:
                    slave1:
                        charset: utf8mb4
                        dbname: 'dbname'
                        user: 'db_user_name'
                        password: 'db_user_password'
                        host: 'db_slave_host'
                        port: 3306

これだけで、selectはスレーブ、update/insertはマスターを参照するようになります。

Vagrantのスナップショット機能

はじめに

こんにちは、イメージ・マジックのもあいです。
弊社開発部ではVirtualBoxを使ってローカルに開発環境を作成して開発を行っていますが、開発環境作成ではVagrantとAnsibleを使って自動で環境を作成しています。
今回は、この開発環境作成で便利なスナップショット機能を多用していましたので、どういった状況でどう使っていたのかを軽く説明します。

Vagrantとは

詳しくはWikiPedia等で確認してください。環境構築用ファイルから自動的に仮想マシンを作成して設定を行えるものです。
会社ではUbuntuをサーバOSとして使用していますので、BoxイメージはUbuntuの提供元が用意しているものを使用しています。

スナップショット機能とは

Vagrant+Ansibleで開発環境を作成するということは、Ansibleスクリプトを実行して正しい状態になることを確認します。
Ansibleスクリプトでは内容にもよりますが、設定変更が必要なときだけ設定変更を行います。
ということは、1回目と2回目で実行内容が異なるというわけです。テストではどちらの挙動も確認する必要があるのですが、
そのたびにVagrantで仮想マシンを破壊/作成を繰り返すのは非常に時間が掛かります。

そこで、VagrantにはVirtualBoxのスナップショット機能を簡単に使うコマンドが用意されていて

vagrant snapshot save foo

とすることでスナップショットを保存することができます。
この状態でAnsibleスクリプトを実行して、環境を戻したい場合は

vagrant snapshot restore foo

とすることで、スナップショットをとった状態に戻すことができます。
これでAnsibleスクリプトの再実行の時間が大幅に短縮されます。
fooとかいてあるところは名称ですので、いくつかスナップショットをとることもできます。

Illustratorスクリプトのあれこれ

はじめに

こんにちは、イメージ・マジックのもあいです。
弊社開発部のメインはWeb(PHP/JavaScript/Css)なのですが、それ以外にも工場を効率的に稼働させるためにもいろいろ行っています。
今年になって、通常は人が手作業で行っているAdobe Illustratorの作業を自動化させようとして苦戦したことについて書いていきます。

オブジェクトを配置するための座標

ここは最後まで悩みの種でした。通常、PCやWebで座標の原点は左上となるのですが、Illustratorスクリプトでは左下となります。長年左上で考える癖がついていると座標計算には苦労しました。最終的には、Illustratorのアクションで真ん中に移動させることができるのと、それで問題が無いように処理する画像を作成することによって対処しました。

距離の単位がPt(ポイント)である

要求がmmやピクセルで表されるのですが、Illustratorではポイントで設定しなければいけないため、変換処理をいたるところに記述していたのですが、最終的には下記のような関数を定義して対処しました。

function convertUnit(num, before, after) {
    return new UnitValue(num, before).as(after);
}

当然ですが、印刷機の解像度(DPI)を考えないといけない場合は単純にはできません。

ドキュメントに載っていない、アクションを実行する

実際に動かして印刷機にデータを送信してみるものの、問題がたくさんでてきましたが、その中で一番困ったのはスクリプトではどうにもならなくてアクションだと問題無く処理ができるというとこ。
ドキュメントを見ているとアクションが実行できるようなメソッドが存在しません。(ただし、Photoshopにはドキュメントにあります)
いろいろ調べてみるとapp.doScriptというメソッドでアクションが実行できることがわかり、一部は対処可能となりました。

アクションでも完全に実行できるものと不完全に実行ができるものがある

アクションに登録すればapp.doScriptで実行はできるが、手動で実行したときとIllustratorスクリプトで実行したときに結果が異なることがありました。
これについては、解決できていないのですが、Illustrator以外で解決させるようにしました。

デバッグをする環境が非常に貧弱

Illustratorスクリプトをデバッグする環境として、Extendscript Toolkit CCというツールが一応存在します。ですが、導入するにはAdobe Createve Cloudアプリの設定で古いアプリケーションを表示にチェックを入れておかないと表示されないのと、app.documents.addでドキュメントを追加しようとすると動かなくなるという事象が発生してほとんど使い物になりませんでした。
従って、alertを入れてデバッグを進めるという非常に古典的な方法しか使えないのが大変でした。

最後に

工場を効率的に稼働させることができれば最終的には利益になるので、こういった作業はWelcomeです。使ったことのないものに触れるというのはそれなりに楽しいです。
今回初めてIllustratorスクリプトを触りましたが、デバッグしにくい等の問題も多くあり非常に大変でした。