Skip to content

Commit 04c1a8d

Browse files
authored
Support generating helper files for real-time facades (#1455)
* Support generating helper files for real-time facades Add changelog entry for real-time facades support Mention real-time facades support in readme Fix code style Import * Remove useless assertions We cannot 100% control the output file has only the real-time facades, anything else can be in the final output
1 parent c290af8 commit 04c1a8d

File tree

7 files changed

+196
-0
lines changed

7 files changed

+196
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ All notable changes to this project will be documented in this file.
2626
### Added
2727
- Add support for nikic/php-parser 5 (next to 4) [#1502 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1502)
2828
- Add support for `immutable_date:*` and `immutable_datetime:*` casts. [#1380 / thekonz](https://github.com/barryvdh/laravel-ide-helper/pull/1380)
29+
- Add support for real-time facades in the helper file. [pending PR](#)
2930

3031
2023-02-04, 2.13.0
3132
------------------

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ The generator tries to identify the real class, but if it cannot be found, you c
113113
Some classes need a working database connection. If you do not have a default working connection, some facades will not be included.
114114
You can use an in-memory SQLite driver by adding the `-M` option.
115115

116+
If you use [real-time facades](https://laravel.com/docs/master/facades#real-time-facades) in your app, those will also be included in the generated file using a `@mixin` annotation and extending the original class underneath the facade.
117+
118+
**Note**: this feature uses the generated real-time facades files in the `storage/framework/cache` folder. Those files are generated on-demand as you use the real-time facade, so if the framework has not generated that first, it will not be included in the helper file. Run the route/command/code first and then regenerate the helper file and this time the real-time facade will be included in it.
119+
116120
You can choose to include helper files. This is not enabled by default, but you can override it with the `--helpers (-H)` option.
117121
The `Illuminate/Support/helpers.php` is already set up, but you can add/remove your own files in the config file.
118122

resources/views/helper.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ public static function <?= $method->getName() ?>(<?= $method->getParamsWithDefau
6969

7070
<?php endforeach; ?>
7171

72+
<?php foreach($real_time_facades as $name): ?>
73+
<?php $nested = explode('\\', str_replace('\\'.class_basename($name), '', $name)); ?>
74+
namespace <?php echo implode('\\', $nested); ?> {
75+
/**
76+
* @mixin <?= str_replace('Facades', '', $name) ?>
77+
*/
78+
class <?= class_basename($name) ?> extends <?= str_replace('Facades', '', $name) ?> {}
79+
}
80+
<?php endforeach; ?>
81+
7282
<?php if ($helpers) : ?>
7383
namespace {
7484
<?= $helpers ?>

src/Generator.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
use Illuminate\Support\Facades\Facade;
1717
use Illuminate\Support\Str;
1818
use Illuminate\Support\Traits\Macroable;
19+
use PhpParser\Lexer\Emulative;
20+
use PhpParser\Node\Stmt\Class_;
21+
use PhpParser\Node\Stmt\Namespace_;
22+
use PhpParser\Parser\Php7;
1923
use ReflectionClass;
2024
use Symfony\Component\Console\Output\OutputInterface;
2125

@@ -76,6 +80,7 @@ public function generate()
7680
return $this->view->make('helper')
7781
->with('namespaces_by_extends_ns', $this->getAliasesByExtendsNamespace())
7882
->with('namespaces_by_alias_ns', $this->getAliasesByAliasNamespace())
83+
->with('real_time_facades', $this->getRealTimeFacades())
7984
->with('helpers', $this->helpers)
8085
->with('version', $app->version())
8186
->with('include_fluent', $this->config->get('ide-helper.include_fluent', true))
@@ -174,6 +179,50 @@ protected function getValidAliases()
174179
return $aliases;
175180
}
176181

182+
protected function getRealTimeFacades()
183+
{
184+
$facades = [];
185+
$realTimeFacadeFiles = glob(storage_path('framework/cache/facade-*.php'));
186+
foreach($realTimeFacadeFiles as $file) {
187+
try {
188+
$name = $this->getFullyQualifiedClassNameInFile($file);
189+
$facades[$name] = $name;
190+
} catch (\Exception $e) {
191+
continue;
192+
}
193+
}
194+
195+
return $facades;
196+
}
197+
198+
protected function getFullyQualifiedClassNameInFile(string $path) {
199+
$contents = file_get_contents($path);
200+
201+
$parsers = new Php7(new Emulative);
202+
203+
$parsed = collect($parsers->parse($contents) ?: []);
204+
205+
$namespace = $parsed->first(function ($node) {
206+
return $node instanceof Namespace_;
207+
});
208+
209+
if($namespace) {
210+
$name = $namespace->name->toString();
211+
212+
$class = collect($namespace->stmts)->first(function ($node) {
213+
return $node instanceof Class_;
214+
});
215+
216+
if($class) {
217+
$name .= '\\' . $class->name->toString();
218+
}
219+
220+
return $name;
221+
}
222+
}
223+
224+
225+
177226
/**
178227
* Regroup aliases by namespace of extended classes
179228
*

tests/RealTimeFacadesTest.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Barryvdh\LaravelIdeHelper\Tests;
6+
7+
use Barryvdh\LaravelIdeHelper\Generator;
8+
use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider;
9+
use Illuminate\Foundation\AliasLoader;
10+
use Illuminate\Http\Request;
11+
use Illuminate\Support\Facades\View;
12+
use PhpParser\Lexer\Emulative;
13+
use PhpParser\Node\Stmt\Class_;
14+
use PhpParser\Node\Stmt\Namespace_;
15+
use PhpParser\Parser\Php7;
16+
17+
class RealTimeFacadesTest extends TestCase
18+
{
19+
public function testRealTimeFacades()
20+
{
21+
// Add the views path to the view finder so the generator actually generates the file
22+
View::addLocation(__DIR__ . '/../resources/views');
23+
24+
// Clear cached real-time facades
25+
$cachedFacades = glob(storage_path('framework/cache/facade-*.php'));
26+
foreach ($cachedFacades as $cachedFacade) {
27+
unlink($cachedFacade);
28+
}
29+
30+
// Copy stubs to storage path as if the real-time facades were cached by the framework
31+
copy(
32+
__DIR__ . '/stubs/facade-0e0385307adf5db34c7986ecbd11646061356ec8.php',
33+
storage_path('framework/cache/facade-0e0385307adf5db34c7986ecbd11646061356ec8.php')
34+
);
35+
copy(
36+
__DIR__ . '/stubs/facade-9431b04ec1494fc71a1bc848f020044aba2af7b1.php',
37+
storage_path('framework/cache/facade-9431b04ec1494fc71a1bc848f020044aba2af7b1.php')
38+
);
39+
40+
// new instance of the generator which we test
41+
$generator = new Generator($this->app['config'], $this->app['view'], null, false);
42+
43+
// Clear aliases and macros to have a small output file
44+
AliasLoader::getInstance()->setAliases([]);
45+
Request::flushMacros();
46+
47+
// Generate the helper file and return the content
48+
$content = $generator->generate();
49+
50+
$this->assertStringContainsString('namespace Facades\Illuminate\Foundation\Exceptions {', $content, 'Could not find Facades\Illuminate\Foundation\Exceptions namespace in the generated helper file.');
51+
$this->assertStringContainsString('namespace Facades\App\Exceptions {', $content, 'Could not find Facades\App\Exceptions namespace in the generated helper file.');
52+
53+
$parsed = collect((new Php7(new Emulative()))->parse($content) ?: []);
54+
55+
// test the Facades\Illuminate\Foundation\Exceptions namespace in the generated helper file
56+
$frameworkExceptionsNamespace = $parsed->first(function ($stmt) {
57+
return ($stmt instanceof Namespace_) && $stmt->name->toString() === 'Facades\Illuminate\Foundation\Exceptions';
58+
});
59+
$this->assertNotNull($frameworkExceptionsNamespace, 'Could not find Facades\Illuminate\Foundation\Exceptions namespace');
60+
$this->assertSame('Facades\Illuminate\Foundation\Exceptions', $frameworkExceptionsNamespace->name->toString());
61+
$this->verifyNamespace($frameworkExceptionsNamespace, 'Illuminate\Foundation\Exceptions\Handler');
62+
63+
// test the Facades\App\Exceptions namespace in the generated helper file
64+
$appExceptionsNamespace = $parsed->first(function ($stmt) {
65+
return ($stmt instanceof Namespace_) && $stmt->name->toString() === 'Facades\App\Exceptions';
66+
});
67+
$this->assertNotNull($appExceptionsNamespace, 'Could not find Facades\App\Exceptions namespace');
68+
$this->assertSame('Facades\App\Exceptions', $appExceptionsNamespace->name->toString());
69+
$this->verifyNamespace($appExceptionsNamespace, 'App\Exceptions\Handler');
70+
}
71+
72+
private function verifyNamespace(Namespace_ $namespace, $target)
73+
{
74+
$stmts = collect($namespace->stmts);
75+
76+
$this->assertInstanceOf(Class_::class, $stmts[0], 'Expected instance of Class_');
77+
78+
$statement = $stmts[0];
79+
$this->assertArrayHasKey('comments', $statement->getAttributes());
80+
81+
$this->assertStringContainsString('@mixin \\' . $target, $statement->getAttributes()['comments'][0]->getText(), 'Mixin comment not found');
82+
$this->assertSame(class_basename($target), $statement->name->toString(), 'Class name not found');
83+
$this->assertSame($target, $statement->extends->toString(), 'Class extends not found');
84+
}
85+
86+
protected function getPackageProviders($app)
87+
{
88+
return [IdeHelperServiceProvider::class];
89+
}
90+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Facades\Illuminate\Foundation\Exceptions;
6+
7+
use Illuminate\Support\Facades\Facade;
8+
9+
/**
10+
* @see \Illuminate\Foundation\Exceptions\Handler
11+
*/
12+
class Handler extends Facade
13+
{
14+
/**
15+
* Get the registered name of the component.
16+
*/
17+
protected static function getFacadeAccessor(): string
18+
{
19+
return 'Illuminate\Foundation\Exceptions\Handler';
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Facades\App\Exceptions;
6+
7+
use Illuminate\Support\Facades\Facade;
8+
9+
/**
10+
* @see \App\Exceptions\Handler
11+
*/
12+
class Handler extends Facade
13+
{
14+
/**
15+
* Get the registered name of the component.
16+
*/
17+
protected static function getFacadeAccessor(): string
18+
{
19+
return 'App\Exceptions\Handler';
20+
}
21+
}

0 commit comments

Comments
 (0)