Skip to content

Commit

Permalink
Improved attribute handling with types and Attribute support (#1567)
Browse files Browse the repository at this point in the history
* 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 3f1057a.

* Re-add barryvdh links

* Revert "Update composer.json"

This reverts commit 98252fe.

* Fix pivot test

* Correct CHANGELOG.md with attribute merge request

* More clear PhpDocTypeParser filename and clarified directory according to PSR12

---------

Co-authored-by: GeoSot <[email protected]>
Co-authored-by: laravel-ide-helper <[email protected]>
Co-authored-by: Luukdewaaier <[email protected]>
Co-authored-by: Luuk de Weijer <[email protected]>
  • Loading branch information
5 people authored Jul 12, 2024
1 parent 6338abb commit c6509b6
Show file tree
Hide file tree
Showing 15 changed files with 463 additions and 30 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------------

Expand Down
61 changes: 33 additions & 28 deletions src/Console/ModelsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -196,7 +197,7 @@ public function handle()
protected function getArguments()
{
return [
['model', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Which models to include', []],
['model', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Which models to include', []],
];
}

Expand All @@ -208,20 +209,21 @@ protected function getArguments()
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', ''],
];
}

Expand Down Expand Up @@ -580,10 +582,7 @@ public function getPropertiesFromMethods($model)
$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 get<name>Attribute
$name = Str::snake(substr($method, 3, -9));
Expand All @@ -599,15 +598,14 @@ public function getPropertiesFromMethods($model)
$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 set<name>Attribute
$name = Str::snake(substr($method, 3, -9));
Expand All @@ -628,7 +626,7 @@ public function getPropertiesFromMethods($model)
get_class($model->newModelQuery())
);
$modelName = $this->getClassNameInDestinationFile(
new \ReflectionClass($model),
new ReflectionClass($model),
get_class($model)
);
$this->setMethod($name, $builder . '|' . $modelName, $args, $comment);
Expand Down Expand Up @@ -712,10 +710,10 @@ public function getPropertiesFromMethods($model)
) {
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
);
Expand Down Expand Up @@ -1249,6 +1247,13 @@ protected function getReturnTypeFromDocBlock(\ReflectionMethod $reflection, \Ref
$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();
}

Expand Down
79 changes: 79 additions & 0 deletions src/Parsers/PhpDocReturnTypeParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace Barryvdh\LaravelIdeHelper\Parsers;

class PhpDocReturnTypeParser
{
/**
* @var string
*/
private string $typeAlias;

/**
* @var array
*/
private array $namespaceAliases;

/**
* @param string $typeAlias
* @param array $namespaceAliases
*/
public function __construct(string $typeAlias, array $namespaceAliases)
{
$this->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;
}
}
59 changes: 59 additions & 0 deletions tests/Console/ModelsCommand/Attributes/Models/BackedAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class BackedAttribute extends Model
{
protected function name(): Attribute
{
return new Attribute(
function (?string $name): ?string {
return $name;
},
function (?string $name): ?string {
return $name;
}
);
}

protected function nameRead(): Attribute
{
return new Attribute(
function (?string $name): ?string {
return $name;
},
);
}

protected function nameWrite(): Attribute
{
return new Attribute(
set: function (?string $name): ?string {
return $name;
},
);
}

protected function nonBackedSet(): Attribute
{
return new Attribute(
set: function (?string $name): ?string {
return $name;
},
);
}

protected function nonBackedGet(): Attribute
{
return new Attribute(
get: function (): ?string {
return 'test';
},
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,83 @@
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

/**
*
*
* @property int $id
* @property string|null $name
* @property string|null $name_read
* @property string|null $name_write
* @property-read string|null $non_backed_get
* @property-write string|null $non_backed_set
* @method static \Illuminate\Database\Eloquent\Builder|BackedAttribute newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|BackedAttribute newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|BackedAttribute query()
* @method static \Illuminate\Database\Eloquent\Builder|BackedAttribute whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|BackedAttribute whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|BackedAttribute whereNameRead($value)
* @method static \Illuminate\Database\Eloquent\Builder|BackedAttribute whereNameWrite($value)
* @mixin \Eloquent
*/
class BackedAttribute extends Model
{
protected function name(): Attribute
{
return new Attribute(
function (?string $name): ?string {
return $name;
},
function (?string $name): ?string {
return $name;
}
);
}

protected function nameRead(): Attribute
{
return new Attribute(
function (?string $name): ?string {
return $name;
},
);
}

protected function nameWrite(): Attribute
{
return new Attribute(
set: function (?string $name): ?string {
return $name;
},
);
}

protected function nonBackedSet(): Attribute
{
return new Attribute(
set: function (?string $name): ?string {
return $name;
},
);
}

protected function nonBackedGet(): Attribute
{
return new Attribute(
get: function (): ?string {
return 'test';
},
);
}
}
<?php

declare(strict_types=1);

namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

/**
*
*
Expand Down
Loading

0 comments on commit c6509b6

Please sign in to comment.