From c6509b658fb5563998667c8a169fa33b9823beab Mon Sep 17 00:00:00 2001 From: stefanScrumble <89080949+stefanScrumble@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:53:04 +0200 Subject: [PATCH] Improved attribute handling with types and Attribute support (#1567) * Add `AllowDynamicProperties` Attribute to cooperate with php8.2 deprecation * lint snapshot * Fix collection attribute typing * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Update composer.json * Fix collectino with template types WIP * Add `AllowDynamicProperties` Attribute to cooperate with php8.2 deprecation * Fix collection attribute typing * Update CHANGELOG.md * Update CHANGELOG.md * Update composer.json * composer fix-style * Fix tests * Fix tests * composer fix-style * Fix collectino with complex template types{ * Delete phpactor * Install from scrumble repo * Implemented correct attribute handling * composer fix-style * Revert "Install from scrumble repo" This reverts commit 3f1057adb63df4163fdc36258ade5cbd2e8770db. * Re-add barryvdh links * Revert "Update composer.json" This reverts commit 98252fe740a5495951b9c0308a2d318f51b96ffa. * Fix pivot test * Correct CHANGELOG.md with attribute merge request * More clear PhpDocTypeParser filename and clarified directory according to PSR12 --------- Co-authored-by: GeoSot Co-authored-by: laravel-ide-helper Co-authored-by: Luukdewaaier <54863704+Luukdewaaier@users.noreply.github.com> Co-authored-by: Luuk de Weijer --- CHANGELOG.md | 7 ++ src/Console/ModelsCommand.php | 61 +++++++------ src/Parsers/PhpDocReturnTypeParser.php | 79 +++++++++++++++++ .../Attributes/Models/BackedAttribute.php | 59 +++++++++++++ .../__snapshots__/Test__test__1.php | 77 +++++++++++++++++ .../Models/WithCollection.php | 51 +++++++++++ .../NonModels/CollectionModel.php | 9 ++ .../NonModels/NonModel.php | 9 ++ .../GenerateMixinCollection/Test.php | 24 ++++++ .../__snapshots__/Test__test__1.php | 85 +++++++++++++++++++ .../Pivot/Models/ModelWithPivot.php | 2 + .../Pivot/Models/Pivots/CustomPivot.php | 3 +- tests/Console/ModelsCommand/Pivot/Test.php | 2 + .../Pivot/__snapshots__/Test__test__1.php | 5 +- .../migrations/____backed_attribute_table.php | 20 +++++ 15 files changed, 463 insertions(+), 30 deletions(-) create mode 100644 src/Parsers/PhpDocReturnTypeParser.php create mode 100644 tests/Console/ModelsCommand/Attributes/Models/BackedAttribute.php create mode 100644 tests/Console/ModelsCommand/GenerateMixinCollection/Models/WithCollection.php create mode 100644 tests/Console/ModelsCommand/GenerateMixinCollection/NonModels/CollectionModel.php create mode 100644 tests/Console/ModelsCommand/GenerateMixinCollection/NonModels/NonModel.php create mode 100644 tests/Console/ModelsCommand/GenerateMixinCollection/Test.php create mode 100644 tests/Console/ModelsCommand/GenerateMixinCollection/__snapshots__/Test__test__1.php create mode 100644 tests/Console/ModelsCommand/migrations/____backed_attribute_table.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 231ae2a..50be63e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,13 @@ All notable changes to this project will be documented in this file. ### Added - Add type to pivot when using a custom pivot class [#1518 / d3v2a](https://github.com/barryvdh/laravel-ide-helper/pull/1518) +2024-01-04, v3.1.0 +------------------ + +### Fixes + +- Fix for getSomethingAttribute functions which return a collection with type templating in the phpDoc. And support for Attribute class in attributes https://github.com/barryvdh/laravel-ide-helper/pull/1567 + 2024-03-01, 3.0.0 ------------------ diff --git a/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php index fd6c72b..a43021c 100644 --- a/src/Console/ModelsCommand.php +++ b/src/Console/ModelsCommand.php @@ -12,6 +12,7 @@ namespace Barryvdh\LaravelIdeHelper\Console; use Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface; +use Barryvdh\LaravelIdeHelper\Parsers\PhpDocReturnTypeParser; use Barryvdh\Reflection\DocBlock; use Barryvdh\Reflection\DocBlock\Context; use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer; @@ -196,7 +197,7 @@ class ModelsCommand extends Command protected function getArguments() { return [ - ['model', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Which models to include', []], + ['model', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Which models to include', []], ]; } @@ -208,20 +209,21 @@ class ModelsCommand extends Command protected function getOptions() { return [ - ['filename', 'F', InputOption::VALUE_OPTIONAL, 'The path to the helper file'], - ['dir', 'D', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, - 'The model dir, supports glob patterns', [], ], - ['write', 'W', InputOption::VALUE_NONE, 'Write to Model file'], - ['write-mixin', 'M', InputOption::VALUE_NONE, - "Write models to {$this->filename} and adds @mixin to each model, avoiding IDE duplicate declaration warnings", - ], - ['nowrite', 'N', InputOption::VALUE_NONE, 'Don\'t write to Model file'], - ['reset', 'R', InputOption::VALUE_NONE, 'Refresh the properties/methods list, but keep the text'], - ['phpstorm-noinspections', 'p', InputOption::VALUE_NONE, - 'Add PhpFullyQualifiedNameUsageInspection and PhpUnnecessaryFullyQualifiedNameInspection PHPStorm ' . - 'noinspection tags', - ], - ['ignore', 'I', InputOption::VALUE_OPTIONAL, 'Which models to ignore', ''], + ['filename', 'F', InputOption::VALUE_OPTIONAL, 'The path to the helper file'], + ['dir', 'D', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, + 'The model dir, supports glob patterns', [], ], + ['write', 'W', InputOption::VALUE_NONE, 'Write to Model file'], + ['write-mixin', 'M', InputOption::VALUE_NONE, + "Write models to {$this->filename} and adds @mixin to each model, avoiding IDE duplicate declaration warnings", + ], + ['nowrite', 'N', InputOption::VALUE_NONE, 'Don\'t write to Model file'], + ['reset', 'R', InputOption::VALUE_NONE, 'Remove the original phpdocs instead of appending'], + ['smart-reset', 'r', InputOption::VALUE_NONE, 'Refresh the properties/methods list, but keep the text'], + ['phpstorm-noinspections', 'p', InputOption::VALUE_NONE, + 'Add PhpFullyQualifiedNameUsageInspection and PhpUnnecessaryFullyQualifiedNameInspection PHPStorm ' . + 'noinspection tags', + ], + ['ignore', 'I', InputOption::VALUE_OPTIONAL, 'Which models to ignore', ''], ]; } @@ -580,10 +582,7 @@ class ModelsCommand extends Command $isAttribute = is_a($type, '\Illuminate\Database\Eloquent\Casts\Attribute', true); $method = $reflection->getName(); if ( - Str::startsWith($method, 'get') && Str::endsWith( - $method, - 'Attribute' - ) && $method !== 'getAttribute' + Str::startsWith($method, 'get') && Str::endsWith($method, 'Attribute') && $method !== 'getAttribute' ) { //Magic getAttribute $name = Str::snake(substr($method, 3, -9)); @@ -599,15 +598,14 @@ class ModelsCommand extends Command $this->setProperty( Str::snake($method), $type, - $types->has('get'), - $types->has('set'), + $types->has('get') ?: null, + $types->has('set') ?: null, $this->getCommentFromDocBlock($reflection) ); } elseif ( - Str::startsWith($method, 'set') && Str::endsWith( - $method, - 'Attribute' - ) && $method !== 'setAttribute' + Str::startsWith($method, 'set') && + Str::endsWith($method, 'Attribute') && + $method !== 'setAttribute' ) { //Magic setAttribute $name = Str::snake(substr($method, 3, -9)); @@ -628,7 +626,7 @@ class ModelsCommand extends Command get_class($model->newModelQuery()) ); $modelName = $this->getClassNameInDestinationFile( - new \ReflectionClass($model), + new ReflectionClass($model), get_class($model) ); $this->setMethod($name, $builder . '|' . $modelName, $args, $comment); @@ -712,10 +710,10 @@ class ModelsCommand extends Command ) { if ($relationObj instanceof BelongsToMany) { $pivot = get_class($relationObj->newPivot()); - if (!in_array($pivot,[ Pivot::class, MorphPivot::class])) { + if (!in_array($pivot, [Pivot::class, MorphPivot::class])) { $this->setProperty( $relationObj->getPivotAccessor(), - $this->getClassNameInDestinationFile($model,$pivot), + $this->getClassNameInDestinationFile($model, $pivot), true, false ); @@ -1249,6 +1247,13 @@ class ModelsCommand extends Command $phpdoc = new DocBlock($reflection, $context); if ($phpdoc->hasTag('return')) { + $returnTag = $phpdoc->getTagsByName('return')[0]; + + $typeParser = new PhpDocReturnTypeParser($returnTag->getContent(), $context->getNamespaceAliases()); + if ($typeAlias = $typeParser->parse()) { + return $typeAlias; + } + $type = $phpdoc->getTagsByName('return')[0]->getType(); } diff --git a/src/Parsers/PhpDocReturnTypeParser.php b/src/Parsers/PhpDocReturnTypeParser.php new file mode 100644 index 0000000..4987e41 --- /dev/null +++ b/src/Parsers/PhpDocReturnTypeParser.php @@ -0,0 +1,79 @@ +typeAlias = $typeAlias; + $this->namespaceAliases = $namespaceAliases; + } + + /** + * @return string|null + */ + public function parse(): string|null + { + $matches = []; + preg_match('/(\w+)(<.*>)/', $this->typeAlias, $matches); + $matchCount = count($matches); + + if ($matchCount === 0 || $matchCount === 1) { + return null; + } + + if (empty($this->namespaceAliases[$matches[1]])) { + return null; + } + + return $this->namespaceAliases[$matches[1]] . $this->parseTemplate($matches[2] ?? null); + } + + /** + * @param string|null $template + * @return string + */ + private function parseTemplate(string|null $template): string + { + if ($template === null || $template === '') { + return ''; + } + + $type = ''; + $result = ''; + + foreach (str_split($template) as $char) { + $match = preg_match('/[A-z]/', $char); + + if (!$match) { + $type = $this->namespaceAliases[$type] ?? $type; + $result .= $type; + $result .= $char; + $type = ''; + + continue; + } + + $type .= $char; + } + + return $result; + } +} diff --git a/tests/Console/ModelsCommand/Attributes/Models/BackedAttribute.php b/tests/Console/ModelsCommand/Attributes/Models/BackedAttribute.php new file mode 100644 index 0000000..c6c8bf2 --- /dev/null +++ b/tests/Console/ModelsCommand/Attributes/Models/BackedAttribute.php @@ -0,0 +1,59 @@ + + */ + public function getCollectionAttribute(): Collection + { + return new Collection(); + } + + /** + * @return Collection + */ + public function getCollectionWithoutTemplateAttribute(): Collection + { + return new Collection(); + } + + public function getCollectionWithoutDocBlockAttribute(): Collection + { + return new Collection(); + } + + /** + * @return Collection + */ + public function getCollectionWithNonModelTemplateAttribute(): Collection + { + return new Collection(); + } + + /** + * @return Collection>> + */ + public function getCollectionWithNestedTemplateAttribute(): Collection + { + return new Collection(); + } +} diff --git a/tests/Console/ModelsCommand/GenerateMixinCollection/NonModels/CollectionModel.php b/tests/Console/ModelsCommand/GenerateMixinCollection/NonModels/CollectionModel.php new file mode 100644 index 0000000..aa8c82b --- /dev/null +++ b/tests/Console/ModelsCommand/GenerateMixinCollection/NonModels/CollectionModel.php @@ -0,0 +1,9 @@ +app->make(ModelsCommand::class); + + $tester = $this->runCommand($command, [ + '--write-mixin' => true, + ]); + + $this->assertSame(0, $tester->getStatusCode()); + $this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay()); + $this->assertMatchesMockedSnapshot(); + } +} diff --git a/tests/Console/ModelsCommand/GenerateMixinCollection/__snapshots__/Test__test__1.php b/tests/Console/ModelsCommand/GenerateMixinCollection/__snapshots__/Test__test__1.php new file mode 100644 index 0000000..d7d64f4 --- /dev/null +++ b/tests/Console/ModelsCommand/GenerateMixinCollection/__snapshots__/Test__test__1.php @@ -0,0 +1,85 @@ + + */ + public function getCollectionAttribute(): Collection + { + return new Collection(); + } + + /** + * @return Collection + */ + public function getCollectionWithoutTemplateAttribute(): Collection + { + return new Collection(); + } + + public function getCollectionWithoutDocBlockAttribute(): Collection + { + return new Collection(); + } + + /** + * @return Collection + */ + public function getCollectionWithNonModelTemplateAttribute(): Collection + { + return new Collection(); + } + + /** + * @return Collection>> + */ + public function getCollectionWithNestedTemplateAttribute(): Collection + { + return new Collection(); + } +} + + */ + + +namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\GenerateMixinCollection\Models{ +/** + * + * + * @property-read \Illuminate\Support\Collection $collection + * @property-read \Illuminate\Support\Collection<\Illuminate\Support\Collection, \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\GenerateMixinCollection\NonModels\CollectionModel<\Illuminate\Support\Collection, \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\GenerateMixinCollection\NonModels\CollectionModel>> $collection_with_nested_template + * @property-read \Illuminate\Support\Collection $collection_with_non_model_template + * @property-read \Illuminate\Support\Collection $collection_without_doc_block + * @property-read \Illuminate\Support\Collection $collection_without_template + * @method static \Illuminate\Database\Eloquent\Builder|WithCollection newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|WithCollection newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|WithCollection query() + * @mixin \Eloquent + */ + #[\AllowDynamicProperties] + class IdeHelperWithCollection {} +} + diff --git a/tests/Console/ModelsCommand/Pivot/Models/ModelWithPivot.php b/tests/Console/ModelsCommand/Pivot/Models/ModelWithPivot.php index f579cf9..3f9e0bd 100644 --- a/tests/Console/ModelsCommand/Pivot/Models/ModelWithPivot.php +++ b/tests/Console/ModelsCommand/Pivot/Models/ModelWithPivot.php @@ -1,5 +1,7 @@ bigIncrements('id'); + $table->string('name'); + $table->string('name_read'); + $table->string('name_write'); + }); + } +}