db2
laravel
AS400

LaravelでDB2/400のデータを使う方法(Windows環境)

経緯

社内で使っている基幹システムがAS/400(IBM i)で稼働しています。
このデータを使った社内Webシステムを作りたい、というのがきっかけでした。

環境

  • Web/APサーバ(OS) → Windows Server 2012 R2
  • Webサーバ → Apache
  • Webフレームワーク → Laravel 5.5
  • DBサーバ → AS/400(V6R1)
  • DBMS → DB2/400

パッケージの選定

まずはこのサイトを「db2」というキーワードで検索しました。

最初に候補に挙がったのが、laravel-ibmiでした。
理由としては…データベースの操作以外にもTS::PgmCallという機能があり、既存のRPGプログラムを呼べることが魅力だったからです。(今回の要件にはなかったのですが、将来的な資産になりそうだったので…)

しかし! 結果としてこのパッケージは使えませんでした…
どうやら、下記の設定でエラー発生…

PDO::I5_ATTR_DBC_SYS_NAMING => false,

エラーコード

Undefined class constant 'I5_ATTR_DBC_SYS_NAMING'

調べてみるとpdo_ibmが必須なんだそうで…

それじゃあ、と張り切ってPDO_IBMを入手しにPECLを訪れてみるとWindows用のDLLは1.3.3までしかない。CHANGELOGを見ると、 PHP7のサポートは1.3.4からだって…orz
それでも、無理矢理に突っ込んでみると…案の定、失敗。

