Skip to content

[9.x] Adds source file to dd function output 💅🏻 #44211

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/Illuminate/Foundation/Concerns/ResolvesDumpSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Illuminate\Foundation\Concerns;

trait ResolvesDumpSource
{
/**
* The source resolver.
*
* @var (callable(): (array{0: string, 1: string, 2: int}|null))|null
*/
protected static $dumpSourceResolver;

/**
* Resolve the source of the dump call.
*
* @return array{0: string, 1: string, 2: int}|null
*/
public function resolveDumpSource()
{
if (static::$dumpSourceResolver) {
return call_user_func(static::$dumpSourceResolver);
}

$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 20);

$file = $trace[7]['file'] ?? null;
$line = $trace[7]['line'] ?? null;

if (is_null($file) || is_null($line)) {
return;
}

$relativeFile = $file;

if (str_starts_with($file, $this->basePath)) {
$relativeFile = substr($file, strlen($this->basePath) + 1);
}

return [$file, $relativeFile, $line];
}

/**
* Set the resolver that resolves the source of the dump call.
*
* @param (callable(): (array{0: string, 1: string, 2: int}|null))|null $callable
* @return void
*/
public static function resolveDumpSourceUsing($callable)
{
static::$dumpSourceResolver = $callable;
}
}
117 changes: 117 additions & 0 deletions src/Illuminate/Foundation/Console/CliDumper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

namespace Illuminate\Foundation\Console;

use Illuminate\Foundation\Concerns\ResolvesDumpSource;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper as BaseCliDumper;
use Symfony\Component\VarDumper\VarDumper;

class CliDumper extends BaseCliDumper
{
use ResolvesDumpSource;

/**
* The base path of the application.
*
* @var string
*/
protected $basePath;

/**
* The output instance.
*
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output;

/**
* If the dumper is currently dumping.
*
* @var bool
*/
protected $dumping = false;

/**
* Create a new CLI dumper instance.
*
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @param string $basePath
* @return void
*/
public function __construct($output, $basePath)
{
parent::__construct();

$this->basePath = $basePath;
$this->output = $output;
}

/**
* Create a new CLI dumper instance and register it as the default dumper.
*
* @param string $basePath
* @return void
*/
public static function register($basePath)
{
$cloner = tap(new VarCloner())->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);

$dumper = new static(new ConsoleOutput(), $basePath);

VarDumper::setHandler(fn ($value) => $dumper->dumpWithSource($cloner->cloneVar($value)));
}

/**
* Dump a variable with its source file / line.
*
* @param \Symfony\Component\VarDumper\Cloner\Data $data
* @return void
*/
public function dumpWithSource(Data $data)
{
if ($this->dumping) {
$this->dump($data);

return;
}

$this->dumping = true;

$output = (string) $this->dump($data, true);
$lines = explode("\n", $output);

$lines[0] .= $this->getDumpSourceContent();

$this->output->write(implode("\n", $lines));

$this->dumping = false;
}

/**
* Get the dump's source console content.
*
* @return string
*/
protected function getDumpSourceContent()
{
if (is_null($dumpSource = $this->resolveDumpSource())) {
return '';
}

[$file, $relativeFile, $line] = $dumpSource;

return sprintf(' <fg=gray>// <fg=gray;href=file://%s#L%s>%s:%s</></>', $file, $line, $relativeFile, $line);
}

/**
* {@inheritDoc}
*/
protected function supportsColors(): bool
{
return $this->output->isDecorated();
}
}
151 changes: 151 additions & 0 deletions src/Illuminate/Foundation/Http/HtmlDumper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

namespace Illuminate\Foundation\Http;

use Illuminate\Foundation\Concerns\ResolvesDumpSource;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\HtmlDumper as BaseHtmlDumper;
use Symfony\Component\VarDumper\VarDumper;
use Throwable;

