├── .github ├── SECURITY.md └── workflows │ ├── update-copyright-years-in-license-file.yml │ ├── coding-standards.yml │ ├── static-analysis.yml │ └── continuous-integration.yml ├── src ├── LoggerInterface.php ├── Logger.php └── DevPdoStatement.php ├── .php_cs ├── LICENSE ├── doc └── demo │ └── run.php ├── composer.json ├── README.ja.md └── README.md /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a vulnerability 2 | 3 | If you have found any issues that might have security implications, 4 | please send a report privately to akihito.koriyama@gmail.com 5 | 6 | Do not report security reports publicly. 7 | -------------------------------------------------------------------------------- /.github/workflows/update-copyright-years-in-license-file.yml: -------------------------------------------------------------------------------- 1 | name: Update copyright year in license file 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 3 1 1 *" 7 | 8 | jobs: 9 | run: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - uses: FantasticFiasco/action-update-license-year@v2 16 | with: 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /src/LoggerInterface.php: -------------------------------------------------------------------------------- 1 | > $explain 13 | * @param array> $warnings 14 | * 15 | * @return void 16 | */ 17 | public function logQuery($query, $time, array $explain, array $warnings); 18 | } 19 | -------------------------------------------------------------------------------- /src/Logger.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public $explain = []; 19 | 20 | /** 21 | * SHOW WARNINGS 22 | * 23 | * @var array 24 | */ 25 | public $warnings = []; 26 | 27 | /** 28 | * {@inheritDoc} 29 | * 30 | * @param string $query 31 | * @param string $time 32 | * @param array> $explain 33 | * @param array> $warnings 34 | */ 35 | public function logQuery($query, $time, array $explain, array $warnings) 36 | { 37 | $this->explain = $explain; 38 | $this->warnings = $warnings; 39 | error_log(sprintf( 40 | 'time:%.6f SQL: %s explain: %s', 41 | (float) $time, 42 | $query, 43 | json_encode($explain) 44 | )); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | in(__DIR__); 4 | 5 | $config = Symfony\CS\Config\Config::create() 6 | ->level(Symfony\CS\FixerInterface::PSR2_LEVEL) 7 | ->finder($finder) 8 | ->fixers( 9 | [ 10 | 'extra_empty_lines', 11 | 'no_blank_lines_after_class_opening', 12 | 'no_empty_lines_after_phpdocs', 13 | 'operators_spaces', 14 | 'duplicate_semicolon', 15 | 'namespace_no_leading_whitespace', 16 | 'phpdoc_indent', 17 | 'phpdoc_no_empty_return', 18 | 'phpdoc_no_package', 19 | 'phpdoc_params', 20 | 'phpdoc_separation', 21 | 'phpdoc_to_comment', 22 | 'phpdoc_trim', 23 | 'phpdoc_var_without_name', 24 | 'remove_leading_slash_use', 25 | 'remove_lines_between_uses', 26 | 'return', 27 | 'single_array_no_trailing_comma', 28 | 'single_quote', 29 | 'spaces_before_semicolon', 30 | 'spaces_cast', 31 | 'standardize_not_equal', 32 | 'ternary_spaces', 33 | 'whitespacy_lines', 34 | 'ordered_use', 35 | 'short_array_syntax' 36 | ] 37 | ); 38 | return $config; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2024 Akihito Koriyama 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /doc/demo/run.php: -------------------------------------------------------------------------------- 1 | setAttribute(\PDO::ATTR_STATEMENT_CLASS, [DevPdoStatement::class, [$pdo, $logger]]); 11 | $pdo->exec('CREATE TABLE user(id integer primary key, name text)'); 12 | $sth = $pdo->prepare('insert into user(name) values (:name)'); 13 | $sth->bindValue(':name', 'koriym', \PDO::PARAM_STR); 14 | $sth->execute(); 15 | 16 | // select 17 | $sth = $pdo->prepare('select name from user where id = :id'); 18 | $sth->bindValue(':id', 1, \PDO::PARAM_INT); 19 | $sth->execute(); 20 | 21 | // dump explain from explain db 22 | 23 | print_r($logger->explain); 24 | 25 | //time: 1.4066696166992E-5 query: insert into user(name) values ('koriym') 26 | //time: 5.9604644775391E-6 query: select name from user where id = 1 27 | //Array 28 | //( 29 | // [0] => Array 30 | // ( 31 | // [time] => 1.4066696166992E-5 32 | // [query] => insert into user(name) values (:name) 33 | // [explain] => [ 34 | // { 35 | // "addr": "0", 36 | // "opcode": "Init", 37 | // ... 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/coding-standards.yml: -------------------------------------------------------------------------------- 1 | name: Coding Standards 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | coding-standards: 10 | name: Coding Standards 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: 8.0 20 | tools: cs2pr 21 | coverage: none 22 | 23 | - name: Get composer cache directory 24 | id: composer-cache 25 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 26 | 27 | - name: Cache dependencies 28 | uses: actions/cache@v2 29 | with: 30 | path: ${{ steps.composer-cache.outputs.dir }} 31 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 32 | restore-keys: ${{ runner.os }}-composer- 33 | 34 | - name: Install dependencies 35 | run: composer require bear/qatools --no-interaction --no-progress --prefer-dist 36 | 37 | - name: Validate composer.json 38 | run: composer validate --strict 39 | 40 | - name: Run PHP_CodeSniffer 41 | run: ./vendor/bin/phpcs -q --no-colors --report=checkstyle src tests | cs2pr 42 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koriym/dev-pdo-statement", 3 | "description": "A PDOStatement for develop", 4 | "keywords": [ 5 | "pdo" 6 | ], 7 | "license": "MIT", 8 | "autoload": { 9 | "psr-4": { 10 | "Koriym\\DevPdoStatement\\": "src/" 11 | } 12 | }, 13 | "autoload-dev": { 14 | "psr-4": { 15 | "Koriym\\DevPdoStatement\\": "tests/" 16 | } 17 | }, 18 | "require": { 19 | "php": ">=7.1.0", 20 | "ext-pdo": "*", 21 | "symfony/polyfill-php81": "^1.24", 22 | "ext-json": "*" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "^9.5" 26 | }, 27 | "config": { 28 | "allow-plugins": { 29 | "composer/package-versions-deprecated": true, 30 | "dealerdirect/phpcodesniffer-composer-installer": true 31 | } 32 | }, 33 | "scripts": { 34 | "test": "./vendor/bin/phpunit", 35 | "coverage": "php -dzend_extension=xdebug.so -dxdebug.mode=coverage ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage", 36 | "pcov": "php -dextension=pcov.so -d pcov.enabled=1 ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage --coverage-clover=coverage.xml", 37 | "cs": "./vendor/bin/phpcs", 38 | "cs-fix": "./vendor/bin/phpcbf src tests", 39 | "clean": [ 40 | "./vendor/bin/phpstan clear-result-cache", 41 | "./vendor/bin/psalm --clear-cache" 42 | ], 43 | "sa": [ 44 | "psalm --show-info=true", 45 | "./vendor/bin/phpstan analyse -c phpstan.neon" 46 | ], 47 | "tests": [ 48 | "@cs", 49 | "@sa", 50 | "@test" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/static-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Static Analysis 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | static-analysis-phpstan: 10 | name: Static Analysis with PHPStan 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: 8.0 20 | tools: cs2pr 21 | coverage: none 22 | 23 | - name: Get composer cache directory 24 | id: composer-cache 25 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 26 | 27 | - name: Cache dependencies 28 | uses: actions/cache@v2 29 | with: 30 | path: ${{ steps.composer-cache.outputs.dir }} 31 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 32 | restore-keys: ${{ runner.os }}-composer- 33 | 34 | - name: Install dependencies 35 | run: composer require bear/qatools --no-interaction --no-progress --prefer-dist 36 | 37 | - name: Run PHPStan 38 | run: ./vendor/bin/phpstan analyse -c phpstan.neon --no-progress --no-interaction --error-format=checkstyle | cs2pr 39 | 40 | static-analysis-psalm: 41 | name: Static Analysis with Psalm 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Checkout 45 | uses: actions/checkout@v2 46 | 47 | - name: Setup PHP 48 | uses: shivammathur/setup-php@v2 49 | with: 50 | php-version: 8.0 51 | tools: cs2pr 52 | coverage: none 53 | 54 | - name: Get composer cache directory 55 | id: composer-cache 56 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 57 | 58 | - name: Install dependencies 59 | run: composer require bear/qatools --no-interaction --no-progress --prefer-dist 60 | 61 | - name: Run Psalm 62 | run: ./vendor/bin/psalm --show-info=false --output-format=checkstyle --shepherd | cs2pr 63 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | phpunit: 10 | name: PHPUnit 11 | runs-on: ubuntu-latest 12 | services: 13 | mysql: 14 | image: mysql:5.7 15 | ports: 16 | - 3306:3306 17 | env: 18 | MYSQL_ALLOW_EMPTY_PASSWORD: yes 19 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 20 | strategy: 21 | matrix: 22 | operating-system: 23 | - ubuntu-latest 24 | php-version: 25 | - '7.3' 26 | - '7.4' 27 | - '8.0' 28 | - '8.1' 29 | dependencies: 30 | - highest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v1 34 | 35 | - name: Setup PHP ${{ matrix.php-version }} 36 | uses: shivammathur/setup-php@v2 37 | with: 38 | php-version: ${{ matrix.php-version }} 39 | coverage: pcov 40 | ini-values: zend.assertions=1 41 | 42 | - name: Get composer cache directory 43 | id: composer-cache 44 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 45 | 46 | - name: Cache dependencies 47 | uses: actions/cache@v2 48 | with: 49 | path: ${{ steps.composer-cache.outputs.dir }} 50 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 51 | restore-keys: ${{ runner.os }}-composer- 52 | 53 | - name: Install lowest dependencies 54 | if: ${{ matrix.dependencies == 'lowest' }} 55 | run: composer update --prefer-lowest --no-interaction --no-progress --no-suggest 56 | 57 | - name: Install highest dependencies 58 | if: ${{ matrix.dependencies == 'highest' }} 59 | run: composer update --no-interaction --no-progress --no-suggest 60 | 61 | - name: Run test suite 62 | run: ./vendor/bin/phpunit --coverage-clover=coverage.xml 63 | 64 | - name: Upload coverage report 65 | uses: codecov/codecov-action@v1 66 | with: 67 | file: ./coverage.xml 68 | -------------------------------------------------------------------------------- /README.ja.md: -------------------------------------------------------------------------------- 1 | # A PdoStatement for Developers 2 | 3 | [![Build Status](https://travis-ci.org/koriym/Koriym.DevPdoStatement.svg?branch=1.x)](https://travis-ci.org/koriym/Koriym.DevPdoStatement) 4 | 5 | [[English](README.md)] 6 | 7 | [koriym/dev-pdo-statement](https://packagist.org/packages/koriym/dev-pdo-statement) はSQLクエリーやデータベース運用を改善するために以下の情報を記録します。 8 | 9 | * クエリー実行時間 10 | * プリペアードステートメントとバインドされた値を合成したSQL 11 | * `EXPLAIN`の結果 12 | * `SHOW WARNINGS`の結果 13 | 14 | ## インストール 15 | 16 | 17 | `$pdo`に`DevPdoStatement`クラスをセットして[PDO](http://php.net/manual/ja/intro.pdo.php)のプリペアドステートメントクラス`PDOStatement`を置き換えログ機能を有効にします。 18 | 19 | ```php 20 | use Koriym\DevPdoStatement\DevPdoStatement; 21 | use Koriym\DevPdoStatement\Logger; 22 | 23 | $pdo->setAttribute(\PDO::ATTR_STATEMENT_CLASS, [DevPdoStatement::class, [$pdo, new Logger]]); 24 | ``` 25 | 26 | これ以降プリペアードステートメントクエリー行うと以下のようにログされます。 27 | 28 | ``` 29 | time:0.00035190582275391 query: INSERT INTO user(id, name) VALUES (99, 'koriym99') 30 | time:0.00020503997802734 query: SELECT id, name FROM user where id > 80 31 | warnings:[ 32 | { 33 | "Level": "Note", 34 | "Code": "1003", 35 | "Message": "\/* select#1 *\/ select `tmp`.`user`.`id` AS `id`,`tmp`.`user`.`name` AS `name` from `tmp`.`user` where (`tmp`.`user`.`id` > 80)" 36 | } 37 | ] 38 | explain :[ 39 | { 40 | "id": "1", 41 | "select_type": "SIMPLE", 42 | "table": "user", 43 | "partitions": null, 44 | "type": "ALL", 45 | "possible_keys": null, 46 | "key": null, 47 | "key_len": null, 48 | "ref": null, 49 | "rows": "100", 50 | "filtered": "33.33", 51 | "Extra": "Using where" 52 | } 53 | ] 54 | ``` 55 | 56 | 57 | ## カスタムログ 58 | 59 | 独自のロガーを実装して実行時間や`$explain`、`$warnings`で異常が検知されたクエリーだけを記録することが出来ます。 60 | 61 | ```php 62 | use Koriym\DevPdoStatement\LoggerInterface; 63 | 64 | class MyPsr3Logger implements LoggerInterface 65 | { 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function logQuery($query, $time, array $explain, array $warnings) 70 | { 71 | // 特定条件でログまたは例外を投げる 72 | } 73 | } 74 | ``` 75 | 76 | 77 | ```php 78 | $pdo->setAttribute(\PDO::ATTR_STATEMENT_CLASS, [DevPdoStatement::class, [$pdo, new MyPsr3Logger]]); 79 | ``` 80 | 81 | 82 | # Demo 83 | 84 | ```php 85 | php doc/demo/run.php 86 | ``` 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A PdoStatement for Developers 2 | 3 | [![Continuous Integration](https://github.com/koriym/Koriym.DevPdoStatement/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/koriym/Koriym.DevPdoStatement/actions/workflows/continuous-integration.yml) 4 | 5 | [[Japanese](README.ja.md)] 6 | 7 | [koriym/dev-pdo-statement](https://packagist.org/packages/koriym/dev-pdo-statement) log following information to help your sql inspection. 8 | 9 | * Query excution time. 10 | * Final SQL query with parameter values interpolated into it from prepared statement. 11 | * The result of `EXPLAIN` query. 12 | * The result of `SHOW WARNINGS` query. 13 | 14 | ## Install 15 | 16 | Attach`DevPdoStatement`class to the target `$pdo`. 17 | 18 | ```php 19 | use Koriym\DevPdoStatement\DevPdoStatement; 20 | use Koriym\DevPdoStatement\Logger; 21 | 22 | $pdo->setAttribute(\PDO::ATTR_STATEMENT_CLASS, [DevPdoStatement::class, [$pdo, new Logger]]); 23 | ``` 24 | 25 | Then `$pdo` start to log as following on each query. 26 | 27 | ``` 28 | time:0.00035190582275391 query: INSERT INTO user(id, name) VALUES (99, 'koriym99') 29 | time:0.00020503997802734 query: SELECT id, name FROM user where id > 80 30 | warnings:[ 31 | { 32 | "Level": "Note", 33 | "Code": "1003", 34 | "Message": "\/* select#1 *\/ select `tmp`.`user`.`id` AS `id`,`tmp`.`user`.`name` AS `name` from `tmp`.`user` where (`tmp`.`user`.`id` > 80)" 35 | } 36 | ] 37 | explain :[ 38 | { 39 | "id": "1", 40 | "select_type": "SIMPLE", 41 | "table": "user", 42 | "partitions": null, 43 | "type": "ALL", 44 | "possible_keys": null, 45 | "key": null, 46 | "key_len": null, 47 | "ref": null, 48 | "rows": "100", 49 | "filtered": "33.33", 50 | "Extra": "Using where" 51 | } 52 | ] 53 | ``` 54 | 55 | 56 | ## Custom Log 57 | 58 | You can implement custom condition for logging or choose your favorite logger. 59 | 60 | ```php 61 | use Koriym\DevPdoStatement\LoggerInterface; 62 | 63 | class MyPsr3Logger implements LoggerInterface 64 | { 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function logQuery($query, $time, array $explain, array $warnings) 69 | { 70 | // log or throw exception in your custom condition. 71 | } 72 | } 73 | ``` 74 | 75 | 76 | # Demo 77 | 78 | ```php 79 | php doc/demo/run.php 80 | ``` 81 | -------------------------------------------------------------------------------- /src/DevPdoStatement.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | private $params = []; 27 | 28 | /** 29 | * Interpolate Query 30 | * 31 | * @var string 32 | */ 33 | public $interpolateQuery = ''; 34 | 35 | /** @var PDO */ 36 | private $pdo; 37 | 38 | /** @var LoggerInterface */ 39 | private $logger; 40 | 41 | protected function __construct(PDO $db, LoggerInterface $logger) 42 | { 43 | $this->pdo = $db; 44 | $this->logger = $logger; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | * 50 | * @param int|string $parameter 51 | * @param int|string $value 52 | * @param int $dataType 53 | */ 54 | #[ReturnTypeWillChange] 55 | public function bindValue($parameter, $value, $dataType = PDO::PARAM_STR) 56 | { 57 | $this->params[$parameter] = $value; 58 | 59 | return parent::bindValue($parameter, $value, $dataType); 60 | } 61 | 62 | /** 63 | * {@inheritDoc} 64 | * 65 | * @param string|int $paramno 66 | * @param mixed $param 67 | * @param int $dataType 68 | * @param int $length 69 | * @param mixed $driverOptions 70 | */ 71 | #[ReturnTypeWillChange] 72 | public function bindParam($paramno, &$param, $dataType = PDO::PARAM_STR, $length = null, $driverOptions = null) 73 | { 74 | $this->params[$paramno] = &$param; 75 | 76 | return parent::bindParam($paramno, $param, $dataType, (int) $length, $driverOptions); 77 | } 78 | 79 | /** 80 | * {@inheritdoc} 81 | * 82 | * @param array $bountInputParameters 83 | */ 84 | #[ReturnTypeWillChange] 85 | public function execute($bountInputParameters = null) 86 | { 87 | $start = microtime(true); 88 | $result = parent::execute($bountInputParameters); 89 | $time = microtime(true) - $start; 90 | $this->interpolateQuery = $this->interpolateQuery($this->queryString, $this->params); 91 | [$explain, $warnings] = $this->getExplain($this->interpolateQuery); 92 | $this->logger->logQuery($this->interpolateQuery, (string) $time, $explain, $warnings); 93 | 94 | return $result; 95 | } 96 | 97 | /** 98 | * Replaces any parameter placeholders in a query with the value of that 99 | * parameter. Useful for debugging. Assumes anonymous parameters from 100 | * $params are are in the same order as specified in $query 101 | * 102 | * @param string $query The sql query with parameter placeholders 103 | * @param array $params The array of substitution parameters 104 | * 105 | * @return string The interpolated query 106 | * 107 | * @link http://stackoverflow.com/a/8403150 108 | * thanks 109 | */ 110 | private function interpolateQuery($query, $params) 111 | { 112 | $keys = []; 113 | $values = $params; 114 | // build a regular expression for each parameter 115 | foreach ($params as $key => $value) { 116 | $keys[] = is_string($key) ? '/' . $key . '/' : '/[?]/'; 117 | if (is_string($value)) { 118 | $values[$key] = "'" . $value . "'"; 119 | } 120 | 121 | if (is_array($value)) { 122 | $values[$key] = "'" . implode("','", $value) . "'"; 123 | } 124 | 125 | if ($value !== null) { 126 | continue; 127 | } 128 | 129 | $values[$key] = 'null'; 130 | } 131 | 132 | $query = preg_replace($keys, $values, $query, 1); 133 | 134 | return (string) $query; 135 | } 136 | 137 | /** 138 | * @param string $interpolateQuery 139 | * 140 | * @return array{0:array>, 1:array>} 141 | */ 142 | private function getExplain($interpolateQuery) 143 | { 144 | $explainSql = sprintf('EXPLAIN %s', $interpolateQuery); 145 | try { 146 | $sth = $this->pdo->query($explainSql); 147 | if ($sth === false) { 148 | return [[], []]; 149 | } 150 | 151 | $explain = $sth->fetchAll(PDO::FETCH_ASSOC); 152 | $warningSth = $this->pdo->query('SHOW WARNINGS'); 153 | if ($warningSth === false) { 154 | return [[], []]; 155 | } 156 | 157 | $warnings = $sth->fetchAll(PDO::FETCH_ASSOC); 158 | } catch (PDOException $e) { 159 | return [[], []]; 160 | } 161 | 162 | /** @var array> $explain */ 163 | $explain = $explain ?: []; 164 | /** @var array> $warnings */ 165 | $warnings = $warnings ?: []; 166 | 167 | return [$explain, $warnings]; 168 | } 169 | } 170 | --------------------------------------------------------------------------------