Skip to content

Commit

Permalink
Add support for transforms in context
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Jan 8, 2024
1 parent e1d19de commit c84dd51
Show file tree
Hide file tree
Showing 17 changed files with 168 additions and 54 deletions.
1 change: 1 addition & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The following things are required when upgrading:
- EmptyData (T/I) and ContextableData (T/I) was added
- If you were calling the transform method on a data object, a `TransformationContextFactory` or `TransformationContext` is now the only parameter you can pass
- Take a look within the docs what has changed
- If you have implemented a custom `Transformer`, update the `transform` method signature with the new `TransformationContext` parameter
- If you were using internal data structures like `DataClass` and `DataProperty` then take a look at what has been changed
- The `DataCollectableTransformer` and `DataTransformer` were replaced with their appropriate resolvers
- If you've cached the data structures, be sure to clear the cache
Expand Down
12 changes: 10 additions & 2 deletions src/Resolvers/TransformedDataResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ protected function resolvePropertyValue(
}

if ($transformer = $this->resolveTransformerForValue($property, $value, $currentContext)) {
return $transformer->transform($property, $value);
return $transformer->transform($property, $value, $currentContext);
}

if (is_array($value) && ! $property->type->kind->isDataCollectable()) {
Expand Down Expand Up @@ -197,7 +197,15 @@ protected function resolveTransformerForValue(
return null;
}

$transformer = $property->transformer ?? $this->dataConfig->findGlobalTransformerForValue($value);
$transformer = $property->transformer;

if ($transformer === null && $context->transformers) {
$transformer = $context->transformers->findTransformerForValue($value);
}

if ($transformer === null) {
$transformer = $this->dataConfig->transformers->findTransformerForValue($value);
}

$shouldUseDefaultDataTransformer = $transformer instanceof ArrayableTransformer
&& $property->type->kind !== DataTypeKind::Default;
Expand Down
1 change: 1 addition & 0 deletions src/Resolvers/VisibleDataFieldsResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public function execute(
$transformationContext->transformValues,
$transformationContext->mapPropertyNames,
$transformationContext->wrapExecutionType,
$transformationContext->transformers,
);
}
}
Expand Down
27 changes: 5 additions & 22 deletions src/Support/DataConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
use ReflectionClass;
use Spatie\LaravelData\Casts\Cast;
use Spatie\LaravelData\Contracts\BaseData;
use Spatie\LaravelData\Support\Transformation\GlobalTransformersCollection;
use Spatie\LaravelData\Transformers\Transformer;

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

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

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

$this->transformers = new GlobalTransformersCollection();

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

foreach ($config['casts'] ?? [] as $castable => $cast) {
Expand Down Expand Up @@ -75,25 +77,6 @@ public function findGlobalCastForProperty(DataProperty $property): ?Cast
return null;
}

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

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

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

return null;
}