class HtmlDumper extends BaseHtmlDumper
{
use ResolvesDumpSource;

/**
* Where the source should be placed on "expanded" kind of dumps.
*
* @var string
*/
const EXPANDED_SEPARATOR = 'class=sf-dump-expanded>';

/**
* Where the source should be placed on "non expanded" kind of dumps.
*
* @var string
*/
const NON_EXPANDED_SEPARATOR = "\n</pre><script>";

/**
* The base path of the application.
*
* @var string
*/
protected $basePath;

/**
* If the dumper is currently dumping.
*
* @var bool
*/
protected $dumping = false;

/**
* Create a new HTML dumper instance.
*
* @param string $basePath
* @return void
*/
public function __construct($basePath)
{
parent::__construct();

$this->basePath = $basePath;
}

/**
* Create a new HTML dumper instance and register it as the default dumper.
*
* @param string $basePath
* @return void
*/
public static function register($basePath)
{
$cloner = tap(new VarCloner())->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);

$dumper = new static($basePath);

VarDumper::setHandler(fn ($value) => $dumper->dumpWithSource($cloner->cloneVar($value)));
}

/**
* Dump a variable with its source file / line.
*
* @param \Symfony\Component\VarDumper\Cloner\Data $data
* @return void
*/
public function dumpWithSource(Data $data)
{
if ($this->dumping) {
$this->dump($data);

return;
}

$this->dumping = true;

$output = (string) $this->dump($data, true);

$output = match (true) {
str_contains($output, static::EXPANDED_SEPARATOR) => str_replace(
static::EXPANDED_SEPARATOR,
static::EXPANDED_SEPARATOR.$this->getDumpSourceContent(),
$output,
),
str_contains($output, static::NON_EXPANDED_SEPARATOR) => str_replace(
static::NON_EXPANDED_SEPARATOR,
$this->getDumpSourceContent().static::NON_EXPANDED_SEPARATOR,
$output,
),
default => $output,
};

fwrite($this->outputStream, $output);

$this->dumping = false;
}

/**
* Get the dump's source HTML content.
*
* @return string
*/
protected function getDumpSourceContent()
{
if (is_null($dumpSource = $this->resolveDumpSource())) {
return '';
}

[$file, $relativeFile, $line] = $dumpSource;

$source = sprintf('%s:%s', $relativeFile, $line);

if ($editor = $this->editor()) {
$source = sprintf(
'<a href="%s://open?file=%s&line=%s">%s</a>',
$editor,
$file,
$line,
$source,
);
}

return sprintf('<span style="color: #A0A0A0; font-family: Menlo"> // %s</span>', $source);
}

/**
* Get the application editor, if applicable.
*
* @return string|null
*/
protected function editor()
{
try {
return config('app.editor');
} catch (Throwable $e) {
// ...
}
}
}
23 changes: 23 additions & 0 deletions src/Illuminate/Foundation/Providers/FoundationServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace Illuminate\Foundation\Providers;

use Illuminate\Contracts\Foundation\MaintenanceMode as MaintenanceModeContract;
use Illuminate\Foundation\Console\CliDumper;
use Illuminate\Foundation\Http\HtmlDumper;
use Illuminate\Foundation\MaintenanceModeManager;
use Illuminate\Foundation\Vite;
use Illuminate\Http\Request;
Expand Down Expand Up @@ -57,12 +59,33 @@ public function register()
{
parent::register();

$this->registerDumper();
$this->registerRequestValidation();
$this->registerRequestSignatureValidation();
$this->registerExceptionTracking();
$this->registerMaintenanceModeManager();
}

/**
* Register an var dumper (with source) to debug variables.
*
* @return void
*/
public function registerDumper()
{
$basePath = $this->app->basePath();

$format = $_SERVER['VAR_DUMPER_FORMAT'] ?? null;

match (true) {
'html' == $format => HtmlDumper::register($basePath),
'cli' == $format => CliDumper::register($basePath),
'server' == $format => null,
$format && 'tcp' == parse_url($format, PHP_URL_SCHEME) => null,
default => in_array(PHP_SAPI, ['cli', 'phpdbg']) ? CliDumper::register($basePath) : HtmlDumper::register($basePath),
};
}

/**
* Register the "validate" macro on the request.
*
Expand Down
Loading