【PHP】PDOでMS ACCESSに接続し、データを取得してみた

PHPにおけるデータベース接続において、ほとんどのDBMSに対応しているPDO。実はMS ACCESSのmdb、accdbファイルにも対応しており、データを取得することができます。

ただ、それにはこまごまと準備が必要です（特にLinux系の場合は色々と準備が必要になり、データの取得に対しても一工夫必要になります）。

※Linux（ここではRed Hat系のことを指す）の場合のデータ取得方法を追記しました。Linuxの場合、本当に困難を極めますが、以下の方法で取得可能です（centOS7にて取得テスト済）。なお、WSL(Ubuntu)なら、ここまで色々しなくても大丈夫なようです。

#１ 接続前の準備
## xamppなどのWindowsの場合
php.iniファイルからpdo-odbcドライバを使用できるようにします。

``` php.ini
extension=pdo_odbc; //コメントを外す
```

その後はapache再起動で準備完了です。

## Linuxの場合
### 必要なドライバのインストール
php-odbcドライバを使用するので、yumコマンドで開発用ドライバ、それからmdbodbcライブラリと一緒にインストールします（当然、php-pdoなどは事前に使えるようにしておく必要があります）。もし、依存関係などで一部のドライバがインストールできなかった場合、remiレポジトリでPHPのバージョンを合わせてインストールするとうまくいきます。例はPHP7.2-xを使用する場合）

