Skip to content

Property type isn't detected when casting Eloquent attributes to spatie/laravel-data Data objects #1400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.

### Added
- Add support for custom casts that implement `CastsInboundAttributes` [#1329 / sforward](https://github.com/barryvdh/laravel-ide-helper/pull/1329)
- A solution to detect property types when casting Eloquent attributes to objects. Add the `@ide-helper-eloquent-cast-to-specified-class` tag to the class being cast to

2022-03-06, 2.12.3
------------------
Expand Down
32 changes: 30 additions & 2 deletions src/Console/ModelsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -935,14 +935,14 @@ protected function createPhpDocs($class)

// remove the already existing tag to prevent duplicates
foreach ($phpdoc->getTagsByName('mixin') as $tag) {
if($tag->getContent() === $eloquentClassNameInModel) {
if ($tag->getContent() === $eloquentClassNameInModel) {
$phpdoc->deleteTag($tag);
}
}

$phpdoc->appendTag(Tag::createInstance('@mixin ' . $eloquentClassNameInModel, $phpdoc));
}

if ($this->phpstorm_noinspections) {
/**
* Facades, Eloquent API
Expand Down Expand Up @@ -1211,6 +1211,30 @@ protected function getReturnTypeFromReflection(\ReflectionMethod $reflection): ?
return $type;
}

/**
* Check to see if a class (or its parents) has a @ide-helper-eloquent-cast-to-specified-class tag
*
* @param \Reflector $reflector
*
* @return boolean
*/
protected function classHasCastSpecifiedTag(\Reflector $reflector = null): bool
{
$phpDocContext = (new ContextFactory())->createFromReflector($reflector);
$context = new Context(
$phpDocContext->getNamespace(),
$phpDocContext->getNamespaceAliases()
);
$phpdoc = new DocBlock($reflector, $context);

if ($phpdoc->hasTag('ide-helper-eloquent-cast-to-specified-class')) {
return true;
}

$parentReflector = $reflector->getParentClass();

return $parentReflector ? $this->classHasCastSpecifiedTag($parentReflector) : false;
}

/**
* Generates methods provided by the SoftDeletes trait
Expand Down Expand Up @@ -1316,6 +1340,10 @@ protected function checkForCastableCasts(string $type, array $params = []): stri
return $type;
}

if ($this->classHasCastSpecifiedTag($reflection)) {
return $type;
}

$cast = call_user_func([$type, 'castUsing'], $params);

if (is_string($cast) && !is_object($cast)) {
Expand Down
18 changes: 18 additions & 0 deletions tests/Console/ModelsCommand/SimpleCasts/Castables/ChildObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SimpleCasts\Castables;

class ChildObject extends ParentObject
{
/**
* Retrieve the value stored in this Value object.
*
* @return mixed
*/
public function getValue()
{
return mb_strtolower($this->value);
}
}
53 changes: 53 additions & 0 deletions tests/Console/ModelsCommand/SimpleCasts/Castables/CustomCaster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SimpleCasts\Castables;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class CustomCaster implements CastsAttributes
{
/** @var class-string The class to cast to. */
private $castToClass;

/**
* Constructor.
*
* @param string $castToClass The class to cast to.
* @return void
*/
public function __construct(string $castToClass)
{
$this->castToClass = $castToClass;
}

/**
* Transform the attribute from the underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
*/
public function get($model, string $key, $value, array $attributes)
{
return new $this->castToClass($value);
}

/**
* Transform the attribute to its underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
*/
public function set($model, string $key, $value, array $attributes)
{
/** @var ParentObject $value */
return [$key => $value->getValue()];
}
}
48 changes: 48 additions & 0 deletions tests/Console/ModelsCommand/SimpleCasts/Castables/ParentObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SimpleCasts\Castables;

use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;

/**
* @ide-helper-eloquent-cast-to-specified-class
*/
class ParentObject implements Castable
{
/** @var mixed The value that this Value object represents. */
protected $value;

/**
* Constructor.
*
* @param mixed $value The value to represent.
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* Retrieve the value stored in this Value object.
*
* @return mixed
*/
public function getValue()
{
return mb_strtoupper($this->value);
}

/**
* Get the name of the caster class to use when casting from / to this cast target.
*
* @param array $arguments
* @return string|CastsAttributes|CastsInboundAttributes
*/
public static function castUsing(array $arguments): CastsAttributes
{
return new CustomCaster(static::class);
}
}
4 changes: 4 additions & 0 deletions tests/Console/ModelsCommand/SimpleCasts/Models/SimpleCast.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

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

use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SimpleCasts\Castables\ChildObject;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SimpleCasts\Castables\ParentObject;
use Illuminate\Database\Eloquent\Model;

class SimpleCast extends Model
Expand Down Expand Up @@ -36,5 +38,7 @@ class SimpleCast extends Model
'cast_to_encrypted_collection' => 'encrypted:collection',
'cast_to_encrypted_json' => 'encrypted:json',
'cast_to_encrypted_object' => 'encrypted:object',
'cast_to_parent_object_using_cast_static_tag' => ParentObject::class,
'cast_to_child_object_using_cast_static_tag' => ChildObject::class,
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

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

use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SimpleCasts\Castables\ChildObject;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\SimpleCasts\Castables\ParentObject;
use Illuminate\Database\Eloquent\Model;

/**
Expand Down Expand Up @@ -36,6 +38,8 @@
* @property \Illuminate\Support\Collection $cast_to_encrypted_collection
* @property array $cast_to_encrypted_json
* @property object $cast_to_encrypted_object
* @property ParentObject $cast_to_parent_object_using_cast_static_tag
* @property ChildObject $cast_to_child_object_using_cast_static_tag
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|SimpleCast query()
Expand Down Expand Up @@ -98,5 +102,7 @@ class SimpleCast extends Model
'cast_to_encrypted_collection' => 'encrypted:collection',
'cast_to_encrypted_json' => 'encrypted:json',
'cast_to_encrypted_object' => 'encrypted:object',
'cast_to_parent_object_using_cast_static_tag' => ParentObject::class,
'cast_to_child_object_using_cast_static_tag' => ChildObject::class,
];
}