Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/EntityList/SharpEntityList.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
*/
Expand Down
59 changes: 59 additions & 0 deletions src/Utils/Transformers/AttributePath.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace Code16\Sharp\Utils\Transformers;

final class AttributePath
{
public function __construct(
public readonly string $key,
public readonly bool $isList,
public readonly ?string $relation,
public readonly ?string $listAttribute,
public readonly ?string $itemAttribute,
public readonly string $attribute,
) {}

public static function parse(string $attribute): self
{
if (str_contains($attribute, '[')) {
$listAttribute = substr($attribute, 0, strpos($attribute, '['));
$itemAttribute = substr($attribute, strpos($attribute, '[') + 1, -1);

$relation = null;
if (($sep = strpos($listAttribute, ':')) !== false) {
$relation = substr($listAttribute, 0, $sep);
$listAttribute = substr($listAttribute, $sep + 1);
}

return new self(
key: $attribute,
isList: true,
relation: $relation,
listAttribute: $listAttribute,
itemAttribute: $itemAttribute,
attribute: $itemAttribute,
);
}

$relation = null;
$attributeName = $attribute;
if (($sep = strpos($attribute, ':')) !== false) {
$relation = substr($attribute, 0, $sep);
$attributeName = substr($attribute, $sep + 1);
}

return new self(
key: $attribute,
isList: false,
relation: $relation,
listAttribute: null,
itemAttribute: null,
attribute: $relation ? $attributeName : $attribute,
);
}

public function isRelated(): bool
{
return $this->relation !== null;
}
}
11 changes: 11 additions & 0 deletions src/Utils/Transformers/CachesEntityListInstances.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Code16\Sharp\Utils\Transformers;

interface CachesEntityListInstances
{
/**
* @param array<int, mixed> $instances
*/
public function cacheEntityListInstances(array $instances): void;
}
87 changes: 87 additions & 0 deletions src/Utils/Transformers/TransformerPipeline.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace Code16\Sharp\Utils\Transformers;

final class TransformerPipeline
{
/**
* @param array<string, SharpAttributeTransformer> $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;
}
}
80 changes: 11 additions & 69 deletions src/Utils/Transformers/WithCustomTransformers.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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,
])
);
}
}
Loading