>参考（remiとか）
[CentOS7にPHP7をyumでインストールする](https://qiita.com/inakadegaebal/items/b57cf10339978d638305)

```
# yum install --enablerepo=remi-php72 php-pdo php-odbc unixODBC unixODBC-devel libmdbodbc
```

### libmdbodbc.so.1ライブラリのインストールと設定

上記のドライバに加え、`libmdbodbc.so.1`ライブラリが必要になりますが、標準ではインストールされていませんので、外部からインストールしてきます。その上で、odbcinst.iniを編集して、ドライバに読み込ませる必要があります。

※`forensics`リポジトリのインストールで認証エラーを起こす場合、あまり推奨はできないのですがwgetコマンドの際に、**--no-check-certificate**で実行してください。

> このページが参考になりました
[MDB Tools missing driver](https://forums.linuxmint.com/viewtopic.php?t=155397)

```
# wget https://forensics.cert.org/cert-forensics-tools-release-el7.rpm //リポジトリのインストール
# rpm -Uvh cert-forensics-tools-release-el7.rpm //リポジトリの解凍
# yum --enablerepo=forensics install libmdbodbc1 //リポジトリを使って、yumコマンドでインストール
# find / -name *libmdbodbc* //インストールされた場所を探す
# cp -ap /usr/lib64/libmdbodbc.so.1 /usr/lib/ //インストール元から複製する（32bit機で動かす場合）
```

これで、ライブラリをセットできました。そして、次はこのライブラリを読み込むためにodbcinst.iniを編集します。

``` 
#vi /etc/odbcinst.ini

/*以下を追記して保存する。また、[***]内の名称（例ではMDBTools）が、LinuxでのPDO接続ドライバ名となる。*/
[MDBTools]
Description = ODBC for MDBTools
Driver64    = /usr/lib64/libmdbodbc.so.1
Setup64     = /usr/lib64/libmdbodbc.so.1
Driver      = /usr/lib/libmdbodbc.so.1
Setup       = /usr/lib/libmdbodbc.so.1
FileUsage   = 1
UsageCount  = 1
```

このようにパスを設定します。これでLinuxの準備は完了です。あとは

```
# systemctl restart httpd //centos7、Apacheの場合
```

でWEBサーバーを再起動します。

# 2 PDOでACCESSに接続する
ここからPDOを使って、ACCESSに接続するのですが、ここにも色々とトラップがあります。

>このサイト（Github）のフォーラムがかなり参考になりました。
[How to handle MS Access MDB files in Linux with PHP5 PDO and ODBC](https://gist.github.com/amirkdv/9672857)
[Problems with MDBTools + unixODBC + php_odbc](https://github.com/brianb/mdbtools/issues/85)

このサイトの記述を使えば、OSがWindowsでもLinuxでも対応できます。
注意点は、WindowsとLinuxでドライバの記述が異なる点です。

###ファイルパス記述の注意###
- ファイルパスは必ず/（スラッシュ）を使ってください（¥は無効となります）。
- パスは必ず絶対パスで記述し、かつ書き方も癖があります（詳しくはサンプルで）
- DbqパラメータはLinuxの場合、DBQと大文字で記述しないとデータベース名を取得できません。

``` PHP:PHP
/*ファイルパスの記述例*/
$dbName = "C:/xampp/htdocs/hogehoge.mdb"; //Xamppの場合はドライブの記述方法に注意。
$dbName = "/var/www/html/hogehoge.mdb"; //Linuxの場合は必ずサーバ内のパスで記述する
```

``` PHP:PHP
$uname = explode(" ",php_uname());
$os = $uname[0];
switch ($os){
  case 'Windows':
    $dbName = "D:/xampp/htdocs/hogehoge.mdb";
    $driver = '{Microsoft Access Driver (*.mdb, *.accdb)}';
    break;
  case 'Linux':
    $dbName = "/var/www/html/hogehoge.mdb";
    $driver = 'MDBTools'; //odbcinst.iniで追記したドライバ名
    break;
  default:
    exit("非対応のOSです");
}
$dbh = new PDO("odbc:Driver=$driver;DBQ=$dbName;");

```

これでCould not find Driver などのエラーが表示されなかったら、PDOへの接続は成功です。

# 3 データを取得する
## Windowsの場合
データは普通にPDOオブジェクトを利用するだけで取得可能です。テーブル名が日本語などの場合でも"テーブル名"と、ダブルクォートで囲えば問題なく取得できます。

※現在、日本語カラムからデータを取得できないかテスト中です。成功次第、記事を追記したいと思います。

## Linuxの場合
色々と注意点があり、各カラムをバインドしないとデータを取得できないようです（普通にFETCHしようとすると、カラムをBINDしてくださいと怒られて何も取得できません）

> このページを参考にしました。
[PDO Error when ODBC_Exec works correctly](https://stackoverflow.com/questions/42301688/pdo-error-when-odbc-exec-works-correctly)

カラムは逐一記述して、それぞれに`PDO::bindColumn()`メソッドでバインド処理を行っていきます。そして各行をfetchしていくのですが、unixODBCの仕様上`PDO::FETCH_NUM`、`PDO::FETCH_ASSOC`などが使用できないため（使用方法を探しましたが見つかりませんでした）、`PDO::FETCH_BOUND`で、各行に対して判定結果をループさせ、個別の変数から直接値を取得するようにします。

また、SQLの記述もかなり厳格なルールがあり、それを守らないと正しくデータを取得してくれません。

- カラム名はしっかりと書くこと。*（アスタリスク）を使った場合でも、取得する行番号をしっかりと付記しないと、正しいデータが取得できません。
- SQL文の定型句（**SELECT FROM WHERE**など）は英大文字で記述しないと、不正なSQL構文とエラー表示されます）
- SQL文の末尾に；（コロン）を付けると、クエリを読み取ってくれません。

```PHP:PHP
   //num,nameというカラムを持つテーブルtest
   $sql = "SELECT num,name FROM test"; //ルールは前述した通りです
   $sth = $dbh -> prepare($sql);
   $sth -> execute();
   //各カラムにバインドしていく
   $sth -> bindColumn(1,$num);
   $sth -> bindColumn(2,$name);
   
   //カラムに対してバインドをかけるため、$flgには判定結果しか帰ってこない
   $values = []; //値を代入する配列
   while( $flg = $sth -> fetch(PDO::FETCH_BOUND) ){
             $values[] = [$num,$name];
   }

```

※なお、例では変数名をカラムに合わせていますが、特に決まりはありません。数量が不確定の場合\$val[1],$val[2],…のような変数でも問題ありません。

###追記
ワンクッション置いて`execute`メソッドを使用すると、`fetch`メソッドも利用できることがわかりました。

```php:php
                $sql = "SELECT num,name FROM test WHERE num > 5";
                $sth = $dbh -> prepare($sql);
                $sth ->execute();
                while( $rows = $sth -> fetch(PDO::FETCH_ASSOC) ){
                    var_dump($rows);
                }
```

####注意
- **ACCESSでメモ型は使わないでください。データの値が取得できなくなるバグがあるようです（テキスト型に直すと取得できます）**
- カラム名でアンダーバー（ _ ）やハイフンを使うと正しくデータを取得できないバグもあるようです。かなのカラムもSQL文に記述すると正しく読み取ってくれませんが、アスタリスクでの全表示には対応しています。
- WHERE句とLIKE演算子、OR演算子、AND演算子は使用できますが、BETWEEN演算子、IN演算子、**否定演算子**は対応していません。
- ORDER BY句も使えないようです。
- COUNT関数、MAX関数なども対応していません。

結局、while文の中で処理を施すか、全ファイルを読み込んでから、後でarray_column関数で処理するかした方が良さそうです…。

# 4 最後に
どうしても実務に必要だったので、ACCESSデータをPHPで取得するという無茶振りをやってみたわけですが、もしこの研究、分析成果が、誰かのお役に立てればと思います。

なお、取得したデータをそのままCSVなどに吐き出すだけならここまで面倒なことをしなくても大丈夫なようです。
[日々修行 LinuxのPHPでAccessファイルを読み込むには](http://tmfm75.blog.jp/archives/68814302.html)