Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Jan 16, 2024
1 parent 06fcfe6 commit 1c1bde5
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 121 deletions.
43 changes: 32 additions & 11 deletions src/Resolvers/DataCollectableFromSomethingResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,33 +78,50 @@ protected function createFromCustomCreationMethod(
$method = $this->dataConfig
->getDataClass($dataClass)
->methods
->filter(function (DataMethod $method) use ($into, $items) {
if ($method->customCreationMethodType !== CustomCreationMethodType::Collection) {
->filter(function (DataMethod $method) use ($creationContext, $into, $items) {
if (
$method->customCreationMethodType !== CustomCreationMethodType::Collection
) {
return false;
}

if (
$creationContext->ignoredMagicalMethods !== null
&& in_array($method->name, $creationContext->ignoredMagicalMethods)
) {
return false;
}

if ($into !== null && ! $method->returns($into)) {
return false;
}

return $method->accepts([$items]);
return $method->accepts($items);
})
->first();

if ($method !== null) {
return $dataClass::{$method->name}(
array_map($this->itemsToDataClosure($dataClass, $creationContext), $items)
);
if ($method === null) {
return null;
}

$payload = [];

foreach ($method->parameters as $parameter) {
if ($parameter->isCreationContext) {
$payload[$parameter->name] = $creationContext;
} else {
$payload[$parameter->name] = $this->normalizeItems($items, $dataClass, $creationContext);
}
}

return null;
return $dataClass::{$method->name}(...$payload);
}

protected function normalizeItems(
mixed $items,
string $dataClass,
CreationContext $creationContext,
): array|Paginator|AbstractPaginator|CursorPaginator|AbstractCursorPaginator {
): array|Paginator|AbstractPaginator|CursorPaginator|AbstractCursorPaginator|Enumerable {
if ($items instanceof PaginatedDataCollection
|| $items instanceof CursorPaginatedDataCollection
|| $items instanceof DataCollection
Expand All @@ -120,7 +137,7 @@ protected function normalizeItems(
}

if ($items instanceof Enumerable) {
$items = $items->all();
return $items->map($this->itemsToDataClosure($dataClass, $creationContext));
}

if (is_array($items)) {
Expand All @@ -134,8 +151,12 @@ protected function normalizeItems(
}

protected function normalizeToArray(
array|Paginator|AbstractPaginator|CursorPaginator|AbstractCursorPaginator $items,
array|Paginator|AbstractPaginator|CursorPaginator|AbstractCursorPaginator|Enumerable $items,
): array {
if ($items instanceof Enumerable) {
return $items->all();
}

return is_array($items)
? $items
: $items->items();
Expand Down
22 changes: 17 additions & 5 deletions src/Resolvers/DataFromSomethingResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,36 +60,48 @@ protected function createFromCustomCreationMethod(
->getDataClass($class)
->methods;

$methodName = null;
$method = null;

foreach ($customCreationMethods as $customCreationMethod) {
if ($customCreationMethod->customCreationMethodType !== CustomCreationMethodType::Object) {
continue;
}

if (
$customCreationMethod->customCreationMethodType === CustomCreationMethodType::Object
&& $creationContext->ignoredMagicalMethods !== null
$creationContext->ignoredMagicalMethods !== null
&& in_array($customCreationMethod->name, $creationContext->ignoredMagicalMethods)
) {
continue;
}

if ($customCreationMethod->accepts(...$payloads)) {
$methodName = $customCreationMethod->name;
$method = $customCreationMethod;

break;
}
}

if ($methodName === null) {
if ($method === null) {
return null;
}

$pipeline = $this->dataConfig->getResolvedDataPipeline($class);

foreach ($payloads as $payload) {
if ($payload instanceof Request) {
// Solely for the purpose of validation
$pipeline->execute($payload, $creationContext);
}
}

foreach ($method->parameters as $index => $parameter) {
if ($parameter->isCreationContext) {
$payloads[$index] = $creationContext;
}
}

$methodName = $method->name;

return $class::$methodName(...$payloads);
}
}
4 changes: 4 additions & 0 deletions src/Support/DataMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ public function accepts(mixed ...$input): bool
? $this->parameters
: $this->parameters->mapWithKeys(fn (DataParameter|DataProperty $parameter) => [$parameter->name => $parameter]);

$parameters = $parameters->reject(
fn (DataParameter|DataProperty $parameter) => $parameter instanceof DataParameter && $parameter->isCreationContext
);

if (count($input) > $parameters->count()) {
return false;
}
Expand Down
9 changes: 8 additions & 1 deletion src/Support/DataParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace Spatie\LaravelData\Support;

use ReflectionParameter;
use Spatie\LaravelData\Support\Creation\CreationContext;
use Spatie\LaravelData\Support\Types\SingleType;
use Spatie\LaravelData\Support\Types\Type;

class DataParameter
Expand All @@ -13,6 +15,8 @@ public function __construct(
public readonly bool $hasDefaultValue,
public readonly mixed $defaultValue,
public readonly Type $type,
// TODO: would be better if we refactor this to type, together with Castable, Lazy, etc
public readonly bool $isCreationContext,
) {
}

Expand All @@ -22,12 +26,15 @@ public static function create(
): self {
$hasDefaultValue = $parameter->isDefaultValueAvailable();

$type = Type::forReflection($parameter->getType(), $class);

return new self(
$parameter->name,
$parameter->isPromoted(),
$hasDefaultValue,
$hasDefaultValue ? $parameter->getDefaultValue() : null,
Type::forReflection($parameter->getType(), $class),
$type,
$type instanceof SingleType && $type->type->name === CreationContext::class
);
}
}
94 changes: 0 additions & 94 deletions tests/CreationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -579,42 +579,6 @@ public function __construct(
->toEqual('json:+{"nested":{"string":"Hello"},"string":"world","casted":"json:"}');
});

it('can magically create a data object', function () {
$dataClass = new class ('', '') extends Data {
public function __construct(
public mixed $propertyA,
public mixed $propertyB,
) {
}

public static function fromStringWithDefault(string $a, string $b = 'World')
{
return new self($a, $b);
}

public static function fromIntsWithDefault(int $a, int $b)
{
return new self($a, $b);
}

public static function fromSimpleDara(SimpleData $data)
{
return new self($data->string, $data->string);
}

public static function fromData(Data $data)
{
return new self('data', json_encode($data));
}
};

expect($dataClass::from('Hello'))->toEqual(new $dataClass('Hello', 'World'))
->and($dataClass::from('Hello', 'World'))->toEqual(new $dataClass('Hello', 'World'))
->and($dataClass::from(42, 69))->toEqual(new $dataClass(42, 69))
->and($dataClass::from(SimpleData::from('Hello')))->toEqual(new $dataClass('Hello', 'Hello'))
->and($dataClass::from(new EnumData(DummyBackedEnum::FOO)))->toEqual(new $dataClass('data', '{"enum":"foo"}'));
});

it(
'will throw a custom exception when a data constructor cannot be called due to missing component',
function () {
Expand Down Expand Up @@ -762,32 +726,6 @@ public function __construct(
->toMatchArray($collectionA->toArray());
});

it('will use magic methods when creating a collection of data objects', function () {
$dataClass = new class ('') extends Data {
public function __construct(public string $otherString)
{
}

public static function fromSimpleData(SimpleData $simpleData): static
{
return new self($simpleData->string);
}
};

$collection = new DataCollection($dataClass::class, [
SimpleData::from('A'),
SimpleData::from('B'),
]);

expect($collection[0])
->toBeInstanceOf($dataClass::class)
->otherString->toEqual('A');

expect($collection[1])
->toBeInstanceOf($dataClass::class)
->otherString->toEqual('B');
});

it('can return a custom data collection when collecting data', function () {
$class = new class ('') extends Data implements DeprecatedDataContract {
use WithDeprecatedCollectionMethod;
Expand Down Expand Up @@ -823,38 +761,6 @@ public function __construct(public string $string)
expect($collection)->toBeInstanceOf(CustomPaginatedDataCollection::class);
});

it('can magically collect data', function () {
class TestSomeCustomCollection extends Collection
{
}

$dataClass = new class () extends Data {
public string $string;

public static function fromString(string $string): self
{
$s = new self();

$s->string = $string;

return $s;
}

public static function collectArray(array $items): \TestSomeCustomCollection
{
return new \TestSomeCustomCollection($items);
}
};

expect($dataClass::collect(['a', 'b', 'c']))
->toBeInstanceOf(\TestSomeCustomCollection::class)
->all()->toEqual([
$dataClass::from('a'),
$dataClass::from('b'),
$dataClass::from('c'),
]);
});

it('will allow a nested data object to cast properties however it wants', function () {
$model = new DummyModel(['id' => 10]);

Expand Down
Loading

0 comments on commit 1c1bde5

Please sign in to comment.