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 1, 2022
1 parent 3d7d449 commit 3caed1b
Show file tree
Hide file tree
Showing 6 changed files with 119 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->type->isReadonly &&
$properties->has($property->name)
)
->each(function (DataProperty $property) use ($properties, $data) {
$data->{$property->name} = $properties->get($property->name);
Expand Down
7 changes: 4 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 @@ -43,9 +44,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())
->reject(fn (ReflectionAttribute $reflectionAttribute) => Str::startsWith($reflectionAttribute->getName(), 'JetBrains\\PhpStorm\\'))
->map(fn (ReflectionAttribute $reflectionAttribute) => $reflectionAttribute->newInstance());

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

Expand Down
7 changes: 4 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 Down Expand Up @@ -41,9 +42,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())
->reject(fn (ReflectionAttribute $reflectionAttribute) => Str::startsWith($reflectionAttribute->getName(), 'JetBrains\\PhpStorm\\'))
->map(fn (ReflectionAttribute $reflectionAttribute) => $reflectionAttribute->newInstance());

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

Expand Down
11 changes: 11 additions & 0 deletions src/Support/DataType.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@

class DataType implements Countable
{
private const READONLY_BITMASK = 128;

public readonly bool $isReadonly;

public readonly bool $isNullable;

public readonly bool $isMixed;
Expand Down Expand Up @@ -51,6 +55,7 @@ public function __construct(ReflectionParameter|ReflectionProperty $reflection)
$type = $reflection->getType();

if ($type === null) {
$this->isReadonly = false;
$this->acceptedTypes = [];
$this->isNullable = true;
$this->isMixed = true;
Expand All @@ -64,6 +69,12 @@ public function __construct(ReflectionParameter|ReflectionProperty $reflection)
return;
}

$this->isReadonly = match (true) {
property_exists($reflection, 'isReadonly') => $reflection->isReadonly,
method_exists($reflection, 'getModifiers') => ($reflection->getModifiers() & self::READONLY_BITMASK) === self::READONLY_BITMASK,
default => false,
};

if ($type instanceof ReflectionNamedType) {
if (is_a($type->getName(), Lazy::class, true)) {
throw InvalidDataType::onlyLazy($reflection);
Expand Down
35 changes: 35 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,36 @@
->hasDefaultValue->toBeTrue()
->defaultValue->toEqual('Hello Again');
});

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

#[\JetBrains\PhpStorm\Immutable]
class PhpStormClassAttributeData extends Data
{
#[\JetBrains\PhpStorm\Immutable]
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);
}
}
61 changes: 61 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,62 @@ public function __construct(
})->validate
)->toBeFalse();
});

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

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 3caed1b

Please sign in to comment.