├── pint.json ├── src ├── NotMysqlException.php ├── Facades │ └── MysqlExplain.php ├── MysqlExplainServiceProvider.php ├── LaravelQuery.php ├── Mixins │ └── BuilderMixin.php └── MysqlExplain.php ├── LICENSE.md ├── CHANGELOG.md ├── composer.json └── README.md /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "declare_strict_types": true, 5 | "ordered_class_elements": { 6 | "sort_algorithm": "alpha" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/NotMysqlException.php: -------------------------------------------------------------------------------- 1 | app->bind(Client::class, fn () => new Client(new Guzzle)); 22 | 23 | EloquentBuilder::mixin(new BuilderMixin); 24 | QueryBuilder::mixin(new BuilderMixin); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) tpetry 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 | -------------------------------------------------------------------------------- /src/LaravelQuery.php: -------------------------------------------------------------------------------- 1 | $parameters 14 | */ 15 | public function __construct( 16 | private ConnectionInterface $connection, 17 | private string $sql, 18 | private array $parameters, 19 | ) {} 20 | 21 | public function execute(string $sql, bool $useParams): array 22 | { 23 | $rows = match ($useParams) { 24 | true => $this->connection->select($sql, $this->parameters), 25 | false => $this->connection->select($sql), 26 | }; 27 | 28 | // Laravel creates array instead of array as requested by interface. 29 | /** @var array> $rows */ 30 | $rows = array_map(fn ($row) => (array) $row, $rows); 31 | 32 | return $rows; 33 | } 34 | 35 | public function getParameters(): array 36 | { 37 | // Transform special values like DateTimeInterface and bool to their string value. 38 | /** @var array $parameters */ 39 | $parameters = $this->connection->prepareBindings($this->parameters); 40 | 41 | return $parameters; 42 | } 43 | 44 | public function getSql(): string 45 | { 46 | return $this->sql; 47 | } 48 | 49 | public function name(): string 50 | { 51 | return 'laravel@'.MysqlExplain::$VERSION; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [2.1.0] - 2025-02-14 9 | ### Added 10 | * Laravel 12 Support 11 | 12 | ## [2.0.0] - 2024-09-27 13 | ### Changed 14 | * New implementation based on tpetry/php-mysql-explain 15 | * Switched namespace from `Tpetry\MysqlExplain` to `Tpetry\LaravelMysqlExplain` 16 | 17 | ## [1.3.2] - 2024-09-13 18 | ### Changed 19 | * Improved the detection for MariaDB databases 20 | 21 | ## [1.3.1] - 2024-07-13 22 | ### Changed 23 | * Removed obsolete logic for old explain api so the spatie/invade dependency can be removed (#7) 24 | * Uses Laravel builder to sql implementation when available 25 | 26 | ## [1.3.0] - 2024-07-11 27 | ### Added 28 | * Improved error messages for unsupported databases/queries 29 | * New visual explain methods 30 | 31 | ### Changed 32 | * Uses api.mysqlexplain.com v2 API 33 | 34 | ## [1.2.0] - 2024-03-13 35 | ### Changed 36 | * Use new mysqlexplain.com project domain 37 | 38 | ## [1.1.0] - 2024-03-12 39 | ### Added 40 | * Laravel 11 compatibility 41 | 42 | ## [1.0.2] - 2023-07-07 43 | ### Fixed 44 | * Bindings had been handled incorrectly leading to errors when DateTimeInterface was used 45 | 46 | ## [1.0.1] - 2023-06-18 47 | ### Fixed 48 | * Called method that is only public since Laravel 10 49 | 50 | ## [1.0.0] - 2023-06-12 51 | ### Added 52 | * `explainForHumans()` for query builders 53 | * `dumpExplainForHumans()` for query builders 54 | * `ddExplainForHumans()` for query builders 55 | * `MysqlExplain::submitBuilder($builder)` 56 | * `MysqlExplain::submitQuery($connection, $sql, $bindings)` 57 | -------------------------------------------------------------------------------- /src/Mixins/BuilderMixin.php: -------------------------------------------------------------------------------- 1 | submitQuery( 24 | $builder->getConnection(), 25 | $builder->toSql(), 26 | $builder->getBindings(), 27 | ); 28 | } 29 | 30 | /** 31 | * @param mixed[] $bindings 32 | */ 33 | public function submitQuery(ConnectionInterface $connection, string $sql, array $bindings = []): string 34 | { 35 | // In reality all connection interfaces should be classes of Connection. But as DB::connection() returns 36 | // a connection interface this method also should accept one to not generate PHPStan errors for users of the 37 | // library. 38 | if (! $connection instanceof Connection) { 39 | throw NotMysqlException::create(null); 40 | } 41 | 42 | // Queries are not executed with the standard Laravel database functions because those metric queries should not 43 | // trigger a Laravel QueryExecuted event 44 | if ($connection->getDriverName() !== 'mysql') { 45 | throw NotMysqlException::create($connection->getDriverName()); 46 | } 47 | 48 | // Laravel 11 added a new MariaDB database driver but older Laravel versions handle MySQL and MariaDB with the 49 | // same driver. This query uses a feature implemented in MariaDB 10.10 (the first one with a different EXPLAIN 50 | // output) to detect MariaDB which is unsupported. 51 | try { 52 | $connection->select('SELECT * FROM seq_1_to_1'); 53 | throw NotMysqlException::create('mariadb'); 54 | } catch (QueryException) { 55 | // This exception is expected when using MySQL as sequence tables are not available. So the exception gets 56 | // silenced as the check for MySQL has succeeded. 57 | } 58 | 59 | $container = Container::getInstance(); 60 | $query = $container->make(LaravelQuery::class, ['connection' => $connection, 'sql' => $sql, 'parameters' => $bindings]); 61 | $metrics = $container->make(Collector::class)->execute($query); 62 | $result = $container->make(Client::class)->submit($metrics); 63 | 64 | return $result->getUrl(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel MySQL Visual Explains 2 | 3 | ![License][icon-license] 4 | ![PHP][icon-php] 5 | ![Laravel][icon-laravel] 6 | [![Latest Version on Packagist][icon-version]][href-version] 7 | [![GitHub Unit Tests Action Status][icon-tests]][href-tests] 8 | [![GitHub Static Analysis Action Status][icon-staticanalysis]][href-staticanalysis] 9 | [![GitHub Code Style Action Status][icon-codestyle]][href-codestyle] 10 | 11 | MySQL Query optimization with the `EXPLAIN` command is unnecessarily complicated: The output contains a lot of cryptic information that is incomprehensible or entirely misleading. 12 | 13 | This Laravel package collects many query metrics that will be sent to [mysqlexplain.com](https://mysqlexplain.com) and transformed to be much easier to understand. 14 | 15 | ## Installation 16 | 17 | You can install the package via composer: 18 | 19 | ```bash 20 | composer require tpetry/laravel-mysql-explain 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### Query Builder 26 | 27 | Three new methods have been added to the query builder for very easy submission of query plans: 28 | 29 | | Type | Action | 30 | |-------------------------------------------|---------------------------------------------------------------------| 31 | | `visualExplain` | returns URL to processed EXPLAIN output | 32 | | `dumpVisualExplain` | dumps URL to processed EXPLAIN output and continue normal execution | 33 | | `ddVisualExplain` | dumps URL to processed EXPLAIN output and stops execution | 34 | | `explainForHumans` ***(deprecated)*** | returns URL to processed EXPLAIN output | 35 | | `dumpExplainForHumans` ***(deprecated)*** | dumps URL to processed EXPLAIN output and continue normal execution | 36 | | `ddExplainForHumans` ***(deprecated)*** | dumps URL to processed EXPLAIN output and stops execution | 37 | 38 | ```php 39 | // $url will be e.g. https://mysqlexplain.com/explain/01j2gcrbsjet9r8rav114vgfsy 40 | $url = Film::where('description', 'like', '%astronaut%') 41 | ->visualExplain(); 42 | 43 | // URL to EXPLAIN will be printed to screen 44 | $users = Film::where('description', 'like', '%astronaut%') 45 | ->dumpVisualExplain() 46 | ->get(); 47 | 48 | // URL to EXPLAIN will be printed to screen & execution is stopped 49 | Film::where('description', 'like', '%astronaut%') 50 | ->ddVisualExplain(); 51 | ``` 52 | 53 | ### Raw Queries 54 | 55 | In some cases you are executing raw SQL queries and don't use the query builder. You can use the `MysqlExplain` facade to get the EXPLAIN url for them: 56 | 57 | ```php 58 | use Tpetry\LaravelMysqlExplain\Facades\MysqlExplain; 59 | 60 | // $url will be e.g. https://mysqlexplain.com/explain/01j2gctgtheyva7a7mhpv8azje 61 | $url = MysqlExplain::submitQuery( 62 | DB::connection('mysql'), 63 | 'SELECT * FROM actor WHERE first_name = ?', 64 | ['PENEL\'OPE'], 65 | ); 66 | ``` 67 | 68 | ## Testing 69 | 70 | ```bash 71 | composer test 72 | ``` 73 | 74 | ## Changelog 75 | 76 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 77 | 78 | ## Credits 79 | 80 | - [Tobias Petry](https://github.com/tpetry) 81 | - [All Contributors](../../contributors) 82 | 83 | ## License 84 | 85 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 86 | 87 | [href-codestyle]: https://github.com/tpetry/laravel-mysql-explain/actions/workflows/code-style.yml 88 | [href-staticanalysis]: https://github.com/tpetry/laravel-mysql-explain/actions/workflows/static-analysis.yml 89 | [href-tests]: https://github.com/tpetry/laravel-mysql-explain/actions/workflows/unit-tests.yml 90 | [href-version]: https://packagist.org/packages/tpetry/laravel-mysql-explain 91 | [icon-laravel]: https://img.shields.io/badge/Laravel-6.*--11.*-blue 92 | [icon-license]: https://img.shields.io/github/license/tpetry/laravel-mysql-explain?color=blue&label=License 93 | [icon-codestyle]: https://img.shields.io/github/actions/workflow/status/tpetry/laravel-mysql-explain/code-style.yml?label=Code%20Style 94 | [icon-php]: https://img.shields.io/packagist/php-v/tpetry/laravel-mysql-explain?color=blue&label=PHP 95 | [icon-staticanalysis]: https://img.shields.io/github/actions/workflow/status/tpetry/laravel-mysql-explain/static-analysis.yml?label=Static%20Analysis 96 | [icon-tests]: https://img.shields.io/github/actions/workflow/status/tpetry/laravel-mysql-explain/unit-tests.yml?label=Tests 97 | [icon-version]: https://img.shields.io/packagist/v/tpetry/laravel-mysql-explain.svg?label=Packagist 98 | 99 | 100 | 101 | --------------------------------------------------------------------------------