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

# パッケージの選定
まずは[このサイト](https://packalyst.com/)を「db2」というキーワードで検索しました。

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

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

```php
PDO::I5_ATTR_DBC_SYS_NAMING => false,
```
> エラーコード

```php
Undefined class constant 'I5_ATTR_DBC_SYS_NAMING'
```

調べてみると[pdo_ibmが必須](https://github.com/cooperl22/laravel-db2/issues/8)なんだそうで…

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

```php
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で文字を取得してしまう)　うーん、何だろなー。

```php
            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](https://packalyst.com/packages/package/jacksonwebservices/laravel-iseries)に挑戦。
こちらのパッケージでは、導入は問題なく出来ました。:thumbsup:

# laravel-iseriesの導入方法

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

```json
"jacksonwebservices/laravel-iseries": "3.0.*"
```
を追加

```php
composer update
```
を実行

`app/config/app.php`に

```php
'providers' => [
    JWS\Iseries\IseriesServiceProvider::class,
],
```
を追加して

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

```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`に

```php
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
<?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
<?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が古い汎用機だから、と諦めずに使ってみるのも良いのではないでしょうか。