Skip to content

Commit

Permalink
A better way of creating data
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Jan 10, 2024
1 parent a72ec14 commit d150fe2
Show file tree
Hide file tree
Showing 35 changed files with 505 additions and 194 deletions.
3 changes: 3 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ The following things are required when upgrading:
- 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 have implemented a custom DataPipe, update the `handle` method signature with the new `TransformationContext` parameter
- If you manually created `ValidatePropertiesDataPipe` using the `allTypes` parameter, please now use the creation context for this
- The `withoutMagicalCreationFrom` method was removed from data in favour for creation by factory
- 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
7 changes: 7 additions & 0 deletions config/data.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,11 @@
'root_namespace' => null,
],
],

/**
* A data object can be validated when created using a factory or when calling the from
* method. By default, only when a request is passed the data is being validated. This
* behaviour can be changed to always validate or to completely disable validation.
*/
'validation_type' => \Spatie\LaravelData\Support\Creation\ValidationType::OnlyRequests->value
];
10 changes: 0 additions & 10 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,6 @@ parameters:
count: 1
path: src/Casts/DateTimeInterfaceCast.php

-
message: "#^Method Spatie\\\\LaravelData\\\\Data\\:\\:from\\(\\) should return static\\(Spatie\\\\LaravelData\\\\Data\\) but returns Spatie\\\\LaravelData\\\\Contracts\\\\BaseData\\.$#"
count: 1
path: src/Data.php

-
message: "#^Method Spatie\\\\LaravelData\\\\Data\\:\\:withoutMagicalCreationFrom\\(\\) should return static\\(Spatie\\\\LaravelData\\\\Data\\) but returns Spatie\\\\LaravelData\\\\Contracts\\\\BaseData\\.$#"
count: 1
path: src/Data.php

-
message: "#^PHPDoc tag @return with type Spatie\\\\LaravelData\\\\DataCollection\\<int, TValue\\> is not subtype of native type static\\(Spatie\\\\LaravelData\\\\DataCollection\\<TKey of \\(int\\|string\\), TValue\\>\\)\\.$#"
count: 1
Expand Down
42 changes: 15 additions & 27 deletions src/Concerns/BaseData.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
use Illuminate\Contracts\Pagination\Paginator as PaginatorContract;
use Illuminate\Pagination\AbstractCursorPaginator;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Pagination\CursorPaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Enumerable;
use Illuminate\Support\LazyCollection;
use Spatie\LaravelData\Contracts\BaseData as BaseDataContract;
use Spatie\LaravelData\CursorPaginatedDataCollection;
use Spatie\LaravelData\DataCollection;
use Spatie\LaravelData\DataPipeline;
Expand All @@ -23,6 +22,8 @@
use Spatie\LaravelData\PaginatedDataCollection;
use Spatie\LaravelData\Resolvers\DataCollectableFromSomethingResolver;
use Spatie\LaravelData\Resolvers\DataFromSomethingResolver;
use Spatie\LaravelData\Support\Creation\CreationContext;
use Spatie\LaravelData\Support\Creation\CreationContextFactory;
use Spatie\LaravelData\Support\DataConfig;
use Spatie\LaravelData\Support\DataProperty;

Expand All @@ -43,38 +44,25 @@ public static function optional(mixed ...$payloads): ?static
return null;
}

public static function from(mixed ...$payloads): static
public static function from(mixed ...$payloads): BaseDataContract
{
return app(DataFromSomethingResolver::class)->execute(
static::class,
...$payloads
);
}

public static function withoutMagicalCreationFrom(mixed ...$payloads): static
{
return app(DataFromSomethingResolver::class)->withoutMagicalCreation()->execute(
static::class,
...$payloads
);
return static::factory()->from(...$payloads);
}

public static function collect(mixed $items, ?string $into = null): array|DataCollection|PaginatedDataCollection|CursorPaginatedDataCollection|Enumerable|AbstractPaginator|PaginatorContract|AbstractCursorPaginator|CursorPaginatorContract|LazyCollection|Collection
{
return app(DataCollectableFromSomethingResolver::class)->execute(
static::class,
$items,
$into
);
return static::factory()->collect($items, $into);
}

