├── testbench.yaml ├── config └── solo.php ├── pint.json ├── src ├── Providers │ └── DumpServiceProvider.php ├── Console │ └── Commands │ │ ├── DumpTestOnly.php │ │ └── Dumps.php └── Support │ └── CustomDumper.php ├── LICENSE ├── CHANGELOG.md ├── composer.json ├── CLAUDE.md └── README.md /testbench.yaml: -------------------------------------------------------------------------------- 1 | providers: 2 | - SoloTerm\Dumps\Providers\DumpServiceProvider 3 | - SoloTerm\Dumps\Tests\Support\DumpTestServiceProvider 4 | - App\Providers\AppServiceProvider 5 | 6 | migrations: false 7 | 8 | workbench: 9 | start: '/' 10 | health: true 11 | discovers: 12 | web: true 13 | config: true 14 | commands: true 15 | -------------------------------------------------------------------------------- /config/solo.php: -------------------------------------------------------------------------------- 1 | env('SOLO_DUMP_SERVER_HOST', 'tcp://127.0.0.1:9984') 10 | ]; 11 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "not_operator_with_successor_space": false, 5 | "heredoc_to_nowdoc": false, 6 | "phpdoc_summary": false, 7 | "concat_space": { 8 | "spacing": "one" 9 | }, 10 | "function_declaration": { 11 | "closure_fn_spacing": "none" 12 | }, 13 | "trailing_comma_in_multiline": false 14 | }, 15 | "xxx_header_comment": { 16 | "header": "@author Aaron Francis \n\n@link https://aaronfrancis.com\n@link https://x.com/aarondfrancis", 17 | "comment_type": "PHPDoc", 18 | "location": "after_declare_strict" 19 | } 20 | } -------------------------------------------------------------------------------- /src/Providers/DumpServiceProvider.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @link https://aaronfrancis.com 7 | * @link https://x.com/aarondfrancis 8 | */ 9 | 10 | namespace SoloTerm\Dumps\Providers; 11 | 12 | use Illuminate\Support\ServiceProvider; 13 | use SoloTerm\Dumps\Console\Commands\Dumps; 14 | use SoloTerm\Dumps\Support\CustomDumper; 15 | 16 | class DumpServiceProvider extends ServiceProvider 17 | { 18 | public function register(): void 19 | { 20 | // 21 | } 22 | 23 | public function boot(): void 24 | { 25 | CustomDumper::register($this->app->basePath(), $this->app['config']->get('view.compiled')); 26 | 27 | if ($this->app->runningInConsole()) { 28 | $this->commands([ 29 | Dumps::class, 30 | ]); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Console/Commands/DumpTestOnly.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @link https://aaronfrancis.com 7 | * @link https://x.com/aarondfrancis 8 | */ 9 | 10 | namespace SoloTerm\Dumps\Console\Commands; 11 | 12 | use Illuminate\Console\Command; 13 | 14 | class DumpTestOnly extends Command 15 | { 16 | protected $signature = 'solo:dump-test-only {--uuid=} {--loop}'; 17 | 18 | protected $description = 'A test command'; 19 | 20 | public function __construct() 21 | { 22 | parent::__construct(); 23 | $this->setHidden(); 24 | } 25 | 26 | public function handle(): void 27 | { 28 | if ($this->option('uuid')) { 29 | dump($this->option('uuid')); 30 | } 31 | 32 | if ($this->option('loop')) { 33 | for ($i = 1; $i <= 5; $i++) { 34 | dump("Loop $i"); 35 | sleep(1); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Aaron Francis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ### Changed 11 | - Added explicit `void` return types to command `handle()` methods 12 | - Added explicit `void` return types to service provider `register()` and `boot()` methods 13 | - Added parameter types to `CustomDumper::register()` method 14 | - Made `compiledViewPath` nullable to handle edge cases 15 | - Moved `DumpTestOnly` command to test-only service provider 16 | - Added `prefer-stable: true` to composer.json 17 | 18 | ### Fixed 19 | - Fixed risky test warning by removing `ob_get_clean()` call in BasicTest 20 | - Fixed `.phpunit.cache` gitignore pattern to ignore entire directory 21 | 22 | ### Removed 23 | - Removed redundant `CliDumper` import alias 24 | - Removed empty `dev` script from composer.json 25 | 26 | ## [1.0.1] - 2024 27 | 28 | ### Fixed 29 | - PHP 8.1 compatibility fixes 30 | 31 | ## [1.0.0] - 2024 32 | 33 | - Initial release 34 | 35 | [Unreleased]: https://github.com/soloterm/dumps/compare/v1.0.1...HEAD 36 | [1.0.1]: https://github.com/soloterm/dumps/compare/v1.0.0...v1.0.1 37 | [1.0.0]: https://github.com/soloterm/dumps/releases/tag/v1.0.0 38 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "soloterm/dumps", 3 | "description": "A Laravel command to intercept dumps from your Laravel application.", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Aaron Francis", 9 | "email": "aarondfrancis@gmail.com" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "prefer-stable": true, 14 | "require": { 15 | "php": "^8.1", 16 | "illuminate/support": "^10|^11|^12", 17 | "illuminate/console": "^10|^11|^12" 18 | }, 19 | "require-dev": { 20 | "illuminate/process": "^10|^11|^12", 21 | "phpunit/phpunit": "^10.5|^11", 22 | "orchestra/testbench": "^8.29|^9.5|^10" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "SoloTerm\\Dumps\\": "src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "SoloTerm\\Dumps\\Tests\\": "tests/", 32 | "App\\": "workbench/app/" 33 | } 34 | }, 35 | "extra": { 36 | "laravel": { 37 | "providers": [ 38 | "SoloTerm\\Dumps\\Providers\\DumpServiceProvider" 39 | ] 40 | } 41 | }, 42 | "scripts": { 43 | "post-autoload-dump": [ 44 | "@clear", 45 | "@prepare" 46 | ], 47 | "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", 48 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 49 | "serve": [ 50 | "Composer\\Config::disableProcessTimeout", 51 | "@php vendor/bin/testbench serve --ansi" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Console/Commands/Dumps.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @link https://aaronfrancis.com 7 | * @link https://x.com/aarondfrancis 8 | */ 9 | 10 | namespace SoloTerm\Dumps\Console\Commands; 11 | 12 | use Illuminate\Console\Command; 13 | use Illuminate\Foundation\Console\CliDumper; 14 | use Illuminate\Support\Arr; 15 | use SoloTerm\Dumps\Support\CustomDumper; 16 | use Symfony\Component\VarDumper\Cloner\Data; 17 | use Symfony\Component\VarDumper\Server\DumpServer; 18 | 19 | class Dumps extends Command 20 | { 21 | protected $signature = 'solo:dumps'; 22 | 23 | protected $description = 'Collect dumps from your Laravel application.'; 24 | 25 | public function handle(): void 26 | { 27 | $dumper = new CliDumper( 28 | output: $this->getOutput()->getOutput(), 29 | basePath: base_path(), 30 | compiledViewPath: config('view.compiled') 31 | ); 32 | 33 | $server = new DumpServer(CustomDumper::dumpServerHost()); 34 | $server->start(); 35 | 36 | $this->info('Listening for dumps on ' . CustomDumper::dumpServerHost()); 37 | 38 | $server->listen(function (Data $data) use ($dumper) { 39 | // We added the dump source on the sending side. If we tried to deduce 40 | // it here it would only point to this command, not the originator. 41 | // Here we set the resolver to just grab it from our context. 42 | CliDumper::resolveDumpSourceUsing(function () use ($data) { 43 | return Arr::get($data->getContext(), 'dumpSource'); 44 | }); 45 | 46 | $dumper->dumpWithSource($data); 47 | }); 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Overview 6 | 7 | Solo Dumps is a Laravel package that intercepts `dump()` calls and redirects them to a dedicated terminal window via a TCP server. This keeps browser/API responses clean while centralizing debug output. 8 | 9 | ## Commands 10 | 11 | ```bash 12 | # Run all tests 13 | ./vendor/bin/phpunit 14 | 15 | # Run specific test suite 16 | ./vendor/bin/phpunit --testsuite=unit 17 | ./vendor/bin/phpunit --testsuite=integration 18 | 19 | # Run single test 20 | ./vendor/bin/phpunit --filter testMethodName 21 | 22 | # Start the dump server (for manual testing) 23 | php vendor/bin/testbench solo:dumps 24 | ``` 25 | 26 | ## Architecture 27 | 28 | ### Data Flow 29 | 30 | ```text 31 | dump() call in app 32 | ↓ 33 | CustomDumper (VarDumper handler) 34 | ↓ resolves source file/line 35 | ↓ clones var with dumpSource context 36 | ServerDumper → TCP socket (127.0.0.1:9984) 37 | ↓ 38 | DumpServer in `solo:dumps` command 39 | ↓ extracts dumpSource from context 40 | CliDumper → terminal output with source info 41 | ``` 42 | 43 | ### Key Components 44 | 45 | - **CustomDumper** (`src/Support/CustomDumper.php`): Registers a custom VarDumper handler that: 46 | - Resolves dump source (file:line) before sending 47 | - Checks if dump server port is open 48 | - Falls back to original handler if server unavailable 49 | 50 | - **Dumps Command** (`src/Console/Commands/Dumps.php`): Runs the TCP server that receives and displays dumps with source information 51 | 52 | ### Fallback Behavior 53 | 54 | When the dump server isn't running, CustomDumper falls back to the original dump handler. The `portOpen()` check prevents drops on first dump (ServerDumper's built-in fallback only kicks in after first failure). 55 | 56 | ## Configuration 57 | 58 | Host configured via `config/solo.php`: 59 | ```php 60 | 'dump_server_host' => 'tcp://127.0.0.1:9984' 61 | ``` 62 | 63 | ## Testing Notes 64 | 65 | Integration tests spawn real processes to test the server/client interaction. Tests use Orchestra Testbench with process helpers. 66 | -------------------------------------------------------------------------------- /src/Support/CustomDumper.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @link https://aaronfrancis.com 7 | * @link https://x.com/aarondfrancis 8 | */ 9 | 10 | namespace SoloTerm\Dumps\Support; 11 | 12 | use Illuminate\Foundation\Console\CliDumper; 13 | use Symfony\Component\Console\Output\StreamOutput; 14 | use Symfony\Component\VarDumper\Caster\ReflectionCaster; 15 | use Symfony\Component\VarDumper\Cloner\Data; 16 | use Symfony\Component\VarDumper\Cloner\VarCloner; 17 | use Symfony\Component\VarDumper\Dumper\DataDumperInterface; 18 | use Symfony\Component\VarDumper\Dumper\ServerDumper; 19 | use Symfony\Component\VarDumper\VarDumper; 20 | use Throwable; 21 | 22 | class CustomDumper 23 | { 24 | public static function register(string $basePath, ?string $compiledViewPath): static 25 | { 26 | return new static($basePath, $compiledViewPath); 27 | } 28 | 29 | public static function dumpServerHost(): string 30 | { 31 | return config()->get('solo.dump_server_host', 'tcp://127.0.0.1:9984'); 32 | } 33 | 34 | public function __construct(public string $basePath, public ?string $compiledViewPath) 35 | { 36 | $cloner = new VarCloner; 37 | $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); 38 | 39 | $original = VarDumper::setHandler(null); 40 | 41 | // We only use this dumper to get the dump source and add it to the context. 42 | $fake = $this->makeSourceResolvingDumper(); 43 | 44 | $server = new ServerDumper(static::dumpServerHost(), $this->makeFallbackDumper()); 45 | 46 | VarDumper::setHandler(function (mixed $var) use ($cloner, $server, $fake, $original) { 47 | $data = $cloner->cloneVar($var)->withContext([ 48 | 'dumpSource' => $fake->resolveDumpSource() 49 | ]); 50 | 51 | try { 52 | // We have to manually check the port to see if it's open. If you don't, the 53 | // ServerDumper drops a dump before it switches to the fallback dumper. 54 | $response = $this->portOpen() ? $server->dump($data) : 'server_dump_failed'; 55 | } catch (Throwable $e) { 56 | $response = 'server_dump_failed'; 57 | } 58 | 59 | if ($response === 'server_dump_failed') { 60 | if (is_callable($original)) { 61 | $original($var); 62 | } else { 63 | print_r($var); 64 | } 65 | } 66 | }); 67 | } 68 | 69 | protected function portOpen(): bool 70 | { 71 | $parts = parse_url(static::dumpServerHost()); 72 | 73 | $host = $parts['host'] ?? null; 74 | $port = $parts['port'] ?? null; 75 | 76 | $fp = @fsockopen($host, $port, $errno, $errstr, 0.1); 77 | 78 | if ($fp) { 79 | fclose($fp); 80 | 81 | return true; 82 | } 83 | 84 | return false; 85 | } 86 | 87 | protected function makeSourceResolvingDumper(): CliDumper 88 | { 89 | $output = new StreamOutput(fopen('php://memory', 'w')); 90 | 91 | return new CliDumper($output, $this->basePath, $this->compiledViewPath); 92 | } 93 | 94 | protected function makeFallbackDumper(): DataDumperInterface 95 | { 96 | return new class implements DataDumperInterface 97 | { 98 | public function dump(Data $data): string 99 | { 100 | return 'server_dump_failed'; 101 | } 102 | }; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solo Dumps for Laravel 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/soloterm/dumps)](https://packagist.org/packages/soloterm/dumps) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/soloterm/dumps)](https://packagist.org/packages/soloterm/dumps) 5 | [![License](https://img.shields.io/packagist/l/soloterm/dumps)](https://packagist.org/packages/soloterm/dumps) 6 | 7 | Solo Dumps for Laravel is a package that intercepts and collects `dump()` calls from your Laravel application and displays 8 | them in a dedicated terminal window. This eliminates the need to clutter your browser or API responses with debug 9 | output. 10 | 11 | ![Screenshot](https://github.com/soloterm/dumps/blob/main/art/screenshot.png?raw=true) 12 | 13 | This library was built to support [Solo](https://github.com/soloterm/solo), your all-in-one Laravel command to tame 14 | local development. 15 | 16 | This package is especially useful when: 17 | 18 | - You're working with APIs where you can't see dumps in the browser 19 | - You want to keep your browser console clean 20 | - You want to centralize all your debugging output in one place 21 | - You need source file information for your dumps 22 | 23 | 24 | ## Installation 25 | 26 | 1. Require the package: 27 | 28 | ```shell 29 | composer require soloterm/dumps --dev 30 | ``` 31 | 32 | The package will automatically register the service provider. 33 | 34 | ## Usage 35 | 36 | Simply run: 37 | 38 | ```shell 39 | php artisan solo:dumps 40 | ``` 41 | 42 | This will start a server that intercepts all `dump()` calls from your Laravel application. The command will keep running 43 | and display any dumps in real-time. 44 | 45 | Now when you use `dump()` anywhere in your application, the output will be sent to this terminal window instead of your 46 | browser or API response. 47 | 48 | ### Features 49 | 50 | - Intercepts all `dump()` calls from your Laravel application 51 | - Shows the exact file and line number where the dump was called 52 | - Formats the output using Laravel's CLI dumper for better readability 53 | - Works with APIs, background jobs, and other contexts where dumps are normally hard to see 54 | - Preserves all the functionality of Laravel's dump helper 55 | - Restores regular dump functionality when the command is stopped 56 | 57 | ### Works with Solo for Laravel 58 | 59 | If you're using [Solo for Laravel](https://github.com/soloterm/solo), you can add the dumps command to your 60 | configuration: 61 | 62 | ```php 63 | // config/solo.php 64 | 65 | 'commands' => [ 66 | // ... other commands 67 | 'Dumps' => 'php artisan solo:dumps', 68 | ], 69 | ``` 70 | 71 | ## Configuration 72 | 73 | You can configure the dump server host in your `config/solo.php` file: 74 | 75 | ```php 76 | // config/solo.php 77 | 78 | return [ 79 | // ... other configurations 80 | 'dump_server_host' => 'tcp://127.0.0.1:9984', 81 | ]; 82 | ``` 83 | 84 | There is no dedicated `dumps` config file. You can either create a `solo.php` and put the `dump_server_host` in there, or add it to your existing `solo.php` file. 85 | 86 | ## How It Works 87 | 88 | The package works by: 89 | 90 | 1. Registering a custom var dumper handler that captures dump calls 91 | 2. Resolving the source file and line number of the dump before sending to the server 92 | 3. Sending the dump data to a server running in a separate process 93 | 4. Displaying the formatted dump with source information in the terminal 94 | 95 | ## FAQ 96 | 97 | #### Does this work with dd()? 98 | 99 | No, this package only intercepts `dump()` calls. The `dd()` function will still halt execution as normal. 100 | 101 | #### Can I use this with APIs? 102 | 103 | Yes! This is one of the main benefits. Your API responses will remain clean while all dumps go to the dedicated terminal 104 | window. 105 | 106 | #### Will this affect my application in production? 107 | 108 | The package is designed to be used in development only. It's recommended to install it with the `--dev` flag to ensure 109 | it's not included in production. 110 | 111 | ## Support 112 | 113 | This is free! If you want to support me: 114 | 115 | - Check out my courses: 116 | - [Database School](https://databaseschool.com) 117 | - [Screencasting](https://screencasting.com) 118 | - Help spread the word about things I make 119 | 120 | ## Credits 121 | 122 | Dumps for Laravel was developed by Aaron Francis. If you like it, please let me know! 123 | 124 | - Twitter: https://twitter.com/aarondfrancis 125 | - Website: https://aaronfrancis.com 126 | - YouTube: https://youtube.com/@aarondfrancis 127 | - GitHub: https://github.com/aarondfrancis 128 | 129 | ## Related Projects 130 | 131 | - [Solo](https://github.com/soloterm/solo) - All-in-one Laravel command for local development 132 | - [Screen](https://github.com/soloterm/screen) - Pure PHP terminal renderer 133 | - [Grapheme](https://github.com/soloterm/grapheme) - Unicode grapheme width calculator 134 | - [Notify](https://github.com/soloterm/notify) - PHP package for desktop notifications via OSC escape sequences 135 | - [Notify Laravel](https://github.com/soloterm/notify-laravel) - Laravel integration for soloterm/notify 136 | - [TNotify](https://github.com/soloterm/tnotify) - Standalone, cross-platform CLI for desktop notifications 137 | - [VTail](https://github.com/soloterm/vtail) - Vendor-aware tail for Laravel logs --------------------------------------------------------------------------------