Skip to content

Commit

Permalink
ignore phpstorm attributes when instantiating and add readonly property
Browse files Browse the repository at this point in the history
  • Loading branch information
jhavens-rcn committed Dec 12, 2022
1 parent 1d005c7 commit 689703a
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 7 deletions.
5 changes: 4 additions & 1 deletion src/Resolvers/DataFromArrayResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ public function execute(string $class, Collection $properties): BaseData
$dataClass
->properties
->filter(
fn (DataProperty $property) => ! $property->isPromoted && $properties->has($property->name)
fn (DataProperty $property) =>
! $property->isPromoted &&
! $property->isReadonly &&
$properties->has($property->name)
)
->each(function (DataProperty $property) use ($properties, $data) {
$data->{$property->name} = $properties->get($property->name);
Expand Down
9 changes: 6 additions & 3 deletions src/Support/DataClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Spatie\LaravelData\Support;

use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionMethod;
Expand Down Expand Up @@ -31,6 +32,7 @@ public function __construct(
public readonly Collection $properties,
public readonly Collection $methods,
public readonly ?DataMethod $constructorMethod,
public readonly bool $isReadonly,
public readonly bool $appendable,
public readonly bool $includeable,
public readonly bool $responsable,
Expand All @@ -43,9 +45,9 @@ public function __construct(

public static function create(ReflectionClass $class): self
{
$attributes = collect($class->getAttributes())->map(
fn (ReflectionAttribute $reflectionAttribute) => $reflectionAttribute->newInstance()
);
$attributes = collect($class->getAttributes())
->filter(fn (ReflectionAttribute $reflectionAttribute) => class_exists($reflectionAttribute->getName()))
->map(fn (ReflectionAttribute $reflectionAttribute) => $reflectionAttribute->newInstance());

$methods = collect($class->getMethods());

Expand All @@ -62,6 +64,7 @@ public static function create(ReflectionClass $class): self
properties: $properties,
methods: self::resolveMethods($class),
constructorMethod: DataMethod::createConstructor($constructor, $properties),
isReadonly: method_exists($class, 'isReadOnly') && $class->isReadOnly(),
appendable: $class->implementsInterface(AppendableData::class),
includeable: $class->implementsInterface(IncludeableData::class),
responsable: $class->implementsInterface(ResponsableData::class),
Expand Down
9 changes: 6 additions & 3 deletions src/Support/DataProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Spatie\LaravelData\Support;

use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use ReflectionAttribute;
use ReflectionProperty;
use Spatie\LaravelData\Attributes\WithCast;
Expand All @@ -24,6 +25,7 @@ public function __construct(
public readonly DataType $type,
public readonly bool $validate,
public readonly bool $isPromoted,
public readonly bool $isReadonly,
public readonly bool $hasDefaultValue,
public readonly mixed $defaultValue,
public readonly ?Cast $cast,
Expand All @@ -41,9 +43,9 @@ public static function create(
?NameMapper $classInputNameMapper = null,
?NameMapper $classOutputNameMapper = null,
): self {
$attributes = collect($property->getAttributes())->map(
fn (ReflectionAttribute $reflectionAttribute) => $reflectionAttribute->newInstance()
);
$attributes = collect($property->getAttributes())
->filter(fn (ReflectionAttribute $reflectionAttribute) => class_exists($reflectionAttribute->getName()))
->map(fn (ReflectionAttribute $reflectionAttribute) => $reflectionAttribute->newInstance());

$mappers = NameMappersResolver::create()->execute($attributes);

Expand All @@ -65,6 +67,7 @@ className: $property->class,
type: DataType::create($property),
validate: ! $attributes->contains(fn (object $attribute) => $attribute instanceof WithoutValidation),
isPromoted: $property->isPromoted(),
isReadonly: $property->isReadOnly(),
hasDefaultValue: $property->isPromoted() ? $hasDefaultValue : $property->hasDefaultValue(),
defaultValue: $property->isPromoted() ? $defaultValue : $property->getDefaultValue(),
cast: $attributes->first(fn (object $attribute) => $attribute instanceof WithCast)?->get(),
Expand Down
46 changes: 46 additions & 0 deletions tests/Support/DataClassTest.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<?php

use Spatie\LaravelData\Data;
use Spatie\LaravelData\Support\DataClass;
use Spatie\LaravelData\Support\DataMethod;
use Spatie\LaravelData\Tests\DataWithDefaults;
use Spatie\LaravelData\Tests\Fakes\DataWithMapper;
use Spatie\LaravelData\Tests\Fakes\DummyModel;
use Spatie\LaravelData\Tests\Fakes\SimpleData;

it('keeps track of a global map from attribute', function () {
Expand Down Expand Up @@ -53,3 +55,47 @@
->hasDefaultValue->toBeTrue()
->defaultValue->toEqual('Hello Again');
});

it('wont throw an error if a non existing attribute is used on a data class', function () {
expect(PhpStormClassAttributeData::from(['property' => 'hello'])->property)->toEqual('hello')
->and(NonExistingAttributeData::from(['property' => 'hello'])->property)->toEqual('hello')
->and(PhpStormClassAttributeData::from((object)['property' => 'hello'])->property)->toEqual('hello')
->and(PhpStormClassAttributeData::from('{"property": "hello"}')->property)->toEqual('hello')
->and(ModelWithPhpStormAttributeData::from((new DummyModel)->fill(['id' => 1]))->id)->toEqual(1);
});

#[\JetBrains\PhpStorm\Immutable]
class PhpStormClassAttributeData extends Data
{
public readonly string $property;

public function __construct(string $property)
{
$this->property = $property;
}
}

#[\Foo\Bar]
class NonExistingAttributeData extends Data
{
public readonly string $property;

public function __construct(string $property)
{
$this->property = $property;
}
}

#[\JetBrains\PhpStorm\Immutable]
class ModelWithPhpStormAttributeData extends Data
{
public function __construct(
public int $id
) {
}

public static function fromDummyModel(DummyModel $model)
{
return new self($model->id);
}
}
73 changes: 73 additions & 0 deletions tests/Support/DataPropertyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
use Spatie\LaravelData\Attributes\WithoutValidation;
use Spatie\LaravelData\Attributes\WithTransformer;
use Spatie\LaravelData\Casts\DateTimeInterfaceCast;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Tests\Fakes\DummyModel;
use Spatie\LaravelData\Tests\Fakes\SimpleData;
use Spatie\LaravelData\Transformers\DateTimeInterfaceTransformer;

Expand Down Expand Up @@ -126,3 +128,74 @@ public function __construct(
})->validate
)->toBeFalse();
});

it('wont throw an error if non existing attribute is used on a data class property', function () {
expect(NonExistingPropertyAttributeData::from(['property' => 'hello'])->property)->toEqual('hello')
->and(PhpStormAttributeData::from(['property' => 'hello'])->property)->toEqual('hello')
->and(PhpStormAttributeData::from('{"property": "hello"}')->property)->toEqual('hello')
->and(PhpStormAttributeData::from((object) ['property' => 'hello'])->property)->toEqual('hello')
->and(ModelWithPhpStormAttributePropertyData::from((new DummyModel)->fill(['id' => 1]))->id)->toEqual(1)
->and(ModelWithPromotedPhpStormAttributePropertyData::from((new DummyModel)->fill(['id' => 1]))->id)->toEqual(1);
});

class NonExistingPropertyAttributeData extends Data
{
#[\Foo\Bar]
public readonly string $property;

public function __construct(string $property)
{
$this->property = $property;
}
}

class PhpStormAttributeData extends Data
{
#[\JetBrains\PhpStorm\Immutable]
public readonly string $property;

public function __construct(string $property)
{
$this->property = $property;
}
}

class PromotedPhpStormAttributeData extends Data
{
public function __construct(
#[\JetBrains\PhpStorm\Immutable]
public readonly string $property)
{
//
}
}

class ModelWithPhpStormAttributePropertyData extends Data
{
#[\JetBrains\PhpStorm\Immutable]
public int $id;

public function __construct(int $id)
{
$this->id = $id;
}

public static function fromDummyModel(DummyModel $model)
{
return new self($model->id);
}
}

class ModelWithPromotedPhpStormAttributePropertyData extends Data
{
public function __construct(
#[\JetBrains\PhpStorm\Immutable]
public int $id
) {
}

public static function fromDummyModel(DummyModel $model)
{
return new self($model->id);
}
}

0 comments on commit 689703a

Please sign in to comment.