├── .phpunit-watcher.yml ├── .styleci.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer-require-checker.json ├── composer.json ├── config └── di-web.php ├── infection.json.dist ├── psalm.xml ├── rector.php ├── src ├── CompositeException.php ├── ErrorData.php ├── ErrorHandler.php ├── Event │ └── ApplicationError.php ├── Exception │ ├── ErrorException.php │ └── UserException.php ├── Factory │ └── ThrowableResponseFactory.php ├── HeadersProvider.php ├── Middleware │ ├── ErrorCatcher.php │ └── ExceptionResponder.php ├── Renderer │ ├── HeaderRenderer.php │ ├── HtmlRenderer.php │ ├── JsonRenderer.php │ ├── PlainTextRenderer.php │ └── XmlRenderer.php ├── RendererProvider │ ├── ClosureRendererProvider.php │ ├── CompositeRendererProvider.php │ ├── ContentTypeRendererProvider.php │ ├── HeadRendererProvider.php │ └── RendererProviderInterface.php ├── ThrowableRendererInterface.php ├── ThrowableResponseFactory.php └── ThrowableResponseFactoryInterface.php ├── templates ├── _call-stack-item.php ├── _call-stack-items.php ├── _previous-exception.php ├── development.css ├── development.php ├── highlight.min.js └── production.php └── tools ├── .gitignore └── composer-require-checker └── composer.json /.phpunit-watcher.yml: -------------------------------------------------------------------------------- 1 | watch: 2 | directories: 3 | - src 4 | - tests 5 | fileMask: '*.php' 6 | notifications: 7 | passingTests: false 8 | failingTests: false 9 | phpunit: 10 | binaryPath: vendor/bin/phpunit 11 | timeout: 180 12 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr12 2 | risky: true 3 | 4 | version: 8.1 5 | 6 | finder: 7 | exclude: 8 | - docs 9 | - vendor 10 | 11 | enabled: 12 | - alpha_ordered_traits 13 | - array_indentation 14 | - array_push 15 | - combine_consecutive_issets 16 | - combine_consecutive_unsets 17 | - combine_nested_dirname 18 | - declare_strict_types 19 | - dir_constant 20 | - fully_qualified_strict_types 21 | - function_to_constant 22 | - hash_to_slash_comment 23 | - is_null 24 | - logical_operators 25 | - magic_constant_casing 26 | - magic_method_casing 27 | - method_separation 28 | - modernize_types_casting 29 | - native_function_casing 30 | - native_function_type_declaration_casing 31 | - no_alias_functions 32 | - no_empty_comment 33 | - no_empty_phpdoc 34 | - no_empty_statement 35 | - no_extra_block_blank_lines 36 | - no_short_bool_cast 37 | - no_superfluous_elseif 38 | - no_unneeded_control_parentheses 39 | - no_unneeded_curly_braces 40 | - no_unneeded_final_method 41 | - no_unset_cast 42 | - no_unused_imports 43 | - no_unused_lambda_imports 44 | - no_useless_else 45 | - no_useless_return 46 | - normalize_index_brace 47 | - php_unit_dedicate_assert 48 | - php_unit_dedicate_assert_internal_type 49 | - php_unit_expectation 50 | - php_unit_mock 51 | - php_unit_mock_short_will_return 52 | - php_unit_namespaced 53 | - php_unit_no_expectation_annotation 54 | - phpdoc_no_empty_return 55 | - phpdoc_no_useless_inheritdoc 56 | - phpdoc_order 57 | - phpdoc_property 58 | - phpdoc_scalar 59 | - phpdoc_singular_inheritdoc 60 | - phpdoc_trim 61 | - phpdoc_trim_consecutive_blank_line_separation 62 | - phpdoc_type_to_var 63 | - phpdoc_types 64 | - phpdoc_types_order 65 | - print_to_echo 66 | - regular_callable_call 67 | - return_assignment 68 | - self_accessor 69 | - self_static_accessor 70 | - set_type_to_cast 71 | - short_array_syntax 72 | - short_list_syntax 73 | - simplified_if_return 74 | - single_quote 75 | - standardize_not_equals 76 | - ternary_to_null_coalescing 77 | - trailing_comma_in_multiline_array 78 | - unalign_double_arrow 79 | - unalign_equals 80 | - empty_loop_body_braces 81 | - integer_literal_case 82 | - union_type_without_spaces 83 | 84 | disabled: 85 | - function_declaration 86 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Yii Error Handler Change Log 2 | 3 | ## 4.1.1 under development 4 | 5 | - no changes in this release. 6 | 7 | ## 4.1.0 April 18, 2025 8 | 9 | - New #145: Add `Yiisoft\ErrorHandler\ThrowableResponseFactory` that provides a response for `Throwable` object with 10 | renderer provider usage (@vjik) 11 | - Chg #145: Mark `Yiisoft\ErrorHandler\Factory\ThrowableResponseFactory` as deprecated (@vjik) 12 | - Enh #145: Set content type header in renderers (@vjik) 13 | - Bug #142: Fix dark mode argument display issues (@pamparam83) 14 | 15 | ## 4.0.0 February 05, 2025 16 | 17 | - Chg #137: Add separate parameters for each of `HtmlRenderer` settings in constructor. Mark `$settings` parameter as 18 | deprecated (@vjik) 19 | - Chg #139: Change PHP constraint in `composer.json` to `~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0` (@vjik) 20 | - Enh #125: Add error code & show function arguments (@xepozz) 21 | - Enh #130: Pass exception message instead of rendered exception to logger in `ErrorHandler` (@olegbaturin) 22 | - Enh #133: Extract response generator from `ErrorCatcher` middleware into separate `ThrowableResponseFactory` 23 | class (@olegbaturin) 24 | - Enh #138, #139: Raise the minimum PHP version to 8.1 and minor refactoring (@vjik) 25 | - Bug #139: Explicitly mark nullable parameters (@vjik) 26 | 27 | ## 3.3.0 July 11, 2024 28 | 29 | - Enh #112: Add copy cURL button, sort request headers, fix UI (@xepozz) 30 | - Enh #113: Simplify error log (@xepozz) 31 | - Enh #114: Show full argument by click (@xepozz) 32 | - Enh #116: Remove @anonymous postfix (@xepozz) 33 | - Enh #117, #120: Show arguments table by click (@xepozz, @vjik) 34 | - Bug #114: Stop `click` event on text selection (@xepozz) 35 | - Bug #122: Do `exit(1)` after all shutdown functions, even postponed ones (@samdark) 36 | 37 | ## 3.2.1 March 07, 2024 38 | 39 | - Enh #102: Add support for `psr/http-message` of `^2.0` version (@vjik) 40 | 41 | ## 3.2.0 January 30, 2024 42 | 43 | - New #98: Add ability to execute `getBody()` on response when `ExceptionResponder` middleware is processing (@vjik) 44 | - Enh #96: Trace PHP errors (@xepozz, @vjik) 45 | 46 | ## 3.1.0 January 07, 2024 47 | 48 | - New #87: Add `CompositeException` to be able to render multiple exceptions (@xepozz) 49 | - Chg #75: Dispatch `ApplicationError` in `ErrorCatcher` (@xepozz) 50 | - Enh #82: Add `HeadersProvider` (@xepozz) 51 | - Enh #86: Add color scheme definition based on system settings (@dood-) 52 | - Bug #87: Fix a bug with try/finally from #75 (@xepozz) 53 | 54 | ## 3.0.0 February 14, 2023 55 | 56 | - Chg #64: Raise PHP version to `^8.0` (@vjik, @xepozz) 57 | - Chg #72: Adapt configuration group names to Yii conventions (@vjik) 58 | - Enh #65: Explicitly add transitive dependencies `ext-mbstring`, `psr/http-factory` and 59 | `psr/http-server-handler` (@vjik) 60 | 61 | ## 2.1.1 January 26, 2023 62 | 63 | - Bug #70: Prevent duplication of throwable rendering (@vjik) 64 | 65 | ## 2.1.0 June 15, 2022 66 | 67 | - Enh #54: Add shutdown event, fix cwd (@rustamwin) 68 | - Enh #55: Defer exit on terminate (@rustamwin) 69 | - Enh #57: Add markdown support for friendly exception solutions (@vjik) 70 | - Enh #58: Add support for `2.0`, `3.0` versions of `psr/log` (@rustamwin) 71 | 72 | ## 2.0.2 February 04, 2022 73 | 74 | - Bug #50: Fix JSON rendering on JSON recursion exception (@thenotsoft) 75 | 76 | ## 2.0.1 January 26, 2022 77 | 78 | - Bug #49: Fix JSON rendering of non-UTF-8 encoded string (@devanych) 79 | 80 | ## 2.0.0 November 09, 2021 81 | 82 | - Chg #48: Transfer `HeaderHelper` to `yiisoft/http` package (@devanych) 83 | - Enh #45: Improve appearance of solution from friendly exceptions (@vjik) 84 | 85 | ## 1.0.0 May 13, 2021 86 | 87 | Initial release. 88 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2008 by Yii Software () 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Yii Software nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Yii 4 | 5 |

Yii Error Handler

6 |
7 |