public function getRuleInferrers(): array
{
return $this->ruleInferrers;
Expand Down
50 changes: 50 additions & 0 deletions src/Support/Transformation/GlobalTransformersCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Spatie\LaravelData\Support\Transformation;

use ArrayIterator;
use IteratorAggregate;
use Spatie\LaravelData\Transformers\Transformer;
use Traversable;

class GlobalTransformersCollection implements IteratorAggregate
{
/**
* @param array<string, Transformer> $transformers
*/
public function __construct(
protected array $transformers = []
) {
}

public function add(string $transformable, Transformer $transformer): self
{
$this->transformers[ltrim($transformable, ' \\')] = $transformer;

return $this;
}

public function findTransformerForValue(mixed $value): ?Transformer
{
if (gettype($value) !== 'object') {
return null;
}

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

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

return null;
}

public function getIterator(): Traversable
{
return new ArrayIterator($this->transformers);
}
}
1 change: 1 addition & 0 deletions src/Support/Transformation/TransformationContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public function __construct(
public bool $transformValues = true,
public bool $mapPropertyNames = true,
public WrapExecutionType $wrapExecutionType = WrapExecutionType::Disabled,
public ?GlobalTransformersCollection $transformers = null,
public ?ResolvedPartialsCollection $includedPartials = null,
public ?ResolvedPartialsCollection $excludedPartials = null,
public ?ResolvedPartialsCollection $onlyPartials = null,
Expand Down
14 changes: 14 additions & 0 deletions src/Support/Transformation/TransformationContextFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Spatie\LaravelData\Support\Partials\PartialsCollection;
use Spatie\LaravelData\Support\Partials\ResolvedPartialsCollection;
use Spatie\LaravelData\Support\Wrapping\WrapExecutionType;
use Spatie\LaravelData\Transformers\Transformer;

class TransformationContextFactory
{
Expand All @@ -23,6 +24,7 @@ protected function __construct(
public bool $transformValues = true,
public bool $mapPropertyNames = true,
public WrapExecutionType $wrapExecutionType = WrapExecutionType::Disabled,
public ?GlobalTransformersCollection $transformers = null,
public ?PartialsCollection $includedPartials = null,
public ?PartialsCollection $excludedPartials = null,
public ?PartialsCollection $onlyPartials = null,
Expand Down Expand Up @@ -93,6 +95,7 @@ public function get(
$this->transformValues,
$this->mapPropertyNames,
$this->wrapExecutionType,
$this->transformers,
$includedPartials,
$excludedPartials,
$onlyPartials,
Expand Down Expand Up @@ -121,6 +124,17 @@ public function wrapExecutionType(WrapExecutionType $wrapExecutionType): static
return $this;
}

public function transformer(string $transformable, Transformer $transformer): static
{
if ($this->transformers === null) {
$this->transformers = new GlobalTransformersCollection();
}

$this->transformers->add($transformable, $transformer);

return $this;
}

public function addIncludePartial(Partial ...$partial): static
{
if ($this->includedPartials === null) {
Expand Down
4 changes: 3 additions & 1 deletion src/Transformers/ArrayableTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

namespace Spatie\LaravelData\Transformers;

use Illuminate\Contracts\Support\Arrayable;
use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Support\Transformation\TransformationContext;

class ArrayableTransformer implements Transformer
{
public function transform(DataProperty $property, mixed $value): array
public function transform(DataProperty $property, mixed $value, TransformationContext $context): array
{
/** @var \Illuminate\Contracts\Support\Arrayable $value */
return $value->toArray();
Expand Down
6 changes: 4 additions & 2 deletions src/Transformers/DateTimeInterfaceTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace Spatie\LaravelData\Transformers;

use DateTimeInterface;
use DateTimeZone;
use Illuminate\Support\Arr;
use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Support\Transformation\TransformationContext;

class DateTimeInterfaceTransformer implements Transformer
{
Expand All @@ -17,9 +19,9 @@ public function __construct(
[$this->format] = Arr::wrap($format ?? config('data.date_format'));
}

public function transform(DataProperty $property, mixed $value): string
public function transform(DataProperty $property, mixed $value, TransformationContext $context): string
{
/** @var \DateTimeInterface $value */
/** @var DateTimeInterface $value */
if ($this->setTimeZone) {
$value = (clone $value)->setTimezone(new DateTimeZone($this->setTimeZone));
}
Expand Down
3 changes: 2 additions & 1 deletion src/Transformers/EnumTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
namespace Spatie\LaravelData\Transformers;

use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Support\Transformation\TransformationContext;

class EnumTransformer implements Transformer
{
public function transform(DataProperty $property, mixed $value): string|int
public function transform(DataProperty $property, mixed $value, TransformationContext $context): string|int
{
return $value->value;
}
Expand Down
7 changes: 6 additions & 1 deletion src/Transformers/Transformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
namespace Spatie\LaravelData\Transformers;

use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Support\Transformation\TransformationContext;

interface Transformer
{
public function transform(DataProperty $property, mixed $value): mixed;
public function transform(
DataProperty $property,
mixed $value,
TransformationContext $context
): mixed;
}
29 changes: 28 additions & 1 deletion tests/DataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
use Spatie\LaravelData\Exceptions\CannotSetComputedValue;
use Spatie\LaravelData\Lazy;
use Spatie\LaravelData\Optional;
use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Support\Transformation\TransformationContext;
use Spatie\LaravelData\Support\Transformation\TransformationContextFactory;
use Spatie\LaravelData\Tests\Fakes\Castables\SimpleCastable;
use Spatie\LaravelData\Tests\Fakes\Casts\ConfidentialDataCast;
Expand All @@ -56,8 +58,8 @@
use Spatie\LaravelData\Tests\Fakes\Transformers\StringToUpperTransformer;
use Spatie\LaravelData\Tests\Fakes\UlarData;
use Spatie\LaravelData\Transformers\DateTimeInterfaceTransformer;
use Spatie\LaravelData\Transformers\Transformer;
use Spatie\LaravelData\WithData;

use function Spatie\Snapshots\assertMatchesSnapshot;

it('can create a resource', function () {
Expand Down Expand Up @@ -1527,3 +1529,28 @@ public function __construct(
yield 'no params' => [[], 'Could not create `Spatie\LaravelData\Tests\Fakes\MultiData`: the constructor requires 2 parameters, 0 given. Parameters missing: first, second.'],
yield 'one param' => [['first' => 'First'], 'Could not create `Spatie\LaravelData\Tests\Fakes\MultiData`: the constructor requires 2 parameters, 1 given. Parameters given: first. Parameters missing: second.'],
]);

it('is possible to add extra global transformers when transforming using context', function () {
$dataClass = new class extends Data {
public DateTime $dateTime;
};

$data = $dataClass::from([
'dateTime' => new DateTime(),
]);

$customTransformer = new class implements Transformer {
public function transform(DataProperty $property, mixed $value, TransformationContext $context): string
{
return "Custom transformed date";
}
};

$transformed = $data->transform(
TransformationContextFactory::create()->transformer(DateTimeInterface::class, $customTransformer)
);

expect($transformed)->toBe([
'dateTime' => 'Custom transformed date',
]);
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

use Spatie\LaravelData\Data;
use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Support\Transformation\TransformationContext;
use Spatie\LaravelData\Transformers\Transformer;

class ConfidentialDataCollectionTransformer implements Transformer
{
public function transform(DataProperty $property, mixed $value): mixed
public function transform(DataProperty $property, mixed $value, TransformationContext $context): mixed
{
/** @var array $value */
return array_map(fn (Data $data) => (new ConfidentialDataTransformer())->transform($property, $data), $value);
return array_map(fn (Data $data) => (new ConfidentialDataTransformer())->transform($property, $data, $context), $value);
}
}
3 changes: 2 additions & 1 deletion tests/Fakes/Transformers/ConfidentialDataTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

namespace Spatie\LaravelData\Tests\Fakes\Transformers;

use Spatie\LaravelData\Support\Transformation\TransformationContext;
use function collect;

use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Transformers\Transformer;

class ConfidentialDataTransformer implements Transformer
{
public function transform(DataProperty $property, mixed $value): mixed
public function transform(DataProperty $property, mixed $value, TransformationContext $context): mixed
{
/** @var \Spatie\LaravelData\Data $value */
return collect($value->toArray())->map(fn (mixed $value) => 'CONFIDENTIAL')->toArray();
Expand Down
7 changes: 5 additions & 2 deletions tests/Fakes/Transformers/StringToUpperTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
namespace Spatie\LaravelData\Tests\Fakes\Transformers;

use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Support\Transformation\TransformationContext;
use Spatie\LaravelData\Transformers\Transformer;

class StringToUpperTransformer implements Transformer
{
public function transform(DataProperty $property, mixed $value): string
public function transform(DataProperty $property, mixed $value, TransformationContext $context): string
{
return strtoupper($value);
}
};
}

;
Loading

0 comments on commit c84dd51

Please sign in to comment.