BETA

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

投稿日:2018-10-20
最終更新:2018-10-24
※この記事は外部サイト(https://qiita.com/mpyw/items/c66209f03c359...)からのクロス投稿です

問題

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): QueryException
    {
        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 とかにコピペして検証もやりやすくなるかも!

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@mpywの技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!