Skip to content

Commit c86e883

Browse files
nunomadurobaselrabiataylorotwell
authored
[9.x] Improves dd source on compiled views (#44347)
* Improves `dd` source on compiled views Co-Authored-By: Basel Rabia <[email protected]> * Renders line pointer if line is not null * Fixes test suite * formatting * formatting * Fix tests Co-authored-by: Basel Rabia <[email protected]> Co-authored-by: Taylor Otwell <[email protected]>
1 parent abd4787 commit c86e883

File tree

8 files changed

+284
-18
lines changed

8 files changed

+284
-18
lines changed

src/Illuminate/Foundation/Concerns/ResolvesDumpSource.php

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ trait ResolvesDumpSource
77
/**
88
* The source resolver.
99
*
10-
* @var (callable(): (array{0: string, 1: string, 2: int}|null))|null
10+
* @var (callable(): (array{0: string, 1: string, 2: int|null}|null))|null
1111
*/
1212
protected static $dumpSourceResolver;
1313

1414
/**
1515
* Resolve the source of the dump call.
1616
*
17-
* @return array{0: string, 1: string, 2: int}|null
17+
* @return array{0: string, 1: string, 2: int|null}|null
1818
*/
1919
public function resolveDumpSource()
2020
{
@@ -33,17 +33,50 @@ public function resolveDumpSource()
3333

3434
$relativeFile = $file;
3535

36+
if ($this->isCompiledViewFile($file)) {
37+
$file = $this->getOriginalFileForCompiledView($file);
38+
$line = null;
39+
}
40+
3641
if (str_starts_with($file, $this->basePath)) {
3742
$relativeFile = substr($file, strlen($this->basePath) + 1);
3843
}
3944

4045
return [$file, $relativeFile, $line];
4146
}
4247

48+
/**
49+
* Determine if the given file is a view compiled.
50+
*
51+
* @param string $file
52+
* @return bool
53+
*/
54+
protected function isCompiledViewFile($file)
55+
{
56+
return str_starts_with($file, $this->compiledViewPath);
57+
}
58+
59+
/**
60+
* Get the original view compiled file by the given compiled file.
61+
*
62+
* @param string $file
63+
* @return string
64+
*/
65+
protected function getOriginalFileForCompiledView($file)
66+
{
67+
preg_match('/\/\*\*PATH\s(.*)\sENDPATH/', file_get_contents($file), $matches);
68+
69+
if (isset($matches[1])) {
70+
$file = $matches[1];
71+
}
72+
73+
return $file;
74+
}
75+
4376
/**
4477
* Set the resolver that resolves the source of the dump call.
4578
*
46-
* @param (callable(): (array{0: string, 1: string, 2: int}|null))|null $callable
79+
* @param (callable(): (array{0: string, 1: string, 2: int|null}|null))|null $callable
4780
* @return void
4881
*/
4982
public static function resolveDumpSourceUsing($callable)

src/Illuminate/Foundation/Console/CliDumper.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ class CliDumper extends BaseCliDumper
2828
*/
2929
protected $output;
3030

31+
/**
32+
* The compiled view path for the application.
33+
*
34+
* @var string
35+
*/
36+
protected $compiledViewPath;
37+
3138
/**
3239
* If the dumper is currently dumping.
3340
*
@@ -40,27 +47,30 @@ class CliDumper extends BaseCliDumper
4047
*
4148
* @param \Symfony\Component\Console\Output\OutputInterface $output
4249
* @param string $basePath
50+
* @param string $compiledViewPath
4351
* @return void
4452
*/
45-
public function __construct($output, $basePath)
53+
public function __construct($output, $basePath, $compiledViewPath)
4654
{
4755
parent::__construct();
4856

4957
$this->basePath = $basePath;
5058
$this->output = $output;
59+
$this->compiledViewPath = $compiledViewPath;
5160
}
5261

5362
/**
5463
* Create a new CLI dumper instance and register it as the default dumper.
5564
*
5665
* @param string $basePath
66+
* @param string $compiledViewPath
5767
* @return void
5868
*/
59-
public static function register($basePath)
69+
public static function register($basePath, $compiledViewPath)
6070
{
6171
$cloner = tap(new VarCloner())->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
6272

63-
$dumper = new static(new ConsoleOutput(), $basePath);
73+
$dumper = new static(new ConsoleOutput(), $basePath, $compiledViewPath);
6474

6575
VarDumper::setHandler(fn ($value) => $dumper->dumpWithSource($cloner->cloneVar($value)));
6676
}
@@ -104,7 +114,13 @@ protected function getDumpSourceContent()
104114

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

107-
return sprintf(' <fg=gray>// <fg=gray;href=file://%s#L%s>%s:%s</></>', $file, $line, $relativeFile, $line);
117+
return sprintf(
118+
' <fg=gray>// <fg=gray;href=file://%s%s>%s%s</></>',
119+
$file,
120+
is_null($line) ? '' : "#L$line",
121+
$relativeFile,
122+
is_null($line) ? '' : ":$line"
123+
);
108124
}
109125

