From 0c57aca8e3f645280029cea7d5b93373e81c1974 Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Wed, 13 Mar 2024 11:53:01 +0100 Subject: [PATCH 1/3] Add support for transformation max depths --- config/data.php | 15 +++ docs/as-a-resource/transformers.md | 33 ++++++ src/Concerns/TransformableData.php | 5 +- .../MaxTransformationDepthReached.php | 13 ++ .../Concerns/ChecksTransformationDepths.php | 23 ++++ .../TransformedDataCollectableResolver.php | 7 ++ src/Resolvers/TransformedDataResolver.php | 11 +- src/Resolvers/VisibleDataFieldsResolver.php | 3 + .../Transformation/TransformationContext.php | 3 + .../TransformationContextFactory.php | 13 ++ tests/PartialsTest.php | 29 +++-- .../TransformationContextFactoryTest.php | 23 ++++ tests/TransformationTest.php | 112 ++++++++++++++++++ 13 files changed, 272 insertions(+), 18 deletions(-) create mode 100644 src/Exceptions/MaxTransformationDepthReached.php create mode 100644 src/Resolvers/Concerns/ChecksTransformationDepths.php diff --git a/config/data.php b/config/data.php index 744d0a50..3638e8b6 100644 --- a/config/data.php +++ b/config/data.php @@ -120,6 +120,21 @@ */ 'ignore_invalid_partials' => false, + /** + * When transforming a nested chain of data objects, the package can end up in an infinite + * loop when including a recursive relationship. The max transformation depth can be + * set as a safety measure to prevent this from happening. When set to null, the + * package will not enforce a maximum depth. + */ + 'max_transformation_depth' => null, + + /** + * When the maximum transformation depth is reached, the package will throw an exception. + * You can disable this behaviour by setting this option to true which will return an + * empty array. + */ + 'fail_when_max_transformation_depth_reached' => true, + /** * When using the `make:data` command, the package will use these settings to generate * the data classes. You can override these settings by passing options to the command. diff --git a/docs/as-a-resource/transformers.md b/docs/as-a-resource/transformers.md index 6b5e2609..5aced9b5 100644 --- a/docs/as-a-resource/transformers.md +++ b/docs/as-a-resource/transformers.md @@ -154,3 +154,36 @@ ArtistData::from($artist)->transform( ); ``` +## Transformation depth + +When transforming a complicated structure of nested data objects it is possible that an infinite loop is created of data objects including each other. +To prevent this, a transformation depth can be set, when that depth is reached when transforming, either an exception will be thrown or an empty +array is returned, stopping the transformation. + +This transformation depth can be set globally in the `data.php` config file: + +```php +'max_transformation_depth' => 20, +``` + +It is also possible if a `MaxTransformationDepthReached` exception should be thrown or an empty array should be returned: + +```php +'fail_when_max_transformation_depth_reached' => true, +``` + +It is also possible to set the transformation depth on a specific transformation by using a `TransformationContextFactory`: + +```php +ArtistData::from($artist)->transform( + TransformationContextFactory::create()->maxDepth(20) +); +``` + +By default, an exception will be thrown when the maximum transformation depth is reached. This can be changed to return an empty array as such: + +```php +ArtistData::from($artist)->transform( + TransformationContextFactory::create()->maxDepth(20, fail: false) +); +``` diff --git a/src/Concerns/TransformableData.php b/src/Concerns/TransformableData.php index 62834ce9..a902ac48 100644 --- a/src/Concerns/TransformableData.php +++ b/src/Concerns/TransformableData.php @@ -21,7 +21,10 @@ public function transform( $transformationContext = match (true) { $transformationContext instanceof TransformationContext => $transformationContext, $transformationContext instanceof TransformationContextFactory => $transformationContext->get($this), - $transformationContext === null => new TransformationContext() + $transformationContext === null => new TransformationContext( + maxDepth: config('data.max_transformation_depth'), + failWhenMaxDepthReached: config('data.fail_when_max_transformation_depth_reached') + ) }; $resolver = match (true) { diff --git a/src/Exceptions/MaxTransformationDepthReached.php b/src/Exceptions/MaxTransformationDepthReached.php new file mode 100644 index 00000000..330f1e0a --- /dev/null +++ b/src/Exceptions/MaxTransformationDepthReached.php @@ -0,0 +1,13 @@ +maxDepth !== null && $context->depth >= $context->maxDepth; + } + + public function handleMaxDepthReached(TransformationContext $context): array + { + if ($context->failWhenMaxDepthReached) { + throw MaxTransformationDepthReached::create($context->maxDepth); + } + + return []; + } +} diff --git a/src/Resolvers/TransformedDataCollectableResolver.php b/src/Resolvers/TransformedDataCollectableResolver.php index db653262..9789743d 100644 --- a/src/Resolvers/TransformedDataCollectableResolver.php +++ b/src/Resolvers/TransformedDataCollectableResolver.php @@ -14,6 +14,7 @@ use Spatie\LaravelData\CursorPaginatedDataCollection; use Spatie\LaravelData\DataCollection; use Spatie\LaravelData\PaginatedDataCollection; +use Spatie\LaravelData\Resolvers\Concerns\ChecksTransformationDepths; use Spatie\LaravelData\Support\DataConfig; use Spatie\LaravelData\Support\Transformation\TransformationContext; use Spatie\LaravelData\Support\Wrapping\Wrap; @@ -22,6 +23,8 @@ class TransformedDataCollectableResolver { + use ChecksTransformationDepths; + public function __construct( protected DataConfig $dataConfig ) { @@ -31,6 +34,10 @@ public function execute( iterable $items, TransformationContext $context, ): array { + if ($this->hasReachedMaxTransformationDepth($context)) { + return $this->handleMaxDepthReached($context); + } + $wrap = $items instanceof WrappableData ? $items->getWrap() : new Wrap(WrapType::UseGlobal); diff --git a/src/Resolvers/TransformedDataResolver.php b/src/Resolvers/TransformedDataResolver.php index c32d21cb..5eccc97c 100644 --- a/src/Resolvers/TransformedDataResolver.php +++ b/src/Resolvers/TransformedDataResolver.php @@ -9,6 +9,7 @@ use Spatie\LaravelData\Contracts\WrappableData; use Spatie\LaravelData\Lazy; use Spatie\LaravelData\Optional; +use Spatie\LaravelData\Resolvers\Concerns\ChecksTransformationDepths; use Spatie\LaravelData\Support\DataClass; use Spatie\LaravelData\Support\DataConfig; use Spatie\LaravelData\Support\DataContainer; @@ -20,6 +21,8 @@ class TransformedDataResolver { + use ChecksTransformationDepths; + public function __construct( protected DataConfig $dataConfig, protected VisibleDataFieldsResolver $visibleDataFieldsResolver, @@ -30,6 +33,10 @@ public function execute( BaseData&TransformableData $data, TransformationContext $context, ): array { + if ($this->hasReachedMaxTransformationDepth($context)) { + return $this->handleMaxDepthReached($context); + } + $dataClass = $this->dataConfig->getDataClass($data::class); $transformed = $this->transform($data, $context, $dataClass); @@ -140,7 +147,7 @@ protected function resolvePropertyValue( protected function transformDataOrDataCollection( mixed $value, TransformationContext $currentContext, - ?TransformationContext $fieldContext + TransformationContext $fieldContext ): mixed { $wrapExecutionType = $this->resolveWrapExecutionType($value, $currentContext); @@ -215,7 +222,7 @@ protected function resolvePotentialPartialArray( array $value, ?TransformationContext $fieldContext, ): array { - if($fieldContext === null) { + if ($fieldContext === null) { return $value; } diff --git a/src/Resolvers/VisibleDataFieldsResolver.php b/src/Resolvers/VisibleDataFieldsResolver.php index 71072d7c..9bb02ada 100644 --- a/src/Resolvers/VisibleDataFieldsResolver.php +++ b/src/Resolvers/VisibleDataFieldsResolver.php @@ -43,6 +43,9 @@ public function execute( $transformationContext->mapPropertyNames, $transformationContext->wrapExecutionType, $transformationContext->transformers, + depth: $transformationContext->depth + 1, + maxDepth: $transformationContext->maxDepth, + failWhenMaxDepthReached: $transformationContext->failWhenMaxDepthReached, ); } } diff --git a/src/Support/Transformation/TransformationContext.php b/src/Support/Transformation/TransformationContext.php index a510cbb1..2ef35677 100644 --- a/src/Support/Transformation/TransformationContext.php +++ b/src/Support/Transformation/TransformationContext.php @@ -24,6 +24,9 @@ public function __construct( public ?PartialsCollection $excludePartials = null, public ?PartialsCollection $onlyPartials = null, public ?PartialsCollection $exceptPartials = null, + public int $depth = 0, + public ?int $maxDepth = null, + public bool $failWhenMaxDepthReached = true, ) { } diff --git a/src/Support/Transformation/TransformationContextFactory.php b/src/Support/Transformation/TransformationContextFactory.php index 879d5028..dd0d5f09 100644 --- a/src/Support/Transformation/TransformationContextFactory.php +++ b/src/Support/Transformation/TransformationContextFactory.php @@ -27,6 +27,8 @@ protected function __construct( public ?PartialsCollection $excludePartials = null, public ?PartialsCollection $onlyPartials = null, public ?PartialsCollection $exceptPartials = null, + public ?int $maxDepth = null, + public bool $failWhenMaxDepthReached = true, ) { } @@ -90,6 +92,9 @@ public function get( $excludePartials, $onlyPartials, $exceptPartials, + depth: 0, + maxDepth: $this->maxDepth, + failWhenMaxDepthReached: $this->failWhenMaxDepthReached, ); } @@ -155,6 +160,14 @@ public function withTransformer(string $transformable, Transformer|string $trans return $this; } + public function maxDepth(?int $maxDepth, bool $fail = true): static + { + $this->maxDepth = $maxDepth; + $this->failWhenMaxDepthReached = $fail; + + return $this; + } + public function mergeIncludePartials(PartialsCollection $partials): static { if ($this->includePartials === null) { diff --git a/tests/PartialsTest.php b/tests/PartialsTest.php index fb981615..a5479e60 100644 --- a/tests/PartialsTest.php +++ b/tests/PartialsTest.php @@ -1476,20 +1476,20 @@ public function __construct() } }; - // expect($dataClass->include('collection.simple')->toArray())->toMatchArray([ - // 'collection' => [ - // [ - // 'simple' => [ - // 'string' => 'Rick Astley', - // ], - // ], - // [ - // 'simple' => [ - // 'string' => 'Jon Bon Jovi', - // ], - // ], - // ], - // ]); + expect($dataClass->include('collection.simple')->toArray())->toMatchArray([ + 'collection' => [ + [ + 'simple' => [ + 'string' => 'Rick Astley', + ], + ], + [ + 'simple' => [ + 'string' => 'Jon Bon Jovi', + ], + ], + ], + ]); $nested = $dataClass->include('collection.simple')->all()['collection']; @@ -1596,6 +1596,5 @@ public function __construct() ], ], ]); - // Not really a test with expectation, we just want to check we don't end up in an infinite loop }); diff --git a/tests/Support/Transformation/TransformationContextFactoryTest.php b/tests/Support/Transformation/TransformationContextFactoryTest.php index dc8c5e36..ab46fbc0 100644 --- a/tests/Support/Transformation/TransformationContextFactoryTest.php +++ b/tests/Support/Transformation/TransformationContextFactoryTest.php @@ -16,6 +16,9 @@ expect($context->mapPropertyNames)->toBeTrue(); expect($context->wrapExecutionType)->toBe(WrapExecutionType::Disabled); expect($context->transformers)->toBeNull(); + expect($context->depth)->toBe(0); + expect($context->maxDepth)->toBeNull(); + expect($context->failWhenMaxDepthReached)->toBeTrue(); }); it('can disable value transformation', function () { @@ -82,3 +85,23 @@ expect($context->transformers)->not()->toBe(null); expect($context->transformers->findTransformerForValue('Hello World'))->toBeInstanceOf(StringToUpperTransformer::class); }); + +it('can set a max transformation depth', function () { + $context = TransformationContextFactory::create() + ->maxDepth(4) + ->get(SimpleData::from('Hello World')); + + expect($context->maxDepth)->toBe(4); + expect($context->depth)->toBe(0); + expect($context->failWhenMaxDepthReached)->toBeTrue(); +}); + +it('can set a max transformation depth without failing', function () { + $context = TransformationContextFactory::create() + ->maxDepth(4, fail: false) + ->get(SimpleData::from('Hello World')); + + expect($context->maxDepth)->toBe(4); + expect($context->depth)->toBe(0); + expect($context->failWhenMaxDepthReached)->toBeFalse(); +}); diff --git a/tests/TransformationTest.php b/tests/TransformationTest.php index 86d8667b..ee5b2c71 100644 --- a/tests/TransformationTest.php +++ b/tests/TransformationTest.php @@ -9,6 +9,7 @@ use Spatie\LaravelData\CursorPaginatedDataCollection; use Spatie\LaravelData\Data; use Spatie\LaravelData\DataCollection; +use Spatie\LaravelData\Exceptions\MaxTransformationDepthReached; use Spatie\LaravelData\Lazy; use Spatie\LaravelData\Optional; use Spatie\LaravelData\PaginatedDataCollection; @@ -400,3 +401,114 @@ public function transform(DataProperty $property, mixed $value, TransformationCo expect($transformed)->toBe(['array' => 'A']); })->skip(fn () => config('data.features.cast_and_transform_iterables') === false); + + +it('it possible to set the max transformation depth when transforming objects', function () { + $a = new stdClass(); + $b = new stdClass(); + + $a->b = $b; + $b->a = $a; + + class TestMaxDataObjectTransformationDepthA extends Data + { + public function __construct( + public Lazy|TestMaxDataObjectTransformationDepthB $dataB + ) { + $this->includePermanently('dataB'); + } + + public static function fromOther(stdClass $b): self + { + return new self(Lazy::create(fn () => TestMaxDataObjectTransformationDepthB::from($b->a))); + } + } + + class TestMaxDataObjectTransformationDepthB extends Data + { + public function __construct( + public Lazy|TestMaxDataObjectTransformationDepthA $dataA + ) { + $this->includePermanently('dataA'); + } + + public static function fromOther(stdClass $a): self + { + return new self(Lazy::create(fn () => TestMaxDataObjectTransformationDepthA::from($a->b))); + } + } + + expect(fn () => TestMaxDataObjectTransformationDepthB::fromOther($a)->transform( + TransformationContextFactory::create()->maxDepth(4) + ))->toThrow(MaxTransformationDepthReached::class); + + expect(TestMaxDataObjectTransformationDepthB::fromOther($a)->transform( + TransformationContextFactory::create()->maxDepth(4, fail: false) + ))->toBe([ + 'dataA' => [ + 'dataB' => [ + 'dataA' => [ + 'dataB' => [], + ], + ], + ], + ]); +}); + +it('it possible to set the max transformation depth when transforming collections', function () { + $a = new stdClass(); + $b = new stdClass(); + + $a->b = $b; + $b->a = $a; + + class TestMaxDatCollectionTransformationDepthA extends Data + { + public function __construct( + #[DataCollectionOf(TestMaxDatCollectionTransformationDepthB::class)] + public Lazy|DataCollection $ca + ) { + $this->includePermanently('ca'); + } + + public static function fromOther(stdClass $b): self + { + return new self(Lazy::create(fn () => TestMaxDatCollectionTransformationDepthB::collect([$b->a]))); + } + } + + class TestMaxDatCollectionTransformationDepthB extends Data + { + public function __construct( + #[DataCollectionOf(TestMaxDatCollectionTransformationDepthA::class)] + public Lazy|DataCollection $cb + ) { + $this->includePermanently('cb'); + } + + public static function fromOther(stdClass $a): self + { + return new self(Lazy::create(fn () => TestMaxDatCollectionTransformationDepthA::collect([$a->b]))); + } + } + + expect(fn () => TestMaxDatCollectionTransformationDepthB::fromOther($a)->transform( + TransformationContextFactory::create()->maxDepth(4) + ))->toThrow(MaxTransformationDepthReached::class); + + expect(TestMaxDatCollectionTransformationDepthB::fromOther($a)->transform( + TransformationContextFactory::create()->maxDepth(4, fail: false) + ))->toBe([ + 'cb' => [ + [ + 'ca' => [ + [ + 'cb' => [ + ['ca' => []], + ], + ], + ], + ], + ], + ]); +}); From 088140201f6a57064a07370c87ac36b988755b0d Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Wed, 13 Mar 2024 11:55:18 +0100 Subject: [PATCH 2/3] Small docs update --- docs/as-a-resource/transformers.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/as-a-resource/transformers.md b/docs/as-a-resource/transformers.md index 5aced9b5..220ae412 100644 --- a/docs/as-a-resource/transformers.md +++ b/docs/as-a-resource/transformers.md @@ -166,6 +166,12 @@ This transformation depth can be set globally in the `data.php` config file: 'max_transformation_depth' => 20, ``` +Setting the transformation depth to `null` will disable the transformation depth check: + +```php +'max_transformation_depth' => null, +``` + It is also possible if a `MaxTransformationDepthReached` exception should be thrown or an empty array should be returned: ```php From 3c9781fcab17a056b1a46da7adf047e0549796e5 Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Wed, 13 Mar 2024 12:07:34 +0100 Subject: [PATCH 3/3] Small changes --- config/data.php | 2 +- docs/as-a-resource/transformers.md | 4 ++-- src/Concerns/TransformableData.php | 2 +- ...ionDepths.php => ChecksTransformationDepth.php} | 4 ++-- .../TransformedDataCollectableResolver.php | 4 ++-- src/Resolvers/TransformedDataResolver.php | 4 ++-- src/Resolvers/VisibleDataFieldsResolver.php | 2 +- .../Transformation/TransformationContext.php | 2 +- .../TransformationContextFactory.php | 14 +++++++++----- .../TransformationContextFactoryTest.php | 8 ++++---- tests/TransformationTest.php | 4 ++-- 11 files changed, 27 insertions(+), 23 deletions(-) rename src/Resolvers/Concerns/{ChecksTransformationDepths.php => ChecksTransformationDepth.php} (87%) diff --git a/config/data.php b/config/data.php index 3638e8b6..f45936ff 100644 --- a/config/data.php +++ b/config/data.php @@ -133,7 +133,7 @@ * You can disable this behaviour by setting this option to true which will return an * empty array. */ - 'fail_when_max_transformation_depth_reached' => true, + 'throw_when_max_transformation_depth_reached' => true, /** * When using the `make:data` command, the package will use these settings to generate diff --git a/docs/as-a-resource/transformers.md b/docs/as-a-resource/transformers.md index 220ae412..7d778b4e 100644 --- a/docs/as-a-resource/transformers.md +++ b/docs/as-a-resource/transformers.md @@ -175,7 +175,7 @@ Setting the transformation depth to `null` will disable the transformation depth It is also possible if a `MaxTransformationDepthReached` exception should be thrown or an empty array should be returned: ```php -'fail_when_max_transformation_depth_reached' => true, +'throw_when_max_transformation_depth_reached' => true, ``` It is also possible to set the transformation depth on a specific transformation by using a `TransformationContextFactory`: @@ -190,6 +190,6 @@ By default, an exception will be thrown when the maximum transformation depth is ```php ArtistData::from($artist)->transform( - TransformationContextFactory::create()->maxDepth(20, fail: false) + TransformationContextFactory::create()->maxDepth(20, throw: false) ); ``` diff --git a/src/Concerns/TransformableData.php b/src/Concerns/TransformableData.php index a902ac48..69855103 100644 --- a/src/Concerns/TransformableData.php +++ b/src/Concerns/TransformableData.php @@ -23,7 +23,7 @@ public function transform( $transformationContext instanceof TransformationContextFactory => $transformationContext->get($this), $transformationContext === null => new TransformationContext( maxDepth: config('data.max_transformation_depth'), - failWhenMaxDepthReached: config('data.fail_when_max_transformation_depth_reached') + throwWhenMaxDepthReached: config('data.throw_when_max_transformation_depth_reached') ) }; diff --git a/src/Resolvers/Concerns/ChecksTransformationDepths.php b/src/Resolvers/Concerns/ChecksTransformationDepth.php similarity index 87% rename from src/Resolvers/Concerns/ChecksTransformationDepths.php rename to src/Resolvers/Concerns/ChecksTransformationDepth.php index 52864f8b..0e448c97 100644 --- a/src/Resolvers/Concerns/ChecksTransformationDepths.php +++ b/src/Resolvers/Concerns/ChecksTransformationDepth.php @@ -5,7 +5,7 @@ use Spatie\LaravelData\Exceptions\MaxTransformationDepthReached; use Spatie\LaravelData\Support\Transformation\TransformationContext; -trait ChecksTransformationDepths +trait ChecksTransformationDepth { public function hasReachedMaxTransformationDepth(TransformationContext $context): bool { @@ -14,7 +14,7 @@ public function hasReachedMaxTransformationDepth(TransformationContext $context) public function handleMaxDepthReached(TransformationContext $context): array { - if ($context->failWhenMaxDepthReached) { + if ($context->throwWhenMaxDepthReached) { throw MaxTransformationDepthReached::create($context->maxDepth); } diff --git a/src/Resolvers/TransformedDataCollectableResolver.php b/src/Resolvers/TransformedDataCollectableResolver.php index 9789743d..edddb079 100644 --- a/src/Resolvers/TransformedDataCollectableResolver.php +++ b/src/Resolvers/TransformedDataCollectableResolver.php @@ -14,7 +14,7 @@ use Spatie\LaravelData\CursorPaginatedDataCollection; use Spatie\LaravelData\DataCollection; use Spatie\LaravelData\PaginatedDataCollection; -use Spatie\LaravelData\Resolvers\Concerns\ChecksTransformationDepths; +use Spatie\LaravelData\Resolvers\Concerns\ChecksTransformationDepth; use Spatie\LaravelData\Support\DataConfig; use Spatie\LaravelData\Support\Transformation\TransformationContext; use Spatie\LaravelData\Support\Wrapping\Wrap; @@ -23,7 +23,7 @@ class TransformedDataCollectableResolver { - use ChecksTransformationDepths; + use ChecksTransformationDepth; public function __construct( protected DataConfig $dataConfig diff --git a/src/Resolvers/TransformedDataResolver.php b/src/Resolvers/TransformedDataResolver.php index 5eccc97c..94008b17 100644 --- a/src/Resolvers/TransformedDataResolver.php +++ b/src/Resolvers/TransformedDataResolver.php @@ -9,7 +9,7 @@ use Spatie\LaravelData\Contracts\WrappableData; use Spatie\LaravelData\Lazy; use Spatie\LaravelData\Optional; -use Spatie\LaravelData\Resolvers\Concerns\ChecksTransformationDepths; +use Spatie\LaravelData\Resolvers\Concerns\ChecksTransformationDepth; use Spatie\LaravelData\Support\DataClass; use Spatie\LaravelData\Support\DataConfig; use Spatie\LaravelData\Support\DataContainer; @@ -21,7 +21,7 @@ class TransformedDataResolver { - use ChecksTransformationDepths; + use ChecksTransformationDepth; public function __construct( protected DataConfig $dataConfig, diff --git a/src/Resolvers/VisibleDataFieldsResolver.php b/src/Resolvers/VisibleDataFieldsResolver.php index 9bb02ada..d75eea1b 100644 --- a/src/Resolvers/VisibleDataFieldsResolver.php +++ b/src/Resolvers/VisibleDataFieldsResolver.php @@ -45,7 +45,7 @@ public function execute( $transformationContext->transformers, depth: $transformationContext->depth + 1, maxDepth: $transformationContext->maxDepth, - failWhenMaxDepthReached: $transformationContext->failWhenMaxDepthReached, + throwWhenMaxDepthReached: $transformationContext->throwWhenMaxDepthReached, ); } } diff --git a/src/Support/Transformation/TransformationContext.php b/src/Support/Transformation/TransformationContext.php index 2ef35677..fab99702 100644 --- a/src/Support/Transformation/TransformationContext.php +++ b/src/Support/Transformation/TransformationContext.php @@ -26,7 +26,7 @@ public function __construct( public ?PartialsCollection $exceptPartials = null, public int $depth = 0, public ?int $maxDepth = null, - public bool $failWhenMaxDepthReached = true, + public bool $throwWhenMaxDepthReached = true, ) { } diff --git a/src/Support/Transformation/TransformationContextFactory.php b/src/Support/Transformation/TransformationContextFactory.php index dd0d5f09..6076b6f1 100644 --- a/src/Support/Transformation/TransformationContextFactory.php +++ b/src/Support/Transformation/TransformationContextFactory.php @@ -13,6 +13,10 @@ class TransformationContextFactory { use ForwardsToPartialsDefinition; + public ?int $maxDepth; + + public bool $throwWhenMaxDepthReached; + public static function create(): self { return new self(); @@ -27,9 +31,9 @@ protected function __construct( public ?PartialsCollection $excludePartials = null, public ?PartialsCollection $onlyPartials = null, public ?PartialsCollection $exceptPartials = null, - public ?int $maxDepth = null, - public bool $failWhenMaxDepthReached = true, ) { + $this->maxDepth = config('data.max_transformation_depth', null); + $this->throwWhenMaxDepthReached = config('data.throw_when_max_transformation_depth_reached', true); } public function get( @@ -94,7 +98,7 @@ public function get( $exceptPartials, depth: 0, maxDepth: $this->maxDepth, - failWhenMaxDepthReached: $this->failWhenMaxDepthReached, + throwWhenMaxDepthReached: $this->throwWhenMaxDepthReached, ); } @@ -160,10 +164,10 @@ public function withTransformer(string $transformable, Transformer|string $trans return $this; } - public function maxDepth(?int $maxDepth, bool $fail = true): static + public function maxDepth(?int $maxDepth, bool $throw = true): static { $this->maxDepth = $maxDepth; - $this->failWhenMaxDepthReached = $fail; + $this->throwWhenMaxDepthReached = $throw; return $this; } diff --git a/tests/Support/Transformation/TransformationContextFactoryTest.php b/tests/Support/Transformation/TransformationContextFactoryTest.php index ab46fbc0..2fb37544 100644 --- a/tests/Support/Transformation/TransformationContextFactoryTest.php +++ b/tests/Support/Transformation/TransformationContextFactoryTest.php @@ -18,7 +18,7 @@ expect($context->transformers)->toBeNull(); expect($context->depth)->toBe(0); expect($context->maxDepth)->toBeNull(); - expect($context->failWhenMaxDepthReached)->toBeTrue(); + expect($context->throwWhenMaxDepthReached)->toBeTrue(); }); it('can disable value transformation', function () { @@ -93,15 +93,15 @@ expect($context->maxDepth)->toBe(4); expect($context->depth)->toBe(0); - expect($context->failWhenMaxDepthReached)->toBeTrue(); + expect($context->throwWhenMaxDepthReached)->toBeTrue(); }); it('can set a max transformation depth without failing', function () { $context = TransformationContextFactory::create() - ->maxDepth(4, fail: false) + ->maxDepth(4, throw: false) ->get(SimpleData::from('Hello World')); expect($context->maxDepth)->toBe(4); expect($context->depth)->toBe(0); - expect($context->failWhenMaxDepthReached)->toBeFalse(); + expect($context->throwWhenMaxDepthReached)->toBeFalse(); }); diff --git a/tests/TransformationTest.php b/tests/TransformationTest.php index ee5b2c71..d158213c 100644 --- a/tests/TransformationTest.php +++ b/tests/TransformationTest.php @@ -443,7 +443,7 @@ public static function fromOther(stdClass $a): self ))->toThrow(MaxTransformationDepthReached::class); expect(TestMaxDataObjectTransformationDepthB::fromOther($a)->transform( - TransformationContextFactory::create()->maxDepth(4, fail: false) + TransformationContextFactory::create()->maxDepth(4, throw: false) ))->toBe([ 'dataA' => [ 'dataB' => [ @@ -497,7 +497,7 @@ public static function fromOther(stdClass $a): self ))->toThrow(MaxTransformationDepthReached::class); expect(TestMaxDatCollectionTransformationDepthB::fromOther($a)->transform( - TransformationContextFactory::create()->maxDepth(4, fail: false) + TransformationContextFactory::create()->maxDepth(4, throw: false) ))->toBe([ 'cb' => [ [