public static function withoutMagicalCreationCollect(mixed $items, ?string $into = null): array|DataCollection|PaginatedDataCollection|CursorPaginatedDataCollection|Enumerable|AbstractPaginator|Paginator|AbstractCursorPaginator|CursorPaginator
public static function factory(?CreationContext $creationContext = null): CreationContextFactory|CreationContext
{
return app(DataCollectableFromSomethingResolver::class)->withoutMagicalCreation()->execute(
static::class,
$items,
$into
);
if ($creationContext) {
$creationContext->dataClass = static::class;

return $creationContext;
}

return CreationContextFactory::createFromConfig(static::class);
}

public static function normalizers(): array
Expand All @@ -101,7 +89,7 @@ public static function prepareForPipeline(Collection $properties): Collection

public function getMorphClass(): string
{
/** @var class-string<\Spatie\LaravelData\Contracts\BaseData> $class */
/** @var class-string<BaseDataContract> $class */
$class = static::class;

return app(DataConfig::class)->morphMap->getDataClassAlias($class) ?? $class;
Expand Down
6 changes: 3 additions & 3 deletions src/Concerns/ValidateableData.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ public static function validate(Arrayable|array $payload): Arrayable|array

public static function validateAndCreate(Arrayable|array $payload): static
{
static::validate($payload);

return static::from($payload);
return static::factory()
->alwaysValidate()
->from($payload);
}

public static function withValidator(Validator $validator): void
Expand Down
10 changes: 7 additions & 3 deletions src/Concerns/WireableData.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Spatie\LaravelData\Concerns;

use Spatie\LaravelData\Resolvers\DataFromSomethingResolver;
use Spatie\LaravelData\Support\Creation\CreationContextFactory;

trait WireableData
{
Expand All @@ -13,8 +14,11 @@ public function toLivewire(): array

public static function fromLivewire($value): static
{
return app(DataFromSomethingResolver::class)
->ignoreMagicalMethods('fromLivewire')
->execute(static::class, $value);
/** @var CreationContextFactory $factory */
$factory = static::factory();

return $factory
->ignoreMagicalMethod('fromLivewire')
->from($value);
}
}
22 changes: 16 additions & 6 deletions src/Contracts/BaseData.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
use Illuminate\Support\Collection;
use Illuminate\Support\Enumerable;
use Illuminate\Support\LazyCollection;
use Spatie\LaravelData\Contracts\BaseData as BaseDataContract;
use Spatie\LaravelData\CursorPaginatedDataCollection;
use Spatie\LaravelData\DataCollection;
use Spatie\LaravelData\DataPipeline;
use Spatie\LaravelData\PaginatedDataCollection;
use Spatie\LaravelData\Support\Creation\CreationContext;
use Spatie\LaravelData\Support\Creation\CreationContextFactory;

/**
* @template TValue
Expand All @@ -25,21 +28,28 @@ interface BaseData
{
public static function optional(mixed ...$payloads): ?static;

public static function from(mixed ...$payloads): static;

public static function withoutMagicalCreationFrom(mixed ...$payloads): static;
/**
* @return static
*/
public static function from(mixed ...$payloads): BaseDataContract;

/**
* @param Collection<TKey, TValue>|EloquentCollection<TKey, TValue>|LazyCollection<TKey, TValue>|Enumerable|array<TKey, TValue>|AbstractPaginator|PaginatorContract|AbstractCursorPaginator|CursorPaginatorContract|DataCollection<TKey, TValue> $items
*
* @return ($into is 'array' ? array<TKey, static> : ($into is class-string<EloquentCollection> ? Collection<TKey, static> : ($into is class-string<Collection> ? Collection<TKey, static> : ($into is class-string<LazyCollection> ? LazyCollection<TKey, static> : ($into is class-string<DataCollection> ? DataCollection<TKey, static> : ($into is class-string<PaginatedDataCollection> ? PaginatedDataCollection<TKey, static> : ($into is class-string<CursorPaginatedDataCollection> ? CursorPaginatedDataCollection<TKey, static> : ($items is EloquentCollection ? Collection<TKey, static> : ($items is Collection ? Collection<TKey, static> : ($items is LazyCollection ? LazyCollection<TKey, static> : ($items is Enumerable ? Enumerable<TKey, static> : ($items is array ? array<TKey, static> : ($items is AbstractPaginator ? AbstractPaginator : ($items is PaginatorContract ? PaginatorContract : ($items is AbstractCursorPaginator ? AbstractCursorPaginator : ($items is CursorPaginatorContract ? CursorPaginatorContract : ($items is DataCollection ? DataCollection<TKey, static> : ($items is CursorPaginator ? CursorPaginatedDataCollection<TKey, static> : ($items is Paginator ? PaginatedDataCollection<TKey, static> : DataCollection<TKey, static>))))))))))))))))))) */
* @return ($into is 'array' ? array<TKey, static> : ($into is class-string<EloquentCollection> ? Collection<TKey, static> : ($into is class-string<Collection> ? Collection<TKey, static> : ($into is class-string<LazyCollection> ? LazyCollection<TKey, static> : ($into is class-string<DataCollection> ? DataCollection<TKey, static> : ($into is class-string<PaginatedDataCollection> ? PaginatedDataCollection<TKey, static> : ($into is class-string<CursorPaginatedDataCollection> ? CursorPaginatedDataCollection<TKey, static> : ($items is EloquentCollection ? Collection<TKey, static> : ($items is Collection ? Collection<TKey, static> : ($items is LazyCollection ? LazyCollection<TKey, static> : ($items is Enumerable ? Enumerable<TKey, static> : ($items is array ? array<TKey, static> : ($items is AbstractPaginator ? AbstractPaginator : ($items is PaginatorContract ? PaginatorContract : ($items is AbstractCursorPaginator ? AbstractCursorPaginator : ($items is CursorPaginatorContract ? CursorPaginatorContract : ($items is DataCollection ? DataCollection<TKey, static> : ($items is CursorPaginator ? CursorPaginatedDataCollection<TKey, static> : ($items is Paginator ? PaginatedDataCollection<TKey, static> : DataCollection<TKey, static>)))))))))))))))))))
*/
public static function collect(mixed $items, ?string $into = null): array|DataCollection|PaginatedDataCollection|CursorPaginatedDataCollection|Enumerable|AbstractPaginator|PaginatorContract|AbstractCursorPaginator|CursorPaginatorContract|LazyCollection|Collection;

public static function withoutMagicalCreationCollect(mixed $items, ?string $into = null);
/**
* @param CreationContext<static>|null $creationContext
*
* @return ($creationContext is null ? CreationContextFactory<static> : CreationContext<static>)
*/
public static function factory(?CreationContext $creationContext = null): CreationContextFactory|CreationContext;

public static function normalizers(): array;

public static function prepareForPipeline(\Illuminate\Support\Collection $properties): \Illuminate\Support\Collection;
public static function prepareForPipeline(Collection $properties): Collection;

public static function pipeline(): DataPipeline;

Expand Down
2 changes: 1 addition & 1 deletion src/Contracts/ValidateableData.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface ValidateableData
{
public static function validate(Arrayable|array $payload): Arrayable|array;

public static function validateAndCreate(Arrayable|array $payload): object;
public static function validateAndCreate(Arrayable|array $payload): static;

public static function withValidator(Validator $validator): void;
}
8 changes: 0 additions & 8 deletions src/DataPipeline.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,4 @@ public function resolve(): ResolvedDataPipeline
$this->dataConfig->getDataClass($this->classString)
);
}

