はじめに
こんばんは。
php-mysql-engineの話を聞いて、ワクワクしながら試してみました。
- https://inside.pixiv.blog/2021/02/18/123000
- https://speakerdeck.com/o0h/phperkaigi2021-lightning-talk
結果、cakephp3だとうまく行かないことがわかりました。
検証した環境は、 3.9.8
でした。
本題
結果的にうまく行かなかったのですが、ハマった箇所は色んな場所でした。
最初は ドライバを用意して、FakePDOに書き換えるだけでイケルと思いましたが、甘かったです。
最初に用意したドライバは、以下です。
<?php namespace App\Test\Driver; use Cake\Database\Driver\Mysql; use Vimeo\MysqlEngine\Php7\FakePdo; class TestMysqlDriver extends Mysql { /** * Establishes a connection to the database server * * @param string $dsn A Driver-specific PDO-DSN * @param array $config configuration to be used for creating connection * @return bool true on success */ 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だしてみました。
もし通らなかったら、先程オーバライドした 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とかはどうなんだろう?試してみようかな。
現場からは以上です。