From 04c1a8d444e9e30ee96ca7befbed7387efbd4037 Mon Sep 17 00:00:00 2001 From: Filip Iulian Pacurar Date: Wed, 7 Feb 2024 22:35:02 +0200 Subject: [PATCH] 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 --- CHANGELOG.md | 1 + README.md | 4 + resources/views/helper.php | 10 +++ src/Generator.php | 49 ++++++++++ tests/RealTimeFacadesTest.php | 90 +++++++++++++++++++ ...0385307adf5db34c7986ecbd11646061356ec8.php | 21 +++++ ...31b04ec1494fc71a1bc848f020044aba2af7b1.php | 21 +++++ 7 files changed, 196 insertions(+) create mode 100644 tests/RealTimeFacadesTest.php create mode 100644 tests/stubs/facade-0e0385307adf5db34c7986ecbd11646061356ec8.php create mode 100644 tests/stubs/facade-9431b04ec1494fc71a1bc848f020044aba2af7b1.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d638d2d..4de848a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ All notable changes to this project will be documented in this file. ### Added - Add support for nikic/php-parser 5 (next to 4) [#1502 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1502) - Add support for `immutable_date:*` and `immutable_datetime:*` casts. [#1380 / thekonz](https://github.com/barryvdh/laravel-ide-helper/pull/1380) +- Add support for real-time facades in the helper file. [pending PR](#) 2023-02-04, 2.13.0 ------------------ diff --git a/README.md b/README.md index bd00f5a..a151675 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,10 @@ The generator tries to identify the real class, but if it cannot be found, you c Some classes need a working database connection. If you do not have a default working connection, some facades will not be included. You can use an in-memory SQLite driver by adding the `-M` option. +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. + +**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. + You can choose to include helper files. This is not enabled by default, but you can override it with the `--helpers (-H)` option. The `Illuminate/Support/helpers.php` is already set up, but you can add/remove your own files in the config file. diff --git a/resources/views/helper.php b/resources/views/helper.php index 90b7413..26a9abc 100644 --- a/resources/views/helper.php +++ b/resources/views/helper.php @@ -69,6 +69,16 @@ namespace { + + +namespace { + /** + * @mixin + */ + class extends {} +} + + namespace { diff --git a/src/Generator.php b/src/Generator.php index 4a92d35..0c7acc6 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -16,6 +16,10 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Facade; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; +use PhpParser\Lexer\Emulative; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Namespace_; +use PhpParser\Parser\Php7; use ReflectionClass; use Symfony\Component\Console\Output\OutputInterface; @@ -76,6 +80,7 @@ class Generator return $this->view->make('helper') ->with('namespaces_by_extends_ns', $this->getAliasesByExtendsNamespace()) ->with('namespaces_by_alias_ns', $this->getAliasesByAliasNamespace()) + ->with('real_time_facades', $this->getRealTimeFacades()) ->with('helpers', $this->helpers) ->with('version', $app->version()) ->with('include_fluent', $this->config->get('ide-helper.include_fluent', true)) @@ -174,6 +179,50 @@ class Generator return $aliases; } + protected function getRealTimeFacades() + { + $facades = []; + $realTimeFacadeFiles = glob(storage_path('framework/cache/facade-*.php')); + foreach($realTimeFacadeFiles as $file) { + try { + $name = $this->getFullyQualifiedClassNameInFile($file); + $facades[$name] = $name; + } catch (\Exception $e) { + continue; + } + } + + return $facades; + } + + protected function getFullyQualifiedClassNameInFile(string $path) { + $contents = file_get_contents($path); + + $parsers = new Php7(new Emulative); + + $parsed = collect($parsers->parse($contents) ?: []); + + $namespace = $parsed->first(function ($node) { + return $node instanceof Namespace_; + }); + + if($namespace) { + $name = $namespace->name->toString(); + + $class = collect($namespace->stmts)->first(function ($node) { + return $node instanceof Class_; + }); + + if($class) { + $name .= '\\' . $class->name->toString(); + } + + return $name; + } + } + + + /** * Regroup aliases by namespace of extended classes * diff --git a/tests/RealTimeFacadesTest.php b/tests/RealTimeFacadesTest.php new file mode 100644 index 0000000..42895b8 --- /dev/null +++ b/tests/RealTimeFacadesTest.php @@ -0,0 +1,90 @@ +app['config'], $this->app['view'], null, false); + + // Clear aliases and macros to have a small output file + AliasLoader::getInstance()->setAliases([]); + Request::flushMacros(); + + // Generate the helper file and return the content + $content = $generator->generate(); + + $this->assertStringContainsString('namespace Facades\Illuminate\Foundation\Exceptions {', $content, 'Could not find Facades\Illuminate\Foundation\Exceptions namespace in the generated helper file.'); + $this->assertStringContainsString('namespace Facades\App\Exceptions {', $content, 'Could not find Facades\App\Exceptions namespace in the generated helper file.'); + + $parsed = collect((new Php7(new Emulative()))->parse($content) ?: []); + + // test the Facades\Illuminate\Foundation\Exceptions namespace in the generated helper file + $frameworkExceptionsNamespace = $parsed->first(function ($stmt) { + return ($stmt instanceof Namespace_) && $stmt->name->toString() === 'Facades\Illuminate\Foundation\Exceptions'; + }); + $this->assertNotNull($frameworkExceptionsNamespace, 'Could not find Facades\Illuminate\Foundation\Exceptions namespace'); + $this->assertSame('Facades\Illuminate\Foundation\Exceptions', $frameworkExceptionsNamespace->name->toString()); + $this->verifyNamespace($frameworkExceptionsNamespace, 'Illuminate\Foundation\Exceptions\Handler'); + + // test the Facades\App\Exceptions namespace in the generated helper file + $appExceptionsNamespace = $parsed->first(function ($stmt) { + return ($stmt instanceof Namespace_) && $stmt->name->toString() === 'Facades\App\Exceptions'; + }); + $this->assertNotNull($appExceptionsNamespace, 'Could not find Facades\App\Exceptions namespace'); + $this->assertSame('Facades\App\Exceptions', $appExceptionsNamespace->name->toString()); + $this->verifyNamespace($appExceptionsNamespace, 'App\Exceptions\Handler'); + } + + private function verifyNamespace(Namespace_ $namespace, $target) + { + $stmts = collect($namespace->stmts); + + $this->assertInstanceOf(Class_::class, $stmts[0], 'Expected instance of Class_'); + + $statement = $stmts[0]; + $this->assertArrayHasKey('comments', $statement->getAttributes()); + + $this->assertStringContainsString('@mixin \\' . $target, $statement->getAttributes()['comments'][0]->getText(), 'Mixin comment not found'); + $this->assertSame(class_basename($target), $statement->name->toString(), 'Class name not found'); + $this->assertSame($target, $statement->extends->toString(), 'Class extends not found'); + } + + protected function getPackageProviders($app) + { + return [IdeHelperServiceProvider::class]; + } +} diff --git a/tests/stubs/facade-0e0385307adf5db34c7986ecbd11646061356ec8.php b/tests/stubs/facade-0e0385307adf5db34c7986ecbd11646061356ec8.php new file mode 100644 index 0000000..6a8ad57 --- /dev/null +++ b/tests/stubs/facade-0e0385307adf5db34c7986ecbd11646061356ec8.php @@ -0,0 +1,21 @@ +