23
17

More than 5 years have passed since last update.

【Laravel】 QueryException で表示される SQL を正しくクオートする

Last updated at Posted at 2018-10-20

問題

Laravel におけるデータベース操作でクエリエラーが発生したとき, PDOException はキャッチされて QueryException に変換される。これは生成時に

  • 元のプレースホルダ ? 入りのSQL文
  • バインドパラメータの配列

を受け取り,メッセージに ? を実際の値に置換したSQLをセットしてくれる機能を持っている。しかし,整数値以外は正常に表示されない。クオート処理が行われていないためである。

\DB::statement('INSERT INTO invalid_grammar(?, ?, ?)', [1, null, 'xxx']);

Illuminate/Database/QueryException with message 'SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1, NULL, 'xxx')' at line 1 (SQL: INSERT INTO invalid_grammar(1, , xxx))'

INSERT INTO invalid_grammar(1, , xxx)

INSERT INTO invalid_grammar(1, null, 'xxx')

こうなってほしい!

解決策

いつものようにコネクションクラスの継承をやっちゃいます。

【Laravel】 MySQL がマスタスレーブ構成のとき,リクエストを超えて sticky 効果を適用する - Qiita

この辺でもすでにやってるけど一応今回も載せておきます。

実装

app/Database/MySqlConnection.php
<?php

declare(strict_types=1);

namespace App\Database;

use Illuminate\Database\MySqlConnection as BaseMySqlConnection;

/**
 * Class MySqlConnection
 */
class MySqlConnection extends BaseMySqlConnection
{
    /**
     * Run a SQL statement.
     *
     * @param  string                       $query
     * @param  array                        $bindings
     * @param  \Closure                     $callback
     * @throws \App\Database\QueryException
     * @return mixed
     */
    protected function runQueryCallback($query, $bindings, Closure $callback)
    {
        try {
            $result = $callback($query, $bindings);
        } catch (\Exception $e) {
            throw new QueryException(
                $query, $this->prepareBindings($bindings), $e, $this->getPdo()
            );
        }
        return $result;
    }
}
app/Providers/DatabaseServiceProvider.php
<?php

declare(strict_types=1);

namespace App\Providers;

use App\Database\MySqlConnection;
use Illuminate\Database\Connection;
use Illuminate\Support\ServiceProvider;

class DatabaseServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Connection::resolverFor('mysql', function (...$parameters) {
            return new MySqlConnection(...$parameters);
        });
    }
}
app/Database/QueryException.php
<?php

declare(strict_types=1);

namespace App\Database;

use Illuminate\Database\QueryException as BaseQueryException;
use Illuminate\Support\Str;

class QueryException extends BaseQueryException
{
    /**
     * @var \PDO
     */
    protected $pdo;

    /**
     * QueryException constructor.
     *
     * @param string     $sql
     * @param array      $bindings
     * @param \Exception $previous
     * @param \PDO       $pdo
     */
    public function __construct(string $sql, array $bindings, \Exception $previous, \PDO $pdo)
    {
        $this->pdo = $pdo;

        parent::__construct($sql, $bindings, $previous);
    }

    /**
     * Format the SQL error message.
     *
     * @param  string     $sql
     * @param  array      $bindings
     * @param  \Exception $previous
     * @return string
     */
    protected function formatMessage($sql, $bindings, $previous)
    {
        return $previous->getMessage() . ' (SQL: ' . Str::replaceArray('?', $this->quoteBindings($bindings), $sql) . ')';
    }

    /**
     * Quote binding parameters.
     *
     * @param  array $bindings
     * @return array
     */
    protected function quoteBindings(array $bindings): array
    {
        return collect($bindings)->map(function ($value) {
            if ($value === null) {
                return 'null';
            }
            if (is_string($value)) {
                return $this->pdo->quote($value);
            }
            if (is_bool($value)) {
                return (int)$value;
            }
            return $value;
        })->toArray();
    }
}

実行結果

\DB::statement('INSERT INTO invalid_grammar(?, ?, ?)', [1, null, 'xxx']);

App/Database/QueryException with message 'SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1, NULL, 'xxx')' at line 1 (SQL: INSERT INTO invalid_grammar(1, null, 'xxx'))'

これで Sequel Pro とかにコピペして検証もやりやすくなるかも!

23
17
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
17