From 4a11cfe9d7b194d2c34f8a2606aa7b59968dbf3a Mon Sep 17 00:00:00 2001 From: Ark4ne Date: Fri, 20 Dec 2024 12:55:31 +0100 Subject: [PATCH 1/5] php: test php 8.4 --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index c68ff75..406a498 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - php_version: ['8.1', '8.2', '8.3'] + php_version: ['8.1', '8.2', '8.3', '8.4'] laravel_version: ['^9.0', '^10.0', '^11.0'] exclude: - php_version: '8.1' From d3b2f8a1e9903d3f41bc4d33957584bf53ead3d1 Mon Sep 17 00:00:00 2001 From: Ark4ne Date: Fri, 20 Dec 2024 12:59:14 +0100 Subject: [PATCH 2/5] php: phpstan run after phpunit --- .github/workflows/php.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 406a498..1689e19 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -43,11 +43,11 @@ jobs: - name: Install dependencies run: composer require laravel/framework ${{ matrix.laravel_version }} --prefer-dist --no-progress - - name: PHPStan - run: vendor/bin/phpstan analyze - - name: PHPUnit run: vendor/bin/phpunit --coverage-clover coverage.xml --configuration phpunit.php${{ matrix.php_version }}.xml.dist + - name: PHPStan + run: vendor/bin/phpstan analyze + - name: Coverage run: bash <(curl -s https://codecov.io/bash) From 3d0c977d728d8c5f14e6d3cf62e14730db09f740 Mon Sep 17 00:00:00 2001 From: Ark4ne Date: Fri, 20 Dec 2024 13:20:24 +0100 Subject: [PATCH 3/5] fix: phpstan & init phpunit --- .github/workflows/php.yml | 6 +-- phpunit.php8.4.xml.dist | 26 ++++++++++ src/Asserts/AssertJsonApiResource.php | 55 +++++++++++++++++++++ src/Asserts/EagerSetAttribute.php | 13 +++++ src/Asserts/FailGenerateSchema.php | 13 +++++ src/Descriptors/Relations/Relation.php | 6 +-- src/Resources/Concerns/Relationships.php | 2 +- src/Resources/Concerns/Schema.php | 2 +- src/Resources/Concerns/SchemaCollection.php | 2 +- src/Resources/Relationship.php | 2 +- src/Support/Arr.php | 4 +- src/Support/Fields.php | 4 +- 12 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 phpunit.php8.4.xml.dist create mode 100644 src/Asserts/AssertJsonApiResource.php create mode 100644 src/Asserts/EagerSetAttribute.php create mode 100644 src/Asserts/FailGenerateSchema.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 1689e19..406a498 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -43,11 +43,11 @@ jobs: - name: Install dependencies run: composer require laravel/framework ${{ matrix.laravel_version }} --prefer-dist --no-progress - - name: PHPUnit - run: vendor/bin/phpunit --coverage-clover coverage.xml --configuration phpunit.php${{ matrix.php_version }}.xml.dist - - name: PHPStan run: vendor/bin/phpstan analyze + - name: PHPUnit + run: vendor/bin/phpunit --coverage-clover coverage.xml --configuration phpunit.php${{ matrix.php_version }}.xml.dist + - name: Coverage run: bash <(curl -s https://codecov.io/bash) diff --git a/phpunit.php8.4.xml.dist b/phpunit.php8.4.xml.dist new file mode 100644 index 0000000..8ef7a48 --- /dev/null +++ b/phpunit.php8.4.xml.dist @@ -0,0 +1,26 @@ + + + + + tests/Unit + + + tests/Feature + + + + + + + + + src + + + diff --git a/src/Asserts/AssertJsonApiResource.php b/src/Asserts/AssertJsonApiResource.php new file mode 100644 index 0000000..6bc1182 --- /dev/null +++ b/src/Asserts/AssertJsonApiResource.php @@ -0,0 +1,55 @@ + $class + */ + public function __construct(protected string $class) + { + } + + public function assert(): void + { + $this->itCanGenerateSchema(); + $this->allAttributesAreLazySet(); + } + + private function itCanGenerateSchema(): void + { + try { + $this->class::schema(); + } catch (\Throwable $throwable) { + throw new FailGenerateSchema($this->class, $throwable); + } + } + + private function allAttributesAreLazySet(): void + { + $reflection = new ReflectionClass($this->class); + $instance = $reflection->newInstanceWithoutConstructor(); + $instance->resource = new FakeModel; + + $method = $reflection->getMethod('toAttributes'); + $method->setAccessible(true); + /** @var iterable $attributes */ + $attributes = $method->invoke($instance, new Request()); + $attributes = Values::mergeValues($attributes); + + foreach ($attributes as $key => $attribute) { + if (!($attribute instanceof \Closure || $attribute instanceof MissingValue || $attribute instanceof Value)) { + throw new EagerSetAttribute($this->class, $key); + } + } + } +} diff --git a/src/Asserts/EagerSetAttribute.php b/src/Asserts/EagerSetAttribute.php new file mode 100644 index 0000000..b3b854d --- /dev/null +++ b/src/Asserts/EagerSetAttribute.php @@ -0,0 +1,13 @@ +whenIncluded ??= true; @@ -83,7 +83,7 @@ public function whenIncluded(bool $whenIncluded = null): static * * @return static */ - public function whenLoaded(string $relation = null): self + public function whenLoaded(null|string $relation = null): self { return $this->when(fn( Request $request, @@ -100,7 +100,7 @@ public function whenLoaded(string $relation = null): self * * @return static */ - public function whenPivotLoaded(string $table, string $accessor = null): self + public function whenPivotLoaded(string $table, null|string $accessor = null): self { return $this->when(fn( Request $request, diff --git a/src/Resources/Concerns/Relationships.php b/src/Resources/Concerns/Relationships.php index abd97f1..131f9c0 100644 --- a/src/Resources/Concerns/Relationships.php +++ b/src/Resources/Concerns/Relationships.php @@ -60,7 +60,7 @@ private function requestedRelationshipsLoadFromSchema(Request $request, Skeleton if ($load && Includes::include($request, $name)) { $include = Includes::through($name, fn() => $this->requestedRelationshipsLoadFromSchema($request, $schema->relationships[$name])); - $apply = static function ($load, string $pre = null) use (&$loads, &$apply) { + $apply = static function ($load, null|string $pre = null) use (&$loads, &$apply) { foreach ((array)$load as $key => $value) { if (is_string($value)) { $loads["$pre.$value"] = []; diff --git a/src/Resources/Concerns/Schema.php b/src/Resources/Concerns/Schema.php index 4e593e4..8499bd8 100644 --- a/src/Resources/Concerns/Schema.php +++ b/src/Resources/Concerns/Schema.php @@ -21,7 +21,7 @@ trait Schema */ private static array $schemas = []; - public static function schema(Request $request = null): Skeleton + public static function schema(null|Request $request = null): Skeleton { if (isset(self::$schemas[static::class])) { return self::$schemas[static::class]; diff --git a/src/Resources/Concerns/SchemaCollection.php b/src/Resources/Concerns/SchemaCollection.php index dedfbb3..39f314d 100644 --- a/src/Resources/Concerns/SchemaCollection.php +++ b/src/Resources/Concerns/SchemaCollection.php @@ -8,7 +8,7 @@ trait SchemaCollection { - public static function schema(Request $request = null): Skeleton + public static function schema(null|Request $request = null): Skeleton { return self::new()->collects::schema($request); } diff --git a/src/Resources/Relationship.php b/src/Resources/Relationship.php index 6083e38..c8a64c9 100644 --- a/src/Resources/Relationship.php +++ b/src/Resources/Relationship.php @@ -100,7 +100,7 @@ public function asCollection(): self * @param bool|null $whenIncluded * @return $this */ - public function whenIncluded(bool $whenIncluded = null): static + public function whenIncluded(null|bool $whenIncluded = null): static { if ($whenIncluded === null) { $this->whenIncluded ??= true; diff --git a/src/Support/Arr.php b/src/Support/Arr.php index 3aae71d..f83b64e 100644 --- a/src/Support/Arr.php +++ b/src/Support/Arr.php @@ -107,7 +107,7 @@ private static function flattenDot(array $array, string $prepend, string $saveKe * @param string|null $saveKey * @return array */ - public static function undot(array $array, string $saveKey = null): array + public static function undot(array $array, null|string $saveKey = null): array { $results = []; @@ -129,7 +129,7 @@ public static function undot(array $array, string $saveKey = null): array * @param string|null $saveKey * @return mixed */ - public static function apply(array &$array, string $path, mixed $value, string $saveKey = null): mixed + public static function apply(array &$array, string $path, mixed $value, null|string $saveKey = null): mixed { $keys = explode('.', $path); diff --git a/src/Support/Fields.php b/src/Support/Fields.php index 0020b35..c17cf59 100644 --- a/src/Support/Fields.php +++ b/src/Support/Fields.php @@ -37,7 +37,7 @@ public static function through(string $type, callable $callable): mixed * * @return string[]|null */ - public static function get(Request $request, string $type = null): ?array + public static function get(Request $request, null|string $type = null): ?array { $type ??= self::$current; @@ -57,7 +57,7 @@ public static function get(Request $request, string $type = null): ?array * * @return bool */ - public static function has(Request $request, string $field, string $type =null): bool { + public static function has(Request $request, string $field, null|string $type =null): bool { $type ??= self::$current; if ($type === null) { From f22f7c8b0c680f08ea08cfb3217822b9172a4cd9 Mon Sep 17 00:00:00 2001 From: Ark4ne Date: Sat, 28 Dec 2024 22:08:10 +0100 Subject: [PATCH 4/5] fix: applyWhen correct behavior --- src/Descriptors/Values/ValueRaw.php | 25 ---------------- .../Concerns/ConditionallyLoadsAttributes.php | 6 ++-- tests/Feature/SchemaTest.php | 9 +++++- tests/Feature/User/ResourceTest.php | 11 ++++++- .../ConditionallyLoadsAttributesTest.php | 29 ++++++++++--------- tests/app/Http/Resources/UserResource.php | 5 ++++ 6 files changed, 41 insertions(+), 44 deletions(-) delete mode 100644 src/Descriptors/Values/ValueRaw.php diff --git a/src/Descriptors/Values/ValueRaw.php b/src/Descriptors/Values/ValueRaw.php deleted file mode 100644 index db0aa72..0000000 --- a/src/Descriptors/Values/ValueRaw.php +++ /dev/null @@ -1,25 +0,0 @@ - - */ -class ValueRaw extends Value -{ - public function __construct( - null|string|Closure $attribute, - protected mixed $raw - ) { - parent::__construct($attribute); - } - - public function value(mixed $of): mixed - { - return $this->raw; - } -} diff --git a/src/Resources/Concerns/ConditionallyLoadsAttributes.php b/src/Resources/Concerns/ConditionallyLoadsAttributes.php index a6cd0d7..feb1d1a 100644 --- a/src/Resources/Concerns/ConditionallyLoadsAttributes.php +++ b/src/Resources/Concerns/ConditionallyLoadsAttributes.php @@ -4,7 +4,7 @@ use Ark4ne\JsonApi\Descriptors\Describer; use Ark4ne\JsonApi\Descriptors\Relations\RelationRaw; -use Ark4ne\JsonApi\Descriptors\Values\ValueRaw; +use Ark4ne\JsonApi\Descriptors\Values\ValueMixed; use Ark4ne\JsonApi\Resources\Relationship; use Ark4ne\JsonApi\Support\Fields; use Ark4ne\JsonApi\Support\Includes; @@ -57,13 +57,13 @@ protected function whenIncluded(Request $request, string $type, mixed $value) */ protected function applyWhen(bool|Closure $condition, iterable $data): MergeValue { - return new MergeValue(collect($data)->map(function ($raw, $key) use ($condition) { + return new MergeValue(collect($data)->map(function ($raw) use ($condition) { if ($raw instanceof Describer) { $value = $raw; } elseif ($raw instanceof Relationship) { $value = RelationRaw::fromRelationship($raw); } else { - $value = new ValueRaw($key, $raw); + $value = new ValueMixed(is_callable($raw) ? $raw : static fn () => $raw); } return $value->when(fn () => value($condition)); diff --git a/tests/Feature/SchemaTest.php b/tests/Feature/SchemaTest.php index 8a3b147..4978272 100644 --- a/tests/Feature/SchemaTest.php +++ b/tests/Feature/SchemaTest.php @@ -13,7 +13,14 @@ class SchemaTest extends FeatureTestCase { public function testSchema() { - $user = new Skeleton(UserResource::class, 'user', ['name', 'email', 'only-with-fields']); + $user = new Skeleton(UserResource::class, 'user', [ + 'name', + 'email', + 'only-with-fields', + 'with-apply-conditional-raw', + 'with-apply-conditional-closure', + 'with-apply-conditional-value', + ]); $post = new Skeleton(PostResource::class, 'post', ['state', 'title', 'content']); $comment = new Skeleton(CommentResource::class, 'comment', ['content']); diff --git a/tests/Feature/User/ResourceTest.php b/tests/Feature/User/ResourceTest.php index 20c67c3..c1ab1c5 100644 --- a/tests/Feature/User/ResourceTest.php +++ b/tests/Feature/User/ResourceTest.php @@ -164,7 +164,16 @@ private function getJsonResult(User $user, ?array $attributes = null, ?array $re 'attributes' => array_filter(array_intersect_key([ 'name' => $user->name, 'email' => $user->email, - ], array_fill_keys($attributes ?? ['name', 'email'], true))), + "with-apply-conditional-closure" => "huge-data-set", + "with-apply-conditional-raw" => "huge-data-set", + "with-apply-conditional-value" => "huge-data-set" + ], array_fill_keys($attributes ?? [ + 'name', + 'email', + "with-apply-conditional-closure", + "with-apply-conditional-raw", + "with-apply-conditional-value", + ], true))), 'relationships' => [ 'main-post' => [], 'posts' => array_filter([ diff --git a/tests/Unit/Resources/Concerns/ConditionallyLoadsAttributesTest.php b/tests/Unit/Resources/Concerns/ConditionallyLoadsAttributesTest.php index d9b4dcb..4af60e2 100644 --- a/tests/Unit/Resources/Concerns/ConditionallyLoadsAttributesTest.php +++ b/tests/Unit/Resources/Concerns/ConditionallyLoadsAttributesTest.php @@ -7,6 +7,7 @@ use Ark4ne\JsonApi\Descriptors\Values\ValueMixed; use Ark4ne\JsonApi\Descriptors\Values\ValueRaw; use Ark4ne\JsonApi\Resources\Concerns\ConditionallyLoadsAttributes; +use Ark4ne\JsonApi\Resources\JsonApiResource; use Ark4ne\JsonApi\Resources\Relationship; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; @@ -75,16 +76,16 @@ public function testApplyWhen() 'missing.2' => 123, ]); $this->assertEquals(new MergeValue([ - 'missing.1' => (new ValueRaw('missing.1', 'abc'))->when(fn () => false), - 'missing.2' => (new ValueRaw('missing.2', 123))->when(fn () => false), + 'missing.1' => (new ValueMixed(fn() => 'abc'))->when(fn() => false), + 'missing.2' => (new ValueMixed(fn() => 123))->when(fn() => false), ]), $actual); $actual = Reflect::invoke($stub, 'applyWhen', true, [ 'present.1' => 'abc', 'present.2' => 123, ]); $this->assertEquals(new MergeValue([ - 'present.1' => (new ValueRaw('present.1', 'abc'))->when(fn () => true), - 'present.2' => (new ValueRaw('present.2', 123))->when(fn () => true), + 'present.1' => (new ValueMixed(fn() => 'abc'))->when(fn() => true), + 'present.2' => (new ValueMixed(fn() => 123))->when(fn() => true), ]), $actual); $actual = Reflect::invoke($stub, 'applyWhen', true, [ 'present.1' => (new ValueMixed(fn() => 'abc')), @@ -94,11 +95,11 @@ public function testApplyWhen() 'present.5' => (new Relationship(UserResource::class, fn() => null)), ]); $this->assertEquals(new MergeValue([ - 'present.1' => (new ValueMixed(fn() => 'abc'))->when(fn () => true), - 'present.2' => (new ValueMixed(fn() => 123))->when(fn () => true), - 'present.3' => (new RelationOne('present', fn() => 'abc'))->when(fn () => true), - 'present.4' => (new RelationOne('present', fn() => 123))->when(fn () => true), - 'present.5' => RelationRaw::fromRelationship(new Relationship(UserResource::class, fn() => null))->when(fn () => true), + 'present.1' => (new ValueMixed(fn() => 'abc'))->when(fn() => true), + 'present.2' => (new ValueMixed(fn() => 123))->when(fn() => true), + 'present.3' => (new RelationOne('present', fn() => 'abc'))->when(fn() => true), + 'present.4' => (new RelationOne('present', fn() => 123))->when(fn() => true), + 'present.5' => RelationRaw::fromRelationship(new Relationship(UserResource::class, fn() => null))->when(fn() => true), ]), $actual); $actual = Reflect::invoke($stub, 'applyWhen', false, [ 'missing.1' => (new ValueMixed(fn() => 'abc')), @@ -108,11 +109,11 @@ public function testApplyWhen() 'missing.5' => (new Relationship(UserResource::class, fn() => null)), ]); $this->assertEquals(new MergeValue([ - 'missing.1' => (new ValueMixed(fn() => 'abc'))->when(fn () => false), - 'missing.2' => (new ValueMixed(fn() => 123))->when(fn () => false), - 'missing.3' => (new RelationOne('present', fn() => 'abc'))->when(fn () => false), - 'missing.4' => (new RelationOne('present', fn() => 123))->when(fn () => false), - 'missing.5' => RelationRaw::fromRelationship(new Relationship(UserResource::class, fn() => null))->when(fn () => false), + 'missing.1' => (new ValueMixed(fn() => 'abc'))->when(fn() => false), + 'missing.2' => (new ValueMixed(fn() => 123))->when(fn() => false), + 'missing.3' => (new RelationOne('present', fn() => 'abc'))->when(fn() => false), + 'missing.4' => (new RelationOne('present', fn() => 123))->when(fn() => false), + 'missing.5' => RelationRaw::fromRelationship(new Relationship(UserResource::class, fn() => null))->when(fn() => false), ]), $actual); } diff --git a/tests/app/Http/Resources/UserResource.php b/tests/app/Http/Resources/UserResource.php index 47e0d66..47ae055 100644 --- a/tests/app/Http/Resources/UserResource.php +++ b/tests/app/Http/Resources/UserResource.php @@ -25,6 +25,11 @@ protected function toAttributes(Request $request): iterable 'name' => $this->resource->name, 'email' => $this->resource->email, 'only-with-fields' => $this->string(fn() => 'huge-data-set')->whenInFields(), + $this->applyWhen(fn () => true, [ + 'with-apply-conditional-raw' => 'huge-data-set', + 'with-apply-conditional-closure' => fn () => 'huge-data-set', + 'with-apply-conditional-value' => $this->string(fn () => 'huge-data-set'), + ]), ]; } From 451ea99cf2e7a05f9d5d8f6852e1d4ab44c9d0c0 Mon Sep 17 00:00:00 2001 From: Ark4ne Date: Sat, 28 Dec 2024 22:09:30 +0100 Subject: [PATCH 5/5] chore: update changelog --- CHANGELOG-1.4.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG-1.4.md b/CHANGELOG-1.4.md index bcc63a2..4ffc537 100644 --- a/CHANGELOG-1.4.md +++ b/CHANGELOG-1.4.md @@ -1,10 +1,20 @@ Release note ============ -# v1.4.4 +# v1.4.6 +### Change +- Support PHP 8.4 +### Fixed +- Fix `applyWhen` behavior + +# v1.4.5 ### Change - `applyWhen` condition can be a closure - CI: test all supported laravel version +# v1.4.4 +### Fixes +- typo on ToResponse : change Content-type to Content-Type + # v1.4.3 ### Added - Add support for Laravel 11