8 | 9 | [![Latest Stable Version](https://poser.pugx.org/yiisoft/error-handler/v)](https://packagist.org/packages/yiisoft/error-handler) 10 | [![Total Downloads](https://poser.pugx.org/yiisoft/error-handler/downloads)](https://packagist.org/packages/yiisoft/error-handler) 11 | [![Build status](https://github.com/yiisoft/error-handler/actions/workflows/build.yml/badge.svg)](https://github.com/yiisoft/error-handler/actions/workflows/build.yml) 12 | [![Code coverage](https://codecov.io/gh/yiisoft/error-handler/graph/badge.svg?token=3ZC0QHQHIN)](https://codecov.io/gh/yiisoft/error-handler) 13 | [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fyiisoft%2Ferror-handler%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/error-handler/master) 14 | [![static analysis](https://github.com/yiisoft/error-handler/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/error-handler/actions?query=workflow%3A%22static+analysis%22) 15 | 16 | The package provides advanced error handling. The features are: 17 | 18 | - PSR-15 middleware for catching unhandled errors. 19 | - PSR-15 middleware for mapping certain exceptions to custom responses. 20 | - Production and debug modes. 21 | - Debug mode displays details, stacktrace, has dark and light themes and handy buttons to search for error without typing. 22 | - Takes PHP settings into account. 23 | - Handles out of memory errors, fatals, warnings, notices and exceptions. 24 | - Can use any PSR-3 compatible logger for error logging. 25 | - Detects response format based on mime type of the request. 26 | - Supports responding with HTML, plain text, JSON, XML and headers out of the box. 27 | - Has ability to implement your own error rendering for additional types. 28 | - [Friendly exceptions](https://github.com/yiisoft/friendly-exception/) support. 29 | 30 | ## Requirements 31 | 32 | - PHP 8.1 or higher. 33 | - `DOM` PHP extension. 34 | - `mbstring` PHP extension. 35 | 36 | ## Installation 37 | 38 | The package could be installed with [Composer](https://getcomposer.org): 39 | 40 | ```shell 41 | composer require yiisoft/error-handler 42 | ``` 43 | 44 | ## General usage 45 | 46 | Creating an error handler: 47 | 48 | ```php 49 | use Yiisoft\ErrorHandler\ErrorHandler; 50 | use Yiisoft\ErrorHandler\Renderer\HtmlRenderer; 51 | 52 | /** 53 | * @var \Psr\Log\LoggerInterface $logger 54 | */ 55 | 56 | $errorHandler = new ErrorHandler($logger, new HtmlRenderer()); 57 | ``` 58 | 59 | The error handler logs information about the error using any [PSR-3](https://www.php-fig.org/psr/psr-3/) 60 | compatible logger. If for some reason you do not want to log error information, 61 | specify an instance of the `\Psr\Log\NullLogger`. 62 | 63 | By default, the error handler is set to production mode and displays no detailed information. 64 | You can enable and disable debug mode as follows: 65 | 66 | ```php 67 | // Enable debug mode: 68 | $errorHandler->debug(); 69 | 70 | // Disable debug mode: 71 | $errorHandler->debug(false); 72 | 73 | // Or define the environment dynamically: 74 | $errorHandler->debug($_ENV['debug'] ?? false); 75 | ``` 76 | 77 | The error handler handles out-of-memory errors. To achieve it, memory is pre-allocated so that if a problem occurs with 78 | a lack of memory, the error handler can handle the error using this reserved memory. You can specify your own reserve 79 | size using the `memoryReserveSize()` method. If you set this value to 0, no memory will be reserved. 80 | 81 | ```php 82 | // Allocate 512KB. Defaults to 256KB. 83 | $errorHandler->memoryReserveSize(524_288); 84 | ``` 85 | 86 | The `register()` method registers the PHP error and exception handlers. 87 | To unregister these and restore the PHP error and exception handlers, use the `unregister()` method. 88 | 89 | ```php 90 | $errorHandler->register(); 91 | // Errors are being handled. 92 | $errorHandler->unregister(); 93 | // Errors are not handled. 94 | ``` 95 | 96 | ### Rendering error data 97 | 98 | The following renderers are available out of the box: 99 | 100 | - `Yiisoft\ErrorHandler\Renderer\HeaderRenderer` - Renders error into HTTP headers. It is used for HEAD requests. 101 | - `Yiisoft\ErrorHandler\Renderer\HtmlRenderer` - Renders error into HTML. 102 | - `Yiisoft\ErrorHandler\Renderer\JsonRenderer` - Renders error into JSON. 103 | - `Yiisoft\ErrorHandler\Renderer\PlainTextRenderer` - Renders error into plain text. 104 | - `Yiisoft\ErrorHandler\Renderer\XmlRenderer` - Renders error into XML. 105 | 106 | If the existing renderers are not enough, you can create your own. To do this, you must implement the 107 | `Yiisoft\ErrorHandler\ThrowableRendererInterface` and specify it when creating an instance of the error handler. 108 | 109 | ```php 110 | use Yiisoft\ErrorHandler\ErrorHandler; 111 | 112 | /** 113 | * @var \Psr\Log\LoggerInterface $logger 114 | * @var \Yiisoft\ErrorHandler\ThrowableRendererInterface $renderer 115 | */ 116 | 117 | $errorHandler = new ErrorHandler($logger, $renderer); 118 | ``` 119 | 120 | For more information about creating your own renders and examples of rendering error data, 121 | [see here](https://github.com/yiisoft/docs/blob/master/guide/en/runtime/handling-errors.md#rendering-error-data). 122 | 123 | ### Using a factory to create a response 124 | 125 | `Yiisoft\ErrorHandler\ThrowableResponseFactory` renders `Throwable` object and produces a response according to the content type provided by the client. 126 | 127 | ```php 128 | use Yiisoft\ErrorHandler\RendererProvider; 129 | use Yiisoft\ErrorHandler\ThrowableResponseFactory; 130 | 131 | /** 132 | * @var \Throwable $throwable 133 | * @var \Psr\Container\ContainerInterface $container 134 | * @var \Psr\Http\Message\ResponseFactoryInterface $responseFactory 135 | * @var \Psr\Http\Message\ServerRequestInterface $request 136 | * @var \Yiisoft\ErrorHandler\ErrorHandler $errorHandler 137 | */ 138 | 139 | $throwableResponseFactory = new ThrowableResponseFactory( 140 | $responseFactory, 141 | $errorHandler, 142 | new RendererProvider\CompositeRendererProvider( 143 | new RendererProvider\HeadRendererProvider(), 144 | new RendererProvider\ContentTypeRendererProvider($container), 145 | ), 146 | ); 147 | 148 | // Creating an instance of the `Psr\Http\Message\ResponseInterface` with error information. 149 | $response = $throwableResponseFactory->create($throwable, $request); 150 | ``` 151 | 152 | `Yiisoft\ErrorHandler\ThrowableResponseFactory` chooses how to render an exception by renderer provider. Providers 153 | available out of the box: 154 | 155 | - `HeadRendererProvider` - renders error into HTTP headers. It is used for HEAD requests. 156 | - `ContentTypeRendererProvider` - renders error based on accept HTTP header. By default, JSON, XML and plain text are 157 | supported. 158 | - `ClosureRendererProvider` - allows you to create your own renderer provider using closures. 159 | - `CompositeRendererProvider` - allows you to combine several renderer providers. 160 | 161 | ### Using a middleware for catching unhandled errors 162 | 163 | `Yiisoft\ErrorHandler\Middleware\ErrorCatcher` is a [PSR-15](https://www.php-fig.org/psr/psr-15/) middleware that 164 | catches exceptions raised during middleware stack execution and passes them to the instance of `Yiisoft\ErrorHandler\ThrowableResponseFactoryInterface` to create a response. 165 | 166 | ```php 167 | use Yiisoft\ErrorHandler\Middleware\ErrorCatcher; 168 | 169 | /** 170 | * @var \Psr\EventDispatcher\EventDispatcherInterface $eventDispatcher 171 | * @var \Psr\Http\Message\ServerRequestInterface $request 172 | * @var \Psr\Http\Server\RequestHandlerInterface $handler 173 | * @var \Yiisoft\ErrorHandler\ThrowableResponseFactoryInterface $throwableResponseFactory 174 | */ 175 | 176 | $errorCatcher = new ErrorCatcher($throwableResponseFactory); 177 | 178 | // In any case, it will return an instance of the `Psr\Http\Message\ResponseInterface`. 179 | // Either the expected response, or a response with error information. 180 | $response = $errorCatcher->process($request, $handler); 181 | ``` 182 | 183 | `Yiisoft\ErrorHandler\Middleware\ErrorCatcher` can be instantiated with [PSR-14](https://www.php-fig.org/psr/psr-14/) event dispatcher as an optional dependency. 184 | In this case `\Yiisoft\ErrorHandler\Event\ApplicationError` will be dispatched when `ErrorCatcher` catches an error. 185 | 186 | ```php 187 | $errorCatcher = new ErrorCatcher($throwableResponseFactory, $eventDispatcher); 188 | ``` 189 | 190 | ### Using a middleware for mapping certain exceptions to custom responses 191 | 192 | `Yiisoft\ErrorHandler\Middleware\ExceptionResponder` is a [PSR-15](https://www.php-fig.org/psr/psr-15/) 193 | middleware that maps certain exceptions to custom responses. 194 | 195 | ```php 196 | use Yiisoft\ErrorHandler\Middleware\ExceptionResponder; 197 | 198 | /** 199 | * @var \Psr\Http\Message\ResponseFactoryInterface $responseFactory 200 | * @var \Psr\Http\Message\ServerRequestInterface $request 201 | * @var \Psr\Http\Server\RequestHandlerInterface $handler 202 | * @var \Yiisoft\Injector\Injector $injector 203 | */ 204 | 205 | $exceptionMap = [ 206 | // Status code with which the response will be created by the factory. 207 | MyNotFoundException::class => 404, 208 | // PHP callable that must return a `Psr\Http\Message\ResponseInterface`. 209 | MyHttpException::class => static fn (MyHttpException $exception) => new MyResponse($exception), 210 | // ... 211 | ]; 212 | 213 | $exceptionResponder = new ExceptionResponder($exceptionMap, $responseFactory, $injector); 214 | 215 | // Returns the expected response, or the response associated with the thrown exception, 216 | // or throws an exception if it does not present in the exception map. 217 | $response = $exceptionResponder->process($request, $handler); 218 | ``` 219 | 220 | In the application middleware stack `Yiisoft\ErrorHandler\Middleware\ExceptionResponder` must be placed before 221 | `Yiisoft\ErrorHandler\Middleware\ErrorCatcher`. 222 | 223 | ## Events 224 | 225 | - When `ErrorCatcher` catches an error it optionally dispatches `\Yiisoft\ErrorHandler\Event\ApplicationError` event. Instance of `Psr\EventDispatcher\EventDispatcherInterface` must be provided to the `ErrorCatcher`. 226 | 227 | ## Friendly Exceptions 228 | 229 | `HtmlRenderer` supports [friendly exceptions](https://github.com/yiisoft/friendly-exception/). 230 | 231 | Code blocks in solution markdown support language syntax highlight: 232 | 233 | | Language | Aliases | 234 | |------------|--------------------------------------------------------| 235 | | Bash | bash, sh, zsh | 236 | | CSS | css | 237 | | HTML, XML | xml, html, xhtml, rss, atom, xjb, xsd, xsl, plist, svg | 238 | | JavaScript | javascript, js, jsx | 239 | | JSON | json | 240 | | PHP | php | 241 | | Plaintext | plaintext, txt, text | 242 | | SQL | sql | 243 | 244 | For example: 245 | 246 | ```html 247 | 248 | 249 |

This text is normal.

250 |

This text is bold.

251 | 252 | 253 | ``` 254 | 255 | ## Documentation 256 | 257 | - [Yii guide to handling errors](https://github.com/yiisoft/docs/blob/master/guide/en/runtime/handling-errors.md) 258 | - [Internals](docs/internals.md) 259 | 260 | If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for that. 261 | You may also check out other [Yii Community Resources](https://www.yiiframework.com/community). 262 | 263 | ## License 264 | 265 | The Yii Error Handler is free software. It is released under the terms of the BSD License. 266 | Please see [`LICENSE`](./LICENSE.md) for more information. 267 | 268 | Maintained by [Yii Software](https://www.yiiframework.com/). 269 | 270 | ## Credits 271 | 272 | The Yii Error Handler use code of [Highlight.js](https://highlightjs.org/) by Ivan Sagalaev and other contributors. 273 | 274 | ## Support the project 275 | 276 | [![Open Collective](https://img.shields.io/badge/Open%20Collective-sponsor-7eadf1?logo=open%20collective&logoColor=7eadf1&labelColor=555555)](https://opencollective.com/yiisoft) 277 | 278 | ## Follow updates 279 | 280 | [![Official website](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](https://www.yiiframework.com/) 281 | [![Twitter](https://img.shields.io/badge/twitter-follow-1DA1F2?logo=twitter&logoColor=1DA1F2&labelColor=555555?style=flat)](https://twitter.com/yiiframework) 282 | [![Telegram](https://img.shields.io/badge/telegram-join-1DA1F2?style=flat&logo=telegram)](https://t.me/yii3en) 283 | [![Facebook](https://img.shields.io/badge/facebook-join-1DA1F2?style=flat&logo=facebook&logoColor=ffffff)](https://www.facebook.com/groups/yiitalk) 284 | [![Slack](https://img.shields.io/badge/slack-join-1DA1F2?style=flat&logo=slack)](https://yiiframework.com/go/slack) 285 | -------------------------------------------------------------------------------- /composer-require-checker.json: -------------------------------------------------------------------------------- 1 | { 2 | "symbol-whitelist" : [ 3 | "xdebug_get_function_stack" 4 | ], 5 | "php-core-extensions" : [ 6 | "Core", 7 | "date", 8 | "json", 9 | "pcre", 10 | "Phar", 11 | "Reflection", 12 | "SPL", 13 | "random", 14 | "standard" 15 | ], 16 | "scan-files" : [] 17 | } 18 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiisoft/error-handler", 3 | "type": "library", 4 | "description": "Yii Error Handling Library", 5 | "keywords": [ 6 | "yiisoft", 7 | "error-handler", 8 | "psr-3", 9 | "psr-7", 10 | "psr-11", 11 | "psr-15" 12 | ], 13 | "homepage": "https://www.yiiframework.com/", 14 | "license": "BSD-3-Clause", 15 | "support": { 16 | "issues": "https://github.com/yiisoft/error-handler/issues?state=open", 17 | "source": "https://github.com/yiisoft/error-handler", 18 | "forum": "https://www.yiiframework.com/forum/", 19 | "wiki": "https://www.yiiframework.com/wiki/", 20 | "irc": "ircs://irc.libera.chat:6697/yii", 21 | "chat": "https://t.me/yii3en" 22 | }, 23 | "funding": [ 24 | { 25 | "type": "opencollective", 26 | "url": "https://opencollective.com/yiisoft" 27 | }, 28 | { 29 | "type": "github", 30 | "url": "https://github.com/sponsors/yiisoft" 31 | } 32 | ], 33 | "require": { 34 | "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", 35 | "ext-dom": "*", 36 | "ext-mbstring": "*", 37 | "alexkart/curl-builder": "^1.0", 38 | "cebe/markdown": "^1.2", 39 | "psr/container": "^1.0|^2.0", 40 | "psr/http-factory": "^1.0", 41 | "psr/http-message": "^1.0|^2.0", 42 | "psr/http-server-handler": "^1.0", 43 | "psr/http-server-middleware": "^1.0", 44 | "psr/log": "^1.1|^2.0|^3.0", 45 | "yiisoft/friendly-exception": "^1.0", 46 | "yiisoft/http": "^1.2", 47 | "yiisoft/injector": "^1.0" 48 | }, 49 | "require-dev": { 50 | "bamarni/composer-bin-plugin": "^1.8.2", 51 | "httpsoft/http-message": "^1.1.6", 52 | "phpunit/phpunit": "^10.5.45", 53 | "psr/event-dispatcher": "^1.0", 54 | "rector/rector": "^2.0.11", 55 | "roave/infection-static-analysis-plugin": "^1.35", 56 | "spatie/phpunit-watcher": "^1.24", 57 | "vimeo/psalm": "^5.26.1 || ^6.9.1", 58 | "yiisoft/di": "^1.3", 59 | "yiisoft/test-support": "^3.0.2" 60 | }, 61 | "autoload": { 62 | "psr-4": { 63 | "Yiisoft\\ErrorHandler\\": "src" 64 | } 65 | }, 66 | "autoload-dev": { 67 | "psr-4": { 68 | "Yiisoft\\ErrorHandler\\Tests\\": "tests" 69 | } 70 | }, 71 | "extra": { 72 | "bamarni-bin": { 73 | "bin-links": true, 74 | "target-directory": "tools", 75 | "forward-command": true 76 | }, 77 | "config-plugin-options": { 78 | "source-directory": "config" 79 | }, 80 | "config-plugin": { 81 | "di-web": "di-web.php" 82 | } 83 | }, 84 | "config": { 85 | "sort-packages": true, 86 | "allow-plugins": { 87 | "bamarni/composer-bin-plugin": true, 88 | "composer/package-versions-deprecated": true, 89 | "infection/extension-installer": true 90 | } 91 | }, 92 | "scripts": { 93 | "test": "phpunit --testdox --no-interaction", 94 | "test-watch": "phpunit-watcher watch" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /config/di-web.php: -------------------------------------------------------------------------------- 1 | HtmlRenderer::class, 16 | ThrowableResponseFactoryInterface::class => ThrowableResponseFactory::class, 17 | ]; 18 | -------------------------------------------------------------------------------- /infection.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "directories": [ 4 | "src" 5 | ] 6 | }, 7 | "logs": { 8 | "text": "php:\/\/stderr", 9 | "stryker": { 10 | "report": "master" 11 | } 12 | }, 13 | "mutators": { 14 | "@default": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([ 14 | __DIR__ . '/src', 15 | __DIR__ . '/tests', 16 | ]) 17 | ->withPhpSets(php81: true) 18 | ->withRules([ 19 | InlineConstructorDefaultToPropertyRector::class, 20 | ]) 21 | ->withSkip([ 22 | ClosureToArrowFunctionRector::class, 23 | NullToStrictStringFuncCallArgRector::class, 24 | RemoveExtraParametersRector::class, 25 | NewInInitializerRector::class, 26 | ]); 27 | -------------------------------------------------------------------------------- /src/CompositeException.php: -------------------------------------------------------------------------------- 1 | rest = $rest; 25 | parent::__construct($first->getMessage(), (int) $first->getCode(), $first); 26 | } 27 | 28 | public function getFirstException(): Throwable 29 | { 30 | return $this->first; 31 | } 32 | 33 | /** 34 | * @return Throwable[] 35 | */ 36 | public function getPreviousExceptions(): array 37 | { 38 | return $this->rest; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ErrorData.php: -------------------------------------------------------------------------------- 1 | $headers The headers to add to the response. 18 | */ 19 | public function __construct( 20 | private readonly string $content, 21 | private readonly array $headers = [], 22 | ) { 23 | } 24 | 25 | /** 26 | * Returns a content to use as response body. 27 | * 28 | * @return string The content to use as response body. 29 | */ 30 | public function __toString(): string 31 | { 32 | return $this->content; 33 | } 34 | 35 | /** 36 | * Returns a response with error data. 37 | * 38 | * @param ResponseInterface $response The response for setting error data. 39 | * 40 | * @return ResponseInterface The response with error data. 41 | */ 42 | public function addToResponse(ResponseInterface $response): ResponseInterface 43 | { 44 | foreach ($this->headers as $name => $value) { 45 | $response = $response->withHeader($name, $value); 46 | } 47 | 48 | $response 49 | ->getBody() 50 | ->write($this->content); 51 | return $response; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ErrorHandler.php: -------------------------------------------------------------------------------- 1 | defaultRenderer; 69 | 70 | try { 71 | $this->logger->error($t->getMessage(), ['throwable' => $t]); 72 | return $this->debug ? $renderer->renderVerbose($t, $request) : $renderer->render($t, $request); 73 | } catch (Throwable $t) { 74 | return new ErrorData((string) $t); 75 | } 76 | } 77 | 78 | /** 79 | * Enables and disables debug mode. 80 | * 81 | * Ensure that is is disabled in production environment since debug mode exposes sensitive details. 82 | * 83 | * @param bool $enable Enable/disable debugging mode. 84 | */ 85 | public function debug(bool $enable = true): void 86 | { 87 | $this->debug = $enable; 88 | } 89 | 90 | /** 91 | * Sets the size of the reserved memory. 92 | * 93 | * @param int $size The size of the reserved memory. 94 | * 95 | * @see $memoryReserveSize 96 | */ 97 | public function memoryReserveSize(int $size): void 98 | { 99 | $this->memoryReserveSize = $size; 100 | } 101 | 102 | /** 103 | * Register PHP exception and error handlers and enable this error handler. 104 | */ 105 | public function register(): void 106 | { 107 | if ($this->enabled) { 108 | return; 109 | } 110 | 111 | if ($this->memoryReserveSize > 0) { 112 | $this->memoryReserve = str_repeat('x', $this->memoryReserveSize); 113 | } 114 | 115 | $this->initializeOnce(); 116 | 117 | // Handles throwable that isn't caught otherwise, echo output and exit. 118 | set_exception_handler(function (Throwable $t): void { 119 | if (!$this->enabled) { 120 | return; 121 | } 122 | 123 | $this->renderThrowableAndTerminate($t); 124 | }); 125 | 126 | // Handles PHP execution errors such as warnings and notices. 127 | set_error_handler(function (int $severity, string $message, string $file, int $line): bool { 128 | if (!$this->enabled) { 129 | return false; 130 | } 131 | 132 | if (!(error_reporting() & $severity)) { 133 | // This error code is not included in error_reporting. 134 | return true; 135 | } 136 | 137 | $backtrace = debug_backtrace(0); 138 | array_shift($backtrace); 139 | throw new ErrorException($message, $severity, $severity, $file, $line, null, $backtrace); 140 | }); 141 | 142 | $this->enabled = true; 143 | } 144 | 145 | /** 146 | * Disable this error handler. 147 | */ 148 | public function unregister(): void 149 | { 150 | if (!$this->enabled) { 151 | return; 152 | } 153 | 154 | $this->memoryReserve = ''; 155 | 156 | $this->enabled = false; 157 | } 158 | 159 | private function initializeOnce(): void 160 | { 161 | if ($this->initialized) { 162 | return; 163 | } 164 | 165 | // Disables the display of error. 166 | if (function_exists('ini_set')) { 167 | ini_set('display_errors', '0'); 168 | } 169 | 170 | // Handles fatal error. 171 | register_shutdown_function(function (): void { 172 | if (!$this->enabled) { 173 | return; 174 | } 175 | 176 | $this->memoryReserve = ''; 177 | $e = error_get_last(); 178 | 179 | if ($e !== null && ErrorException::isFatalError($e)) { 180 | $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); 181 | $error = new ErrorException( 182 | $e['message'], 183 | $e['type'], 184 | $e['type'], 185 | $e['file'], 186 | $e['line'], 187 | null, 188 | $backtrace 189 | ); 190 | $this->renderThrowableAndTerminate($error); 191 | } 192 | }); 193 | 194 | if (!(PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg')) { 195 | /** 196 | * @var string 197 | */ 198 | $this->workingDirectory = getcwd(); 199 | } 200 | 201 | $this->initialized = true; 202 | } 203 | 204 | /** 205 | * Renders the throwable and terminates the script. 206 | */ 207 | private function renderThrowableAndTerminate(Throwable $t): void 208 | { 209 | if (!empty($this->workingDirectory)) { 210 | chdir($this->workingDirectory); 211 | } 212 | // Disable error capturing to avoid recursive errors while handling exceptions. 213 | $this->unregister(); 214 | // Set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent. 215 | http_response_code(Status::INTERNAL_SERVER_ERROR); 216 | 217 | echo $this->handle($t); 218 | $this->eventDispatcher?->dispatch(new ApplicationError($t)); 219 | 220 | $handler = $this->wrapShutdownHandler( 221 | static function (): void { 222 | exit(1); 223 | }, 224 | $this->exitShutdownHandlerDepth 225 | ); 226 | 227 | register_shutdown_function($handler); 228 | } 229 | 230 | /** 231 | * Wraps shutdown handler into another shutdown handler to ensure it is called last after all other shutdown 232 | * functions, even those added to the end. 233 | * 234 | * @param callable $handler Shutdown handler to wrap. 235 | * @param int $depth Wrapping depth. 236 | * @return callable Wrapped handler. 237 | */ 238 | private function wrapShutdownHandler(callable $handler, int $depth): callable 239 | { 240 | $currentDepth = 0; 241 | while ($currentDepth < $depth) { 242 | $handler = static function() use ($handler): void { 243 | register_shutdown_function($handler); 244 | }; 245 | $currentDepth++; 246 | } 247 | return $handler; 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/Event/ApplicationError.php: -------------------------------------------------------------------------------- 1 | throwable; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Exception/ErrorException.php: -------------------------------------------------------------------------------- 1 | ,class?:class-string,file?:string,function:string,line?:int,object?:object,type?:string}> 18 | * 19 | * @final 20 | */ 21 | class ErrorException extends \ErrorException implements FriendlyExceptionInterface 22 | { 23 | /** @psalm-suppress MissingClassConstType Private constants never change. */ 24 | private const ERROR_NAMES = [ 25 | E_ERROR => 'PHP Fatal Error', 26 | E_WARNING => 'PHP Warning', 27 | E_PARSE => 'PHP Parse Error', 28 | E_NOTICE => 'PHP Notice', 29 | E_CORE_ERROR => 'PHP Core Error', 30 | E_CORE_WARNING => 'PHP Core Warning', 31 | E_COMPILE_ERROR => 'PHP Compile Error', 32 | E_COMPILE_WARNING => 'PHP Compile Warning', 33 | E_USER_ERROR => 'PHP User Error', 34 | E_USER_WARNING => 'PHP User Warning', 35 | E_USER_NOTICE => 'PHP User Notice', 36 | 2048 => 'PHP Strict Warning', // E_STRICT 37 | E_RECOVERABLE_ERROR => 'PHP Recoverable Error', 38 | E_DEPRECATED => 'PHP Deprecated Warning', 39 | E_USER_DEPRECATED => 'PHP User Deprecated Warning', 40 | ]; 41 | 42 | /** @psalm-param DebugBacktraceType $backtrace */ 43 | public function __construct(string $message = '', int $code = 0, int $severity = 1, string $filename = __FILE__, int $line = __LINE__, ?Exception $previous = null, private readonly array $backtrace = []) 44 | { 45 | parent::__construct($message, $code, $severity, $filename, $line, $previous); 46 | $this->addXDebugTraceToFatalIfAvailable(); 47 | } 48 | 49 | /** 50 | * Returns if error is one of fatal type. 51 | * 52 | * @param array $error error got from error_get_last() 53 | * 54 | * @return bool If error is one of fatal type. 55 | */ 56 | public static function isFatalError(array $error): bool 57 | { 58 | return isset($error['type']) && in_array( 59 | $error['type'], 60 | [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING], 61 | true, 62 | ); 63 | } 64 | 65 | /** 66 | * @return string The user-friendly name of this exception. 67 | */ 68 | public function getName(): string 69 | { 70 | return self::ERROR_NAMES[$this->getCode()] ?? 'Error'; 71 | } 72 | 73 | public function getSolution(): ?string 74 | { 75 | return null; 76 | } 77 | 78 | /** 79 | * @psalm-return DebugBacktraceType 80 | */ 81 | public function getBacktrace(): array 82 | { 83 | return $this->backtrace; 84 | } 85 | 86 | /** 87 | * Fatal errors normally do not provide any trace making it harder to debug. In case XDebug is installed, we 88 | * can get a trace using `xdebug_get_function_stack()`. 89 | */ 90 | private function addXDebugTraceToFatalIfAvailable(): void 91 | { 92 | if ($this->isXdebugStackAvailable()) { 93 | /** 94 | * XDebug trace can't be modified and used directly with PHP 7 95 | * 96 | * @see https://github.com/yiisoft/yii2/pull/11723 97 | * 98 | * @psalm-var array 99 | */ 100 | $xDebugTrace = array_slice(array_reverse(xdebug_get_function_stack()), 1, -1); 101 | $trace = []; 102 | 103 | foreach ($xDebugTrace as $frame) { 104 | if (!isset($frame['function'])) { 105 | $frame['function'] = 'unknown'; 106 | } 107 | 108 | // XDebug < 2.1.1: https://bugs.xdebug.org/view.php?id=695 109 | if (!isset($frame['type']) || $frame['type'] === 'static') { 110 | $frame['type'] = '::'; 111 | } elseif ($frame['type'] === 'dynamic') { 112 | $frame['type'] = '->'; 113 | } 114 | 115 | // XDebug has a different key name 116 | if (isset($frame['params']) && !isset($frame['args'])) { 117 | /** @var mixed */ 118 | $frame['args'] = $frame['params']; 119 | } 120 | $trace[] = $frame; 121 | } 122 | 123 | $ref = new ReflectionProperty(Exception::class, 'trace'); 124 | $ref->setAccessible(true); 125 | $ref->setValue($this, $trace); 126 | } 127 | } 128 | 129 | /** 130 | * Ensures that Xdebug stack trace is available based on Xdebug version. 131 | * Idea taken from developer bishopb at https://github.com/rollbar/rollbar-php 132 | */ 133 | private function isXdebugStackAvailable(): bool 134 | { 135 | if (!function_exists('\xdebug_get_function_stack')) { 136 | return false; 137 | } 138 | 139 | // check for Xdebug being installed to ensure origin of xdebug_get_function_stack() 140 | $version = phpversion('xdebug'); 141 | 142 | if ($version === false) { 143 | return false; 144 | } 145 | 146 | // Xdebug 2 and prior 147 | if (version_compare($version, '3.0.0', '<')) { 148 | return true; 149 | } 150 | 151 | // Xdebug 3 and later, proper mode is required 152 | return str_contains((string) \ini_get('xdebug.mode'), 'develop'); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Exception/UserException.php: -------------------------------------------------------------------------------- 1 | > 46 | */ 47 | private array $renderers = [ 48 | 'application/json' => JsonRenderer::class, 49 | 'application/xml' => XmlRenderer::class, 50 | 'text/xml' => XmlRenderer::class, 51 | 'text/plain' => PlainTextRenderer::class, 52 | 'text/html' => HtmlRenderer::class, 53 | '*/*' => HtmlRenderer::class, 54 | ]; 55 | private ?string $contentType = null; 56 | 57 | public function __construct( 58 | private readonly ResponseFactoryInterface $responseFactory, 59 | private readonly ErrorHandler $errorHandler, 60 | private readonly ContainerInterface $container, 61 | ?HeadersProvider $headersProvider = null, 62 | ) { 63 | $this->headersProvider = $headersProvider ?? new HeadersProvider(); 64 | } 65 | 66 | public function create(Throwable $throwable, ServerRequestInterface $request): ResponseInterface 67 | { 68 | $contentType = $this->contentType ?? $this->getContentType($request); 69 | $renderer = $request->getMethod() === Method::HEAD ? new HeaderRenderer() : $this->getRenderer($contentType); 70 | 71 | $data = $this->errorHandler->handle($throwable, $renderer, $request); 72 | $response = $this->responseFactory->createResponse(Status::INTERNAL_SERVER_ERROR); 73 | foreach ($this->headersProvider->getAll() as $name => $value) { 74 | $response = $response->withHeader($name, $value); 75 | } 76 | return $data->addToResponse($response->withHeader(Header::CONTENT_TYPE, $contentType)); 77 | } 78 | 79 | /** 80 | * Returns a new instance with the specified content type and renderer class. 81 | * 82 | * @param string $contentType The content type to add associated renderers for. 83 | * @param string $rendererClass The classname implementing the {@see ThrowableRendererInterface}. 84 | */ 85 | public function withRenderer(string $contentType, string $rendererClass): self 86 | { 87 | if (!is_subclass_of($rendererClass, ThrowableRendererInterface::class)) { 88 | throw new InvalidArgumentException(sprintf( 89 | 'Class "%s" does not implement "%s".', 90 | $rendererClass, 91 | ThrowableRendererInterface::class, 92 | )); 93 | } 94 | 95 | $new = clone $this; 96 | $new->renderers[$this->normalizeContentType($contentType)] = $rendererClass; 97 | return $new; 98 | } 99 | 100 | /** 101 | * Returns a new instance without renderers by the specified content types. 102 | * 103 | * @param string[] $contentTypes The content types to remove associated renderers for. 104 | * If not specified, all renderers will be removed. 105 | */ 106 | public function withoutRenderers(string ...$contentTypes): self 107 | { 108 | $new = clone $this; 109 | 110 | if (count($contentTypes) === 0) { 111 | $new->renderers = []; 112 | return $new; 113 | } 114 | 115 | foreach ($contentTypes as $contentType) { 116 | unset($new->renderers[$this->normalizeContentType($contentType)]); 117 | } 118 | 119 | return $new; 120 | } 121 | 122 | /** 123 | * Force content type to respond with regardless of request. 124 | * 125 | * @param string $contentType The content type to respond with regardless of request. 126 | */ 127 | public function forceContentType(string $contentType): self 128 | { 129 | $contentType = $this->normalizeContentType($contentType); 130 | 131 | if (!isset($this->renderers[$contentType])) { 132 | throw new InvalidArgumentException(sprintf('The renderer for %s is not set.', $contentType)); 133 | } 134 | 135 | $new = clone $this; 136 | $new->contentType = $contentType; 137 | return $new; 138 | } 139 | 140 | /** 141 | * Returns the renderer by the specified content type, or null if the renderer was not set. 142 | * 143 | * @param string $contentType The content type associated with the renderer. 144 | */ 145 | private function getRenderer(string $contentType): ?ThrowableRendererInterface 146 | { 147 | if (isset($this->renderers[$contentType])) { 148 | /** @var ThrowableRendererInterface */ 149 | return $this->container->get($this->renderers[$contentType]); 150 | } 151 | 152 | return null; 153 | } 154 | 155 | /** 156 | * Returns the priority content type from the accept request header. 157 | * 158 | * @return string The priority content type. 159 | */ 160 | private function getContentType(ServerRequestInterface $request): string 161 | { 162 | try { 163 | foreach (HeaderValueHelper::getSortedAcceptTypes($request->getHeader(Header::ACCEPT)) as $header) { 164 | if (array_key_exists($header, $this->renderers)) { 165 | return $header; 166 | } 167 | } 168 | } catch (InvalidArgumentException) { 169 | // The Accept header contains an invalid q factor. 170 | } 171 | 172 | return '*/*'; 173 | } 174 | 175 | /** 176 | * Normalizes the content type. 177 | * 178 | * @param string $contentType The raw content type. 179 | * 180 | * @return string Normalized content type. 181 | */ 182 | private function normalizeContentType(string $contentType): string 183 | { 184 | if (!str_contains($contentType, '/')) { 185 | throw new InvalidArgumentException('Invalid content type.'); 186 | } 187 | 188 | return strtolower(trim($contentType)); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/HeadersProvider.php: -------------------------------------------------------------------------------- 1 | $headers Default headers list. 17 | */ 18 | public function __construct( 19 | private array $headers = [], 20 | ) { 21 | } 22 | 23 | /** 24 | * Adds a header to the list of headers. 25 | * 26 | * @param string $name The header name. 27 | * @param string|string[] $values The header value. 28 | */ 29 | public function add(string $name, string|array $values): void 30 | { 31 | $this->headers[$name] = (array)$values; 32 | } 33 | 34 | /** 35 | * Returns all headers. 36 | * 37 | * @return array The headers list. 38 | */ 39 | public function getAll(): array 40 | { 41 | return $this->headers; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Middleware/ErrorCatcher.php: -------------------------------------------------------------------------------- 1 | handle($request); 33 | } catch (Throwable $t) { 34 | try { 35 | $this->eventDispatcher?->dispatch(new ApplicationError($t)); 36 | } catch (Throwable $e) { 37 | $t = new CompositeException($e, $t); 38 | } 39 | 40 | return $this->throwableResponseFactory->create($t, $request); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Middleware/ExceptionResponder.php: -------------------------------------------------------------------------------- 1 | callable, \DomainException::class => callable, ...]` 27 | * - int format: `[\Exception::class => 404, \DomainException::class => 500, ...]` 28 | * 29 | * When an exception is thrown, the map in callable format allows to take control of the response. 30 | * Сallable must return `Psr\Http\Message\ResponseInterface`. If specified exception classes are equal, 31 | * then the first one will be processed. Below are some examples: 32 | * 33 | * ```php 34 | * $exceptionMap = [ 35 | * DomainException::class => function (\Psr\Http\Message\ResponseFactoryInterface $responseFactory) { 36 | * return $responseFactory->createResponse(\Yiisoft\Http\Status::CREATED); 37 | * }, 38 | * MyHttpException::class => static fn (MyHttpException $exception) => new MyResponse($exception), 39 | * ] 40 | * ``` 41 | * 42 | * When an exception is thrown, the map in int format allows to send the response with set http code. 43 | * If specified exception classes are equal, then the first one will be processed. Below are some examples: 44 | * 45 | * ```php 46 | * $exceptionMap = [ 47 | * \DomainException::class => \Yiisoft\Http\Status::BAD_REQUEST, 48 | * \InvalidArgumentException::class => \Yiisoft\Http\Status::BAD_REQUEST, 49 | * MyNotFoundException::class => \Yiisoft\Http\Status::NOT_FOUND, 50 | * ] 51 | * ``` 52 | * 53 | * @param callable[]|int[] $exceptionMap A callable that must return a `ResponseInterface` or response status code. 54 | * @param bool $checkResponseBody Whether executing `getBody()` on response needs to be done. It's useful for 55 | * catching exceptions that can be thrown in the process of body generation. 56 | */ 57 | public function __construct( 58 | private readonly array $exceptionMap, 59 | private readonly ResponseFactoryInterface $responseFactory, 60 | private readonly Injector $injector, 61 | private readonly bool $checkResponseBody = false, 62 | ) { 63 | } 64 | 65 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface 66 | { 67 | try { 68 | $response = $handler->handle($request); 69 | if ($this->checkResponseBody) { 70 | $response->getBody(); 71 | } 72 | } catch (Throwable $t) { 73 | foreach ($this->exceptionMap as $exceptionType => $responseHandler) { 74 | if ($t instanceof $exceptionType) { 75 | if (is_int($responseHandler)) { 76 | return $this->responseFactory->createResponse($responseHandler); 77 | } 78 | 79 | if (is_callable($responseHandler)) { 80 | /** @var ResponseInterface */ 81 | return $this->injector->invoke($responseHandler, ['exception' => $t]); 82 | } 83 | } 84 | } 85 | throw $t; 86 | } 87 | 88 | return $response; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Renderer/HeaderRenderer.php: -------------------------------------------------------------------------------- 1 | addContentTypeHeader([ 31 | 'X-Error-Message' => self::DEFAULT_ERROR_MESSAGE, 32 | ]), 33 | ); 34 | } 35 | 36 | public function renderVerbose(Throwable $t, ?ServerRequestInterface $request = null): ErrorData 37 | { 38 | return new ErrorData( 39 | '', 40 | $this->addContentTypeHeader([ 41 | 'X-Error-Type' => $t::class, 42 | 'X-Error-Message' => $t->getMessage(), 43 | 'X-Error-Code' => (string) $t->getCode(), 44 | 'X-Error-File' => $t->getFile(), 45 | 'X-Error-Line' => (string) $t->getLine(), 46 | ]), 47 | ); 48 | } 49 | 50 | /** 51 | * @param array $headers 52 | * @return array 53 | */ 54 | private function addContentTypeHeader(array $headers): array 55 | { 56 | if ($this->contentType !== null) { 57 | $headers[Header::CONTENT_TYPE] = $this->contentType; 58 | } 59 | return $headers; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Renderer/HtmlRenderer.php: -------------------------------------------------------------------------------- 1 | {icon} 98 | * ``` 99 | */ 100 | private readonly ?string $traceHeaderLine; 101 | 102 | /** 103 | * @var string[]|null The list of vendor paths is determined automatically. 104 | * 105 | * One path if the error handler is installed as a vendor package, or a list of package vendor paths 106 | * if the error handler is installed for development in {@link https://github.com/yiisoft/yii-dev-tool}. 107 | */ 108 | private ?array $vendorPaths = null; 109 | 110 | /** 111 | * @param array $settings (deprecated) Settings can have the following keys: 112 | * - template: string, full path of the template file for rendering exceptions without call stack information. 113 | * - verboseTemplate: string, full path of the template file for rendering exceptions with call stack information. 114 | * - maxSourceLines: int, maximum number of source code lines to be displayed. Defaults to 19. 115 | * - maxTraceLines: int, maximum number of trace source code lines to be displayed. Defaults to 13. 116 | * - traceHeaderLine: string, trace header line with placeholders to be substituted. Defaults to null. 117 | * @param string|null $template The full path of the template file for rendering exceptions without call stack 118 | * information. 119 | * @param string|null $verboseTemplate The full path of the template file for rendering exceptions with call stack 120 | * information. 121 | * @param int|null $maxSourceLines The maximum number of source code lines to be displayed. Defaults to 19. 122 | * @param int|null $maxTraceLines The maximum number of trace source code lines to be displayed. Defaults to 13. 123 | * @param string|null $traceHeaderLine The trace header line with placeholders to be substituted. Defaults to null. 124 | * 125 | * @psalm-param array{ 126 | * template?: string, 127 | * verboseTemplate?: string, 128 | * maxSourceLines?: int, 129 | * maxTraceLines?: int, 130 | * traceHeaderLine?: string, 131 | * } $settings 132 | */ 133 | public function __construct( 134 | array $settings = [], 135 | ?string $template = null, 136 | ?string $verboseTemplate = null, 137 | ?int $maxSourceLines = null, 138 | ?int $maxTraceLines = null, 139 | ?string $traceHeaderLine = null, 140 | ) { 141 | $this->markdownParser = new GithubMarkdown(); 142 | $this->markdownParser->html5 = true; 143 | 144 | $this->defaultTemplatePath = dirname(__DIR__, 2) . '/templates'; 145 | $this->template = $template 146 | ?? $settings['template'] 147 | ?? $this->defaultTemplatePath . '/production.php'; 148 | $this->verboseTemplate = $verboseTemplate 149 | ?? $settings['verboseTemplate'] 150 | ?? $this->defaultTemplatePath . '/development.php'; 151 | $this->maxSourceLines = $maxSourceLines 152 | ?? $settings['maxSourceLines'] 153 | ?? 19; 154 | $this->maxTraceLines = $maxTraceLines 155 | ?? $settings['maxTraceLines'] 156 | ?? 13; 157 | $this->traceHeaderLine = $traceHeaderLine 158 | ?? $settings['traceHeaderLine'] 159 | ?? null; 160 | } 161 | 162 | public function render(Throwable $t, ?ServerRequestInterface $request = null): ErrorData 163 | { 164 | return new ErrorData( 165 | $this->renderTemplate($this->template, [ 166 | 'request' => $request, 167 | 'throwable' => $t, 168 | ]), 169 | [Header::CONTENT_TYPE => self::CONTENT_TYPE], 170 | ); 171 | } 172 | 173 | public function renderVerbose(Throwable $t, ?ServerRequestInterface $request = null): ErrorData 174 | { 175 | return new ErrorData( 176 | $this->renderTemplate($this->verboseTemplate, [ 177 | 'request' => $request, 178 | 'throwable' => $t, 179 | ]), 180 | [Header::CONTENT_TYPE => self::CONTENT_TYPE], 181 | ); 182 | } 183 | 184 | /** 185 | * Encodes special characters into HTML entities for use as a content. 186 | * 187 | * @param string $content The content to be encoded. 188 | * 189 | * @return string Encoded content. 190 | */ 191 | public function htmlEncode(string $content): string 192 | { 193 | return htmlspecialchars($content, ENT_QUOTES, 'UTF-8'); 194 | } 195 | 196 | public function parseMarkdown(string $content): string 197 | { 198 | $html = $this->markdownParser->parse($content); 199 | /** 200 | * @psalm-suppress InvalidArgument 201 | * 202 | * @link https://github.com/vimeo/psalm/issues/4317 203 | */ 204 | return strip_tags($html, [ 205 | 'h1', 206 | 'h2', 207 | 'h3', 208 | 'h4', 209 | 'h5', 210 | 'h6', 211 | 'hr', 212 | 'pre', 213 | 'code', 214 | 'blockquote', 215 | 'table', 216 | 'tr', 217 | 'td', 218 | 'th', 219 | 'thead', 220 | 'tbody', 221 | 'strong', 222 | 'em', 223 | 'b', 224 | 'i', 225 | 'u', 226 | 's', 227 | 'span', 228 | 'a', 229 | 'p', 230 | 'br', 231 | 'nobr', 232 | 'ul', 233 | 'ol', 234 | 'li', 235 | 'img', 236 | ]); 237 | } 238 | 239 | /** 240 | * Renders the previous exception stack for a given Exception. 241 | * 242 | * @param Throwable $t The exception whose precursors should be rendered. 243 | * 244 | * @throws Throwable 245 | * 246 | * @return string HTML content of the rendered previous exceptions. Empty string if there are none. 247 | */ 248 | public function renderPreviousExceptions(Throwable $t): string 249 | { 250 | $templatePath = $this->defaultTemplatePath . '/_previous-exception.php'; 251 | 252 | if ($t instanceof CompositeException) { 253 | $result = []; 254 | foreach ($t->getPreviousExceptions() as $exception) { 255 | $result[] = $this->renderTemplate($templatePath, ['throwable' => $exception]); 256 | } 257 | return implode('', $result); 258 | } 259 | if ($t->getPrevious() !== null) { 260 | return $this->renderTemplate($templatePath, ['throwable' => $t->getPrevious()]); 261 | } 262 | 263 | return ''; 264 | } 265 | 266 | /** 267 | * Renders call stack. 268 | * 269 | * @param Throwable $t The exception to get call stack from. 270 | * 271 | * @throws Throwable 272 | * 273 | * @return string HTML content of the rendered call stack. 274 | * 275 | * @psalm-param DebugBacktraceType $trace 276 | */ 277 | public function renderCallStack(Throwable $t, array $trace = []): string 278 | { 279 | $application = $vendor = []; 280 | $application[1] = $this->renderCallStackItem( 281 | $t->getFile(), 282 | $t->getLine(), 283 | null, 284 | null, 285 | [], 286 | 1, 287 | false, 288 | [], 289 | ); 290 | 291 | $index = 1; 292 | if ($t instanceof ErrorException) { 293 | $index = 0; 294 | } 295 | 296 | foreach ($trace as $traceItem) { 297 | $file = !empty($traceItem['file']) ? $traceItem['file'] : null; 298 | $line = !empty($traceItem['line']) ? $traceItem['line'] : null; 299 | $class = !empty($traceItem['class']) ? $traceItem['class'] : null; 300 | $args = !empty($traceItem['args']) ? $traceItem['args'] : []; 301 | 302 | $parameters = []; 303 | $function = null; 304 | if (!empty($traceItem['function']) && $traceItem['function'] !== 'unknown') { 305 | $function = $traceItem['function']; 306 | if (!str_contains($function, '{closure}')) { 307 | try { 308 | if ($class !== null && class_exists($class)) { 309 | $parameters = (new \ReflectionMethod($class, $function))->getParameters(); 310 | } elseif (function_exists($function)) { 311 | $parameters = (new \ReflectionFunction($function))->getParameters(); 312 | } 313 | } catch (\ReflectionException) { 314 | // pass 315 | } 316 | } 317 | } 318 | $index++; 319 | 320 | if ($this->isVendorFile($file)) { 321 | $vendor[$index] = $this->renderCallStackItem( 322 | $file, 323 | $line, 324 | $class, 325 | $function, 326 | $args, 327 | $index, 328 | true, 329 | $parameters, 330 | ); 331 | } else { 332 | $application[$index] = $this->renderCallStackItem( 333 | $file, 334 | $line, 335 | $class, 336 | $function, 337 | $args, 338 | $index, 339 | false, 340 | $parameters, 341 | ); 342 | } 343 | } 344 | 345 | return $this->renderTemplate($this->defaultTemplatePath . '/_call-stack-items.php', [ 346 | 'applicationItems' => $application, 347 | 'vendorItemGroups' => $this->groupVendorCallStackItems($vendor), 348 | ]); 349 | } 350 | 351 | /** 352 | * Converts arguments array to its string representation. 353 | * 354 | * @param array $args arguments array to be converted 355 | * 356 | * @return string The string representation of the arguments array. 357 | */ 358 | public function argumentsToString(array $args, bool $truncate = true): string 359 | { 360 | $count = 0; 361 | $isAssoc = $args !== array_values($args); 362 | 363 | /** 364 | * @var mixed $value 365 | */ 366 | foreach ($args as $key => $value) { 367 | $count++; 368 | 369 | if ($truncate && $count >= 5) { 370 | if ($count > 5) { 371 | unset($args[$key]); 372 | } else { 373 | $args[$key] = '...'; 374 | } 375 | continue; 376 | } 377 | 378 | if (is_object($value)) { 379 | $args[$key] = '' . $this->htmlEncode($this->removeAnonymous($value::class) . '#' . spl_object_id($value)) . ''; 380 | } elseif (is_bool($value)) { 381 | $args[$key] = '' . ($value ? 'true' : 'false') . ''; 382 | } elseif (is_string($value)) { 383 | $fullValue = $this->htmlEncode($value); 384 | if ($truncate && mb_strlen($value, 'UTF-8') > 32) { 385 | $displayValue = $this->htmlEncode(mb_substr($value, 0, 32, 'UTF-8')) . '...'; 386 | $args[$key] = "'$displayValue'"; 387 | } else { 388 | $args[$key] = "'$fullValue'"; 389 | } 390 | } elseif (is_array($value)) { 391 | unset($args[$key]); 392 | $args[$key] = '[' . $this->argumentsToString($value, $truncate) . ']'; 393 | } elseif ($value === null) { 394 | $args[$key] = 'null'; 395 | } elseif (is_resource($value)) { 396 | $args[$key] = 'resource'; 397 | } else { 398 | $args[$key] = '' . (string) $value . ''; 399 | } 400 | 401 | if (is_string($key)) { 402 | $args[$key] = '\'' . $this->htmlEncode($key) . "' => $args[$key]"; 403 | } elseif ($isAssoc) { 404 | $args[$key] = "$key => $args[$key]"; 405 | } 406 | } 407 | 408 | /** @var string[] $args */ 409 | 410 | ksort($args); 411 | return implode(', ', $args); 412 | } 413 | 414 | /** 415 | * Renders the information about request. 416 | * 417 | * @return string The rendering result. 418 | */ 419 | public function renderRequest(ServerRequestInterface $request): string 420 | { 421 | $output = $request->getMethod() . ' ' . $request->getUri() . "\n"; 422 | 423 | $headers = $request->getHeaders(); 424 | unset($headers['Host']); 425 | ksort($headers); 426 | 427 | foreach ($headers as $name => $values) { 428 | foreach ($values as $value) { 429 | $output .= "$name: $value\n"; 430 | } 431 | } 432 | 433 | $body = (string)$request->getBody(); 434 | if (!empty($body)) { 435 | $output .= "\n" . $body . "\n\n"; 436 | } 437 | 438 | return $output; 439 | } 440 | 441 | /** 442 | * Renders the information about curl request. 443 | * 444 | * @return string The rendering result. 445 | */ 446 | public function renderCurl(ServerRequestInterface $request): string 447 | { 448 | try { 449 | $output = (new Command()) 450 | ->setRequest($request) 451 | ->build(); 452 | } catch (Throwable $e) { 453 | return 'Error generating curl command: ' . $e->getMessage(); 454 | } 455 | 456 | return $output; 457 | } 458 | 459 | /** 460 | * Creates string containing HTML link which refers to the home page 461 | * of determined web-server software and its full name. 462 | * 463 | * @return string The server software information hyperlink. 464 | */ 465 | public function createServerInformationLink(ServerRequestInterface $request): string 466 | { 467 | $serverSoftware = (string) ($request->getServerParams()['SERVER_SOFTWARE'] ?? ''); 468 | 469 | if ($serverSoftware === '') { 470 | return ''; 471 | } 472 | 473 | $serverUrls = [ 474 | 'https://httpd.apache.org/' => ['apache'], 475 | 'https://nginx.org/' => ['nginx'], 476 | 'https://lighttpd.net/' => ['lighttpd'], 477 | 'https://iis.net/' => ['iis', 'services'], 478 | 'https://www.php.net/manual/en/features.commandline.webserver.php' => ['development'], 479 | ]; 480 | 481 | foreach ($serverUrls as $url => $keywords) { 482 | foreach ($keywords as $keyword) { 483 | if (stripos($serverSoftware, $keyword) !== false) { 484 | return '' 485 | . $this->htmlEncode($serverSoftware) . ''; 486 | } 487 | } 488 | } 489 | 490 | return ''; 491 | } 492 | 493 | /** 494 | * Returns the name of the throwable instance. 495 | * 496 | * @return string The name of the throwable instance. 497 | */ 498 | public function getThrowableName(Throwable $throwable): string 499 | { 500 | $name = $throwable::class; 501 | 502 | if ($throwable instanceof FriendlyExceptionInterface) { 503 | $name = $throwable->getName() . ' (' . $name . ')'; 504 | } 505 | 506 | return $name; 507 | } 508 | 509 | /** 510 | * Renders a template. 511 | * 512 | * @param string $path The full path of the template file for rendering. 513 | * @param array $parameters The name-value pairs that will be extracted and made available in the template file. 514 | * 515 | * @throws Throwable 516 | * 517 | * @return string The rendering result. 518 | * 519 | * @psalm-suppress PossiblyInvalidFunctionCall 520 | * @psalm-suppress PossiblyFalseArgument 521 | * @psalm-suppress UnresolvableInclude 522 | */ 523 | private function renderTemplate(string $path, array $parameters): string 524 | { 525 | if (!file_exists($path)) { 526 | throw new RuntimeException("Template not found at $path"); 527 | } 528 | 529 | $renderer = function (): void { 530 | /** @psalm-suppress MixedArgument */ 531 | extract(func_get_arg(1), EXTR_OVERWRITE); 532 | require func_get_arg(0); 533 | }; 534 | 535 | $obInitialLevel = ob_get_level(); 536 | ob_start(); 537 | ob_implicit_flush(false); 538 | 539 | try { 540 | /** @psalm-suppress PossiblyNullFunctionCall */ 541 | $renderer->bindTo($this)($path, $parameters); 542 | return (string) ob_get_clean(); 543 | } catch (Throwable $e) { 544 | while (ob_get_level() > $obInitialLevel) { 545 | if (!@ob_end_clean()) { 546 | ob_clean(); 547 | } 548 | } 549 | throw $e; 550 | } 551 | } 552 | 553 | /** 554 | * Renders a single call stack element. 555 | * 556 | * @param string|null $file The name where call has happened. 557 | * @param int|null $line The number on which call has happened. 558 | * @param string|null $class The called class name. 559 | * @param string|null $function The called function/method name. 560 | * @param array $args The array of method arguments. 561 | * @param int $index The number of the call stack element. 562 | * @param bool $isVendorFile Whether given name of the file belongs to the vendor package. 563 | * 564 | * @throws Throwable 565 | * 566 | * @return string HTML content of the rendered call stack element. 567 | */ 568 | private function renderCallStackItem( 569 | ?string $file, 570 | ?int $line, 571 | ?string $class, 572 | ?string $function, 573 | array $args, 574 | int $index, 575 | bool $isVendorFile, 576 | array $reflectionParameters, 577 | ): string { 578 | $lines = []; 579 | $begin = $end = 0; 580 | 581 | if ($file !== null && $line !== null) { 582 | $line--; // adjust line number from one-based to zero-based 583 | $lines = @file($file); 584 | if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line) { 585 | return ''; 586 | } 587 | $half = (int) (($index === 1 ? $this->maxSourceLines : $this->maxTraceLines) / 2); 588 | $begin = $line - $half > 0 ? $line - $half : 0; 589 | $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1; 590 | } 591 | 592 | return $this->renderTemplate($this->defaultTemplatePath . '/_call-stack-item.php', [ 593 | 'file' => $file, 594 | 'line' => $line, 595 | 'class' => $class, 596 | 'function' => $function, 597 | 'index' => $index, 598 | 'lines' => $lines, 599 | 'begin' => $begin, 600 | 'end' => $end, 601 | 'args' => $args, 602 | 'isVendorFile' => $isVendorFile, 603 | 'reflectionParameters' => $reflectionParameters, 604 | ]); 605 | } 606 | 607 | /** 608 | * Groups a vendor call stack items to render. 609 | * 610 | * @param array $items The list of the vendor call stack items. 611 | * 612 | * @return array> The grouped items of the vendor call stack. 613 | */ 614 | private function groupVendorCallStackItems(array $items): array 615 | { 616 | $groupIndex = null; 617 | $groupedItems = []; 618 | 619 | foreach ($items as $index => $item) { 620 | if ($groupIndex === null) { 621 | $groupIndex = $index; 622 | $groupedItems[$groupIndex][$index] = $item; 623 | continue; 624 | } 625 | 626 | if (isset($items[$index - 1])) { 627 | $groupedItems[$groupIndex][$index] = $item; 628 | continue; 629 | } 630 | 631 | $groupIndex = $index; 632 | $groupedItems[$groupIndex][$index] = $item; 633 | } 634 | 635 | /** @psalm-var array> $groupedItems It's needed for Psalm <=4.30 only. */ 636 | 637 | return $groupedItems; 638 | } 639 | 640 | /** 641 | * Determines whether given name of the file belongs to the vendor package. 642 | * 643 | * @param string|null $file The name to be checked. 644 | * 645 | * @return bool Whether given name of the file belongs to the vendor package. 646 | */ 647 | private function isVendorFile(?string $file): bool 648 | { 649 | if ($file === null) { 650 | return false; 651 | } 652 | 653 | $file = realpath($file); 654 | 655 | if ($file === false) { 656 | return false; 657 | } 658 | 659 | foreach ($this->getVendorPaths() as $vendorPath) { 660 | if (str_starts_with($file, $vendorPath)) { 661 | return true; 662 | } 663 | } 664 | 665 | return false; 666 | } 667 | 668 | /** 669 | * Returns a list of vendor paths. 670 | * 671 | * @return string[] The list of vendor paths. 672 | * 673 | * @see $vendorPaths 674 | */ 675 | private function getVendorPaths(): array 676 | { 677 | if ($this->vendorPaths !== null) { 678 | return $this->vendorPaths; 679 | } 680 | 681 | $rootPath = dirname(__DIR__, 4); 682 | 683 | // If the error handler is installed as a vendor package. 684 | /** @psalm-suppress InvalidLiteralArgument It is Psalm bug, {@see https://github.com/vimeo/psalm/issues/9196} */ 685 | if (strlen($rootPath) > 6 && str_contains($rootPath, 'vendor')) { 686 | $this->vendorPaths = [$rootPath]; 687 | return $this->vendorPaths; 688 | } 689 | 690 | // If the error handler is installed for development in `yiisoft/yii-dev-tool`. 691 | if (is_file("{$rootPath}/yii-dev") || is_file("{$rootPath}/yii-dev.bat")) { 692 | $vendorPaths = glob("{$rootPath}/dev/*/vendor"); 693 | /** @var string[] */ 694 | $this->vendorPaths = empty($vendorPaths) ? [] : str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $vendorPaths); 695 | return $this->vendorPaths; 696 | } 697 | 698 | $this->vendorPaths = []; 699 | return $this->vendorPaths; 700 | } 701 | 702 | public function removeAnonymous(string $value): string 703 | { 704 | $anonymousPosition = strpos($value, '@anonymous'); 705 | 706 | return $anonymousPosition !== false ? substr($value, 0, $anonymousPosition) : $value; 707 | } 708 | } 709 | -------------------------------------------------------------------------------- /src/Renderer/JsonRenderer.php: -------------------------------------------------------------------------------- 1 | self::DEFAULT_ERROR_MESSAGE, 28 | ], 29 | JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES 30 | ), 31 | [Header::CONTENT_TYPE => self::CONTENT_TYPE], 32 | ); 33 | } 34 | 35 | public function renderVerbose(Throwable $t, ?ServerRequestInterface $request = null): ErrorData 36 | { 37 | return new ErrorData( 38 | json_encode( 39 | [ 40 | 'type' => $t::class, 41 | 'message' => $t->getMessage(), 42 | 'code' => $t->getCode(), 43 | 'file' => $t->getFile(), 44 | 'line' => $t->getLine(), 45 | 'trace' => $t->getTrace(), 46 | ], 47 | JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR 48 | ), 49 | [Header::CONTENT_TYPE => self::CONTENT_TYPE], 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Renderer/PlainTextRenderer.php: -------------------------------------------------------------------------------- 1 | $this->contentType], 30 | ); 31 | } 32 | 33 | public function renderVerbose(Throwable $t, ?ServerRequestInterface $request = null): ErrorData 34 | { 35 | return new ErrorData( 36 | self::throwableToString($t), 37 | [Header::CONTENT_TYPE => $this->contentType], 38 | ); 39 | } 40 | 41 | public static function throwableToString(Throwable $t): string 42 | { 43 | return sprintf( 44 | <<getMessage(), 54 | $t->getFile(), 55 | $t->getLine(), 56 | $t->getTraceAsString() 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Renderer/XmlRenderer.php: -------------------------------------------------------------------------------- 1 | '; 25 | $content .= "\n\n"; 26 | $content .= $this->tag('message', self::DEFAULT_ERROR_MESSAGE); 27 | $content .= ''; 28 | return new ErrorData( 29 | $content, 30 | [Header::CONTENT_TYPE => self::CONTENT_TYPE], 31 | ); 32 | } 33 | 34 | public function renderVerbose(Throwable $t, ?ServerRequestInterface $request = null): ErrorData 35 | { 36 | $content = ''; 37 | $content .= "\n\n"; 38 | $content .= $this->tag('type', $t::class); 39 | $content .= $this->tag('message', $this->cdata($t->getMessage())); 40 | $content .= $this->tag('code', $this->cdata((string) $t->getCode())); 41 | $content .= $this->tag('file', $t->getFile()); 42 | $content .= $this->tag('line', (string) $t->getLine()); 43 | $content .= $this->tag('trace', $t->getTraceAsString()); 44 | $content .= ''; 45 | return new ErrorData( 46 | $content, 47 | [Header::CONTENT_TYPE => self::CONTENT_TYPE], 48 | ); 49 | } 50 | 51 | private function tag(string $name, string $value): string 52 | { 53 | return "<$name>" . $value . "\n"; 54 | } 55 | 56 | private function cdata(string $value): string 57 | { 58 | return '', ']]]]>', $value) . ']]>'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/RendererProvider/ClosureRendererProvider.php: -------------------------------------------------------------------------------- 1 | |ThrowableRendererInterface|null) 18 | */ 19 | final class ClosureRendererProvider implements RendererProviderInterface 20 | { 21 | /** 22 | * @psalm-param TClosure $closure 23 | */ 24 | public function __construct( 25 | private readonly Closure $closure, 26 | private readonly ContainerInterface $container, 27 | ) { 28 | } 29 | 30 | public function get(ServerRequestInterface $request): ?ThrowableRendererInterface 31 | { 32 | $result = ($this->closure)($request); 33 | 34 | if (is_string($result)) { 35 | /** @var ThrowableRendererInterface */ 36 | return $this->container->get($result); 37 | } 38 | 39 | return $result; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/RendererProvider/CompositeRendererProvider.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | private readonly array $providers; 19 | 20 | /** 21 | * @no-named-arguments 22 | */ 23 | public function __construct(RendererProviderInterface ...$providers) 24 | { 25 | $this->providers = $providers; 26 | } 27 | 28 | public function get(ServerRequestInterface $request): ?ThrowableRendererInterface 29 | { 30 | foreach ($this->providers as $provider) { 31 | $renderer = $provider->get($request); 32 | if ($renderer !== null) { 33 | return $renderer; 34 | } 35 | } 36 | 37 | return null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/RendererProvider/ContentTypeRendererProvider.php: -------------------------------------------------------------------------------- 1 | > 27 | */ 28 | private readonly array $renderers; 29 | 30 | /** 31 | * @psalm-param array>|null $renderers 32 | */ 33 | public function __construct( 34 | private readonly ContainerInterface $container, 35 | ?array $renderers = null, 36 | ) { 37 | $this->renderers = $renderers ?? [ 38 | 'application/json' => JsonRenderer::class, 39 | 'application/xml' => XmlRenderer::class, 40 | 'text/xml' => XmlRenderer::class, 41 | 'text/plain' => PlainTextRenderer::class, 42 | 'text/html' => HtmlRenderer::class, 43 | '*/*' => HtmlRenderer::class, 44 | ]; 45 | } 46 | 47 | public function get(ServerRequestInterface $request): ?ThrowableRendererInterface 48 | { 49 | $rendererClass = $this->selectRendererClass($request); 50 | if ($rendererClass === null) { 51 | return null; 52 | } 53 | 54 | /** @var ThrowableRendererInterface */ 55 | return $this->container->get($rendererClass); 56 | } 57 | 58 | /** 59 | * @psalm-return class-string|null 60 | */ 61 | private function selectRendererClass(ServerRequestInterface $request): ?string 62 | { 63 | $acceptHeader = $request->getHeader(Header::ACCEPT); 64 | 65 | try { 66 | $contentTypes = HeaderValueHelper::getSortedAcceptTypes($acceptHeader); 67 | } catch (InvalidArgumentException) { 68 | // The "Accept" header contains an invalid "q" factor. 69 | return null; 70 | } 71 | 72 | foreach ($contentTypes as $contentType) { 73 | if (array_key_exists($contentType, $this->renderers)) { 74 | return $this->renderers[$contentType]; 75 | } 76 | } 77 | 78 | return null; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/RendererProvider/HeadRendererProvider.php: -------------------------------------------------------------------------------- 1 | getMethod() === Method::HEAD) { 23 | return new HeaderRenderer( 24 | $this->getAcceptContentType($request), 25 | ); 26 | } 27 | 28 | return null; 29 | } 30 | 31 | private function getAcceptContentType(ServerRequestInterface $request): ?string 32 | { 33 | $acceptHeader = $request->getHeader(Header::ACCEPT); 34 | 35 | try { 36 | $contentTypes = HeaderValueHelper::getSortedAcceptTypes($acceptHeader); 37 | } catch (InvalidArgumentException) { 38 | // The "Accept" header contains an invalid "q" factor. 39 | return null; 40 | } 41 | 42 | return empty($contentTypes) ? null : reset($contentTypes); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/RendererProvider/RendererProviderInterface.php: -------------------------------------------------------------------------------- 1 | headersProvider = $headersProvider ?? new HeadersProvider(); 28 | } 29 | 30 | public function create(Throwable $throwable, ServerRequestInterface $request): ResponseInterface 31 | { 32 | $renderer = $this->rendererProvider->get($request); 33 | 34 | $response = $this->responseFactory->createResponse(Status::INTERNAL_SERVER_ERROR); 35 | foreach ($this->headersProvider->getAll() as $name => $value) { 36 | $response = $response->withHeader($name, $value); 37 | } 38 | 39 | return $this->errorHandler 40 | ->handle($throwable, $renderer, $request) 41 | ->addToResponse($response); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ThrowableResponseFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 22 | Open the target page 23 | 24 | 25 | 26 | HTML; 27 | ?> 28 |
  • 30 |
    31 |
    32 | 33 | 34 | htmlEncode($file)}" ?> 35 | traceHeaderLine !== null): ?> 36 | traceHeaderLine, ['{file}' => $file, '{line}' => $line + 1, '{icon}' => $icon]) ?> 37 | 38 | 39 | 40 | 41 | 42 | 43 | removeAnonymous($class)}::$function"; 46 | 47 | echo '' . $this->htmlEncode($function) . ''; 48 | echo '('; 49 | echo '' . $this->argumentsToString($args, true) . ''; 50 | echo ''; 51 | echo ')'; 52 | ?> 53 | 54 | 55 |
    56 | 57 | 58 |
    59 | 60 | 61 | 62 | 63 |
    64 | 65 |
    66 | 92 | 93 |
    94 |
    95 |
    96 |
    97 |
    98 | 99 |
    htmlEncode($lines[$i]);
    103 |                         }
    104 |                         ?>
    105 |
    106 |
    107 |
    108 | 109 |
  • 110 | -------------------------------------------------------------------------------- /templates/_call-stack-items.php: -------------------------------------------------------------------------------- 1 | */ 3 | /* @var $vendorItemGroups array> */ 4 | /* @var $this \Yiisoft\ErrorHandler\Renderer\HtmlRenderer */ 5 | $insertItem = static function (array &$items, array $item, int $offset = 0): void { 6 | $itemIndex = array_key_first($item); 7 | foreach (array_keys($items) as $index) { 8 | $offset++; 9 | if ($index === ($itemIndex - 1)) { 10 | break; 11 | } 12 | } 13 | $items = array_slice($items, 0, $offset, true) + $item + array_slice($items, $offset, null, true); 14 | }; 15 | foreach ($vendorItemGroups as $key => $vendorItemGroup) { 16 | $count = count($vendorItemGroup); 17 | if ($count === 0) { 18 | continue; 19 | } 20 | if ($count === 1) { 21 | $insertItem($applicationItems, $vendorItemGroup); 22 | continue; 23 | } 24 | $firstIndex = array_key_first($vendorItemGroup); 25 | $lastIndex = array_key_last($vendorItemGroup); 26 | $itemsContent = implode('', $vendorItemGroup); 27 | $itemGroupContent = << 29 |
    30 |
    31 | + 32 | {$firstIndex} - {$lastIndex} Vendor package files ({$count}) 33 |
    34 |
    35 |
      36 | {$itemsContent} 37 |
    38 | 39 | HTML; 40 | $insertItem($applicationItems, [$firstIndex => $itemGroupContent]); 41 | } 42 | ?> 43 |
      44 | 45 | 46 | 47 |
    48 | -------------------------------------------------------------------------------- /templates/_previous-exception.php: -------------------------------------------------------------------------------- 1 | 5 | 17 | -------------------------------------------------------------------------------- /templates/development.css: -------------------------------------------------------------------------------- 1 | /* reset */ 2 | html, 3 | body, 4 | div, 5 | span, 6 | p, 7 | pre, 8 | a, 9 | code, 10 | em, 11 | img, 12 | strong, 13 | b, 14 | i, 15 | ul, 16 | li { 17 | margin: 0; 18 | padding: 0; 19 | border: 0; 20 | font: inherit; 21 | vertical-align: baseline; 22 | } 23 | 24 | body { 25 | font-family: 'Roboto', sans-serif; 26 | min-width: 800px; 27 | color: var(--page-text-color); 28 | background: var(--page-bg-color); 29 | line-height: 1; 30 | } 31 | 32 | ul { 33 | list-style: none; 34 | } 35 | /* end reset */ 36 | 37 | 38 | /* light theme */ 39 | :root { 40 | --page-bg-color: #fff; 41 | --page-text-color: #505050; 42 | --page-text-muted-color: #888; 43 | --icon-color: #505050; 44 | --icon-hover-color: #000; 45 | --table-line-even-bg: #fff; 46 | --table-line-even-color: #141414; 47 | --table-line-odd-bg: #eee; 48 | --table-line-odd-color: #141414; 49 | --table-line-hover: #ccc; 50 | --button-bg: #e3e5e7; 51 | --button-color: #333; 52 | --button-bg-hover: #c4c7cb; 53 | --button-color-hover: #333; 54 | } 55 | 56 | .table { 57 | border-collapse: collapse; 58 | width: 100%; 59 | } 60 | 61 | .table td, .table th { 62 | border: 1px solid #ddd; 63 | padding: 8px; 64 | } 65 | 66 | .table tr:nth-child(odd) { 67 | border-color: var(--table-line-odd-bg); 68 | background-color: var(--table-line-odd-bg); 69 | color: var(--table-line-odd-color); 70 | } 71 | 72 | .table tr:nth-child(even) { 73 | border-color: var(--table-line-even-bg); 74 | background-color: var(--table-line-even-bg); 75 | color: var(--table-line-even-color); 76 | } 77 | 78 | .table tr:hover { 79 | background-color: var(--table-line-hover); 80 | } 81 | 82 | 83 | .button { 84 | padding: 4px 8px; 85 | font-size: 14px; 86 | box-sizing: border-box; 87 | border: 0; 88 | background: var(--button-bg); 89 | color: var(--button-color); 90 | border-radius: 6px; 91 | cursor: pointer; 92 | } 93 | .button:hover { 94 | background: var(--button-bg-hover); 95 | color: var(--button-color-hover); 96 | } 97 | 98 | .functionArguments { 99 | padding: 20px 0; 100 | border-top: 1px solid var(--element-wrap-border-color); 101 | --function-arguments-bg: #f8f8f8; 102 | --function-arguments-border-color: #e3e3e3; 103 | } 104 | .dark-theme .functionArguments { 105 | --function-arguments-bg: #303030; 106 | --function-arguments-border-color: #272727; 107 | } 108 | .functionArguments_title { 109 | padding: 0 30px 12px; 110 | color: var(--page-text-muted-color); 111 | } 112 | .functionArguments table { 113 | padding: 4px 0; 114 | width: 100%; 115 | background: var(--function-arguments-bg); 116 | border-collapse: collapse; 117 | } 118 | .functionArguments td { 119 | padding: 12px 8px; 120 | border-top: 1px solid var(--function-arguments-border-color); 121 | } 122 | .functionArguments tr:first-child td { 123 | border-top: 0; 124 | } 125 | .functionArguments_key { 126 | padding-left: 30px !important; 127 | white-space: nowrap; 128 | } 129 | .functionArguments_type { 130 | color: var(--page-text-muted-color); 131 | white-space: nowrap; 132 | } 133 | .functionArguments_value { 134 | width: 100%; 135 | padding-right: 30px !important; 136 | } 137 | .toggleFunctionArguments { 138 | margin-top: 4px; 139 | } 140 | 141 | header { 142 | --header-bg-color: #ededed; 143 | --previous-text-color: inherit; 144 | --previous-arrow-color: #e51717; 145 | } 146 | 147 | header .solution { 148 | --text-color: var(--page-text-color); 149 | --link-color: #00617b; 150 | --link-hover-color: #1191b3; 151 | --blockquote-text-color: #707070; 152 | --blockquote-border-color: #e4e4e4; 153 | --code-bg-color: #f3f3f3; 154 | --pre-bg-color: #f3f3f3; 155 | --table-border-color: #e4e4e4; 156 | --separator-color: #ddd; 157 | } 158 | 159 | .exception-card { 160 | --exception-card-bg-color: #fafafa; 161 | --exception-card-border-color: #d83c24; 162 | --exception-class-text-color: #e51717; 163 | --exception-class-friendly-text-color: #E79185; 164 | --exception-class-friendly-link-color: #E57373; 165 | --exception-message-text-color: #4b4b4b; 166 | } 167 | 168 | .call-stack { 169 | --bg-color: #fff; 170 | --border-color: #d0d0d0; 171 | --box-shadow: 0 15px 20px rgba(0, 0, 0, 0.05); 172 | --link-color: #505050; 173 | --error-line-bg-color: #ffebeb; 174 | --hover-line-bg-color: #edf9ff; 175 | --element-wrap-border-color: #d0d0d0; 176 | --element-wrap-text-color: #4b4b4b; 177 | --element-wrap-hover-text-color: #086eb6; 178 | --vendor-bg-color: #ededed; 179 | --vendor-border-color: var(--border-color); 180 | --vendor-state-bg-color: #999; 181 | --vendor-content-bg-color: #ededed; 182 | } 183 | 184 | .hljs { 185 | --hljs-text-color: #2f3337; 186 | --hljs-comment-text-color: #656e77; 187 | --hljs-keyword-text-color: #015692; 188 | --hljs-attribute-text-color: #803378; 189 | --hljs-name-text-color: #b75501; 190 | --hljs-string-text-color: #54790d; 191 | --hljs-code-text-color: #535a60; 192 | --hljs-delition-text-color: #c02d2e; 193 | --hljs-addition-text-color: #2f6f44; 194 | } 195 | 196 | /* base */ 197 | a { 198 | text-decoration: none; 199 | } 200 | 201 | a:hover { 202 | text-decoration: underline; 203 | } 204 | /* end base */ 205 | 206 | /* header */ 207 | header { 208 | padding: 65px 100px 270px 100px; 209 | background: var(--header-bg-color); 210 | } 211 | 212 | header .tools { 213 | margin-bottom: 50px; 214 | text-align: right; 215 | } 216 | 217 | header .tools a { 218 | margin-left: 45px; 219 | text-decoration: none; 220 | } 221 | 222 | header .tools a:hover svg path { 223 | fill: var(--icon-hover-color); 224 | } 225 | 226 | header .exception-card { 227 | position: relative; 228 | background: var(--exception-card-bg-color); 229 | border: 2px solid var(--exception-card-border-color); 230 | box-sizing: border-box; 231 | border-radius: 3px; 232 | padding: 40px 30px; 233 | word-break: break-word; 234 | } 235 | 236 | header .exception-class { 237 | padding-right: 114px; 238 | margin-bottom: 30px; 239 | font-weight: 500; 240 | font-size: 36px; 241 | line-height: 42px; 242 | color: var(--exception-class-friendly-text-color); 243 | } 244 | 245 | header .exception-class a { 246 | color: var(--exception-class-friendly-link-color); 247 | } 248 | 249 | header .exception-class span, 250 | header .exception-class span a { 251 | color: var(--exception-class-text-color); 252 | } 253 | 254 | header .exception-message { 255 | font-size: 24px; 256 | line-height: 28px; 257 | color: var(--exception-message-text-color); 258 | } 259 | 260 | header .solution { 261 | margin-top: 24px; 262 | font-size: 16px; 263 | line-height: 22px; 264 | color: var(--text-color); 265 | } 266 | 267 | header .solution h1 { 268 | margin-top: 24px; 269 | font-size: 26px; 270 | line-height: 32px; 271 | font-weight: bold; 272 | } 273 | 274 | header .solution h2 { 275 | margin-top: 24px; 276 | font-size: 22px; 277 | line-height: 28px; 278 | font-weight: bold; 279 | } 280 | 281 | header .solution h3 { 282 | margin-top: 24px; 283 | font-size: 20px; 284 | line-height: 26px; 285 | font-weight: bold; 286 | } 287 | 288 | header .solution h4 { 289 | margin-top: 24px; 290 | font-size: 18px; 291 | line-height: 24px; 292 | font-weight: bold; 293 | } 294 | 295 | header .solution h5 { 296 | margin-top: 24px; 297 | font-size: 16px; 298 | line-height: 22px; 299 | font-weight: bold; 300 | } 301 | 302 | header .solution h6 { 303 | margin-top: 24px; 304 | font-size: 14px; 305 | line-height: 20px; 306 | font-weight: bold; 307 | } 308 | 309 | header .solution p { 310 | margin-top: 16px; 311 | } 312 | 313 | header .solution a { 314 | color: var(--link-color); 315 | text-decoration: underline; 316 | } 317 | header .solution a:hover { 318 | color: var(--link-hover-color); 319 | } 320 | 321 | header .solution h1:first-child, 322 | header .solution h2:first-child, 323 | header .solution h3:first-child, 324 | header .solution h4:first-child, 325 | header .solution h5:first-child, 326 | header .solution h6:first-child, 327 | header .solution p:first-child { 328 | margin-top: 0; 329 | } 330 | 331 | header .solution blockquote { 332 | margin: 18px 0 18px 4px; 333 | padding: 3px 0 2px 16px; 334 | border-left: 4px solid var(--blockquote-border-color); 335 | color: var(--blockquote-text-color); 336 | } 337 | 338 | header .solution ul, 339 | header .solution ol { 340 | padding: 0; 341 | margin: 16px 0 0 32px; 342 | } 343 | header .solution li ul, 344 | header .solution li ol { 345 | margin: 0 0 0 24px; 346 | } 347 | 348 | header .solution li { 349 | margin: 8px 0 0 0; 350 | } 351 | 352 | header .solution ul { 353 | list-style: outside; 354 | } 355 | 356 | header .solution pre, 357 | header .solution code { 358 | font-family: monospace; 359 | } 360 | 361 | header .solution code { 362 | padding: 2px 6px; 363 | font-size: 90%; 364 | background-color: var(--code-bg-color); 365 | border-radius: 6px; 366 | } 367 | 368 | header .solution pre { 369 | margin: 24px 0; 370 | width: 100%; 371 | box-sizing: border-box; 372 | overflow: auto; 373 | padding: 14px; 374 | border-radius: 8px; 375 | background: var(--pre-bg-color); 376 | } 377 | header .solution pre code { 378 | font-size: 100%; 379 | padding: 0; 380 | width: max-content; 381 | } 382 | 383 | header .solution table { 384 | margin: 16px 0 0 0; 385 | border-collapse: collapse; 386 | } 387 | header .solution td, 388 | header .solution th { 389 | padding: 6px 12px; 390 | border: 1px solid var(--table-border-color); 391 | } 392 | 393 | header .solution HR { 394 | margin: 24px 0; 395 | border: 1px solid var(--separator-color); 396 | border-width: 1px 0 0 0; 397 | } 398 | 399 | header .previous { 400 | display: flex; 401 | margin-top: 20px; 402 | color: var(--previous-text-color); 403 | } 404 | 405 | header .previous .arrow { 406 | display: inline-block; 407 | transform: scale(-1, 1); 408 | font-size: 26px; 409 | color: var(--previous-arrow-color); 410 | margin-top: -5px; 411 | margin-right: 10px; 412 | } 413 | 414 | header .previous h2 { 415 | font-size: 20px; 416 | color: #e57373; 417 | margin-bottom: 10px; 418 | } 419 | 420 | header .previous h2 span { 421 | color: var(--previous-arrow-color); 422 | } 423 | 424 | header .previous h3 { 425 | font-size: 14px; 426 | margin: 10px 0; 427 | } 428 | 429 | #clipboard { 430 | position: absolute; 431 | top: -500px; 432 | right: 300px; 433 | width: 750px; 434 | height: 150px; 435 | } 436 | 437 | .copy-clipboard { 438 | position: absolute; 439 | right: 40px; 440 | top: 44px; 441 | } 442 | 443 | .copy-clipboard:hover svg path { 444 | fill: var(--icon-hover-color); 445 | } 446 | 447 | #copied { 448 | display: none; 449 | position: absolute; 450 | right: 76px; 451 | top: 51px; 452 | } 453 | 454 | #light-mode { 455 | display: none; 456 | } 457 | /* end header */ 458 | 459 | main { 460 | margin-left: 100px; 461 | margin-right: 100px; 462 | } 463 | 464 | @media screen and (max-width: 1200px) { 465 | header { 466 | padding-left: 50px; 467 | padding-right: 50px; 468 | } 469 | 470 | main { 471 | margin-left: 50px; 472 | margin-right: 50px; 473 | } 474 | } 475 | 476 | .hidden { 477 | display: none; 478 | } 479 | 480 | .flex { 481 | display: flex; 482 | } 483 | 484 | .flex-column { 485 | flex-direction: column; 486 | } 487 | 488 | .flex-1 { 489 | flex: 1; 490 | } 491 | 492 | .mw-100 { 493 | max-width: 100%; 494 | } 495 | 496 | .w-100 { 497 | width: 100%; 498 | } 499 | 500 | .bold { 501 | font-weight: bold; 502 | } 503 | 504 | .word-break { 505 | overflow-wrap: break-word; 506 | word-break: break-word; 507 | } 508 | 509 | /* call stack */ 510 | .call-stack ul li, 511 | .request { 512 | border: 2px solid var(--border-color); 513 | box-shadow: var(--box-shadow); 514 | background: var(--bg-color); 515 | margin-bottom: 30px; 516 | border-radius: 3px; 517 | } 518 | 519 | .call-stack > ul > li:first-child { 520 | margin-top: -200px; 521 | } 522 | 523 | .call-stack > ul > li:last-child { 524 | margin-bottom: 50px; 525 | } 526 | 527 | .call-stack > ul > li.call-stack-vendor-group { 528 | border: 2px solid var(--vendor-border-color); 529 | background: var(--vendor-bg-color); 530 | } 531 | 532 | .call-stack > ul > li.call-stack-vendor-group .call-stack-vendor-state { 533 | display: inline-block; 534 | height: 22px; 535 | width: 22px; 536 | font-size: 20px; 537 | color: #fff; 538 | background: var(--vendor-state-bg-color); 539 | border-radius: 3px; 540 | text-align: center; 541 | margin-right: 15px; 542 | } 543 | 544 | .call-stack > ul > li.call-stack-vendor-group > ul { 545 | display: none; 546 | background: var(--vendor-content-bg-color); 547 | } 548 | 549 | .call-stack > ul > li.call-stack-vendor-group > ul > li { 550 | border-left: 0; 551 | border-right: 0; 552 | box-shadow: none; 553 | border-color: var(--vendor-border-color); 554 | } 555 | 556 | .call-stack > ul > li.call-stack-vendor-group > ul > li:last-child { 557 | border-bottom: 2px solid transparent; 558 | margin-bottom: 0; 559 | } 560 | 561 | .call-stack ul li .element-wrap { 562 | display: flex; 563 | cursor: pointer; 564 | padding: 20px 30px; 565 | font-weight: 500; 566 | font-size: 18px; 567 | line-height: 21px; 568 | color: var(--element-wrap-text-color); 569 | } 570 | 571 | .call-stack ul li .element-wrap .file-name { 572 | color: var(--element-wrap-text-color); 573 | } 574 | 575 | .call-stack ul li .element-wrap .file-name:hover, 576 | .call-stack ul li .element-code-wrap .code-wrap .lines-item:hover { 577 | color: var(--element-wrap-hover-text-color); 578 | } 579 | 580 | .call-stack ul li .arguments:hover { 581 | color: var(--element-wrap-hover-text-color); 582 | } 583 | 584 | .call-stack ul li .element-wrap .function-info { 585 | display: inline-block; 586 | line-break: normal; 587 | } 588 | 589 | .call-stack ul li a { 590 | color: var(--link-color); 591 | } 592 | 593 | .call-stack ul li a:hover { 594 | color: #000; 595 | } 596 | 597 | .call-stack ul li a .external-link { 598 | vertical-align: middle; 599 | } 600 | 601 | .call-stack ul li a .external-link:hover path { 602 | fill: var(--icon-hover-color); 603 | } 604 | 605 | .call-stack ul li a .external-link path { 606 | fill: var(--icon-color); 607 | } 608 | 609 | .call-stack ul li .element-code-wrap { 610 | border-top: 1px solid var(--element-wrap-border-color); 611 | overflow-x: auto; 612 | } 613 | 614 | .call-stack ul li .element-code-wrap .code-wrap { 615 | display: none; 616 | position: relative; 617 | } 618 | 619 | .call-stack ul li.application .element-code-wrap .code-wrap { 620 | display: block; 621 | } 622 | 623 | .call-stack ul li .error-line, 624 | .call-stack ul li .hover-line { 625 | background-color: var(--error-line-bg-color); 626 | position: absolute; 627 | width: 100%; 628 | z-index: 100; 629 | margin-top: 0; 630 | } 631 | 632 | .call-stack ul li .hover-line { 633 | background: none; 634 | } 635 | 636 | .call-stack ul li .hover-line.hover, 637 | .call-stack ul li .hover-line:hover { 638 | background: var(--hover-line-bg-color) !important; 639 | } 640 | 641 | .call-stack ul li .code { 642 | min-width: 700px; 643 | /* 800px - 50px * 2 */ 644 | margin: 15px auto; 645 | padding: 0 50px; 646 | position: relative; 647 | } 648 | 649 | .call-stack ul li .code .lines-item { 650 | position: absolute; 651 | z-index: 200; 652 | display: block; 653 | width: 25px; 654 | text-align: right; 655 | color: #aaa; 656 | line-height: 20px; 657 | font-size: 12px; 658 | margin-top: 1px; 659 | font-family: JetBrains Mono, Consolas, monospace; 660 | } 661 | 662 | .call-stack ul li .code pre { 663 | position: relative; 664 | z-index: 200; 665 | left: 50px; 666 | line-height: 20px; 667 | font-size: 12px; 668 | font-family: JetBrains Mono, Consolas, monospace; 669 | display: inline; 670 | } 671 | 672 | @-moz-document url-prefix() { 673 | .call-stack ul li .code pre { 674 | line-height: 20px; 675 | } 676 | } 677 | /* end call stack */ 678 | 679 | /* request */ 680 | .request { 681 | position: relative; 682 | font-size: 14px; 683 | line-height: 18px; 684 | overflow-x: auto; 685 | font-family: JetBrains Mono, Consolas, monospace; 686 | } 687 | .request .body { 688 | overflow: auto; 689 | } 690 | /* end request */ 691 | 692 | /* footer */ 693 | .footer { 694 | display: flex; 695 | } 696 | 697 | .footer div { 698 | align-self: center; 699 | } 700 | 701 | .footer .timestamp, 702 | .footer .server { 703 | margin-bottom: 20px; 704 | } 705 | 706 | .footer p, 707 | .footer p a { 708 | font-size: 24px; 709 | line-height: 28px; 710 | color: #9c9c9c; 711 | } 712 | 713 | .footer p a:hover { 714 | color: #000; 715 | } 716 | 717 | .footer svg { 718 | margin-right: -50px; 719 | } 720 | /* end footer */ 721 | 722 | /* highlight.js */ 723 | .hljs { 724 | display: block; 725 | color: var(--hljs-text-color); 726 | } 727 | 728 | .hljs-comment { 729 | color: var(--hljs-comment-text-color); 730 | } 731 | 732 | .hljs-keyword, 733 | .hljs-selector-tag, 734 | .hljs-meta-keyword, 735 | .hljs-doctag, 736 | .hljs-section, 737 | .hljs-selector-class, 738 | .hljs-meta, 739 | .hljs-selector-pseudo, 740 | .hljs-attr { 741 | color: var(--hljs-keyword-text-color); 742 | } 743 | 744 | .hljs-attribute { 745 | color: var(--hljs-attribute-text-color); 746 | } 747 | 748 | .hljs-name, 749 | .hljs-type, 750 | .hljs-number, 751 | .hljs-selector-id, 752 | .hljs-quote, 753 | .hljs-template-tag, 754 | .hljs-built_in, 755 | .hljs-title, 756 | .hljs-literal { 757 | color: var(--hljs-name-text-color); 758 | } 759 | 760 | .hljs-string, 761 | .hljs-regexp, 762 | .hljs-symbol, 763 | .hljs-variable, 764 | .hljs-template-variable, 765 | .hljs-link, 766 | .hljs-selector-attr, 767 | .hljs-meta-string { 768 | color: var(--hljs-string-text-color); 769 | } 770 | 771 | .hljs-bullet, 772 | .hljs-code { 773 | color: var(--hljs-code-text-color); 774 | } 775 | 776 | .hljs-deletion { 777 | color: var(--hljs-delition-text-color); 778 | } 779 | 780 | .hljs-addition { 781 | color: var(--hljs-addition-text-color); 782 | } 783 | 784 | .hljs-emphasis { 785 | font-style: italic; 786 | } 787 | 788 | .hljs-strong { 789 | font-weight: bold; 790 | } 791 | /* end highlight.js */ 792 | 793 | /* start dark-theme */ 794 | 795 | .dark-theme { 796 | --page-bg-color: rgba(46, 46, 46, 0.9); 797 | --page-text-color: #fff; 798 | --page-text-muted-color: #aaa; 799 | --icon-color: #989898; 800 | --icon-hover-color: #fff; 801 | 802 | --table-line-even-bg: #555; 803 | --table-line-even-color: #eee; 804 | --table-line-odd-bg: #999; 805 | --table-line-odd-color: #eee; 806 | --table-line-hover: #141414; 807 | 808 | --button-bg: #6c757d; 809 | --button-color: #fff; 810 | --button-bg-hover: #5c636a; 811 | --button-color-hover: #fff; 812 | } 813 | 814 | .dark-theme header { 815 | --header-bg-color: #2e2e2e; 816 | --previous-text-color: rgba(255, 255, 255, 0.8); 817 | --previous-arrow-color: #fff; 818 | } 819 | 820 | .dark-theme .exception-card { 821 | --exception-card-bg-color: #222; 822 | --exception-card-border-color: #591e15; 823 | --exception-class-text-color: #fff; 824 | --exception-class-friendly-text-color: rgba(255, 255, 255, 0.5); 825 | --exception-class-friendly-link-color: #E57373; 826 | --exception-message-text-color: rgba(255, 255, 255, 0.8); 827 | } 828 | 829 | .dark-theme header .solution { 830 | --text-color: rgba(255, 255, 255, 0.8); 831 | --link-color: #03a9f4; 832 | --link-hover-color: #39b9f3; 833 | --blockquote-text-color: #999; 834 | --blockquote-border-color: #484c50; 835 | --code-bg-color: #2d333b; 836 | --pre-bg-color: #2d333b; 837 | --table-border-color: #484c50; 838 | --separator-color: #484c50; 839 | } 840 | 841 | .dark-theme .call-stack { 842 | --bg-color: #1e1e1e; 843 | --border-color: transparent; 844 | --box-shadow: 0 13px 20px rgba(0, 0, 0, 0.25); 845 | --link-color: rgba(255, 255, 255, 0.5); 846 | --error-line-bg-color: #422c2c; 847 | --hover-line-bg-color: #292929; 848 | --element-wrap-border-color: #141414; 849 | --element-wrap-text-color: #fff; 850 | --element-wrap-hover-text-color: #9cdcfe; 851 | --vendor-bg-color: rgba(46,46,46, 0.9); 852 | --vendor-border-color: #666; 853 | --vendor-state-bg-color: #666; 854 | --vendor-content-bg-color: rgba(46,46,46, 0.9); 855 | } 856 | 857 | .dark-theme .hljs { 858 | --hljs-text-color: #fff; 859 | --hljs-comment-text-color: #999; 860 | --hljs-keyword-text-color: #88aece; 861 | --hljs-attribute-text-color: #c59bc1; 862 | --hljs-name-text-color: #f08d49; 863 | --hljs-string-text-color: #b5bd68; 864 | --hljs-code-text-color: #cccccc; 865 | --hljs-delition-text-color: #de7176; 866 | --hljs-addition-text-color: #76c490; 867 | } 868 | 869 | .dark-theme #dark-mode { 870 | display: none; 871 | } 872 | .dark-theme #light-mode { 873 | display: inline; 874 | } 875 | 876 | @media (prefers-color-scheme: dark) { 877 | body:not(.light-theme) { 878 | --page-bg-color: rgba(46, 46, 46, 0.9); 879 | --page-text-color: #fff; 880 | --page-text-muted-color: #aaa; 881 | --icon-color: #989898; 882 | --icon-hover-color: #fff; 883 | 884 | --table-line-even-bg: #555; 885 | --table-line-even-color: #eee; 886 | --table-line-odd-bg: #999; 887 | --table-line-odd-color: #eee; 888 | --table-line-hover: #141414; 889 | 890 | --button-bg: #6c757d; 891 | --button-color: #fff; 892 | --button-bg-hover: #5c636a; 893 | --button-color-hover: #fff; 894 | } 895 | 896 | body:not(.light-theme) header { 897 | --header-bg-color: #2e2e2e; 898 | --previous-text-color: rgba(255, 255, 255, 0.8); 899 | --previous-arrow-color: #fff; 900 | } 901 | 902 | body:not(.light-theme) .exception-card { 903 | --exception-card-bg-color: #222; 904 | --exception-card-border-color: #591e15; 905 | --exception-class-text-color: #fff; 906 | --exception-class-friendly-text-color: rgba(255, 255, 255, 0.5); 907 | --exception-class-friendly-link-color: #E57373; 908 | --exception-message-text-color: rgba(255, 255, 255, 0.8); 909 | } 910 | 911 | body:not(.light-theme) header .solution { 912 | --text-color: rgba(255, 255, 255, 0.8); 913 | --link-color: #03a9f4; 914 | --link-hover-color: #39b9f3; 915 | --blockquote-text-color: #999; 916 | --blockquote-border-color: #484c50; 917 | --code-bg-color: #2d333b; 918 | --pre-bg-color: #2d333b; 919 | --table-border-color: #484c50; 920 | --separator-color: #484c50; 921 | } 922 | 923 | body:not(.light-theme) .call-stack { 924 | --bg-color: #1e1e1e; 925 | --border-color: transparent; 926 | --box-shadow: 0 13px 20px rgba(0, 0, 0, 0.25); 927 | --link-color: rgba(255, 255, 255, 0.5); 928 | --error-line-bg-color: #422c2c; 929 | --hover-line-bg-color: #292929; 930 | --element-wrap-border-color: #141414; 931 | --element-wrap-text-color: #fff; 932 | --element-wrap-hover-text-color: #9cdcfe; 933 | --vendor-bg-color: rgba(46,46,46, 0.9); 934 | --vendor-border-color: #666; 935 | --vendor-state-bg-color: #666; 936 | --vendor-content-bg-color: rgba(46,46,46, 0.9); 937 | } 938 | 939 | body:not(.light-theme) .functionArguments { 940 | --function-arguments-bg: #303030; 941 | --function-arguments-border-color: #272727; 942 | } 943 | body:not(.light-theme) .hljs { 944 | --hljs-text-color: #fff; 945 | --hljs-comment-text-color: #999; 946 | --hljs-keyword-text-color: #88aece; 947 | --hljs-attribute-text-color: #c59bc1; 948 | --hljs-name-text-color: #f08d49; 949 | --hljs-string-text-color: #b5bd68; 950 | --hljs-code-text-color: #cccccc; 951 | --hljs-delition-text-color: #de7176; 952 | --hljs-addition-text-color: #76c490; 953 | } 954 | 955 | body:not(.light-theme) #dark-mode { 956 | display: none; 957 | } 958 | 959 | body:not(.light-theme) #light-mode { 960 | display: inline; 961 | } 962 | } 963 | 964 | /* end dark-theme */ 965 | -------------------------------------------------------------------------------- /templates/development.php: -------------------------------------------------------------------------------- 1 | getFirstException(); 20 | } 21 | $isFriendlyException = $throwable instanceof FriendlyExceptionInterface; 22 | $solution = $isFriendlyException ? $throwable->getSolution() : null; 23 | $exceptionClass = get_class($throwable); 24 | $exceptionMessage = $throwable->getMessage(); 25 | 26 | ?> 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | <?= $this->htmlEncode($this->getThrowableName($throwable)) ?> 35 | 36 | 39 | 40 | 50 | 51 | htmlEncode($theme)}\"" : '' ?>> 52 |
    53 | 77 | 78 |
    79 |
    80 | 82 | htmlEncode($throwable->getName())?> 83 | — 84 | 85 | 86 | 87 | 88 | (Code #getCode() ?>) 89 |
    90 | 91 |
    92 | htmlEncode($exceptionMessage)) ?> 93 |
    94 | 95 | 96 |
    parseMarkdown($solution) ?>
    97 | 98 | 99 | renderPreviousExceptions($originalException) ?> 100 | 101 | 102 | Copied! 103 | 104 | 109 | 110 | 111 | 112 | 113 |
    114 |
    115 | 116 |
    117 |
    118 | renderCallStack( 119 | $throwable, 120 | $originalException === $throwable && $originalException instanceof ErrorException 121 | ? $originalException->getBacktrace() 122 | : $throwable->getTrace() 123 | ) ?> 124 |
    125 | renderRequest($request)) !== ''): ?> 126 |
    127 |

    Request info

    128 |
    129 |
    htmlEncode(rtrim($requestInfo, "\n")) ?>
    130 |
    131 |
    132 | 133 | renderCurl($request)) !== 'curl'): ?> 134 |
    135 | 136 | Copied! 137 |

    cURL

    138 | 144 | 145 | 146 | 147 | 148 |
    149 |
    htmlEncode($curlInfo) ?>
    150 |
    151 |
    152 | 153 | 198 |
    199 | 200 | 203 | 414 | 415 | 416 | 417 | -------------------------------------------------------------------------------- /templates/highlight.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Highlight.js v11.5.1 (git: b8f233c8e2) 3 | (c) 2006-2022 Ivan Sagalaev and other contributors 4 | License: BSD-3-Clause 5 | */ 6 | var hljs=function(){"use strict";var e={exports:{}};function t(e){ 7 | return e instanceof Map?e.clear=e.delete=e.set=()=>{ 8 | throw Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=()=>{ 9 | throw Error("set is read-only") 10 | }),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((n=>{var i=e[n] 11 | ;"object"!=typeof i||Object.isFrozen(i)||t(i)})),e} 12 | e.exports=t,e.exports.default=t;var n=e.exports;class i{constructor(e){ 13 | void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} 14 | ignoreMatch(){this.isMatchIgnored=!0}}function r(e){ 15 | return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") 16 | }function s(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] 17 | ;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const o=e=>!!e.kind 18 | ;class a{constructor(e,t){ 19 | this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ 20 | this.buffer+=r(e)}openNode(e){if(!o(e))return;let t=e.kind 21 | ;t=e.sublanguage?"language-"+t:((e,{prefix:t})=>{if(e.includes(".")){ 22 | const n=e.split(".") 23 | ;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ") 24 | }return`${t}${e}`})(t,{prefix:this.classPrefix}),this.span(t)}closeNode(e){ 25 | o(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ 26 | this.buffer+=``}}class c{constructor(){this.rootNode={ 27 | children:[]},this.stack=[this.rootNode]}get top(){ 28 | return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ 29 | this.top.children.push(e)}openNode(e){const t={kind:e,children:[]} 30 | ;this.add(t),this.stack.push(t)}closeNode(){ 31 | if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ 32 | for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} 33 | walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ 34 | return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), 35 | t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ 36 | "string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ 37 | c._collapse(e)})))}}class l extends c{constructor(e){super(),this.options=e} 38 | addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())} 39 | addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root 40 | ;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){ 41 | return new a(this,this.options).value()}finalize(){return!0}}function g(e){ 42 | return e?"string"==typeof e?e:e.source:null}function d(e){return f("(?=",e,")")} 43 | function u(e){return f("(?:",e,")*")}function h(e){return f("(?:",e,")?")} 44 | function f(...e){return e.map((e=>g(e))).join("")}function p(...e){const t=(e=>{ 45 | const t=e[e.length-1] 46 | ;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{} 47 | })(e);return"("+(t.capture?"":"?:")+e.map((e=>g(e))).join("|")+")"} 48 | function b(e){return RegExp(e.toString()+"|").exec("").length-1} 49 | const m=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ 50 | ;function E(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n 51 | ;let i=g(e),r="";for(;i.length>0;){const e=m.exec(i);if(!e){r+=i;break} 52 | r+=i.substring(0,e.index), 53 | i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?r+="\\"+(Number(e[1])+t):(r+=e[0], 54 | "("===e[0]&&n++)}return r})).map((e=>`(${e})`)).join(t)} 55 | const x="[a-zA-Z]\\w*",w="[a-zA-Z_]\\w*",y="\\b\\d+(\\.\\d+)?",_="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",k="\\b(0b[01]+)",v={ 56 | begin:"\\\\[\\s\\S]",relevance:0},O={scope:"string",begin:"'",end:"'", 57 | illegal:"\\n",contains:[v]},N={scope:"string",begin:'"',end:'"',illegal:"\\n", 58 | contains:[v]},M=(e,t,n={})=>{const i=s({scope:"comment",begin:e,end:t, 59 | contains:[]},n);i.contains.push({scope:"doctag", 60 | begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", 61 | end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) 62 | ;const r=p("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) 63 | ;return i.contains.push({begin:f(/[ ]+/,"(",r,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i 64 | },S=M("//","$"),R=M("/\\*","\\*/"),j=M("#","$");var A=Object.freeze({ 65 | __proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:x,UNDERSCORE_IDENT_RE:w, 66 | NUMBER_RE:y,C_NUMBER_RE:_,BINARY_NUMBER_RE:k, 67 | RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", 68 | SHEBANG:(e={})=>{const t=/^#![ ]*\// 69 | ;return e.binary&&(e.begin=f(t,/.*\b/,e.binary,/\b.*/)),s({scope:"meta",begin:t, 70 | end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)}, 71 | BACKSLASH_ESCAPE:v,APOS_STRING_MODE:O,QUOTE_STRING_MODE:N,PHRASAL_WORDS_MODE:{ 72 | begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ 73 | },COMMENT:M,C_LINE_COMMENT_MODE:S,C_BLOCK_COMMENT_MODE:R,HASH_COMMENT_MODE:j, 74 | NUMBER_MODE:{scope:"number",begin:y,relevance:0},C_NUMBER_MODE:{scope:"number", 75 | begin:_,relevance:0},BINARY_NUMBER_MODE:{scope:"number",begin:k,relevance:0}, 76 | REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{scope:"regexp",begin:/\//, 77 | end:/\/[gimuy]*/,illegal:/\n/,contains:[v,{begin:/\[/,end:/\]/,relevance:0, 78 | contains:[v]}]}]},TITLE_MODE:{scope:"title",begin:x,relevance:0}, 79 | UNDERSCORE_TITLE_MODE:{scope:"title",begin:w,relevance:0},METHOD_GUARD:{ 80 | begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{ 81 | "on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ 82 | t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function I(e,t){ 83 | "."===e.input[e.index-1]&&t.ignoreMatch()}function T(e,t){ 84 | void 0!==e.className&&(e.scope=e.className,delete e.className)}function L(e,t){ 85 | t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", 86 | e.__beforeBegin=I,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, 87 | void 0===e.relevance&&(e.relevance=0))}function B(e,t){ 88 | Array.isArray(e.illegal)&&(e.illegal=p(...e.illegal))}function D(e,t){ 89 | if(e.match){ 90 | if(e.begin||e.end)throw Error("begin & end are not supported with match") 91 | ;e.begin=e.match,delete e.match}}function H(e,t){ 92 | void 0===e.relevance&&(e.relevance=1)}const P=(e,t)=>{if(!e.beforeMatch)return 93 | ;if(e.starts)throw Error("beforeMatch cannot be used with starts") 94 | ;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t] 95 | })),e.keywords=n.keywords,e.begin=f(n.beforeMatch,d(n.begin)),e.starts={ 96 | relevance:0,contains:[Object.assign(n,{endsParent:!0})] 97 | },e.relevance=0,delete n.beforeMatch 98 | },C=["of","and","for","in","not","or","if","then","parent","list","value"] 99 | ;function $(e,t,n="keyword"){const i=Object.create(null) 100 | ;return"string"==typeof e?r(n,e.split(" ")):Array.isArray(e)?r(n,e):Object.keys(e).forEach((n=>{ 101 | Object.assign(i,$(e[n],t,n))})),i;function r(e,n){ 102 | t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") 103 | ;i[n[0]]=[e,U(n[0],n[1])]}))}}function U(e,t){ 104 | return t?Number(t):(e=>C.includes(e.toLowerCase()))(e)?0:1}const z={},K=e=>{ 105 | console.error(e)},W=(e,...t)=>{console.log("WARN: "+e,...t)},X=(e,t)=>{ 106 | z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),z[`${e}/${t}`]=!0) 107 | },G=Error();function Z(e,t,{key:n}){let i=0;const r=e[n],s={},o={} 108 | ;for(let e=1;e<=t.length;e++)o[e+i]=r[e],s[e+i]=!0,i+=b(t[e-1]) 109 | ;e[n]=o,e[n]._emit=s,e[n]._multi=!0}function F(e){(e=>{ 110 | e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, 111 | delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ 112 | _wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope 113 | }),(e=>{if(Array.isArray(e.begin)){ 114 | if(e.skip||e.excludeBegin||e.returnBegin)throw K("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), 115 | G 116 | ;if("object"!=typeof e.beginScope||null===e.beginScope)throw K("beginScope must be object"), 117 | G;Z(e,e.begin,{key:"beginScope"}),e.begin=E(e.begin,{joinWith:""})}})(e),(e=>{ 118 | if(Array.isArray(e.end)){ 119 | if(e.skip||e.excludeEnd||e.returnEnd)throw K("skip, excludeEnd, returnEnd not compatible with endScope: {}"), 120 | G 121 | ;if("object"!=typeof e.endScope||null===e.endScope)throw K("endScope must be object"), 122 | G;Z(e,e.end,{key:"endScope"}),e.end=E(e.end,{joinWith:""})}})(e)}function V(e){ 123 | function t(t,n){ 124 | return RegExp(g(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":"")) 125 | }class n{constructor(){ 126 | this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} 127 | addRule(e,t){ 128 | t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), 129 | this.matchAt+=b(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) 130 | ;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(E(e,{joinWith:"|" 131 | }),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex 132 | ;const t=this.matcherRe.exec(e);if(!t)return null 133 | ;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n] 134 | ;return t.splice(0,n),Object.assign(t,i)}}class i{constructor(){ 135 | this.rules=[],this.multiRegexes=[], 136 | this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ 137 | if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n 138 | ;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), 139 | t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ 140 | return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ 141 | this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ 142 | const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex 143 | ;let n=t.exec(e) 144 | ;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ 145 | const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} 146 | return n&&(this.regexIndex+=n.position+1, 147 | this.regexIndex===this.count&&this.considerAll()),n}} 148 | if(e.compilerExtensions||(e.compilerExtensions=[]), 149 | e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") 150 | ;return e.classNameAliases=s(e.classNameAliases||{}),function n(r,o){const a=r 151 | ;if(r.isCompiled)return a 152 | ;[T,D,F,P].forEach((e=>e(r,o))),e.compilerExtensions.forEach((e=>e(r,o))), 153 | r.__beforeBegin=null,[L,B,H].forEach((e=>e(r,o))),r.isCompiled=!0;let c=null 154 | ;return"object"==typeof r.keywords&&r.keywords.$pattern&&(r.keywords=Object.assign({},r.keywords), 155 | c=r.keywords.$pattern, 156 | delete r.keywords.$pattern),c=c||/\w+/,r.keywords&&(r.keywords=$(r.keywords,e.case_insensitive)), 157 | a.keywordPatternRe=t(c,!0), 158 | o&&(r.begin||(r.begin=/\B|\b/),a.beginRe=t(a.begin),r.end||r.endsWithParent||(r.end=/\B|\b/), 159 | r.end&&(a.endRe=t(a.end)), 160 | a.terminatorEnd=g(a.end)||"",r.endsWithParent&&o.terminatorEnd&&(a.terminatorEnd+=(r.end?"|":"")+o.terminatorEnd)), 161 | r.illegal&&(a.illegalRe=t(r.illegal)), 162 | r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>s(e,{ 163 | variants:null},t)))),e.cachedVariants?e.cachedVariants:q(e)?s(e,{ 164 | starts:e.starts?s(e.starts):null 165 | }):Object.isFrozen(e)?s(e):e))("self"===e?r:e)))),r.contains.forEach((e=>{n(e,a) 166 | })),r.starts&&n(r.starts,o),a.matcher=(e=>{const t=new i 167 | ;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" 168 | }))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" 169 | }),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function q(e){ 170 | return!!e&&(e.endsWithParent||q(e.starts))}class J extends Error{ 171 | constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}} 172 | const Y=r,Q=s,ee=Symbol("nomatch");var te=(e=>{ 173 | const t=Object.create(null),r=Object.create(null),s=[];let o=!0 174 | ;const a="Could not find the language '{}', did you forget to load/include a language module?",c={ 175 | disableAutodetect:!0,name:"Plain text",contains:[]};let g={ 176 | ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, 177 | languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", 178 | cssSelector:"pre code",languages:null,__emitter:l};function b(e){ 179 | return g.noHighlightRe.test(e)}function m(e,t,n){let i="",r="" 180 | ;"object"==typeof t?(i=e, 181 | n=t.ignoreIllegals,r=t.language):(X("10.7.0","highlight(lang, code, ...args) has been deprecated."), 182 | X("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), 183 | r=e,i=t),void 0===n&&(n=!0);const s={code:i,language:r};N("before:highlight",s) 184 | ;const o=s.result?s.result:E(s.language,s.code,n) 185 | ;return o.code=s.code,N("after:highlight",o),o}function E(e,n,r,s){ 186 | const c=Object.create(null);function l(){if(!O.keywords)return void M.addText(S) 187 | ;let e=0;O.keywordPatternRe.lastIndex=0;let t=O.keywordPatternRe.exec(S),n="" 188 | ;for(;t;){n+=S.substring(e,t.index) 189 | ;const r=y.case_insensitive?t[0].toLowerCase():t[0],s=(i=r,O.keywords[i]);if(s){ 190 | const[e,i]=s 191 | ;if(M.addText(n),n="",c[r]=(c[r]||0)+1,c[r]<=7&&(R+=i),e.startsWith("_"))n+=t[0];else{ 192 | const n=y.classNameAliases[e]||e;M.addKeyword(t[0],n)}}else n+=t[0] 193 | ;e=O.keywordPatternRe.lastIndex,t=O.keywordPatternRe.exec(S)}var i 194 | ;n+=S.substr(e),M.addText(n)}function d(){null!=O.subLanguage?(()=>{ 195 | if(""===S)return;let e=null;if("string"==typeof O.subLanguage){ 196 | if(!t[O.subLanguage])return void M.addText(S) 197 | ;e=E(O.subLanguage,S,!0,N[O.subLanguage]),N[O.subLanguage]=e._top 198 | }else e=x(S,O.subLanguage.length?O.subLanguage:null) 199 | ;O.relevance>0&&(R+=e.relevance),M.addSublanguage(e._emitter,e.language) 200 | })():l(),S=""}function u(e,t){let n=1;const i=t.length-1;for(;n<=i;){ 201 | if(!e._emit[n]){n++;continue}const i=y.classNameAliases[e[n]]||e[n],r=t[n] 202 | ;i?M.addKeyword(r,i):(S=r,l(),S=""),n++}}function h(e,t){ 203 | return e.scope&&"string"==typeof e.scope&&M.openNode(y.classNameAliases[e.scope]||e.scope), 204 | e.beginScope&&(e.beginScope._wrap?(M.addKeyword(S,y.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), 205 | S=""):e.beginScope._multi&&(u(e.beginScope,t),S="")),O=Object.create(e,{parent:{ 206 | value:O}}),O}function f(e,t,n){let r=((e,t)=>{const n=e&&e.exec(t) 207 | ;return n&&0===n.index})(e.endRe,n);if(r){if(e["on:end"]){const n=new i(e) 208 | ;e["on:end"](t,n),n.isMatchIgnored&&(r=!1)}if(r){ 209 | for(;e.endsParent&&e.parent;)e=e.parent;return e}} 210 | if(e.endsWithParent)return f(e.parent,t,n)}function p(e){ 211 | return 0===O.matcher.regexIndex?(S+=e[0],1):(I=!0,0)}function b(e){ 212 | const t=e[0],i=n.substr(e.index),r=f(O,e,i);if(!r)return ee;const s=O 213 | ;O.endScope&&O.endScope._wrap?(d(), 214 | M.addKeyword(t,O.endScope._wrap)):O.endScope&&O.endScope._multi?(d(), 215 | u(O.endScope,e)):s.skip?S+=t:(s.returnEnd||s.excludeEnd||(S+=t), 216 | d(),s.excludeEnd&&(S=t));do{ 217 | O.scope&&M.closeNode(),O.skip||O.subLanguage||(R+=O.relevance),O=O.parent 218 | }while(O!==r.parent);return r.starts&&h(r.starts,e),s.returnEnd?0:t.length} 219 | let m={};function w(t,s){const a=s&&s[0];if(S+=t,null==a)return d(),0 220 | ;if("begin"===m.type&&"end"===s.type&&m.index===s.index&&""===a){ 221 | if(S+=n.slice(s.index,s.index+1),!o){const t=Error(`0 width match regex (${e})`) 222 | ;throw t.languageName=e,t.badRule=m.rule,t}return 1} 223 | if(m=s,"begin"===s.type)return(e=>{ 224 | const t=e[0],n=e.rule,r=new i(n),s=[n.__beforeBegin,n["on:begin"]] 225 | ;for(const n of s)if(n&&(n(e,r),r.isMatchIgnored))return p(t) 226 | ;return n.skip?S+=t:(n.excludeBegin&&(S+=t), 227 | d(),n.returnBegin||n.excludeBegin||(S=t)),h(n,e),n.returnBegin?0:t.length})(s) 228 | ;if("illegal"===s.type&&!r){ 229 | const e=Error('Illegal lexeme "'+a+'" for mode "'+(O.scope||"")+'"') 230 | ;throw e.mode=O,e}if("end"===s.type){const e=b(s);if(e!==ee)return e} 231 | if("illegal"===s.type&&""===a)return 1 232 | ;if(A>1e5&&A>3*s.index)throw Error("potential infinite loop, way more iterations than matches") 233 | ;return S+=a,a.length}const y=k(e) 234 | ;if(!y)throw K(a.replace("{}",e)),Error('Unknown language: "'+e+'"') 235 | ;const _=V(y);let v="",O=s||_;const N={},M=new g.__emitter(g);(()=>{const e=[] 236 | ;for(let t=O;t!==y;t=t.parent)t.scope&&e.unshift(t.scope) 237 | ;e.forEach((e=>M.openNode(e)))})();let S="",R=0,j=0,A=0,I=!1;try{ 238 | for(O.matcher.considerAll();;){ 239 | A++,I?I=!1:O.matcher.considerAll(),O.matcher.lastIndex=j 240 | ;const e=O.matcher.exec(n);if(!e)break;const t=w(n.substring(j,e.index),e) 241 | ;j=e.index+t}return w(n.substr(j)),M.closeAllNodes(),M.finalize(),v=M.toHTML(),{ 242 | language:e,value:v,relevance:R,illegal:!1,_emitter:M,_top:O}}catch(t){ 243 | if(t.message&&t.message.includes("Illegal"))return{language:e,value:Y(n), 244 | illegal:!0,relevance:0,_illegalBy:{message:t.message,index:j, 245 | context:n.slice(j-100,j+100),mode:t.mode,resultSoFar:v},_emitter:M};if(o)return{ 246 | language:e,value:Y(n),illegal:!1,relevance:0,errorRaised:t,_emitter:M,_top:O} 247 | ;throw t}}function x(e,n){n=n||g.languages||Object.keys(t);const i=(e=>{ 248 | const t={value:Y(e),illegal:!1,relevance:0,_top:c,_emitter:new g.__emitter(g)} 249 | ;return t._emitter.addText(e),t})(e),r=n.filter(k).filter(O).map((t=>E(t,e,!1))) 250 | ;r.unshift(i);const s=r.sort(((e,t)=>{ 251 | if(e.relevance!==t.relevance)return t.relevance-e.relevance 252 | ;if(e.language&&t.language){if(k(e.language).supersetOf===t.language)return 1 253 | ;if(k(t.language).supersetOf===e.language)return-1}return 0})),[o,a]=s,l=o 254 | ;return l.secondBest=a,l}function w(e){let t=null;const n=(e=>{ 255 | let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"" 256 | ;const n=g.languageDetectRe.exec(t);if(n){const t=k(n[1]) 257 | ;return t||(W(a.replace("{}",n[1])), 258 | W("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"} 259 | return t.split(/\s+/).find((e=>b(e)||k(e)))})(e);if(b(n))return 260 | ;if(N("before:highlightElement",{el:e,language:n 261 | }),e.children.length>0&&(g.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), 262 | console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), 263 | console.warn("The element with unescaped HTML:"), 264 | console.warn(e)),g.throwUnescapedHTML))throw new J("One of your code blocks includes unescaped HTML.",e.innerHTML) 265 | ;t=e;const i=t.textContent,s=n?m(i,{language:n,ignoreIllegals:!0}):x(i) 266 | ;e.innerHTML=s.value,((e,t,n)=>{const i=t&&r[t]||n 267 | ;e.classList.add("hljs"),e.classList.add("language-"+i) 268 | })(e,n,s.language),e.result={language:s.language,re:s.relevance, 269 | relevance:s.relevance},s.secondBest&&(e.secondBest={ 270 | language:s.secondBest.language,relevance:s.secondBest.relevance 271 | }),N("after:highlightElement",{el:e,result:s,text:i})}let y=!1;function _(){ 272 | "loading"!==document.readyState?document.querySelectorAll(g.cssSelector).forEach(w):y=!0 273 | }function k(e){return e=(e||"").toLowerCase(),t[e]||t[r[e]]} 274 | function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ 275 | r[e.toLowerCase()]=t}))}function O(e){const t=k(e) 276 | ;return t&&!t.disableAutodetect}function N(e,t){const n=e;s.forEach((e=>{ 277 | e[n]&&e[n](t)}))} 278 | "undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ 279 | y&&_()}),!1),Object.assign(e,{highlight:m,highlightAuto:x,highlightAll:_, 280 | highlightElement:w, 281 | highlightBlock:e=>(X("10.7.0","highlightBlock will be removed entirely in v12.0"), 282 | X("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{g=Q(g,e)}, 283 | initHighlighting:()=>{ 284 | _(),X("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, 285 | initHighlightingOnLoad:()=>{ 286 | _(),X("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") 287 | },registerLanguage:(n,i)=>{let r=null;try{r=i(e)}catch(e){ 288 | if(K("Language definition for '{}' could not be registered.".replace("{}",n)), 289 | !o)throw e;K(e),r=c} 290 | r.name||(r.name=n),t[n]=r,r.rawDefinition=i.bind(null,e),r.aliases&&v(r.aliases,{ 291 | languageName:n})},unregisterLanguage:e=>{delete t[e] 292 | ;for(const t of Object.keys(r))r[t]===e&&delete r[t]}, 293 | listLanguages:()=>Object.keys(t),getLanguage:k,registerAliases:v, 294 | autoDetection:O,inherit:Q,addPlugin:e=>{(e=>{ 295 | e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ 296 | e["before:highlightBlock"](Object.assign({block:t.el},t)) 297 | }),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ 298 | e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),s.push(e)} 299 | }),e.debugMode=()=>{o=!1},e.safeMode=()=>{o=!0 300 | },e.versionString="11.5.1",e.regex={concat:f,lookahead:d,either:p,optional:h, 301 | anyNumberOfTimes:u};for(const e in A)"object"==typeof A[e]&&n(A[e]) 302 | ;return Object.assign(e,A),e})({});return te}() 303 | ;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `css` grammar compiled for Highlight.js 11.5.1 */ 304 | (()=>{var e=(()=>{"use strict" 305 | ;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],i=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],r=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],t=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse() 306 | ;return n=>{const a=n.regex,l=(e=>({IMPORTANT:{scope:"meta",begin:"!important"}, 307 | BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number", 308 | begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{ 309 | className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{ 310 | scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", 311 | contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{ 312 | scope:"number", 313 | begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", 314 | relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z][A-Za-z0-9_-]*/} 315 | }))(n),s=[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE];return{name:"CSS", 316 | case_insensitive:!0,illegal:/[=|'\$]/,keywords:{keyframePosition:"from to"}, 317 | classNameAliases:{keyframePosition:"selector-tag"},contains:[l.BLOCK_COMMENT,{ 318 | begin:/-(webkit|moz|ms|o)-(?=[a-z])/},l.CSS_NUMBER_MODE,{ 319 | className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0},{ 320 | className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0 321 | },l.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{ 322 | begin:":("+r.join("|")+")"},{begin:":(:)?("+t.join("|")+")"}]},l.CSS_VARIABLE,{ 323 | className:"attribute",begin:"\\b("+o.join("|")+")\\b"},{begin:/:/,end:/[;}{]/, 324 | contains:[l.BLOCK_COMMENT,l.HEXCOLOR,l.IMPORTANT,l.CSS_NUMBER_MODE,...s,{ 325 | begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri" 326 | },contains:[{className:"string",begin:/[^)]/,endsWithParent:!0,excludeEnd:!0}] 327 | },l.FUNCTION_DISPATCH]},{begin:a.lookahead(/@/),end:"[{;]",relevance:0, 328 | illegal:/:/,contains:[{className:"keyword",begin:/@-?\w[\w]*(-\w+)*/},{ 329 | begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{ 330 | $pattern:/[a-z-]+/,keyword:"and or not only",attribute:i.join(" ")},contains:[{ 331 | begin:/[a-z-]+(?=:)/,className:"attribute"},...s,l.CSS_NUMBER_MODE]}]},{ 332 | className:"selector-tag",begin:"\\b("+e.join("|")+")\\b"}]}}})() 333 | ;hljs.registerLanguage("css",e)})();/*! `json` grammar compiled for Highlight.js 11.5.1 */ 334 | (()=>{var e=(()=>{"use strict";return e=>({name:"JSON",contains:[{ 335 | className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01},{ 336 | match:/[{}[\],:]/,className:"punctuation",relevance:0},e.QUOTE_STRING_MODE,{ 337 | beginKeywords:"true false null" 338 | },e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],illegal:"\\S"}) 339 | })();hljs.registerLanguage("json",e)})();/*! `bash` grammar compiled for Highlight.js 11.5.1 */ 340 | (()=>{var e=(()=>{"use strict";return e=>{const s=e.regex,t={},n={begin:/\$\{/, 341 | end:/\}/,contains:["self",{begin:/:-/,contains:[t]}]};Object.assign(t,{ 342 | className:"variable",variants:[{ 343 | begin:s.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},n]});const a={ 344 | className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},i={ 345 | begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/, 346 | end:/(\w+)/,className:"string"})]}},c={className:"string",begin:/"/,end:/"/, 347 | contains:[e.BACKSLASH_ESCAPE,t,a]};a.contains.push(c);const o={begin:/\$\(\(/, 348 | end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,t] 349 | },r=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10 350 | }),l={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0, 351 | contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{ 352 | name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/, 353 | keyword:["if","then","else","elif","fi","for","while","in","do","done","case","esac","function"], 354 | literal:["true","false"], 355 | built_in:["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset","alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias","set","shopt","autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp","chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"] 356 | },contains:[r,e.SHEBANG(),l,o,e.HASH_COMMENT_MODE,i,{match:/(\/[a-z._-]+)+/},c,{ 357 | className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},t]}}})() 358 | ;hljs.registerLanguage("bash",e)})();/*! `php` grammar compiled for Highlight.js 11.5.1 */ 359 | (()=>{var e=(()=>{"use strict";return e=>{ 360 | const t=e.regex,a=/(?![A-Za-z0-9])(?![$])/,r=t.concat(/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,a),n=t.concat(/(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,a),o={ 361 | scope:"variable",match:"\\$+"+r},c={scope:"subst",variants:[{begin:/\$\w+/},{ 362 | begin:/\{\$/,end:/\}/}]},i=e.inherit(e.APOS_STRING_MODE,{illegal:null 363 | }),s="[ \t\n]",l={scope:"string",variants:[e.inherit(e.QUOTE_STRING_MODE,{ 364 | illegal:null,contains:e.QUOTE_STRING_MODE.contains.concat(c) 365 | }),i,e.END_SAME_AS_BEGIN({begin:/<<<[ \t]*(\w+)\n/,end:/[ \t]*(\w+)\b/, 366 | contains:e.QUOTE_STRING_MODE.contains.concat(c)})]},_={scope:"number", 367 | variants:[{begin:"\\b0[bB][01]+(?:_[01]+)*\\b"},{ 368 | begin:"\\b0[oO][0-7]+(?:_[0-7]+)*\\b"},{ 369 | begin:"\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b"},{ 370 | begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?" 371 | }],relevance:0 372 | },d=["false","null","true"],p=["__CLASS__","__DIR__","__FILE__","__FUNCTION__","__COMPILER_HALT_OFFSET__","__LINE__","__METHOD__","__NAMESPACE__","__TRAIT__","die","echo","exit","include","include_once","print","require","require_once","array","abstract","and","as","binary","bool","boolean","break","callable","case","catch","class","clone","const","continue","declare","default","do","double","else","elseif","empty","enddeclare","endfor","endforeach","endif","endswitch","endwhile","enum","eval","extends","final","finally","float","for","foreach","from","global","goto","if","implements","instanceof","insteadof","int","integer","interface","isset","iterable","list","match|0","mixed","new","never","object","or","private","protected","public","readonly","real","return","string","switch","throw","trait","try","unset","use","var","void","while","xor","yield"],b=["Error|0","AppendIterator","ArgumentCountError","ArithmeticError","ArrayIterator","ArrayObject","AssertionError","BadFunctionCallException","BadMethodCallException","CachingIterator","CallbackFilterIterator","CompileError","Countable","DirectoryIterator","DivisionByZeroError","DomainException","EmptyIterator","ErrorException","Exception","FilesystemIterator","FilterIterator","GlobIterator","InfiniteIterator","InvalidArgumentException","IteratorIterator","LengthException","LimitIterator","LogicException","MultipleIterator","NoRewindIterator","OutOfBoundsException","OutOfRangeException","OuterIterator","OverflowException","ParentIterator","ParseError","RangeException","RecursiveArrayIterator","RecursiveCachingIterator","RecursiveCallbackFilterIterator","RecursiveDirectoryIterator","RecursiveFilterIterator","RecursiveIterator","RecursiveIteratorIterator","RecursiveRegexIterator","RecursiveTreeIterator","RegexIterator","RuntimeException","SeekableIterator","SplDoublyLinkedList","SplFileInfo","SplFileObject","SplFixedArray","SplHeap","SplMaxHeap","SplMinHeap","SplObjectStorage","SplObserver","SplPriorityQueue","SplQueue","SplStack","SplSubject","SplTempFileObject","TypeError","UnderflowException","UnexpectedValueException","UnhandledMatchError","ArrayAccess","BackedEnum","Closure","Fiber","Generator","Iterator","IteratorAggregate","Serializable","Stringable","Throwable","Traversable","UnitEnum","WeakReference","WeakMap","Directory","__PHP_Incomplete_Class","parent","php_user_filter","self","static","stdClass"],E={ 373 | keyword:p,literal:(e=>{const t=[];return e.forEach((e=>{ 374 | t.push(e),e.toLowerCase()===e?t.push(e.toUpperCase()):t.push(e.toLowerCase()) 375 | })),t})(d),built_in:b},u=e=>e.map((e=>e.replace(/\|\d+$/,""))),g={variants:[{ 376 | match:[/new/,t.concat(s,"+"),t.concat("(?!",u(b).join("\\b|"),"\\b)"),n],scope:{ 377 | 1:"keyword",4:"title.class"}}]},h=t.concat(r,"\\b(?!\\()"),m={variants:[{ 378 | match:[t.concat(/::/,t.lookahead(/(?!class\b)/)),h],scope:{2:"variable.constant" 379 | }},{match:[/::/,/class/],scope:{2:"variable.language"}},{ 380 | match:[n,t.concat(/::/,t.lookahead(/(?!class\b)/)),h],scope:{1:"title.class", 381 | 3:"variable.constant"}},{match:[n,t.concat("::",t.lookahead(/(?!class\b)/))], 382 | scope:{1:"title.class"}},{match:[n,/::/,/class/],scope:{1:"title.class", 383 | 3:"variable.language"}}]},I={scope:"attr", 384 | match:t.concat(r,t.lookahead(":"),t.lookahead(/(?!::)/))},f={relevance:0, 385 | begin:/\(/,end:/\)/,keywords:E,contains:[I,o,m,e.C_BLOCK_COMMENT_MODE,l,_,g] 386 | },O={relevance:0, 387 | match:[/\b/,t.concat("(?!fn\\b|function\\b|",u(p).join("\\b|"),"|",u(b).join("\\b|"),"\\b)"),r,t.concat(s,"*"),t.lookahead(/(?=\()/)], 388 | scope:{3:"title.function.invoke"},contains:[f]};f.contains.push(O) 389 | ;const v=[I,m,e.C_BLOCK_COMMENT_MODE,l,_,g];return{case_insensitive:!1, 390 | keywords:E,contains:[{begin:t.concat(/#\[\s*/,n),beginScope:"meta",end:/]/, 391 | endScope:"meta",keywords:{literal:d,keyword:["new","array"]},contains:[{ 392 | begin:/\[/,end:/]/,keywords:{literal:d,keyword:["new","array"]}, 393 | contains:["self",...v]},...v,{scope:"meta",match:n}] 394 | },e.HASH_COMMENT_MODE,e.COMMENT("//","$"),e.COMMENT("/\\*","\\*/",{contains:[{ 395 | scope:"doctag",match:"@[A-Za-z]+"}]}),{match:/__halt_compiler\(\);/, 396 | keywords:"__halt_compiler",starts:{scope:"comment",end:e.MATCH_NOTHING_RE, 397 | contains:[{match:/\?>/,scope:"meta",endsParent:!0}]}},{scope:"meta",variants:[{ 398 | begin:/<\?php/,relevance:10},{begin:/<\?=/},{begin:/<\?/,relevance:.1},{ 399 | begin:/\?>/}]},{scope:"variable.language",match:/\$this\b/},o,O,m,{ 400 | match:[/const/,/\s/,r],scope:{1:"keyword",3:"variable.constant"}},g,{ 401 | scope:"function",relevance:0,beginKeywords:"fn function",end:/[;{]/, 402 | excludeEnd:!0,illegal:"[$%\\[]",contains:[{beginKeywords:"use" 403 | },e.UNDERSCORE_TITLE_MODE,{begin:"=>",endsParent:!0},{scope:"params", 404 | begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:E, 405 | contains:["self",o,m,e.C_BLOCK_COMMENT_MODE,l,_]}]},{scope:"class",variants:[{ 406 | beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"class interface trait", 407 | illegal:/[:($"]/}],relevance:0,end:/\{/,excludeEnd:!0,contains:[{ 408 | beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{ 409 | beginKeywords:"namespace",relevance:0,end:";",illegal:/[.']/, 410 | contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{scope:"title.class"})]},{ 411 | beginKeywords:"use",relevance:0,end:";",contains:[{ 412 | match:/\b(as|const|function)\b/,scope:"keyword"},e.UNDERSCORE_TITLE_MODE]},l,_]} 413 | }})();hljs.registerLanguage("php",e)})();/*! `xml` grammar compiled for Highlight.js 11.5.1 */ 414 | (()=>{var e=(()=>{"use strict";return e=>{ 415 | const a=e.regex,n=a.concat(/[A-Z_]/,a.optional(/[A-Z0-9_.-]*:/),/[A-Z0-9_.-]*/),s={ 416 | className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},t={begin:/\s/, 417 | contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}] 418 | },i=e.inherit(t,{begin:/\(/,end:/\)/}),c=e.inherit(e.APOS_STRING_MODE,{ 419 | className:"string"}),l=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),r={ 420 | endsWithParent:!0,illegal:/`]+/}]}]}]};return{ 424 | name:"HTML, XML", 425 | aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"], 426 | case_insensitive:!0,contains:[{className:"meta",begin://, 427 | relevance:10,contains:[t,l,c,i,{begin:/\[/,end:/\]/,contains:[{className:"meta", 428 | begin://,contains:[t,i,l,c]}]}]},e.COMMENT(//,{ 429 | relevance:10}),{begin://,relevance:10},s,{ 430 | className:"meta",end:/\?>/,variants:[{begin:/<\?xml/,relevance:10,contains:[l] 431 | },{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag",begin:/)/,end:/>/, 432 | keywords:{name:"style"},contains:[r],starts:{end:/<\/style>/,returnEnd:!0, 433 | subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/, 434 | keywords:{name:"script"},contains:[r],starts:{end:/<\/script>/,returnEnd:!0, 435 | subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/ 436 | },{className:"tag", 437 | begin:a.concat(//,/>/,/\s/)))), 438 | end:/\/?>/,contains:[{className:"name",begin:n,relevance:0,starts:r}]},{ 439 | className:"tag",begin:a.concat(/<\//,a.lookahead(a.concat(n,/>/))),contains:[{ 440 | className:"name",begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}} 441 | })();hljs.registerLanguage("xml",e)})();/*! `sql` grammar compiled for Highlight.js 11.5.1 */ 442 | (()=>{var e=(()=>{"use strict";return e=>{ 443 | const r=e.regex,t=e.COMMENT("--","$"),n=["true","false","unknown"],a=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],i=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],s=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],o=i,c=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!i.includes(e))),l={ 444 | begin:r.concat(/\b/,r.either(...o),/\s*\(/),relevance:0,keywords:{built_in:o}} 445 | ;return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{ 446 | $pattern:/\b[\w\.]+/,keyword:((e,{exceptions:r,when:t}={})=>{const n=t 447 | ;return r=r||[],e.map((e=>e.match(/\|\d+$/)||r.includes(e)?e:n(e)?e+"|0":e)) 448 | })(c,{when:e=>e.length<3}),literal:n,type:a, 449 | built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"] 450 | },contains:[{begin:r.either(...s),relevance:0,keywords:{$pattern:/[\w\.]+/, 451 | keyword:c.concat(s),literal:n,type:a}},{className:"type", 452 | begin:r.either("double precision","large object","with timezone","without timezone") 453 | },l,{className:"variable",begin:/@[a-z0-9]+/},{className:"string",variants:[{ 454 | begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/,contains:[{ 455 | begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,{className:"operator", 456 | begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,relevance:0}]}}})() 457 | ;hljs.registerLanguage("sql",e)})();/*! `javascript` grammar compiled for Highlight.js 11.5.1 */ 458 | (()=>{var e=(()=>{"use strict" 459 | ;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","module","global"],i=[].concat(r,t,s) 460 | ;return o=>{const l=o.regex,b=e,d={begin:/<[A-Za-z0-9\\._:-]+/, 461 | end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ 462 | const a=e[0].length+e.index,t=e.input[a] 463 | ;if("<"===t||","===t)return void n.ignoreMatch();let s 464 | ;">"===t&&(((e,{after:n})=>{const a="",M={ 507 | match:[/const|var|let/,/\s+/,b,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(C)], 508 | keywords:"async",className:{1:"keyword",3:"title.function"},contains:[S]} 509 | ;return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ 510 | PARAMS_CONTAINS:p,CLASS_REFERENCE:R},illegal:/#(?![$_A-z])/, 511 | contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ 512 | label:"use_strict",className:"meta",relevance:10, 513 | begin:/^\s*['"]use (strict|asm)['"]/ 514 | },o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,y,N,_,f,E,R,{className:"attr", 515 | begin:b+l.lookahead(":"),relevance:0},M,{ 516 | begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", 517 | keywords:"return throw case",relevance:0,contains:[f,o.REGEXP_MODE,{ 518 | className:"function",begin:C,returnBegin:!0,end:"\\s*=>",contains:[{ 519 | className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ 520 | className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, 521 | excludeEnd:!0,keywords:g,contains:p}]}]},{begin:/,/,relevance:0},{match:/\s+/, 522 | relevance:0},{variants:[{begin:"<>",end:""},{ 523 | match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:d.begin, 524 | "on:begin":d.isTrulyOpeningTag,end:d.end}],subLanguage:"xml",contains:[{ 525 | begin:d.begin,end:d.end,skip:!0,contains:["self"]}]}]},O,{ 526 | beginKeywords:"while if switch catch for"},{ 527 | begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", 528 | returnBegin:!0,label:"func.def",contains:[S,o.inherit(o.TITLE_MODE,{begin:b, 529 | className:"title.function"})]},{match:/\.\.\./,relevance:0},x,{match:"\\$"+b, 530 | relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, 531 | contains:[S]},k,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, 532 | className:"variable.constant"},w,T,{match:/\$[(.]/}]}}})() 533 | ;hljs.registerLanguage("javascript",e)})();/*! `plaintext` grammar compiled for Highlight.js 11.5.1 */ 534 | (()=>{var t=(()=>{"use strict";return t=>({name:"Plain text", 535 | aliases:["text","txt"],disableAutodetect:!0})})() 536 | ;hljs.registerLanguage("plaintext",t)})(); -------------------------------------------------------------------------------- /templates/production.php: -------------------------------------------------------------------------------- 1 | getThrowableName($throwable); 10 | $message = $throwable->getMessage(); 11 | } else { 12 | $name = 'Error'; 13 | $message = ThrowableRendererInterface::DEFAULT_ERROR_MESSAGE; 14 | } 15 | ?> 16 | 17 | 18 | 19 | 20 | 21 | <?= $this->htmlEncode($name) ?> 22 | 23 | 59 | 60 | 61 | 62 |

    htmlEncode($name) ?>

    63 |

    htmlEncode($message)) ?>

    64 |

    65 | The above error occurred while the Web server was processing your request. 66 |

    67 |

    68 | Please contact us if you think this is a server error. Thank you. 69 |

    70 |
    71 | 72 |
    73 | 74 | 75 | -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | /*/vendor 2 | /*/composer.lock 3 | -------------------------------------------------------------------------------- /tools/composer-require-checker/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require-dev": { 3 | "maglnet/composer-require-checker": "^4.7.1" 4 | } 5 | } 6 | --------------------------------------------------------------------------------