Linux環境でCakePHP2をSQLServerにつなぐ

2014-05-01
PHP
CakePHP2
SQLServer

意外と困っている人が多そうだったので、まとめておく。

結論から言うと、デフォルトの状態ではLinuxからCakePHP2でSQLServerには繋がらないです。
DataSourceの接続部分のソースを少し書き換える必要があります。 下記が手順です。

1. 下準備をする。

下記がインストールされていることをチェック!

(1) php > 5.4

1
2
3
4
$ php -v
PHP 5.4.27 (cli) (built: Apr 23 2014 01:18:28)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2014 Zend Technologies

(2) freetds

1
2
$ which tsql
/usr/local/bin/tsql

うまい確認方法が見当たらなかったのですが、freetdsがインストールされていればtsqlも一緒にインストールされているはずなので tsqlチェックで多分大丈夫。

(3) pdo_dblib

1
2
$ php -m | grep pdo
pdo_dblib

peclでインストール出来るよ。

(4) cakephp

cakephpのバージョンは2.4.7を用意しました。
※2系なら多分大体一緒。

2. cakephpの基本設定

通常通りにcakeのブランクアプリを作成して、ブラウザでアクセスします。

cakephp

Warningが出ていますね。

1
2
3
CakePHP is NOT able to connect to the database.

Database connection "Sqlserver" is missing, or could not be created.

つまり、databaseの設定はあるけど、databaseには接続出来ていません。

この段階でdatabase.phpの設定内容下記の通りです。

1
2
3
4
5
6
7
8
 'Database/Sqlserver',
'persistent' => false,
'host' => 'localhost',
'login' => 'hoge',
'password' => 'hoge',
'database' => 'test',
);
}

何故繋がらないのか?

それは、cakephp2のDatabase/Sqlserverというdatasourceがpdo_sqlsrvというPDOモジュールを想定して作られているからです。

pdo_sqlsrvはMicrosoftがwindows用に用意しているモジュールで、Linux用はありません。
実際のソースコード内では、利用可能なPDOモジュールを確認してsqlsrvが無いからNGってことでエラーになっています。

つまり、通常状態のcakephpではSQLServerには接続出来ないのです。

3. cakephpの改造

改造と言っても、coreのファイルに手を入れる必要はありません。 自分のアプリケーションの同名PATHにSqlserver.phpとDboSource.phpをコピーして、少し手直しして上げるだけです。

ディレクトリ構成は下記の通り。Model配下のDatasourceディレクトリにコピーしたファイルを置きます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app── Config
├── Console
├── Controller
│   └── Component
├── Lib
├── Model
│   ├── Behavior
│   └── Datasource
│ ├── DboSource.php
│ └── Database
│ └── Sqlserver.php
├── Plugin
├── Vendor
├── View
├── tmp
└── webroot

下は修正内容のdiffです。ひとまず、動かすことを目的にしてますので、修正方法は荒いです。 DboSource.php

1
2
3
4
5
6
7
8
9
@@ -304,7 +304,7 @@ class DboSource extends DataSource {
* @return string The database version
*/
public function getVersion() {
- return $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
+ return false;
}

/**

Sqlserver.php ※PHP5.3の方はページ下部の追記分も修正を行う必要あり。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@@ -123,16 +123,13 @@ class Sqlserver extends DboSource {
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);

- if (!empty($config['encoding'])) {
- $flags[PDO::SQLSRV_ATTR_ENCODING] = $config['encoding'];
- }
+ $enc = !empty($config['encoding']) ? $config['encoding'] : 'UTF-8';

try {
$this->_connection = new PDO(
- "sqlsrv:server={$config['host']};Database={$config['database']}",
+ "dblib:host={$config['host']};dbname={$config['database']};charset={$enc}",
$config['login'],
- $config['password'],
- $flags
+ $config['password']
);
$this->connected = true;
if (!empty($config['settings'])) {
@@ -156,7 +153,7 @@ class Sqlserver extends DboSource {
* @return boolean
*/
public function enabled() {
- return in_array('sqlsrv', PDO::getAvailableDrivers());
+ return in_array('dblib', PDO::getAvailableDrivers());
}

/**
@@ -494,7 +491,7 @@ class Sqlserver extends DboSource {
} else {
$map = array(0, $name);
}
- $map[] = ($column['sqlsrv:decl_type'] === 'bit') ? 'boolean' : $column['native_type'];
+ $map[] = ($column['native_type'] === 'bit') ? 'boolean' : $column['native_type'];
$this->map[$index++] = $map;
}
}

この状態で、再度ブラウザでアクセスしてみると・・・

Screen Shot 2014-04-28 at 1.49.42 AM

見事につながりました。
pdo_sqlsrvとpdo_dblibの仕様の違いがあるので完璧とまでは行かないのですが、ひとまず一通りのCRUDは出来ます。

もし、SQLServerにLinux環境下のCakephp2から接続出来ないのであれば、上記をお試し下さい。

※2014/5/12追記
PHP5.3の場合、pdo_dblibがgetColumnMetaという関数をサポートしていないため、更に下記の修正を行ってください。

Sqlserver.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
--- a/app/Model/Datasource/Database/Sqlserver.php
+++ b/app/Model/Datasource/Database/Sqlserver.php
@@ -475,24 +475,39 @@ class Sqlserver extends DboSource {
$this->map = array();
$numFields = $results->columnCount();
$index = 0;
+ $j = 0;
+ $querystring = $results->queryString;
+ if (stripos($querystring, 'SELECT') === 0) {
+ $last = strripos($querystring, 'FROM');
+ if ($last !== false) {
+ $selectpart = substr($querystring, 7, $last - 8);
+ $selects = String::tokenize($selectpart, ',', '(', ')');
+ }
+ }
+ while ($j < $numFields) {
+ if (!isset($selects[$j])) {
+ $j++;
+ continue;
+ }
+ if (preg_match('/\bAS\s+\[(.*)\]/i', $selects[$j], $matches)) {
+ $columnName = trim($matches[1], '"');
+ } else {
+ $columnName = trim(str_replace('"', '', $selects[$j]));
+ }