はじめに
こんばんは。
php-mysql-engineの話を聞いて、ワクワクしながら試してみました。
結果、cakephp3だとうまく行かないことがわかりました。
検証した環境は、 3.9.8
でした。
本題
結果的にうまく行かなかったのですが、ハマった箇所は色んな場所でした。
最初は ドライバを用意して、FakePDOに書き換えるだけでイケルと思いましたが、甘かったです。
最初に用意したドライバは、以下です。
<?php
namespace App\Test\Driver;
use Cake\Database\Driver\Mysql;
use Vimeo\MysqlEngine\Php7\FakePdo;
class TestMysqlDriver extends Mysql
{
@param
@param
@return
protected function _connect($dsn, array $config)
{
$connection = new FakePdo(
$dsn,
$config['username'],
$config['password'],
$config['flags']
);
$this->setConnection($connection);
return true;
}
}
無理やりクリアしたエラー
1. Exception: The SQL code SET foreign_key_checks = 0 could not be parsed: Unexpected SET in [/path/to/vendor/vimeo/php-mysql-engine/src/FakePdoStatementTrait.php, line 133]
これは FixtureManager.php
が load()
を実行したした際に内部で実行される _runOperation()
の中の以下の
disableConstraints()
が実行しているSQLです。
/**
* {@inheritDoc}
*
* ### Example:
*
* $connection->disableConstraints(function ($connection) {
* $connection->newQuery()->delete('users')->execute();
* });
*/
public function disableConstraints(callable $operation)
{
return $this->getDisconnectRetry()->run(function () use ($operation) {
$this->disableForeignKeys();
try {
$result = $operation($this);
} catch (Exception $e) {
$this->enableForeignKeys();
throw $e;
}
$this->enableForeignKeys();
return $result;
});
}
外部キー制約のチェックを外すというSQLです。
幸い実行しているのが MysqlDriverが use している MysqlDialectTrait
というTraitクラスだったので、 MysqlDriverをextendsしている今回の独自ドライバで上書き可能でした。
なので一旦、用意したドライバで、通りそうなSQLを用意してお茶を濁しました。
/**
* {@inheritDoc}
*/
public function disableForeignKeySQL()
{
return 'SELECT "1"';
}
2. Exception: The SQL code SET foreign_key_checks = 1 could not be parsed: Unexpected SET in [/path/to/vendor/vimeo/php-mysql-engine/src/FakePdoStatementTrait.php, line 133]
これも同じく、 disableConstraints()
が内部で実行しているSQLです。
さっきの逆ですね。
これも同じようななんでもないSQLをドライバで用意しました。
/**
* {@inheritDoc}
*/
public function enableForeignKeySQL()
{
return 'SELECT "1"';
}
3. Exception: The SQL code SHOW TABLES FROM example_database
could not be parsed: Parser error: expected SHOW TABLES LIKE in [/path/to/vendor/vimeo/php-mysql-engine/src/FakePdoStatementTrait.php, line 133]
php-mysql-engineでは SWOW TABLESで全テーブル取得を許可していないようです。 必ずLIKEで絞ることが条件になっていました。
しかし、 FixtureManager.php
の load()
で呼ばれている listTables()
が SHOW TABLES を実行していました...
これは、 実際には vendor/cakephp/cakephp/src/Database/Schema/Collection.php
が実行しています。
そしてMysqlの場合は MysqlSchema
が該当のSQLを持っています。
/**
* {@inheritDoc}
*/
public function listTablesSql($config)
{
return ['SHOW TABLES FROM ' . $this->_driver->quoteIdentifier($config['database']), []];
}
MysqlSchema
は MysqlDialectTrait
の schemaDialect()
というメソッドで _schemaDialect
というプロパティが何もセットされてない場合、初期値としてセットされ、以降それが使用されます。
なので、作成した新たなドライバのコンストラクタで 事前に _schemaDialect
プロパティに独自のSchemaクラスをセットすることで回避できました。
/**
* Constructor
*
* @param array $config The configuration for the driver.
* @throws \InvalidArgumentException
*/
public function __construct($config = [])
{
parent::__construct($config);
$this->_schemaDialect = new TestMysqlSchema($this);
}
<?php
namespace App\Test\Driver;
use Cake\Database\Schema\MysqlSchema;
use Cake\Database\Schema\TableSchema;
class TestMysqlSchema extends MysqlSchema
{
{@inheritDoc}
public function listTablesSql($config)
{
return ['SHOW TABLES LIKE "%%"', []];
}
}
4. Warning Error: PDO::getAttribute(): SQLSTATE[00000]: No error: PDO constructor was not called in [/path/to/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php, line 174]
PDO::ATTR_SERVER_VERSION
を実行する部分で、エラーが警告が起きていました。
if ($this->_version === null) {
$this->_version = $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
}
ここも versionを設定しておけばとりあえず回避できると思い、 先ほど作成した コンストラクタで $this->_version = 5.7
という感じで決め打ちで指定しました。
5. Warning Error: Fixture creation for "users" failed "No default collation or character set given" in [path/to/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php, line 312]
collationやcharacter setの指定が create table文にないことで警告が出ており、その後のテーブル作成が失敗していました。
charsetのオプションがfixtureクラスにあれば、このエラーは出ないと思いますが、試したプロジェクトの場合は全fixtureになかったので、全部のテストで同じエラーがでました。
今回の場合は、とりあえず utf8 に設定できればいいので、TestMysqlSchemaで createTableSql()
をオーバライドして対応しました。
/**
* {@inheritDoc}
*/
public function createTableSql(TableSchema $schema, $columns, $constraints, $indexes)
{
$contents = parent::createTableSql($schema, $columns, $constraints, $indexes);
$contents[0] .= ' DEFAULT CHARACTER SET = utf8';
return $contents;
}
無理矢理は無理だったけど、なんとかなりそうなエラー
1. Warning Error: Fixture creation for "users" failed "Column type INTEGER not recognized" in [/path/to/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php, line 312]
create table文でint型のカラムがあるばあい、intは通るけどintegerは通らないという感じでした。
これに関しては、PRだしてみました。
github.com
もし通らなかったら、先程オーバライドした createTableSql
内で置換する感じで対応はできそうです。
どうにもならなかったエラー
1. UnexpectedValueException: The SQL code SHOW FULL COLUMNS FROM users
could not be parsed: Parser error: expected SHOW TABLES
cakephpでは、find()やselect()を実行したときに、 内部でgetSchemaが呼ばれて、上記の SHOW FULL COLUMNSが実行されます。
php-mysql-engineは SHOW FULL COLUMNS も SHOW COLUMNSも非対応なので、これはどうにもなりませんでした...
終わりに
ワクワクしながら頑張ったのですが、結局ダメでした。。。
参考サイトのスライドでは cakephp4.2.4を使っているとのことなので、4系ではうまくいくのかもしれません 。
laravelとかはどうなんだろう?試してみようかな。
現場からは以上です。