Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add context to transformers #589

Closed
wants to merge 2 commits into from
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
2 changes: 1 addition & 1 deletion src/Attributes/WithTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Spatie\LaravelData\Exceptions\CannotCreateTransformerAttribute;
use Spatie\LaravelData\Transformers\Transformer;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class WithTransformer
{
public array $arguments;
Expand Down
3 changes: 2 additions & 1 deletion src/Concerns/BaseData.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,9 @@ public function transform(
bool $transformValues = true,
WrapExecutionType $wrapExecutionType = WrapExecutionType::Disabled,
bool $mapPropertyNames = true,
?string $context = null,
): array {
return DataTransformer::create($transformValues, $wrapExecutionType, $mapPropertyNames)->transform($this);
return DataTransformer::create($transformValues, $wrapExecutionType, $mapPropertyNames, $context)->transform($this);
}

public function getMorphClass(): string
Expand Down
2 changes: 2 additions & 0 deletions src/Concerns/BaseDataCollectable.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ public function transform(
bool $transformValues = true,
WrapExecutionType $wrapExecutionType = WrapExecutionType::Disabled,
bool $mapPropertyNames = true,
?string $context = null,
): array {
$transformer = new DataCollectableTransformer(
$this->dataClass,
$transformValues,
$wrapExecutionType,
$mapPropertyNames,
$context,
$this->getPartialTrees(),
$this->items,
$this->getWrap(),
Expand Down
1 change: 1 addition & 0 deletions src/Concerns/ResponsableData.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function toResponse($request)
return new JsonResponse(
data: $this->transform(
wrapExecutionType: WrapExecutionType::Enabled,
context: 'response'
),
status: $this->calculateResponseStatus($request)
);
Expand Down
11 changes: 8 additions & 3 deletions src/Concerns/TransformableData.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,22 @@ public function all(): array

public function toArray(): array
{
return $this->transform();
return $this->transform(context: 'array');
}

public function toJson($options = 0): string
{
return json_encode($this->transform(), $options);
return json_encode($this->transform(context: 'json'), $options);
}

public function toEloquent(): string
{
return json_encode($this->transform(context: 'eloquent'));
}

public function jsonSerialize(): array
{
return $this->transform();
return $this->transform(context: 'json');
}

public static function castUsing(array $arguments)
Expand Down
3 changes: 3 additions & 0 deletions src/Contracts/TransformableData.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public function all(): array;

public function toArray(): array;

public function toEloquent(): string;

public function toJson($options = 0): string;

public function jsonSerialize(): array;
Expand All @@ -22,6 +24,7 @@ public function transform(
bool $transformValues = true,
WrapExecutionType $wrapExecutionType = WrapExecutionType::Disabled,
bool $mapPropertyNames = true,
?string $context = null,
): array;

public static function castUsing(array $arguments);
Expand Down
29 changes: 18 additions & 11 deletions src/Support/DataConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

namespace Spatie\LaravelData\Support;

use Illuminate\Support\Arr;
use ReflectionClass;
use Spatie\LaravelData\Casts\Cast;
use Spatie\LaravelData\Contracts\BaseData;
use Spatie\LaravelData\Transformers\Transformer;

class DataConfig
{
/** @var array<string, \Spatie\LaravelData\Support\DataClass> */
protected array $dataClasses = [];

/** @var array<string, \Spatie\LaravelData\Transformers\Transformer> */
/** @var array<string, \Spatie\LaravelData\Transformers\Transformer[]> */
protected array $transformers = [];

/** @var array<string, \Spatie\LaravelData\Casts\Cast> */
Expand All @@ -33,9 +33,7 @@ public function __construct(array $config)
$config['rule_inferrers'] ?? []
);

foreach ($config['transformers'] ?? [] as $transformable => $transformer) {
$this->transformers[ltrim($transformable, ' \\')] = app($transformer);
}
$this->setTransformers($config['transformers'] ?? []);

foreach ($config['casts'] ?? [] as $castable => $cast) {
$this->casts[ltrim($castable, ' \\')] = app($cast);
Expand All @@ -44,6 +42,15 @@ public function __construct(array $config)
$this->morphMap = new DataClassMorphMap();
}

public function setTransformers(array $transformers): self
{
foreach ($transformers as $transformable => $transformers) {
$this->transformers[ltrim($transformable, ' \\')] = array_map(resolve(...), Arr::wrap($transformers));
}

return $this;
}

public function getDataClass(string $class): DataClass
{
if (array_key_exists($class, $this->dataClasses)) {
Expand Down Expand Up @@ -75,23 +82,23 @@ public function findGlobalCastForProperty(DataProperty $property): ?Cast
return null;
}

public function findGlobalTransformerForValue(mixed $value): ?Transformer
public function findGlobalTransformersForValue(mixed $value): array
{
if (gettype($value) !== 'object') {
return null;
return [];
}

foreach ($this->transformers as $transformable => $transformer) {
foreach ($this->transformers as $transformable => $transformers) {
if ($value::class === $transformable) {
return $transformer;
return $transformers;
}

if (is_a($value::class, $transformable, true)) {
return $transformer;
return $transformers;
}
}

return null;
return [];
}

public function getRuleInferrers(): array
Expand Down
5 changes: 2 additions & 3 deletions src/Support/DataProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use Spatie\LaravelData\Casts\Cast;
use Spatie\LaravelData\Mappers\NameMapper;
use Spatie\LaravelData\Resolvers\NameMappersResolver;
use Spatie\LaravelData\Transformers\Transformer;

/**
* @property Collection<string, object> $attributes
Expand All @@ -32,7 +31,7 @@ public function __construct(
public readonly bool $hasDefaultValue,
public readonly mixed $defaultValue,
public readonly ?Cast $cast,
public readonly ?Transformer $transformer,
public readonly Collection $transformers,
public readonly ?string $inputMappedName,
public readonly ?string $outputMappedName,
public readonly Collection $attributes,
Expand Down Expand Up @@ -86,7 +85,7 @@ className: $property->class,
hasDefaultValue: $property->isPromoted() ? $hasDefaultValue : $property->hasDefaultValue(),
defaultValue: $property->isPromoted() ? $defaultValue : $property->getDefaultValue(),
cast: $attributes->first(fn (object $attribute) => $attribute instanceof GetsCast)?->get(),
transformer: $attributes->first(fn (object $attribute) => $attribute instanceof WithTransformer)?->get(),
transformers: $attributes->filter(fn (object $attribute) => $attribute instanceof WithTransformer)->map->get(),
inputMappedName: $inputMappedName,
outputMappedName: $outputMappedName,
attributes: $attributes,
Expand Down
4 changes: 2 additions & 2 deletions src/Support/EloquentCasts/DataEloquentCast.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ public function set($model, string $key, $value, array $attributes): ?string
if ($isAbstractClassCast) {
return json_encode([
'type' => $this->dataConfig->morphMap->getDataClassAlias($value::class) ?? $value::class,
'data' => json_decode($value->toJson(), associative: true, flags: JSON_THROW_ON_ERROR),
'data' => json_decode($value->toEloquent(), associative: true, flags: JSON_THROW_ON_ERROR),
]);
}

return $value->toJson();
return $value->toEloquent();
}

protected function isAbstractClassCast(): bool
Expand Down
2 changes: 1 addition & 1 deletion src/Transformers/ArrayableTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class ArrayableTransformer implements Transformer
{
public function transform(DataProperty $property, mixed $value): array
public function transform(DataProperty $property, mixed $value, ?string $context = null): array
{
/** @var \Illuminate\Contracts\Support\Arrayable $value */
return $value->toArray();
Expand Down
6 changes: 5 additions & 1 deletion src/Transformers/DataCollectableTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public function __construct(
protected bool $transformValues,
protected WrapExecutionType $wrapExecutionType,
protected bool $mapPropertyNames,
protected ?string $context,
protected PartialTrees $trees,
protected Enumerable|CursorPaginator|Paginator $items,
protected Wrap $wrap,
Expand All @@ -33,7 +34,9 @@ public function transform(): array
}

$this->items->through(
$this->transformItemClosure()
fn (mixed $item) => $item instanceof BaseData
? $this->transformItemClosure()($item)->transform(context: $this->context)
: $item
);

return $this->transformValues
Expand All @@ -60,6 +63,7 @@ protected function transformCollection(Enumerable $items): array
? WrapExecutionType::TemporarilyDisabled
: $this->wrapExecutionType,
$this->mapPropertyNames,
$this->context,
);
}

Expand Down
47 changes: 30 additions & 17 deletions src/Transformers/DataTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Spatie\LaravelData\Transformers;

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Spatie\LaravelData\Contracts\AppendableData;
use Spatie\LaravelData\Contracts\BaseData;
use Spatie\LaravelData\Contracts\BaseDataCollectable;
Expand Down Expand Up @@ -30,14 +31,16 @@ public static function create(
bool $transformValues,
WrapExecutionType $wrapExecutionType,
bool $mapPropertyNames,
?string $context = null,
): self {
return new self($transformValues, $wrapExecutionType, $mapPropertyNames);
return new self($transformValues, $wrapExecutionType, $mapPropertyNames, $context);
}

public function __construct(
protected bool $transformValues,
protected WrapExecutionType $wrapExecutionType,
protected bool $mapPropertyNames,
protected ?string $context = null,
) {
$this->config = app(DataConfig::class);
}
Expand Down Expand Up @@ -197,7 +200,7 @@ protected function isPropertyLazyIncluded(
protected function resolvePropertyValue(
DataProperty $property,
mixed $value,
PartialTrees $trees
PartialTrees $trees,
): mixed {
if ($value instanceof Lazy) {
$value = $value->resolve();
Expand All @@ -207,16 +210,20 @@ protected function resolvePropertyValue(
return null;
}

if (is_array($value) && ($trees->only instanceof AllTreeNode || $trees->only instanceof PartialTreeNode)) {
if (\is_array($value) && ($trees->only instanceof AllTreeNode || $trees->only instanceof PartialTreeNode)) {
$value = Arr::only($value, $trees->only->getFields());
}

if (is_array($value) && ($trees->except instanceof AllTreeNode || $trees->except instanceof PartialTreeNode)) {
if (\is_array($value) && ($trees->except instanceof AllTreeNode || $trees->except instanceof PartialTreeNode)) {
$value = Arr::except($value, $trees->except->getFields());
}

if ($transformer = $this->resolveTransformerForValue($property, $value)) {
return $transformer->transform($property, $value);
if (\count($transformers = $this->resolveTransformersForValue($property, $value))) {
foreach ($transformers as $transformer) {
if ($result = $transformer->transform($property, $value, $this->context)) {
return $result;
}
}
}

if (! $value instanceof BaseData && ! $value instanceof BaseDataCollectable) {
Expand All @@ -238,29 +245,35 @@ protected function resolvePropertyValue(
};

if ($value instanceof TransformableData && $this->transformValues) {
return $value->transform($this->transformValues, $wrapExecutionType, $this->mapPropertyNames, );
return $value->transform($this->transformValues, $wrapExecutionType, $this->mapPropertyNames, $this->context);
}

return $value;
}

protected function resolveTransformerForValue(
/** @return Collection<int,Transformer> */
protected function resolveTransformersForValue(
DataProperty $property,
mixed $value,
): ?Transformer {
): Collection {
if (! $this->transformValues) {
return null;
return collect();
}

$transformer = $property->transformer ?? $this->config->findGlobalTransformerForValue($value);
// NOTE: maybe these should be merged instead, but that would be a breaking change.
$transformers = $property->transformers->isNotEmpty()
? $property->transformers
: collect($this->config->findGlobalTransformersForValue($value));

$shouldUseDefaultDataTransformer = $transformer instanceof ArrayableTransformer
&& ($property->type->isDataObject || $property->type->isDataCollectable);
return $transformers->filter(function (Transformer $transformer) use ($property) {
$shouldUseDefaultDataTransformer = $transformer instanceof ArrayableTransformer
&& ($property->type->isDataObject || $property->type->isDataCollectable);

if ($shouldUseDefaultDataTransformer) {
return null;
}
if ($shouldUseDefaultDataTransformer) {
return false;
}

return $transformer;
return true;
});
}
}
2 changes: 1 addition & 1 deletion src/Transformers/DateTimeInterfaceTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function __construct(
) {
}

public function transform(DataProperty $property, mixed $value): string
public function transform(DataProperty $property, mixed $value, ?string $context = null): string
{
[$format] = Arr::wrap($this->format ?? config('data.date_format'));

Expand Down
2 changes: 1 addition & 1 deletion src/Transformers/EnumTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class EnumTransformer implements Transformer
{
public function transform(DataProperty $property, mixed $value): string|int
public function transform(DataProperty $property, mixed $value, ?string $context = null): string|int
{
return $value->value;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Transformers/Transformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

interface Transformer
{
public function transform(DataProperty $property, mixed $value): mixed;
public function transform(DataProperty $property, mixed $value, ?string $context = null): mixed;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

class ConfidentialDataCollectionTransformer implements Transformer
{
public function transform(DataProperty $property, mixed $value): mixed
public function transform(DataProperty $property, mixed $value, ?string $context = null): mixed
{
/** @var \Spatie\LaravelData\DataCollection $value */
return $value->toCollection()->map(fn (Data $data) => (new ConfidentialDataTransformer())->transform($property, $data))->toArray();
return $value->toCollection()->map(fn (Data $data) => (new ConfidentialDataTransformer())->transform($property, $data, $context))->toArray();
}
}
2 changes: 1 addition & 1 deletion tests/Fakes/Transformers/ConfidentialDataTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class ConfidentialDataTransformer implements Transformer
{
public function transform(DataProperty $property, mixed $value): mixed
public function transform(DataProperty $property, mixed $value, ?string $context = null): mixed
{
/** @var \Spatie\LaravelData\Data $value */
return collect($value->toArray())->map(fn (mixed $value) => 'CONFIDENTIAL')->toArray();
Expand Down
Loading
Loading