/** @deprecated */
public function execute(): Collection
{
return $this->dataConfig
->getResolvedDataPipeline($this->classString)
->execute($this->value);
}
}
9 changes: 7 additions & 2 deletions src/DataPipes/AuthorizedDataPipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Spatie\LaravelData\Support\Creation\CreationContext;
use Spatie\LaravelData\Support\DataClass;

class AuthorizedDataPipe implements DataPipe
{
public function handle(mixed $payload, DataClass $class, Collection $properties): Collection
{
public function handle(
mixed $payload,
DataClass $class,
Collection $properties,
CreationContext $creationContext
): Collection {
if (! $payload instanceof Request) {
return $properties;
}
Expand Down
22 changes: 16 additions & 6 deletions src/DataPipes/CastPropertiesDataPipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Support\Collection;
use Spatie\LaravelData\Lazy;
use Spatie\LaravelData\Optional;
use Spatie\LaravelData\Support\Creation\CreationContext;
use Spatie\LaravelData\Support\DataClass;
use Spatie\LaravelData\Support\DataConfig;
use Spatie\LaravelData\Support\DataProperty;
Expand All @@ -16,8 +17,12 @@ public function __construct(
) {
}

public function handle(mixed $payload, DataClass $class, Collection $properties): Collection
{
public function handle(
mixed $payload,
DataClass $class,
Collection $properties,
CreationContext $creationContext
): Collection {
$castContext = $properties->all();

foreach ($properties as $name => $value) {
Expand All @@ -31,7 +36,7 @@ public function handle(mixed $payload, DataClass $class, Collection $properties)
continue;
}

$properties[$name] = $this->cast($dataProperty, $value, $castContext);
$properties[$name] = $this->cast($dataProperty, $value, $castContext, $creationContext);
}

return $properties;
Expand All @@ -41,6 +46,7 @@ protected function cast(
DataProperty $property,
mixed $value,
array $castContext,
CreationContext $creationContext
): mixed {
$shouldCast = $this->shouldBeCasted($property, $value);

Expand All @@ -52,16 +58,20 @@ protected function cast(
return $cast->cast($property, $value, $castContext);
}

if ($cast = $creationContext->casts?->findCastForValue($property)) {
return $cast->cast($property, $value, $castContext);
}

if ($cast = $this->dataConfig->casts->findCastForValue($property)) {
return $cast->cast($property, $value, $castContext);
}

if ($property->type->kind->isDataObject()) {
return $property->type->dataClass::from($value);
if ($property->type->kind->isDataObject() && $property->type->dataClass) {
return $property->type->dataClass::factory($creationContext)->from($value);
}

if ($property->type->kind->isDataCollectable()) {
return $property->type->dataClass::collect($value, $property->type->dataCollectableClass);
return $property->type->dataClass::factory($creationContext)->collect($value, $property->type->dataCollectableClass);
}

return $value;
Expand Down
8 changes: 7 additions & 1 deletion src/DataPipes/DataPipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@
namespace Spatie\LaravelData\DataPipes;

use Illuminate\Support\Collection;
use Spatie\LaravelData\Support\Creation\CreationContext;
use Spatie\LaravelData\Support\DataClass;

interface DataPipe
{
public function handle(mixed $payload, DataClass $class, Collection $properties): Collection;
public function handle(
mixed $payload,
DataClass $class,
Collection $properties,
CreationContext $creationContext
): Collection;
}
9 changes: 7 additions & 2 deletions src/DataPipes/DefaultValuesDataPipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@

use Illuminate\Support\Collection;
use Spatie\LaravelData\Optional;
use Spatie\LaravelData\Support\Creation\CreationContext;
use Spatie\LaravelData\Support\DataClass;
use Spatie\LaravelData\Support\DataProperty;

class DefaultValuesDataPipe implements DataPipe
{
public function handle(mixed $payload, DataClass $class, Collection $properties): Collection
{
public function handle(
mixed $payload,
DataClass $class,
Collection $properties,
CreationContext $creationContext
): Collection {
$dataDefaults = $class->defaultable
? app()->call([$class->name, 'defaults'])
: [];
Expand Down
9 changes: 7 additions & 2 deletions src/DataPipes/FillRouteParameterPropertiesDataPipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
use Spatie\LaravelData\Attributes\FromRouteParameter;
use Spatie\LaravelData\Attributes\FromRouteParameterProperty;
use Spatie\LaravelData\Exceptions\CannotFillFromRouteParameterPropertyUsingScalarValue;
use Spatie\LaravelData\Support\Creation\CreationContext;
use Spatie\LaravelData\Support\DataClass;
use Spatie\LaravelData\Support\DataProperty;

class FillRouteParameterPropertiesDataPipe implements DataPipe
{
public function handle(mixed $payload, DataClass $class, Collection $properties): Collection
{
public function handle(
mixed $payload,
DataClass $class,
Collection $properties,
CreationContext $creationContext
): Collection {
if (! $payload instanceof Request) {
return $properties;
}
Expand Down
13 changes: 11 additions & 2 deletions src/DataPipes/MapPropertiesDataPipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Spatie\LaravelData\Support\Creation\CreationContext;
use Spatie\LaravelData\Support\DataClass;

class MapPropertiesDataPipe implements DataPipe
{
public function handle(mixed $payload, DataClass $class, Collection $properties): Collection
{
public function handle(
mixed $payload,
DataClass $class,
Collection $properties,
CreationContext $creationContext
): Collection {
if ($creationContext->mapPropertyNames === false) {
return $properties;
}

foreach ($class->properties as $dataProperty) {
if ($dataProperty->inputMappedName === null) {
continue;
Expand Down
Loading

0 comments on commit d150fe2

Please sign in to comment.