From c5df595d0d091f01bc5e40fb5e622077432c9f46 Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 3 Feb 2026 15:39:00 +0100 Subject: [PATCH] Refactor transformers pipeline and list caching --- src/EntityList/SharpEntityList.php | 19 +++- src/Utils/Transformers/AttributePath.php | 59 +++++++++++++ .../CachesEntityListInstances.php | 11 +++ .../Transformers/TransformerPipeline.php | 87 +++++++++++++++++++ .../Transformers/WithCustomTransformers.php | 80 +++-------------- 5 files changed, 186 insertions(+), 70 deletions(-) create mode 100644 src/Utils/Transformers/AttributePath.php create mode 100644 src/Utils/Transformers/CachesEntityListInstances.php create mode 100644 src/Utils/Transformers/TransformerPipeline.php diff --git a/src/EntityList/SharpEntityList.php b/src/EntityList/SharpEntityList.php index bac259936..7a75078b1 100644 --- a/src/EntityList/SharpEntityList.php +++ b/src/EntityList/SharpEntityList.php @@ -10,12 +10,13 @@ use Code16\Sharp\EntityList\Traits\HandleInstanceCommands; use Code16\Sharp\Filters\Concerns\HasFilters; use Code16\Sharp\Utils\Traits\HandlePageAlertMessage; +use Code16\Sharp\Utils\Transformers\CachesEntityListInstances; use Code16\Sharp\Utils\Transformers\WithCustomTransformers; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Pagination\AbstractPaginator; use Illuminate\Support\Arr; -abstract class SharpEntityList +abstract class SharpEntityList implements CachesEntityListInstances { use HandleEntityCommands; use HandleEntityState; @@ -136,6 +137,22 @@ final public function configureInstanceIdAttribute(string $instanceIdAttribute): return $this; } + /** + * @internal + */ + final public function cacheEntityListInstances(array $instances): void + { + $idAttr = $this->instanceIdAttribute; + + sharp()->context()->cacheListInstances( + collect($instances) + ->filter(fn ($instance) => (((object) $instance)->$idAttr ?? null) !== null) + ->mapWithKeys(fn ($instance) => [ + ((object) $instance)->$idAttr => $instance, + ]) + ); + } + /** * @internal */ diff --git a/src/Utils/Transformers/AttributePath.php b/src/Utils/Transformers/AttributePath.php new file mode 100644 index 000000000..0835ff928 --- /dev/null +++ b/src/Utils/Transformers/AttributePath.php @@ -0,0 +1,59 @@ +relation !== null; + } +} diff --git a/src/Utils/Transformers/CachesEntityListInstances.php b/src/Utils/Transformers/CachesEntityListInstances.php new file mode 100644 index 000000000..37965ff7e --- /dev/null +++ b/src/Utils/Transformers/CachesEntityListInstances.php @@ -0,0 +1,11 @@ + $instances + */ + public function cacheEntityListInstances(array $instances): void; +} diff --git a/src/Utils/Transformers/TransformerPipeline.php b/src/Utils/Transformers/TransformerPipeline.php new file mode 100644 index 000000000..04885841e --- /dev/null +++ b/src/Utils/Transformers/TransformerPipeline.php @@ -0,0 +1,87 @@ + $transformers + */ + public function __construct(private array $transformers) {} + + public function apply(array $attributes, array|object $model): array + { + foreach ($this->transformers as $attribute => $transformer) { + $path = AttributePath::parse($attribute); + + if ($path->isList) { + $attributes = $this->applyListTransformer($attributes, $model, $path, $transformer); + + continue; + } + + $attributes = $this->applySingleTransformer($attributes, $model, $path, $transformer); + } + + return $attributes; + } + + private function applyListTransformer( + array $attributes, + array|object $model, + AttributePath $path, + SharpAttributeTransformer $transformer, + ): array { + if (! array_key_exists($path->listAttribute, $attributes)) { + return $attributes; + } + + $listModel = $model; + if ($path->isRelated()) { + $listModel = $model[$path->relation]; + } + + foreach ($listModel[$path->listAttribute] as $k => $itemModel) { + $attributes[$path->listAttribute][$k][$path->itemAttribute] = $transformer->apply( + $attributes[$path->listAttribute][$k][$path->itemAttribute] ?? null, + $itemModel, + $path->itemAttribute, + ); + } + + return $attributes; + } + + private function applySingleTransformer( + array $attributes, + array|object $model, + AttributePath $path, + SharpAttributeTransformer $transformer, + ): array { + if (! isset($attributes[$path->key])) { + if (method_exists($transformer, 'applyIfAttributeIsMissing') + && ! $transformer->applyIfAttributeIsMissing()) { + // The attribute is missing and the transformer code declared to be ignored in this case + return $attributes; + } + } + + if ($path->isRelated()) { + $attributes[$path->key] = $transformer->apply( + $attributes[$path->key] ?? null, + $model[$path->relation] ?? null, + $path->attribute, + ); + + return $attributes; + } + + $attributes[$path->key] = $transformer->apply( + $attributes[$path->key] ?? null, + $model, + $path->attribute, + ); + + return $attributes; + } +} diff --git a/src/Utils/Transformers/WithCustomTransformers.php b/src/Utils/Transformers/WithCustomTransformers.php index 341e107e5..db98da71a 100644 --- a/src/Utils/Transformers/WithCustomTransformers.php +++ b/src/Utils/Transformers/WithCustomTransformers.php @@ -53,13 +53,15 @@ public function transform($models): array|AbstractPaginator // SharpEntityList case: collection of models (potentially paginated) - $this->cacheEntityListInstances( - match (true) { - $models instanceof AbstractPaginator => $models->items(), - $models instanceof Arrayable => $models->all(), - default => $models, - } - ); + if ($this instanceof CachesEntityListInstances) { + $this->cacheEntityListInstances( + match (true) { + $models instanceof AbstractPaginator => $models->items(), + $models instanceof Arrayable => $models->all(), + default => $models, + } + ); + } if ($models instanceof AbstractPaginator) { return $models->setCollection( @@ -103,56 +105,8 @@ protected function applyTransformers(array|object $model, bool $forceFullObject $attributes = $this->handleAutoRelatedAttributes($attributes, $model); } - // Apply transformers - foreach ($this->transformers as $attribute => $transformer) { - if (str_contains($attribute, '[')) { - // List item case: apply transformer to each item - $listAttribute = substr($attribute, 0, strpos($attribute, '[')); - $itemAttribute = substr($attribute, strpos($attribute, '[') + 1, -1); - - if (! array_key_exists($listAttribute, $attributes)) { - continue; - } - - $listModel = $model; - if (($sep = strpos($listAttribute, ':')) !== false) { - $listModel = $model[substr($listAttribute, 0, $sep)]; - $listAttribute = substr($listAttribute, $sep + 1); - } - - foreach ($listModel[$listAttribute] as $k => $itemModel) { - $attributes[$listAttribute][$k][$itemAttribute] = $transformer->apply( - $attributes[$listAttribute][$k][$itemAttribute] ?? null, $itemModel, $itemAttribute, - ); - } - } else { - if (! isset($attributes[$attribute])) { - if (method_exists($transformer, 'applyIfAttributeIsMissing') - && ! $transformer->applyIfAttributeIsMissing()) { - // The attribute is missing and the transformer code declared to be ignored in this case - continue; - } - } - - if (($sep = strpos($attribute, ':')) !== false) { - $attributes[$attribute] = $transformer->apply( - $attributes[$attribute] ?? null, - $model[substr($attribute, 0, $sep)] ?? null, - substr($attribute, $sep + 1), - ); - - continue; - } - - $attributes[$attribute] = $transformer->apply( - $attributes[$attribute] ?? null, - $model, - $attribute, - ); - } - } - - return $attributes; + return (new TransformerPipeline($this->transformers)) + ->apply($attributes, $model); } /** @@ -173,16 +127,4 @@ protected function handleAutoRelatedAttributes(array $attributes, $model): array return $attributes; } - private function cacheEntityListInstances(array $instances): void - { - $idAttr = $this->instanceIdAttribute; - - sharp()->context()->cacheListInstances( - collect($instances) - ->filter(fn ($instance) => (((object) $instance)->$idAttr ?? null) !== null) - ->mapWithKeys(fn ($instance) => [ - ((object) $instance)->$idAttr => $instance, - ]) - ); - } }