Unable to load dynamic library 'C:\\xampp\\php\\ext\\php_pdo_ibm.dll' - \xef\xbf\xbdw\xef\xbf\xbd\xe8\x82\xb3\xef\xbf\xbd\xea\x82\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbdW\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd[\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xc2\x82\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xdc\x82\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbdB\r\n in Unknown on line 0

ついに、ここで挫折:sob:


追記

この記事を書いた後に、ふと我に返って下記の設定を消したらlaravel-ibmiも問題なく動きました…
(自分のアホさにビックリ:anguished:)
でも、下記で書いた文字化けは解消せず…(sjisで文字を取得してしまう) うーん、何だろなー。

            PDO::I5_ATTR_DBC_SYS_NAMING => false,
            PDO::I5_ATTR_COMMIT => PDO::I5_TXN_NO_COMMIT,
            PDO::I5_ATTR_JOB_SORT => false,
            PDO::I5_ATTR_DBC_LIBL => '',
            PDO::I5_ATTR_DBC_CURLIB => '',

ならばと、laravel-iseriesに挑戦。
こちらのパッケージでは、導入は問題なく出来ました。:thumbsup:

laravel-iseriesの導入方法

導入の仕方としては上記のサイトに書いてある通りで、composer.json

"jacksonwebservices/laravel-iseries": "3.0.*"

を追加

composer update

を実行

app/config/app.php

'providers' => [
    JWS\Iseries\IseriesServiceProvider::class,
],

を追加して

config/database.phpに下記を追加します。

      'odbc' => [
            'driver'               => 'odbc',
            'driverName'           => '{iSeries Access ODBC Driver}',
             // General settings
            'host'                 => env('DB2_HOST'),
            'username'             => env('DB2_USER'),
            'password'             => env('DB2_PASSWORD'),
            //Server settings
            'database'             => env('DB2_NAME'),
            'prefix'               => '',
            'schema'               => env('DB2_DEFAULT_SCHEMA'),
            'signon'               => 3,
            'ssl'                  => 0,
            'commitMode'           => 2,
            'connectionType'       => 0,
            'defaultLibraries'     => '',
            'naming'               => 0,
            'unicodeSql'           => 1,
            // Format settings
            'dateFormat'           => 5,
            'dateSeperator'        => 0,
            'decimal'              => 0,
            'timeFormat'           => 0,
            'timeSeparator'        => 0,
            // Performances settings
            'blockFetch'           => 1,
            'blockSizeKB'          => 32,
            'allowDataCompression' => 1,
            'concurrency'          => 0,
            'lazyClose'            => 0,
            'maxFieldLength'       => 15360,
            'prefetch'             => 0,
            'queryTimeout'         => 1,
            // Modules settings
            'defaultPkgLibrary'    => 'HOGELIB',
            'defaultPackage'       => 'A/DEFAULT(IBM),2,0,1,0',
            'extendedDynamic'      => 1,
            // Diagnostic settings
            'QAQQINILibrary'       => '',
            'sqDiagCode'           => '',
            // Sort settings
            'languageId'           => 'JPN',
            'sortTable'            => '',
            'sortSequence'         => 0,
            'sortWeight'           => 0,
            'jobSort'              => 0,
            // Conversion settings
            'allowUnsupportedChar' => 0,
            'ccsid'                => 930,
            'graphic'              => 0,
            'forceTranslation'     => 1,
            // Other settings
            'allowProcCalls'       => 0,
            'DB2SqlStates'         => 0,
            'debug'                => 0,
            'trueAutoCommit'       => 0,
            'catalogOptions'       => 3,
            'libraryView'          => 0,
            'ODBCRemarks'          => 0,
            'searchPattern'        => 1,
            'translationDLL'       => '',
            'translationOption'    => 0,
            'maxTraceSize'         => 0,
            'multipleTraceFiles'   => 1,
            'trace'                => 0,
            'traceFilename'        => '',
            'extendedColInfo'      => 0,
            'options'  => [
                PDO::ATTR_CASE => PDO::CASE_LOWER,
                PDO::ATTR_EMULATE_PREPARES => false,
                PDO::ATTR_PERSISTENT => false
            ]

        ],

最後に、.env

DB_CONNECTION=odbc
DB2_host=AS/400のIP
DB2_user=接続用のUSER
DB2_PASSWORD=パスワード
DB2_NAME=dummy
DB2_DEFAULT_SCHEMA=初期スキーマライブラリ

を追加して完了。そこまで難しくはありませんでした。

未解決な事

ですが、さらに困難が待ち受けていました。なんと、日本語がShift-JISとしてデータ取得されてしまっています。
UTF-8で動かしたいのでこれは困る…
というわけで、optionのCCSIDに1208をしてみると…何と、何も変化しない。
何故!?というわけでソースを追うと、

<?php
namespace JWS\Iseries\Connectors;

use Illuminate\Database\Connectors\Connector;
use Illuminate\Database\Connectors\ConnectorInterface;

class ODBCConnector extends Connector implements ConnectorInterface
{

    public function connect(array $config)
    {
        $dsn = $this->getDsn($config);

        $options = $this->getOptions($config);

        $connection = $this->createConnection($dsn, $config, $options);

        if (isset($config['schema']))
        {
            $schema = $config['schema'];

          $connection->prepare("set schema $schema")->execute();
        }

        return $connection;
    }

    protected function getDsn(array $config) {
        extract($config);

        $dsn = "odbc:"
             // General settings
             . "Driver=$driverName;"
             . "System=$host;"
             . "UserID=$username;"
             . "Password=$password;"
             //Server settings
             . "Database=$database;"
             . "CommitMode=$commitMode;"
             . "ConnectionType=$connectionType;"
             . "DefaultLibraries=$defaultLibraries;"
             . "Naming=$naming;"
             . "AllowDataCompression=$allowDataCompression;"
             . "DefaultPkgLibrary=$defaultPkgLibrary;"
             . "DefaultPackage=$defaultPackage;"
             . "ExtendedDynamic=$extendedDynamic;"
             . "AllowUnsupportedChar=$allowUnsupportedChar;"
             . "ForceTranslation=$forceTranslation;"
             . "LibraryView=$libraryView;"
             . "Trace=$trace;"
             ;

        return $dsn;
    }

}

getDsnでCCSIDの指定してない!何のためのoptionなんだ:scream:
というわけで、手動でccsid=1208を追加してみたところ、とりあえずUTF-8になったものの半角カナ等が文字化けしたまま…
もはや、良く分からん:cry:

というわけで最終的な苦肉の策として、SJISをUTF-8に変換するFacadeを作ってView側で変換するという作りに…

<?php
namespace App\Services;
/**
 * 文字列操作用のファサード
 */
class ManipulateString{
    /**
     * DB2より日本語データを取得した時、SJIS+後ろスペース詰めと
     * なってしまうのでこれを整形するメソッド
     * @param string DB2/400より取得したSJISの文字列
     * @return string UTF8に変換後にトリムした文字列
     */
    public function convertToUtf8(string $string){
        return trim(mb_convert_encoding($string, 'utf-8','sjis-win'));
    }
}

解決策をご存知の方、コメントなど頂けると助かります。
まぁ、最終的には作りたいものは作れたのですがちょっと、課題の多いアウトプットとなってしまいました。

とはいうものの、これでLaravelとAS/400の接続が出来るようになりました。
AS/400が古い汎用機だから、と諦めずに使ってみるのも良いのではないでしょうか。