├── LICENSE ├── README.md ├── composer.json ├── demo ├── autoload.php ├── backtrace.php ├── error-handling.php ├── traces │ ├── a.php │ ├── b.php │ └── c.php ├── vdd.php └── welcome.php ├── src ├── Client.php ├── Curl.php ├── Exceptions │ └── StopException.php ├── Inspector.php ├── InspectorInstance.php ├── InspectorNull.php ├── Interfaces │ ├── ClientInterface.php │ ├── CurlInterface.php │ ├── InspectorInterface.php │ ├── MessageInterface.php │ └── XrInterface.php ├── Message.php ├── ThrowableParser.php ├── Traits │ ├── ClientTrait.php │ └── CurlTrait.php ├── VarDump │ └── Output │ │ └── xrDebugHtmlOutput.php ├── WriterInstance.php ├── Xr.php ├── XrInstance.php └── functions.php └── xr.svg /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xrDebug PHP client 2 | 3 | xrDebug 4 | 5 | [![Build](https://img.shields.io/github/actions/workflow/status/xrdebug/php/test.yml?branch=3.0&style=flat-square)](https://github.com/xrdebug/php/actions) 6 | ![Code size](https://img.shields.io/github/languages/code-size/xrdebug/php?style=flat-square) 7 | [![Apache-2.0](https://img.shields.io/github/license/xrdebug/php?style=flat-square)](LICENSE) 8 | [![PHPStan](https://img.shields.io/badge/PHPStan-level%209-blueviolet?style=flat-square)](https://phpstan.org/) 9 | [![Mutation testing badge](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fxrdebug%2Fphp%2F3.0)](https://dashboard.stryker-mutator.io/reports/github.com/xrdebug/php/3.0) 10 | 11 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=xrdebug_php&metric=alert_status)](https://sonarcloud.io/dashboard?id=xrdebug_php) 12 | [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=xrdebug_php&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=xrdebug_php) 13 | [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=xrdebug_php&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=xrdebug_php) 14 | [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=xrdebug_php&metric=security_rating)](https://sonarcloud.io/dashboard?id=xrdebug_php) 15 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=xrdebug_php&metric=coverage)](https://sonarcloud.io/dashboard?id=xrdebug_php) 16 | [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=xrdebug_php&metric=sqale_index)](https://sonarcloud.io/dashboard?id=xrdebug_php) 17 | [![CodeFactor](https://www.codefactor.io/repository/github/xrdebug/php/badge)](https://www.codefactor.io/repository/github/xrdebug/php) 18 | 19 | ## Summary 20 | 21 | PHP client library for [xrDebug](https://xrdebug.com/). This library provides a set of functions to dump variables, send raw messages, and interact with the inspector from your codebase. 22 | 23 | ## Quick start 24 | 25 | Install using [Composer](https://packagist.org/packages/xrdebug/php). 26 | 27 | ```sh 28 | composer require --dev xrdebug/php 29 | ``` 30 | 31 | Use `xr()` directly in your code to dump any variable. For example: 32 | 33 | ```php 34 | require_once __DIR__ . '/vendor/autoload.php'; 35 | 36 | // ... 37 | xr('Hello, world!'); 38 | ``` 39 | 40 | ## Configuring 41 | 42 | This xrDebug PHP client uses the following default configuration. 43 | 44 | > Skip this section if running from the xrDebug binary and on the same machine. 45 | 46 | ```php 47 | [ 48 | 'isEnabled' => true, 49 | 'isHttps' => false, 50 | 'host' => 'localhost', 51 | 'port' => 27420, 52 | 'key' => '', 53 | ] 54 | ``` 55 | 56 | | Property | Type | Effect | 57 | | --------- | ------ | ----------------------------------------- | 58 | | isEnabled | bool | Controls sending messages to the server | 59 | | isHttps | bool | Controls use of https | 60 | | host | string | The host where xrDebug server is running | 61 | | port | int | The Port to connect to the `host` | 62 | | key | string | Private key (ed25519) for signed requests | 63 | 64 | > `host` The hostname or IP. When running xrDebug on Docker use `host.docker.internal`. 65 | 66 | ### File-based config 67 | 68 | Configure the client by placing a `xr.php` file in project's root directory. Need to define only the properties that override the default config. 69 | 70 | We recommend adding `xr.php` to your `.gitignore`. 71 | 72 | Here some examples of `xr.php`: 73 | 74 | - Run with Docker at a `27980` port: 75 | 76 | ```php 77 | 'host.docker.internal', 81 | 'port' => 27980, 82 | ]; 83 | ``` 84 | 85 | - Run with sign verification: 86 | 87 | ```php 88 | file_get_contents('private.key'), 92 | ]; 93 | ``` 94 | 95 | ### Code-based config 96 | 97 | Use function `xrConfig()` to configure the xrDebug server connection directly in your logic. Need to define only the properties that override the default config. 98 | 99 | Here some examples of `xrConfig()`: 100 | 101 | - Run with Docker at a `27980` port: 102 | 103 | ```php 104 | xrConfig( 105 | host: 'host.docker.internal', 106 | port: 27980, 107 | ); 108 | ``` 109 | 110 | - Run with sign verification: 111 | 112 | ```php 113 | xrConfig( 114 | key: file_get_contents('private.key'), 115 | ); 116 | ``` 117 | 118 | ## Debug helpers 119 | 120 | This xrDebug PHP client provides the following helper functions in the root namespace. Use these anywhere in your code. 121 | 122 | | Function | Purpose | 123 | | ----------- | ---------------------------- | 124 | | [xr](#xr) | Dump one or more variables | 125 | | [xrr](#xrr) | Dump raw message | 126 | | [xri](#xri) | Dump inspector (pauses, etc) | 127 | 128 | ### xr 129 | 130 | Use function `xr($var1, $var2,...)` to dump one or more variable(s). 131 | 132 | ```php 133 | xr($var, 'Hola, mundo!'); 134 | ``` 135 | 136 | Pass a topic using `t:`. 137 | 138 | ```php 139 | xr($var, t: 'Epic win'); 140 | ``` 141 | 142 | Pass an emote using `e:`. 143 | 144 | ```php 145 | xr($var, e: '😎'); 146 | ``` 147 | 148 | Pass bitwise flags to trigger special behavior. 149 | 150 | - `f: XR_BACKTRACE` to include debug backtrace. 151 | 152 | ```php 153 | xr($var, f: XR_BACKTRACE); 154 | ``` 155 | 156 | ### xrr 157 | 158 | Use function `xrr()` to send a raw message. 159 | 160 | ```php 161 | xrr('

Hola, mundo!

'); 162 | xrr('Test', t: 'Epic win'); 163 | xrr('test', e: '😎'); 164 | xrr('some string
', f: XR_BACKTRACE); 165 | ``` 166 | 167 | ### xri 168 | 169 | Use function `xri()` to interact with the inspector. 170 | 171 | Use `pause` to pause code execution. 172 | 173 | ```php 174 | xri()->pause(); 175 | ``` 176 | 177 | Use `memory` to send memory usage information. 178 | 179 | ```php 180 | xri()->memory(); 181 | ``` 182 | 183 | ### Debug helpers (VarDump) 184 | 185 | This xrDebug PHP client also provides the following helper functions provided by the [VarDump](https://chevere.org/packages/var-dump) package. 186 | 187 | | Function | Purpose | 188 | | ----------- | --------------------------------- | 189 | | [vd](#vd) | VarDump to output stream | 190 | | [vdd](#vdd) | VarDump to output stream and die | 191 | 192 | ### vd 193 | 194 | Function `vd` is a drop-in replacement for `var_dump`. It prints information about one or more variables to the output stream. 195 | 196 | ```php 197 | vd($var1, $var2,); 198 | // more code 199 | ``` 200 | 201 | ### vdd 202 | 203 | Function `vdd` does same as vd, but with die(0) which halts further execution. 204 | 205 | ```php 206 | vdd($var); 207 | // does exit(); 208 | ``` 209 | 210 | ## Exception handling 211 | 212 | The PHP client provides a throwable handler that can hook or replace existing exception handler logic thanks to the [ThrowableHandler](https://chevere.org/packages/throwable-handler) package. 213 | 214 | ### Register handler 215 | 216 | Use `registerThrowableHandler` to enable xrDebug throwable handling. 217 | 218 | ```php 219 | 220 | use Chevere\xrDebug\PHP\registerThrowableHandler; 221 | 222 | // True append xrDebug to your existing handler 223 | // False use only xrDebug handler 224 | registerThrowableHandler(true); 225 | ``` 226 | 227 | ## Error handling 228 | 229 | To handle errors with xrDebug you will require to configure your project to handle errors as exceptions and register a shutdown function: 230 | 231 | ```php 232 | use Chevere\ThrowableHandler\ThrowableHandler; 233 | 234 | set_error_handler( 235 | ThrowableHandler::ERROR_AS_EXCEPTION 236 | ); 237 | register_shutdown_function( 238 | ThrowableHandler::SHUTDOWN_ERROR_AS_EXCEPTION 239 | ); 240 | ``` 241 | 242 | ### Triggered handler 243 | 244 | Use `throwableHandler` in any existing exception handler logic: 245 | 246 | ```php 247 | use Chevere\xrDebug\PHP\throwableHandler; 248 | 249 | set_exception_handler( 250 | function(Throwable $throwable) { 251 | // ... 252 | try { 253 | throwableHandler($throwable); 254 | } catch(Throwable) { 255 | // Don't panic 256 | } 257 | } 258 | ); 259 | ``` 260 | 261 | ## Custom inspectors 262 | 263 | Extra inspectors can be defined to provide more context aware debug information. To create a custom inspector use `XrInspectorTrait` to implement the `XrInspectorInterface` and use `sendCommand` method. 264 | 265 | For code below, `myDump` defines a method that will stream data from your application logic and `myPause` sends a pause with debug backtrace by default. 266 | 267 | ```php 268 | sendCommand( 284 | command: 'message', 285 | body: $data, 286 | topic: $t, 287 | emote: $e, 288 | flags: $f, 289 | ); 290 | } 291 | 292 | public function myPause( 293 | int $f = XR_DEBUG_BACKTRACE, 294 | ): void { 295 | $this->sendCommand( 296 | command: 'pause', 297 | flags: $f, 298 | ); 299 | } 300 | } 301 | ``` 302 | 303 | The method `sendCommand` enables to interact with the existing xrDebug instance. 304 | 305 | ```php 306 | private function sendCommand( 307 | string $command, 308 | string $body = '', 309 | string $topic = '', 310 | string $emote = '', 311 | int $flags = 0 312 | ); 313 | ``` 314 | 315 | ### Null inspector 316 | 317 | A null inspector is required to void any inspection call **if xrDebug is disabled**. The null inspector should implement the same methods as the real inspector, but without carrying any action. 318 | 319 | 💡 Use `XrInspectorNullTrait` to implement the `XrInspectorInterface` when providing null inspector. 320 | 321 | ```php 322 | enable() 360 | ? MyInspector::class 361 | : MyInspectorNull::class; 362 | $client = getXr()->client(); 363 | $inspector = new $inspector($client); 364 | $instance = new XrInspectorInstance($inspector); 365 | 366 | return $instance::get(); 367 | } 368 | } 369 | ``` 370 | 371 | To use your custom helper: 372 | 373 | ```php 374 | my_inspector()->myDump(); 375 | my_inspector()->myPause(); 376 | ``` 377 | 378 | ## Documentation 379 | 380 | Documentation available at [docs.xrdebug.com](https://docs.xrdebug.com/). 381 | 382 | ## License 383 | 384 | Copyright [Rodolfo Berrios A.](https://rodolfoberrios.com/) 385 | 386 | xrDebug is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text. 387 | 388 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 389 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xrdebug/php", 3 | "description": "PHP client library for xrDebug", 4 | "homepage": "https://github.com/xrdebug/php", 5 | "keywords": [ 6 | "chevere", 7 | "dump", 8 | "debug", 9 | "debugging", 10 | "xrdebug" 11 | ], 12 | "license": "Apache-2.0", 13 | "authors": [ 14 | { 15 | "name": "Rodolfo Berrios", 16 | "email": "rodolfo@chevere.org", 17 | "homepage": "https://rodolfoberrios.com" 18 | } 19 | ], 20 | "require": { 21 | "php": "^8.1", 22 | "ext-curl": "*", 23 | "ext-json": "*", 24 | "chevere/filesystem": "^1.0.0", 25 | "chevere/message": "^1.0.0", 26 | "chevere/standard": "^1.0.1", 27 | "chevere/throwable-handler": "^1.0.2", 28 | "chevere/trace": "^2.0.0", 29 | "chevere/var-dump": "^2.0.2", 30 | "phpseclib/phpseclib": "~3.0" 31 | }, 32 | "require-dev": { 33 | "dg/bypass-finals": "^1.4", 34 | "phpstan/phpstan": "^2.0", 35 | "phpunit/phpunit": "^9.5", 36 | "symplify/easy-coding-standard": "^11.1" 37 | }, 38 | "autoload": { 39 | "files": [ 40 | "src/functions.php" 41 | ], 42 | "psr-4": { 43 | "Chevere\\xrDebug\\PHP\\": "src/" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "Chevere\\Tests\\": "tests/" 49 | } 50 | }, 51 | "config": { 52 | "optimize-autoloader": true, 53 | "sort-packages": true 54 | }, 55 | "scripts": { 56 | "all": [ 57 | "composer phpstan", 58 | "composer test", 59 | "composer test-coverage", 60 | "composer infection -- --skip-initial-tests --coverage=build/logs" 61 | ], 62 | "infection": [ 63 | "Composer\\Config::disableProcessTimeout", 64 | "infection --only-covered -j10" 65 | ], 66 | "infection-filter": "sh -c 'sh -c \"composer infection -- --filter=$0 --test-framework-options=--filter=$0\"' $1", 67 | "phpstan": "vendor/bin/phpstan analyze src/ --memory-limit 512M --level 9", 68 | "test": [ 69 | "Composer\\Config::disableProcessTimeout", 70 | "vendor/bin/phpunit -c phpunit.xml" 71 | ], 72 | "test-coverage": [ 73 | "Composer\\Config::disableProcessTimeout", 74 | "vendor/bin/phpunit -c phpunit-coverage.xml" 75 | ], 76 | "cs-update": "mkdir -p .ecs && cd .ecs && curl -O https://raw.githubusercontent.com/chevere/code-style/main/.ecs/ecs-chevere.php", 77 | "cs-fix": "vendor/bin/ecs --config='.ecs/ecs.php' check src --fix", 78 | "open-coverage": "open build/logs/html/index.html", 79 | "open-infection": "open build/logs/html/infection.html" 80 | }, 81 | "scripts-descriptions": { 82 | "all": "Runs all checks", 83 | "infection": "Runs infection", 84 | "infection-filter": "Runs infection (filtered)", 85 | "phpstan": "Runs phpstan", 86 | "test": "Run test suite", 87 | "test-coverage": "Run test suite (coverage)", 88 | "cs-update": "Update Chevere code style definition", 89 | "cs-fix": "Update Chevere code style definition", 90 | "open-coverage": "Open code coverage report", 91 | "open-infection": "Open infection report" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /demo/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | foreach (['/../', '/../../../../'] as $path) { 15 | $autoload = __DIR__ . $path . 'vendor/autoload.php'; 16 | if (stream_resolve_include_path($autoload)) { 17 | require $autoload; 18 | 19 | break; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demo/backtrace.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | require_once 'autoload.php'; 15 | 16 | xr('BACKTRACE', f: XR_BACKTRACE); 17 | 18 | include __DIR__ . '/traces/a.php'; 19 | -------------------------------------------------------------------------------- /demo/error-handling.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | use Chevere\ThrowableHandler\ThrowableHandler; 15 | use Chevere\Writer\StreamWriter; 16 | use Chevere\Writer\Writers; 17 | use Chevere\Writer\WritersInstance; 18 | use function Chevere\Writer\streamFor; 19 | use function Chevere\xrDebug\PHP\registerThrowableHandler; 20 | 21 | require 'autoload.php'; 22 | 23 | new WritersInstance( 24 | (new Writers()) 25 | ->withOutput( 26 | new StreamWriter( 27 | streamFor('php://stdout', 'w') 28 | ) 29 | ) 30 | ->withError( 31 | new StreamWriter( 32 | streamFor('php://stderr', 'w') 33 | ) 34 | ) 35 | ); 36 | set_error_handler( 37 | ThrowableHandler::ERROR_AS_EXCEPTION 38 | ); 39 | register_shutdown_function( 40 | ThrowableHandler::SHUTDOWN_ERROR_AS_EXCEPTION 41 | ); 42 | set_exception_handler( 43 | ThrowableHandler::CONSOLE 44 | ); 45 | registerThrowableHandler(true); 46 | 47 | throw new RuntimeException( 48 | message: 'Ch bah puta la güeá', 49 | code: 12345, 50 | previous: new Exception( 51 | message: 'A la chuchesumare', 52 | code: 678, 53 | previous: new LogicException( 54 | message: 'Ese conchesumare', 55 | code: 0, 56 | ) 57 | ) 58 | ); 59 | -------------------------------------------------------------------------------- /demo/traces/a.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | xr('A', f: XR_BACKTRACE); 15 | 16 | include __DIR__ . '/b.php'; 17 | -------------------------------------------------------------------------------- /demo/traces/b.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | xr('B', f: XR_BACKTRACE); 15 | 16 | include __DIR__ . '/c.php'; 17 | -------------------------------------------------------------------------------- /demo/traces/c.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | xr('C', f: XR_BACKTRACE); 15 | -------------------------------------------------------------------------------- /demo/vdd.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | require 'autoload.php'; 15 | 16 | /** 17 | * Mimic xdebug var_dump example to showcase Chevere's VarDump. 18 | * https://xdebug.org/docs/develop#improved_var_dump 19 | */ 20 | 21 | class test 22 | { 23 | public self $pub; 24 | 25 | protected $prot = 42; 26 | 27 | private $priv = true; 28 | 29 | public function __construct() 30 | { 31 | $this->pub = $this; 32 | } 33 | } 34 | 35 | $array = [ 36 | 'one' => 'a somewhat long string!', 37 | 'two' => [ 38 | 'two.one' => [ 39 | 'two.one.zero' => 210, 40 | 'two.one.one' => [ 41 | 'two.one.one.zero' => M_PI, 42 | 'two.one.one.one' => 2.7, 43 | ], 44 | ], 45 | ], 46 | 'three' => new test(), 47 | 'four' => range(0, 5), 48 | ]; 49 | 50 | vdd($array); 51 | -------------------------------------------------------------------------------- /demo/welcome.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | require_once 'autoload.php'; 15 | 16 | xrr('👋 Welcome to xrDebug!'); 17 | xri()->pause(); 18 | xr( 19 | 🤓: 'xrDebug is a lightweight debugger.', 20 | t: 'hello-world', 21 | e: '🐘' 22 | ); 23 | sleep(5); 24 | xr( 25 | 👆: 'Edit session title by clicking on "xrDebug" up there.', 26 | t: 'how-to', 27 | e: '😜' 28 | ); 29 | sleep(5); 30 | xr( 31 | 😉: 'Use keyboard keys to Resume (R), pause (P), stop (S) and clear (C) the debug session.', 32 | t: 'how-to', 33 | e: '🕹️' 34 | ); 35 | sleep(5); 36 | xr( 37 | ✅: 'Clicking a topic (how-to button) or emote (👻 emoji) will apply filtering.', 38 | 🤤: 'Filters will appear on top, click to remove.', 39 | t: 'how-to', 40 | e: '👻' 41 | ); 42 | sleep(5); 43 | xr( 44 | 👈: 'Delete, copy and export with these buttons.', 45 | t: 'how-to', 46 | e: '😯' 47 | ); 48 | sleep(5); 49 | xr( 50 | 👇: 'Open caller file path by clicking on ' . basename(__FILE__) . ':' . (string) (__LINE__ - 1) . ' here below.', 51 | t: 'how-to', 52 | e: '📎' 53 | ); 54 | sleep(5); 55 | xr( 56 | 💅: 'Dark/light mode follows your system preferences.', 57 | t: 'how-to', 58 | e: '🌚🌝' 59 | ); 60 | sleep(5); 61 | xrr( 62 | '🎉 Enjoy xrDebug', 63 | e: '😊' 64 | ); 65 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP; 15 | 16 | use Chevere\xrDebug\PHP\Interfaces\ClientInterface; 17 | use Chevere\xrDebug\PHP\Traits\ClientTrait; 18 | 19 | final class Client implements ClientInterface 20 | { 21 | use ClientTrait; 22 | } 23 | -------------------------------------------------------------------------------- /src/Curl.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP; 15 | 16 | use Chevere\xrDebug\PHP\Interfaces\CurlInterface; 17 | use Chevere\xrDebug\PHP\Traits\CurlTrait; 18 | 19 | final class Curl implements CurlInterface 20 | { 21 | use CurlTrait; 22 | } 23 | -------------------------------------------------------------------------------- /src/Exceptions/StopException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP\Exceptions; 15 | 16 | use Exception; 17 | 18 | /** 19 | * Exception thrown when STOP, XR TIME. 20 | */ 21 | final class StopException extends Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Inspector.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP; 15 | 16 | use Chevere\xrDebug\PHP\Interfaces\ClientInterface; 17 | use Chevere\xrDebug\PHP\Interfaces\InspectorInterface; 18 | use Throwable; 19 | 20 | class Inspector implements InspectorInterface 21 | { 22 | public function __construct( 23 | protected ClientInterface $client, 24 | ) { 25 | } 26 | 27 | public function pause( 28 | string $t = '', 29 | string $e = '', 30 | int $f = 0, 31 | ): void { 32 | $this->command( 33 | action: 'sendPause', 34 | topic: $t, 35 | emote: $e, 36 | flags: $f, 37 | ); 38 | } 39 | 40 | public function memory( 41 | string $t = '', 42 | string $e = '', 43 | int $f = 0, 44 | ): void { 45 | $memory = memory_get_usage(true); 46 | $body = sprintf('%.2F MB', $memory / 1000000); 47 | $this->command( 48 | action: 'sendMessage', 49 | body: $body, 50 | topic: $t, 51 | emote: $e, 52 | flags: $f, 53 | ); 54 | } 55 | 56 | protected function command( 57 | string $action, 58 | string $body = '', 59 | string $topic = '', 60 | string $emote = '', 61 | int $flags = 0 62 | ): void { 63 | $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); 64 | array_shift($backtrace); 65 | $message = new Message(backtrace: $backtrace); 66 | $message = $message 67 | ->withBody($body) 68 | ->withTopic($topic) 69 | ->withEmote($emote) 70 | ->withFlags($flags); 71 | 72 | try { 73 | $this->client->{$action}($message); 74 | } catch (Throwable $e) { 75 | if (PHP_SAPI === 'cli') { 76 | echo <<getMessage()} 78 | 79 | PLAIN; 80 | $this->client->exit(255); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/InspectorInstance.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP; 15 | 16 | use Chevere\xrDebug\PHP\Interfaces\InspectorInterface; 17 | use LogicException; 18 | use function Chevere\Message\message; 19 | 20 | /** 21 | * @codeCoverageIgnore 22 | */ 23 | final class InspectorInstance 24 | { 25 | private static InspectorInterface $instance; 26 | 27 | public function __construct(InspectorInterface $xrInspector) 28 | { 29 | self::$instance = $xrInspector; 30 | } 31 | 32 | public static function get(): InspectorInterface 33 | { 34 | if (! isset(self::$instance)) { 35 | throw new LogicException( 36 | (string) message( 37 | 'No `%class%` instance present', 38 | class: static::class 39 | ) 40 | ); 41 | } 42 | 43 | return self::$instance; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/InspectorNull.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP; 15 | 16 | use Chevere\xrDebug\PHP\Interfaces\InspectorInterface; 17 | 18 | /** 19 | * @codeCoverageIgnore 20 | */ 21 | class InspectorNull implements InspectorInterface 22 | { 23 | public function pause( 24 | string $e = '', 25 | string $t = '', 26 | int $f = 0, 27 | ): void { 28 | // null 29 | } 30 | 31 | public function memory( 32 | string $e = '', 33 | string $t = '', 34 | int $f = 0, 35 | ): void { 36 | // null 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Interfaces/ClientInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP\Interfaces; 15 | 16 | /** 17 | * Describes the component in charge of defining the client which send messages 18 | * to the xrDebug server. 19 | */ 20 | interface ClientInterface 21 | { 22 | public function sendMessage(MessageInterface $message): void; 23 | 24 | public function sendPause(MessageInterface $message): void; 25 | 26 | public function isPaused(string $id): bool; 27 | 28 | public function curl(): CurlInterface; 29 | 30 | public function getUrl(string $endpoint): string; 31 | 32 | /** 33 | * @return array 34 | */ 35 | public function options(): array; 36 | 37 | public function exit(int $exitCode = 0): void; 38 | } 39 | -------------------------------------------------------------------------------- /src/Interfaces/CurlInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP\Interfaces; 15 | 16 | use CurlHandle; 17 | 18 | /** 19 | * Describes the component in charge of defining a curl abstraction. 20 | */ 21 | interface CurlInterface 22 | { 23 | public function __destruct(); 24 | 25 | public function handle(): ?CurlHandle; 26 | 27 | public function error(): string; 28 | 29 | public function exec(): string|bool; 30 | 31 | /** 32 | * @param array $options 33 | */ 34 | public function setOptArray(array $options): bool; 35 | 36 | public function close(): void; 37 | } 38 | -------------------------------------------------------------------------------- /src/Interfaces/InspectorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP\Interfaces; 15 | 16 | /** 17 | * Describes the component in charge of defining the XR inspector default interface. 18 | */ 19 | interface InspectorInterface 20 | { 21 | public function pause( 22 | string $t = '', 23 | string $e = '', 24 | int $f = 0, 25 | ): void; 26 | 27 | public function memory( 28 | string $t = '', 29 | string $e = '', 30 | int $f = 0, 31 | ): void; 32 | } 33 | -------------------------------------------------------------------------------- /src/Interfaces/MessageInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP\Interfaces; 15 | 16 | use Chevere\Writer\Interfaces\WriterInterface; 17 | 18 | /** 19 | * Describes the component in charge of defining the XR message. 20 | */ 21 | interface MessageInterface 22 | { 23 | public function body(): string; 24 | 25 | public function topic(): string; 26 | 27 | public function emote(): string; 28 | 29 | public function filePath(): string; 30 | 31 | public function fileLine(): int; 32 | 33 | public function isEnableBacktrace(): bool; 34 | 35 | /** 36 | * @return array 37 | */ 38 | public function vars(): array; 39 | 40 | public function id(): string; 41 | 42 | public function writer(): WriterInterface; 43 | 44 | public function withBody(string $body): self; 45 | 46 | public function withTopic(string $topic): self; 47 | 48 | public function withEmote(string $emote): self; 49 | 50 | public function withWriter(WriterInterface $writer): self; 51 | 52 | public function withVariables(mixed ...$variables): self; 53 | 54 | public function withFlags(int $flags): self; 55 | 56 | /** 57 | * @return array 58 | */ 59 | public function toArray(): array; 60 | } 61 | -------------------------------------------------------------------------------- /src/Interfaces/XrInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP\Interfaces; 15 | 16 | use Chevere\Filesystem\Interfaces\DirectoryInterface; 17 | 18 | /** 19 | * Describes the component in charge of defining XR. 20 | */ 21 | interface XrInterface 22 | { 23 | public const CONFIG_NAMES = ['isEnabled', 'isHttps', 'host', 'port', 'key']; 24 | 25 | public function withConfigDir(DirectoryInterface $config): self; 26 | 27 | public function isEnabled(): bool; 28 | 29 | public function isHttps(): bool; 30 | 31 | public function host(): string; 32 | 33 | public function port(): int; 34 | 35 | public function key(): string; 36 | 37 | public function client(): ClientInterface; 38 | } 39 | -------------------------------------------------------------------------------- /src/Message.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP; 15 | 16 | use Chevere\ThrowableHandler\Formats\HtmlFormat as ThrowableHandlerHtmlFormat; 17 | use Chevere\Trace\Trace; 18 | use Chevere\VarDump\Formats\HtmlFormat as VarDumpHtmlFormat; 19 | use Chevere\VarDump\VarDump; 20 | use Chevere\Writer\Interfaces\WriterInterface; 21 | use Chevere\Writer\NullWriter; 22 | use Chevere\xrDebug\PHP\Interfaces\MessageInterface; 23 | use Chevere\xrDebug\PHP\VarDump\Output\xrDebugHtmlOutput; 24 | use function Chevere\Standard\uuidV4; 25 | 26 | final class Message implements MessageInterface 27 | { 28 | private string $body = ''; 29 | 30 | private string $topic = ''; 31 | 32 | private string $emote = ''; 33 | 34 | private string $filePath = ''; 35 | 36 | private int $fileLine = 0; 37 | 38 | private bool $isFlagBacktrace = false; 39 | 40 | /** 41 | * @var array 42 | */ 43 | private array $vars = []; 44 | 45 | private WriterInterface $writer; 46 | 47 | private string $id; 48 | 49 | /** 50 | * @param array> $backtrace 51 | */ 52 | public function __construct( 53 | private array $backtrace = [] 54 | ) { 55 | if ($backtrace === []) { 56 | $this->backtrace = debug_backtrace(); 57 | } 58 | $this->writer = new NullWriter(); 59 | /** @var string $file */ 60 | $file = $this->backtrace[0]['file'] ?? ''; 61 | /** @var int $line */ 62 | $line = $this->backtrace[0]['line'] ?? 0; 63 | $this->filePath = strval($file); 64 | $this->fileLine = intval($line); 65 | $this->id = uuidV4(); 66 | } 67 | 68 | public function body(): string 69 | { 70 | return $this->body; 71 | } 72 | 73 | public function topic(): string 74 | { 75 | return $this->topic; 76 | } 77 | 78 | public function emote(): string 79 | { 80 | return $this->emote; 81 | } 82 | 83 | public function filePath(): string 84 | { 85 | return $this->filePath; 86 | } 87 | 88 | public function fileLine(): int 89 | { 90 | return $this->fileLine; 91 | } 92 | 93 | public function isEnableBacktrace(): bool 94 | { 95 | return $this->isFlagBacktrace; 96 | } 97 | 98 | public function vars(): array 99 | { 100 | return $this->vars; 101 | } 102 | 103 | public function id(): string 104 | { 105 | return $this->id; 106 | } 107 | 108 | public function writer(): WriterInterface 109 | { 110 | return $this->writer; 111 | } 112 | 113 | public function withBody(string $body): self 114 | { 115 | $new = clone $this; 116 | $new->body = $body; 117 | 118 | return $new; 119 | } 120 | 121 | public function withTopic(string $topic): self 122 | { 123 | $new = clone $this; 124 | $new->topic = $topic; 125 | 126 | return $new; 127 | } 128 | 129 | public function withEmote(string $emote): self 130 | { 131 | $new = clone $this; 132 | $new->emote = $emote; 133 | 134 | return $new; 135 | } 136 | 137 | public function withWriter(WriterInterface $writer): self 138 | { 139 | $new = clone $this; 140 | $new->writer = $writer; 141 | 142 | return $new; 143 | } 144 | 145 | public function withVariables(mixed ...$variables): self 146 | { 147 | $new = clone $this; 148 | $new->vars = $variables; 149 | 150 | return $new; 151 | } 152 | 153 | public function withFlags(int $flags): self 154 | { 155 | $new = clone $this; 156 | if ($flags & XR_BACKTRACE) { 157 | $new->isFlagBacktrace = true; 158 | } 159 | 160 | return $new; 161 | } 162 | 163 | public function toArray(): array 164 | { 165 | $this->handleDumpVars(); 166 | $this->handleBacktrace(); 167 | 168 | return [ 169 | 'body' => $this->body, 170 | 'emote' => $this->emote, 171 | 'file_line' => strval($this->fileLine), 172 | 'file_path' => $this->filePath, 173 | 'id' => $this->id, 174 | 'topic' => $this->topic, 175 | ]; 176 | } 177 | 178 | private function handleDumpVars(): void 179 | { 180 | if ($this->vars === []) { 181 | return; 182 | } 183 | (new VarDump( 184 | new VarDumpHtmlFormat(), 185 | new xrDebugHtmlOutput() 186 | )) 187 | ->withVariables(...$this->vars) 188 | ->process($this->writer); 189 | $dump = $this->writer->__toString(); 190 | $this->body .= <<{$dump} 192 | HTML; 193 | } 194 | 195 | private function handleBacktrace(): void 196 | { 197 | if (! $this->isFlagBacktrace) { 198 | return; 199 | } 200 | $trace = new Trace( 201 | $this->backtrace, 202 | new ThrowableHandlerHtmlFormat() 203 | ); 204 | $count = count($trace->toArray()); 205 | $this->body .= <<{$trace->__toString()} 207 | HTML; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/ThrowableParser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP; 15 | 16 | use Chevere\ThrowableHandler\Formats\HtmlFormat; 17 | use Chevere\ThrowableHandler\Interfaces\FormatInterface; 18 | use Chevere\ThrowableHandler\Interfaces\ThrowableReadInterface; 19 | use Chevere\ThrowableHandler\ThrowableRead; 20 | use Chevere\Trace\Trace; 21 | use Throwable; 22 | 23 | final class ThrowableParser 24 | { 25 | public const OPEN_TEMPLATE = '
'; 26 | 27 | public const CLOSE_TEMPLATE = '
'; 28 | 29 | public const ITEM_TEMPLATE = << 31 |

%title%

32 |
%code%
33 |
%message%
34 | %extra% 35 |
%trace%
36 | 37 | HTML; 38 | 39 | private string $topic = ''; 40 | 41 | private string $body = ''; 42 | 43 | private string $emote = '⚠️Throwable'; 44 | 45 | private int $index = 0; 46 | 47 | private FormatInterface $format; 48 | 49 | public function __construct( 50 | private ThrowableReadInterface $throwableRead, 51 | private string $extra = '', 52 | ) { 53 | $this->format = new HtmlFormat(); 54 | $throwable = $this->throwableRead->throwable(); 55 | $this->topic = basename( 56 | str_replace( 57 | '\\', 58 | DIRECTORY_SEPARATOR, 59 | $this->throwableRead->className() 60 | ) 61 | ); 62 | $this->appendBodyLine(static::OPEN_TEMPLATE); 63 | do { 64 | $this->index++; 65 | } while ($throwable = $this->parse($throwable)); 66 | $this->appendBodyLine(static::CLOSE_TEMPLATE); 67 | } 68 | 69 | public function body(): string 70 | { 71 | return $this->body; 72 | } 73 | 74 | public function topic(): string 75 | { 76 | return $this->topic; 77 | } 78 | 79 | public function emote(): string 80 | { 81 | return $this->emote; 82 | } 83 | 84 | public function throwableRead(): ThrowableReadInterface 85 | { 86 | return $this->throwableRead; 87 | } 88 | 89 | private function appendBodyLine(string $body): void 90 | { 91 | $this->body .= $body . "\n"; 92 | } 93 | 94 | private function parse(Throwable $throwable): ?Throwable 95 | { 96 | if ($this->index === 1) { 97 | $read = $this->throwableRead; 98 | $trace = $this->throwableRead->trace(); 99 | } else { 100 | $read = new ThrowableRead($throwable); 101 | $trace = [ 102 | [ 103 | 'function' => '{main}', 104 | 'file' => $read->file(), 105 | 'line' => $read->line(), 106 | ], 107 | ]; 108 | } 109 | $traceDocument = new Trace($trace, $this->format); 110 | $translate = [ 111 | '%title%' => $read->className(), 112 | '%code%' => $read->code(), 113 | '%message%' => $read->message(), 114 | '%extra%' => $this->index === 1 115 | ? $this->extra 116 | : '', 117 | '%trace%' => $traceDocument->__toString(), 118 | '%trace_count%' => count($trace), 119 | ]; 120 | $this->appendBodyLine( 121 | strtr(static::ITEM_TEMPLATE, $translate) 122 | ); 123 | 124 | return $throwable->getPrevious(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Traits/ClientTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP\Traits; 15 | 16 | use Chevere\xrDebug\PHP\Curl; 17 | use Chevere\xrDebug\PHP\Exceptions\StopException; 18 | use Chevere\xrDebug\PHP\Interfaces\CurlInterface; 19 | use Chevere\xrDebug\PHP\Interfaces\MessageInterface; 20 | use phpseclib3\Crypt\EC\PrivateKey; 21 | use function Chevere\Message\message; 22 | use function Chevere\xrDebug\PHP\sign; 23 | 24 | trait ClientTrait 25 | { 26 | private CurlInterface $curl; 27 | 28 | private string $scheme = 'http'; 29 | 30 | /** 31 | * @var array 32 | */ 33 | private array $options = []; 34 | 35 | public function __construct( 36 | private string $host = 'localhost', 37 | private int $port = 27420, 38 | bool $isHttps = false, 39 | private ?PrivateKey $privateKey = null, 40 | ?CurlInterface $curl = null, 41 | ) { 42 | $this->curl = $curl ?? new Curl(); 43 | if ($isHttps) { 44 | $this->scheme = 'https'; 45 | } 46 | } 47 | 48 | public function curl(): CurlInterface 49 | { 50 | return $this->curl; 51 | } 52 | 53 | public function getUrl(string $endpoint): string 54 | { 55 | return "{$this->scheme}://{$this->host}:{$this->port}/{$endpoint}"; 56 | } 57 | 58 | public function sendMessage(MessageInterface $message): void 59 | { 60 | try { 61 | $curl = $this->getCurlHandle( 62 | 'POST', 63 | 'messages', 64 | $message->toArray() 65 | ); 66 | $curl->exec(); 67 | } finally { 68 | unset($curl); 69 | } 70 | } 71 | 72 | public function sendPause(MessageInterface $message): void 73 | { 74 | try { 75 | $curl = $this->getCurlHandle( 76 | 'POST', 77 | 'pauses', 78 | $message->toArray(), 79 | ); 80 | $curl->exec(); 81 | $curlError = $curl->error(); 82 | if ($curlError === '') { 83 | do { 84 | sleep(1); 85 | } while ($this->isPaused($message->id())); 86 | } 87 | } finally { 88 | unset($curl); 89 | } 90 | } 91 | 92 | public function isPaused(string $id): bool 93 | { 94 | try { 95 | $curl = $this->getCurlHandle( 96 | 'GET', 97 | 'pauses/' . $id, 98 | [] 99 | ); 100 | $curlResult = $curl->exec(); 101 | if (! $curlResult || $curl->error() !== '') { 102 | return false; 103 | } 104 | /** @var object $response */ 105 | $response = json_decode(strval($curlResult)); 106 | if ($response->stop ?? false) { 107 | throw new StopException( 108 | (string) message( 109 | '[STOP EXECUTION] triggered from %remote%', 110 | remote: $this->host . ':' . $this->port 111 | ) 112 | ); 113 | } 114 | 115 | return true; 116 | } finally { 117 | unset($curl); 118 | } 119 | } 120 | 121 | public function options(): array 122 | { 123 | return $this->options; 124 | } 125 | 126 | /** 127 | * @codeCoverageIgnore 128 | */ 129 | public function exit(int $exitCode = 0): void 130 | { 131 | exit($exitCode); 132 | } 133 | 134 | /** 135 | * @param array $data 136 | */ 137 | private function getCurlHandle(string $method, string $url, array $data): CurlInterface 138 | { 139 | $this->options = [ 140 | CURLINFO_HEADER_OUT => true, 141 | CURLOPT_ENCODING => '', 142 | CURLOPT_FAILONERROR => true, 143 | CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, 144 | CURLOPT_CUSTOMREQUEST => $method, 145 | CURLOPT_POSTFIELDS => http_build_query($data), 146 | CURLOPT_RETURNTRANSFER => true, 147 | CURLOPT_SSL_VERIFYPEER => true, 148 | CURLOPT_TIMEOUT => 2, 149 | CURLOPT_URL => $this->getUrl($url), 150 | CURLOPT_USERAGENT => 'xrdebug/php', 151 | ]; 152 | $this->handleSignature($data); 153 | $this->curl->setOptArray($this->options); 154 | 155 | return $this->curl; 156 | } 157 | 158 | /** 159 | * @param array $data 160 | */ 161 | private function handleSignature(array $data): void 162 | { 163 | if ($this->privateKey !== null) { 164 | $signatureDisplay = sign($this->privateKey, $data); 165 | $this->options[CURLOPT_HTTPHEADER] = [ 166 | 'X-Signature: ' . $signatureDisplay, 167 | ]; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Traits/CurlTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP\Traits; 15 | 16 | use CurlHandle; 17 | use LogicException; 18 | use function Chevere\Message\message; 19 | 20 | trait CurlTrait 21 | { 22 | private CurlHandle $handle; 23 | 24 | /** 25 | * @var string[] 26 | */ 27 | private array $functions = [ 28 | 'curl_close', 29 | 'curl_error', 30 | 'curl_exec', 31 | 'curl_init', 32 | 'curl_setopt_array', 33 | ]; 34 | 35 | /** 36 | * @codeCoverageIgnore 37 | */ 38 | public function __construct(?string $url = null) 39 | { 40 | $this->assertCurl(); 41 | $this->handle = curl_init($url) 42 | ?: throw new LogicException( 43 | (string) message( 44 | 'No `%class%` instance present', 45 | class: static::class 46 | ) 47 | ); 48 | } 49 | 50 | public function __destruct() 51 | { 52 | if (isset($this->handle)) { 53 | $this->close(); 54 | } 55 | } 56 | 57 | public function handle(): ?CurlHandle 58 | { 59 | return isset($this->handle) ? $this->handle : null; 60 | } 61 | 62 | public function error(): string 63 | { 64 | return curl_error($this->handle); 65 | } 66 | 67 | public function exec(): string|bool 68 | { 69 | return curl_exec($this->handle); 70 | } 71 | 72 | public function setOptArray(array $options): bool 73 | { 74 | return curl_setopt_array($this->handle, $options); 75 | } 76 | 77 | public function close(): void 78 | { 79 | unset($this->handle); 80 | } 81 | 82 | private function assertCurl(): void 83 | { 84 | foreach ($this->functions as $function) { 85 | if (! function_exists($function)) { 86 | // @codeCoverageIgnoreStart 87 | throw new LogicException( 88 | (string) message( 89 | 'Function `%function%` is not available', 90 | function: $function 91 | ) 92 | ); 93 | // @codeCoverageIgnoreEnd 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/VarDump/Output/xrDebugHtmlOutput.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Chevere\xrDebug\PHP\VarDump\Output; 15 | 16 | use Chevere\VarDump\Interfaces\FormatInterface; 17 | use Chevere\VarDump\Outputs\Output; 18 | 19 | final class xrDebugHtmlOutput extends Output 20 | { 21 | public function finalize(): void 22 | { 23 | $this->writer()->write(''); 24 | } 25 | 26 | public function prepare(): void 27 | { 28 | $this->writer()->write('
');
29 |     }
30 | 
31 |     public function writeCallerFile(FormatInterface $format): void
32 |     {
33 |         // null override
34 |     }
35 | }
36 | 


--------------------------------------------------------------------------------
/src/WriterInstance.php:
--------------------------------------------------------------------------------
 1 | 
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | declare(strict_types=1);
13 | 
14 | namespace Chevere\xrDebug\PHP;
15 | 
16 | use Chevere\Writer\Interfaces\WriterInterface;
17 | use LogicException;
18 | use function Chevere\Message\message;
19 | 
20 | /**
21 |  * @codeCoverageIgnore
22 |  */
23 | final class WriterInstance
24 | {
25 |     private static WriterInterface $instance;
26 | 
27 |     public function __construct(WriterInterface $writer)
28 |     {
29 |         self::$instance = $writer;
30 |     }
31 | 
32 |     public static function get(): WriterInterface
33 |     {
34 |         if (! isset(self::$instance)) {
35 |             throw new LogicException(
36 |                 (string) message(
37 |                     'No `%class%` instance present',
38 |                     class: static::class
39 |                 )
40 |             );
41 |         }
42 | 
43 |         return self::$instance;
44 |     }
45 | }
46 | 


--------------------------------------------------------------------------------
/src/Xr.php:
--------------------------------------------------------------------------------
  1 | 
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | declare(strict_types=1);
 13 | 
 14 | namespace Chevere\xrDebug\PHP;
 15 | 
 16 | use Chevere\Filesystem\Interfaces\DirectoryInterface;
 17 | use Chevere\xrDebug\PHP\Interfaces\ClientInterface;
 18 | use Chevere\xrDebug\PHP\Interfaces\XrInterface;
 19 | use phpseclib3\Crypt\EC\PrivateKey;
 20 | use phpseclib3\Crypt\PublicKeyLoader;
 21 | use Throwable;
 22 | use function Chevere\Filesystem\filePhpReturnForPath;
 23 | 
 24 | final class Xr implements XrInterface
 25 | {
 26 |     private ClientInterface $client;
 27 | 
 28 |     private DirectoryInterface $directory;
 29 | 
 30 |     private string $configFile = '';
 31 | 
 32 |     /**
 33 |      * @var array
 34 |      */
 35 |     private array $configNames = ['xr.php'];
 36 | 
 37 |     private ?PrivateKey $privateKey = null;
 38 | 
 39 |     public function __construct(
 40 |         private bool $isEnabled = true,
 41 |         private bool $isHttps = false,
 42 |         private string $host = 'localhost',
 43 |         private int $port = 27420,
 44 |         private string $key = '',
 45 |     ) {
 46 |         $this->setClient();
 47 |     }
 48 | 
 49 |     public function withConfigDir(DirectoryInterface $config): XrInterface
 50 |     {
 51 |         $new = clone $this;
 52 |         $new->directory = $config;
 53 | 
 54 |         try {
 55 |             $new->configFile = $new->getConfigFile();
 56 |         }
 57 |         // @codeCoverageIgnoreStart
 58 |         catch (Throwable) {
 59 |             // Ignore directory not found
 60 |         }
 61 |         // @codeCoverageIgnoreEnd
 62 |         if ($new->configFile !== '') {
 63 |             $new->setConfigFromFile();
 64 |         }
 65 |         $new->setClient();
 66 | 
 67 |         return $new;
 68 |     }
 69 | 
 70 |     public function isEnabled(): bool
 71 |     {
 72 |         return $this->isEnabled;
 73 |     }
 74 | 
 75 |     public function isHttps(): bool
 76 |     {
 77 |         return $this->isHttps;
 78 |     }
 79 | 
 80 |     public function client(): ClientInterface
 81 |     {
 82 |         return $this->client;
 83 |     }
 84 | 
 85 |     public function host(): string
 86 |     {
 87 |         return $this->host;
 88 |     }
 89 | 
 90 |     public function port(): int
 91 |     {
 92 |         return $this->port;
 93 |     }
 94 | 
 95 |     public function key(): string
 96 |     {
 97 |         return $this->key;
 98 |     }
 99 | 
100 |     private function setConfigFromFile(): void
101 |     {
102 |         try {
103 |             /** @var array $return */
104 |             $return = filePhpReturnForPath($this->configFile)->cast()->array();
105 |             foreach (static::CONFIG_NAMES as $prop) {
106 |                 $this->{$prop} = $return[$prop] ?? $this->{$prop};
107 |             }
108 |         }
109 |         // @codeCoverageIgnoreStart
110 |         catch (Throwable) {
111 |             // Ignore to use defaults
112 |         }
113 |         // @codeCoverageIgnoreEnd
114 |     }
115 | 
116 |     private function getConfigFile(): string
117 |     {
118 |         $configDirectory = $this->directory->path()->__toString();
119 |         while (is_dir($configDirectory)) {
120 |             foreach ($this->configNames as $configName) {
121 |                 $configFullPath = $configDirectory . $configName;
122 |                 if (file_exists($configFullPath)) {
123 |                     return $configFullPath;
124 |                 }
125 |             }
126 |             // @infection-ignore-all
127 |             $parentDirectory = dirname($configDirectory) . DIRECTORY_SEPARATOR;
128 |             // @infection-ignore-all
129 |             if ($parentDirectory === $configDirectory) {
130 |                 return '';
131 |             }
132 |             $configDirectory = $parentDirectory;
133 |         }
134 | 
135 |         return ''; // @codeCoverageIgnore
136 |     }
137 | 
138 |     private function setClient(): void
139 |     {
140 |         if ($this->key !== '') {
141 |             /** @var ?PrivateKey $loadKey */
142 |             $loadKey = PublicKeyLoader::load($this->key);
143 |             $this->privateKey = $loadKey;
144 |         }
145 |         $this->client = new Client(
146 |             host: $this->host,
147 |             port: $this->port,
148 |             isHttps: $this->isHttps,
149 |             privateKey: $this->privateKey,
150 |         );
151 |     }
152 | }
153 | 


--------------------------------------------------------------------------------
/src/XrInstance.php:
--------------------------------------------------------------------------------
 1 | 
 7 |  *
 8 |  * For the full copyright and license information, please view the LICENSE
 9 |  * file that was distributed with this source code.
10 |  */
11 | 
12 | declare(strict_types=1);
13 | 
14 | namespace Chevere\xrDebug\PHP;
15 | 
16 | use Chevere\xrDebug\PHP\Interfaces\XrInterface;
17 | use LogicException;
18 | use function Chevere\Message\message;
19 | 
20 | /**
21 |  * @codeCoverageIgnore
22 |  */
23 | final class XrInstance
24 | {
25 |     private static XrInterface $instance;
26 | 
27 |     public function __construct(XrInterface $xr)
28 |     {
29 |         self::$instance = $xr;
30 |     }
31 | 
32 |     public static function get(): XrInterface
33 |     {
34 |         if (! isset(self::$instance)) {
35 |             throw new LogicException(
36 |                 (string) message(
37 |                     'No `%class%` instance present',
38 |                     class: static::class
39 |                 )
40 |             );
41 |         }
42 | 
43 |         return self::$instance;
44 |     }
45 | }
46 | 


--------------------------------------------------------------------------------
/src/functions.php:
--------------------------------------------------------------------------------
  1 | 
  7 |  *
  8 |  * For the full copyright and license information, please view the LICENSE
  9 |  * file that was distributed with this source code.
 10 |  */
 11 | 
 12 | declare(strict_types=1);
 13 | 
 14 | namespace Chevere\xrDebug\PHP {
 15 |     use Chevere\ThrowableHandler\ThrowableRead;
 16 |     use Chevere\Writer\Interfaces\WriterInterface;
 17 |     use Chevere\Writer\StreamWriter;
 18 |     use Chevere\xrDebug\PHP\Interfaces\XrInterface;
 19 |     use LogicException;
 20 |     use phpseclib3\Crypt\EC\PrivateKey;
 21 |     use Throwable;
 22 |     use function Chevere\Filesystem\directoryForPath;
 23 |     use function Chevere\Writer\streamTemp;
 24 |     use function getcwd;
 25 | 
 26 |     /**
 27 |      * @codeCoverageIgnore
 28 |      */
 29 |     function getWriter(): WriterInterface
 30 |     {
 31 |         try {
 32 |             return WriterInstance::get();
 33 |         } catch (LogicException) {
 34 |             return new StreamWriter(streamTemp(''));
 35 |         }
 36 |     }
 37 | 
 38 |     function getXr(): XrInterface
 39 |     {
 40 |         try {
 41 |             return XrInstance::get();
 42 |         } catch (LogicException) {
 43 |             $cwd = getcwd();
 44 |             if ($cwd === false) {
 45 |                 throw new LogicException('Unable to get current working directory');
 46 |             }
 47 |             $xr = (new Xr())
 48 |                 ->withConfigDir(
 49 |                     directoryForPath($cwd)
 50 |                 );
 51 | 
 52 |             return (new XrInstance($xr))::get();
 53 |         }
 54 |     }
 55 | 
 56 |     /**
 57 |      * @codeCoverageIgnore
 58 |      */
 59 |     function getXrFailover(): ?XrInterface
 60 |     {
 61 |         try {
 62 |             return getXr();
 63 |         } catch (Throwable $e) {
 64 |             $caller = debug_backtrace(0, 2)[1];
 65 |             $file = $caller['file'] ?? '@unknown';
 66 |             $line = strval($caller['line'] ?? 0);
 67 |             error_log(
 68 |                 strtr(
 69 |                     'Unable to use xrDebug at %s: %e',
 70 |                     [
 71 |                         '%s' => "{$file}:{$line}",
 72 |                         '%e' => $e->getMessage(),
 73 |                     ]
 74 |                 )
 75 |             );
 76 | 
 77 |             return null;
 78 |         }
 79 |     }
 80 | 
 81 |     /**
 82 |      * Register xrDebug throwable handler.
 83 |      *
 84 |      * @param bool $callPrevious True to call the previous handler.
 85 |      * False to disable the previous handler.
 86 |      *
 87 |      * @codeCoverageIgnore
 88 |      */
 89 |     function registerThrowableHandler(bool $callPrevious = true): void
 90 |     {
 91 |         /** @var callable $xrHandler */
 92 |         $xrHandler = __NAMESPACE__ . '\\throwableHandler';
 93 |         $previous = set_exception_handler($xrHandler);
 94 |         if ($callPrevious === false || $previous === null) {
 95 |             return;
 96 |         }
 97 |         set_exception_handler(
 98 |             function (Throwable $throwable) use ($xrHandler, $previous) {
 99 |                 $xrHandler($throwable);
100 |                 $previous($throwable);
101 |             }
102 |         );
103 |     }
104 | 
105 |     /**
106 |      * Handle a Throwable using xrDebug.
107 |      *
108 |      * @param Throwable $throwable The throwable to handle
109 |      * @param string $extra Extra contents to append to the xrDebug message
110 |      *
111 |      * @codeCoverageIgnore
112 |      */
113 |     function throwableHandler(Throwable $throwable, string $extra = ''): void
114 |     {
115 |         $xr = getXrFailover();
116 |         if ($xr === null || $xr->isEnabled() === false) {
117 |             return; // @codeCoverageIgnore
118 |         }
119 |         $read = new ThrowableRead($throwable);
120 |         $parser = new ThrowableParser($read, $extra);
121 |         $xr->client()
122 |             ->sendMessage(
123 |                 (new Message(
124 |                     backtrace: $parser->throwableRead()->trace(),
125 |                 ))
126 |                     ->withBody($parser->body())
127 |                     ->withTopic($parser->topic())
128 |                     ->withEmote($parser->emote())
129 |             );
130 |     }
131 | 
132 |     /**
133 |      * @param array $data
134 |      */
135 |     function sign(PrivateKey $privateKey, array $data): string
136 |     {
137 |         $serialize = serialize($data);
138 |         /** @var string $signature */
139 |         $signature = $privateKey->sign($serialize);
140 | 
141 |         return base64_encode($signature);
142 |     }
143 | 
144 |     /**
145 |      * @param array $data
146 |      * @return string Serialized data (key-value pairs concatenated)
147 |      */
148 |     function serialize(array $data): string
149 |     {
150 |         return array_reduce(
151 |             array_keys($data),
152 |             fn ($acc, $key) => $acc . $key . $data[$key],
153 |             ''
154 |         );
155 |     }
156 | }
157 | 
158 | namespace {
159 |     use Chevere\xrDebug\PHP\Inspector;
160 |     use Chevere\xrDebug\PHP\InspectorInstance;
161 |     use Chevere\xrDebug\PHP\InspectorNull;
162 |     use Chevere\xrDebug\PHP\Interfaces\InspectorInterface;
163 |     use Chevere\xrDebug\PHP\Message;
164 |     use Chevere\xrDebug\PHP\Xr;
165 |     use Chevere\xrDebug\PHP\XrInstance;
166 |     use function Chevere\xrDebug\PHP\getWriter;
167 |     use function Chevere\xrDebug\PHP\getXrFailover;
168 | 
169 |     // @codeCoverageIgnoreStart
170 |     if (! defined('XR_BACKTRACE')) {
171 |         define('XR_BACKTRACE', 1);
172 |     }
173 |     // @codeCoverageIgnoreEnd
174 |     if (! function_exists('xr')) { // @codeCoverageIgnore
175 |         /**
176 |          * Dumps information about one or more variables to xrDebug.
177 |          *
178 |          * ```php
179 |          * xr($foo, $bar,...);
180 |          * ```
181 |          *
182 |          * @param mixed $vars Variables to dump
183 |          */
184 |         function xr(mixed ...$vars): void
185 |         {
186 |             $xr = getXrFailover();
187 |             if ($xr === null || $xr->isEnabled() === false) {
188 |                 return; // @codeCoverageIgnore
189 |             }
190 |             $defaultArgs = [
191 |                 'e' => '',
192 |                 't' => '',
193 |                 'f' => 0,
194 |             ];
195 |             /** @var array */
196 |             $args = array_merge($defaultArgs, $vars);
197 |             foreach (array_keys($defaultArgs) as $name) {
198 |                 if (array_key_exists($name, $vars)) {
199 |                     unset($vars[$name]);
200 |                 }
201 |             }
202 |             $xr->client()
203 |                 ->sendMessage(
204 |                     (new Message(
205 |                         backtrace: debug_backtrace(),
206 |                     ))
207 |                         ->withWriter(getWriter())
208 |                         ->withVariables(...$vars)
209 |                         ->withTopic(strval($args['t']))
210 |                         ->withEmote(strval($args['e']))
211 |                         ->withFlags(intval($args['f']))
212 |                 );
213 |         }
214 |     }
215 |     if (! function_exists('xrr')) { // @codeCoverageIgnore
216 |         /**
217 |          * Send a raw html message to xrDebug.
218 |          *
219 |          * ```php
220 |          * xrr($html, ...);
221 |          * ```
222 |          *
223 |          * @param string $body Message to send
224 |          * @param string $t Topic
225 |          * @param string $e Emote
226 |          * @param int $f `XR_BACKTRACE`
227 |          *
228 |          * @codeCoverageIgnore
229 |          */
230 |         function xrr(
231 |             string $body,
232 |             string $t = '',
233 |             string $e = '',
234 |             int $f = 0
235 |         ): void {
236 |             $xr = getXrFailover();
237 |             if ($xr === null || $xr->isEnabled() === false) {
238 |                 return;
239 |             }
240 |             $xr->client()
241 |                 ->sendMessage(
242 |                     (new Message(
243 |                         backtrace: debug_backtrace(),
244 |                     ))
245 |                         ->withWriter(getWriter())
246 |                         ->withBody($body)
247 |                         ->withTopic($t)
248 |                         ->withEmote($e)
249 |                         ->withFlags($f)
250 |                 );
251 |         }
252 |     }
253 |     if (! function_exists('xri')) { // @codeCoverageIgnore
254 |         /**
255 |          * Access xrDebug inspector to send debug information.
256 |          *
257 |          * @codeCoverageIgnore
258 |          */
259 |         function xri(): InspectorInterface
260 |         {
261 |             $xr = getXrFailover();
262 |             if ($xr === null) {
263 |                 return new InspectorNull();
264 |             }
265 | 
266 |             try {
267 |                 return InspectorInstance::get();
268 |             } catch (LogicException) {
269 |                 $xrInspector = $xr->isEnabled()
270 |                     ? new Inspector($xr->client())
271 |                     : new InspectorNull();
272 | 
273 |                 return (new InspectorInstance($xrInspector))::get();
274 |             }
275 |         }
276 |     }
277 |     if (! function_exists('xrConfig')) { // @codeCoverageIgnore
278 |         /**
279 |          * Init a new XrInstance with provided config.
280 |          *
281 |          * @codeCoverageIgnore
282 |          */
283 |         function xrConfig(
284 |             bool $isEnabled = true,
285 |             bool $isHttps = false,
286 |             string $host = 'localhost',
287 |             int $port = 27420,
288 |             string $key = '',
289 |         ): void {
290 |             new XrInstance(
291 |                 new Xr(
292 |                     isEnabled: $isEnabled,
293 |                     isHttps: $isHttps,
294 |                     host: $host,
295 |                     port: $port,
296 |                     key: $key
297 |                 )
298 |             );
299 |         }
300 |     }
301 | }
302 | 


--------------------------------------------------------------------------------
/xr.svg:
--------------------------------------------------------------------------------
 1 | 
 2 |     
 3 |         
 4 |             
 5 |             
 6 |         
 7 |     
 8 |     
 9 |         
10 |         
11 |         
12 |         
13 |     
14 | 


--------------------------------------------------------------------------------