110126
/**

src/Illuminate/Foundation/Http/HtmlDumper.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ class HtmlDumper extends BaseHtmlDumper
3535
*/
3636
protected $basePath;
3737

38+
/**
39+
* The compiled view path of the application.
40+
*
41+
* @var string
42+
*/
43+
protected $compiledViewPath;
44+
3845
/**
3946
* If the dumper is currently dumping.
4047
*
@@ -46,26 +53,29 @@ class HtmlDumper extends BaseHtmlDumper
4653
* Create a new HTML dumper instance.
4754
*
4855
* @param string $basePath
56+
* @param string $compiledViewPath
4957
* @return void
5058
*/
51-
public function __construct($basePath)
59+
public function __construct($basePath, $compiledViewPath)
5260
{
5361
parent::__construct();
5462

5563
$this->basePath = $basePath;
64+
$this->compiledViewPath = $compiledViewPath;
5665
}
5766

5867
/**
5968
* Create a new HTML dumper instance and register it as the default dumper.
6069
*
6170
* @param string $basePath
71+
* @param string $compiledViewPath
6272
* @return void
6373
*/
64-
public static function register($basePath)
74+
public static function register($basePath, $compiledViewPath)
6575
{
6676
$cloner = tap(new VarCloner())->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
6777

68-
$dumper = new static($basePath);
78+
$dumper = new static($basePath, $compiledViewPath);
6979

7080
VarDumper::setHandler(fn ($value) => $dumper->dumpWithSource($cloner->cloneVar($value)));
7181
}
@@ -120,14 +130,14 @@ protected function getDumpSourceContent()
120130

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

123-
$source = sprintf('%s:%s', $relativeFile, $line);
133+
$source = sprintf('%s%s', $relativeFile, is_null($line) ? '' : ":$line");
124134

125135
if ($editor = $this->editor()) {
126136
$source = sprintf(
127-
'<a href="%s://open?file=%s&line=%s">%s</a>',
137+
'<a href="%s://open?file=%s%s">%s</a>',
128138
$editor,
129139
$file,
130-
$line,
140+
is_null($line) ? '' : "&line=$line",
131141
$source,
132142
);
133143
}

src/Illuminate/Foundation/Providers/FoundationServiceProvider.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,16 @@ public function registerDumper()
7575
{
7676
$basePath = $this->app->basePath();
7777

78+
$compiledViewPath = $this->app['config']->get('view.compiled');
79+
7880
$format = $_SERVER['VAR_DUMPER_FORMAT'] ?? null;
7981

8082
match (true) {
81-
'html' == $format => HtmlDumper::register($basePath),
82-
'cli' == $format => CliDumper::register($basePath),
83+
'html' == $format => HtmlDumper::register($basePath, $compiledViewPath),
84+
'cli' == $format => CliDumper::register($basePath, $compiledViewPath),
8385
'server' == $format => null,
8486
$format && 'tcp' == parse_url($format, PHP_URL_SCHEME) => null,
85-
default => in_array(PHP_SAPI, ['cli', 'phpdbg']) ? CliDumper::register($basePath) : HtmlDumper::register($basePath),
87+
default => in_array(PHP_SAPI, ['cli', 'phpdbg']) ? CliDumper::register($basePath, $compiledViewPath) : HtmlDumper::register($basePath, $compiledViewPath),
8688
};
8789
}
8890

tests/Foundation/Console/CliDumperTest.php

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Foundation\Console\CliDumper;
66
use PHPUnit\Framework\TestCase;
7+
use ReflectionClass;
78
use stdClass;
89
use Symfony\Component\Console\Output\BufferedOutput;
910
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
@@ -107,6 +108,82 @@ public function testNull()
107108
$this->assertSame($expected, $output);
108109
}
109110

111+
public function testWhenIsFileViewIsNotViewCompiled()
112+
{
113+
$file = '/my-work-directory/routes/console.php';
114+
115+
$output = new BufferedOutput();
116+
$dumper = new CliDumper(
117+
$output,
118+
'/my-work-directory',
119+
'/my-work-directory/storage/framework/views'
120+
);
121+
122+
$reflection = new ReflectionClass($dumper);
123+
$method = $reflection->getMethod('isCompiledViewFile');
124+
$method->setAccessible(true);
125+
$isCompiledViewFile = $method->invoke($dumper, $file);
126+
127+
$this->assertFalse($isCompiledViewFile);
128+
}
129+
130+
public function testWhenIsFileViewIsViewCompiled()
131+
{
132+
$file = '/my-work-directory/storage/framework/views/6687c33c38b71a8560.php';
133+
134+
$output = new BufferedOutput();
135+
$dumper = new CliDumper(
136+
$output,
137+
'/my-work-directory',
138+
'/my-work-directory/storage/framework/views'
139+
);
140+
141+
$reflection = new ReflectionClass($dumper);
142+
$method = $reflection->getMethod('isCompiledViewFile');
143+
$method->setAccessible(true);
144+
$isCompiledViewFile = $method->invoke($dumper, $file);
145+
146+
$this->assertTrue($isCompiledViewFile);
147+
}
148+
149+
public function testGetOriginalViewCompiledFile()
150+
{
151+
$compiled = __DIR__.'/../fixtures/fake-compiled-view.php';
152+
$original = '/my-work-directory/resources/views/welcome.blade.php';
153+
154+
$output = new BufferedOutput();
155+
$dumper = new CliDumper(
156+
$output,
157+
'/my-work-directory',
158+
'/my-work-directory/storage/framework/views'
159+
);
160+
161+
$reflection = new ReflectionClass($dumper);
162+
$method = $reflection->getMethod('getOriginalFileForCompiledView');
163+
$method->setAccessible(true);
164+
165+
$this->assertSame($original, $method->invoke($dumper, $compiled));
166+
}
167+
168+
public function testWhenGetOriginalViewCompiledFileFails()
169+
{
170+
$compiled = __DIR__.'/../fixtures/fake-compiled-view-without-source-map.php';
171+
$original = $compiled;
172+
173+
$output = new BufferedOutput();
174+
$dumper = new CliDumper(
175+
$output,
176+
'/my-work-directory',
177+
'/my-work-directory/storage/framework/views'
178+
);
179+
180+
$reflection = new ReflectionClass($dumper);
181+
$method = $reflection->getMethod('getOriginalFileForCompiledView');
182+
$method->setAccessible(true);
183+
184+
$this->assertSame($original, $method->invoke($dumper, $compiled));
185+
}
186+
110187
public function testUnresolvableSource()
111188
{
112189
CliDumper::resolveDumpSourceUsing(fn () => null);
@@ -118,10 +195,31 @@ public function testUnresolvableSource()
118195
$this->assertSame($expected, $output);
119196
}
120197

198+
public function testUnresolvableLine()
199+
{
200+
CliDumper::resolveDumpSourceUsing(function () {
201+
return [
202+
'/my-work-directory/resources/views/welcome.blade.php',
203+
'resources/views/welcome.blade.php',
204+
null,
205+
];
206+
});
207+
208+
$output = $this->dump('hey from view');
209+
210+
$expected = "\"hey from view\" // resources/views/welcome.blade.php\n";
211+
212+
$this->assertSame($expected, $output);
213+
}
214+
121215
protected function dump($value)
122216
{
123217
$output = new BufferedOutput();
124-
$dumper = new CliDumper($output, '/my-work-directory');
218+
$dumper = new CliDumper(
219+
$output,
220+
'/my-work-directory',
221+
'/my-work-directory/storage/framework/views',
222+
);
125223

126224
$cloner = tap(new VarCloner())->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
127225

0 commit comments

Comments
 (0)