Skip to content
Merged
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
13 changes: 10 additions & 3 deletions src/Attribute/ObjectType/Slots.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Pinto\Slots\Build;
use Pinto\Slots\Definition;
use Pinto\Slots\NoDefaultValue;
use Pinto\Slots\Origin;
use Pinto\Slots\RenameSlots;
use Pinto\Slots\Slot;
use Pinto\Slots\SlotList;
Expand Down Expand Up @@ -78,15 +79,20 @@ public function __construct(
$r = new \ReflectionClass($slot);
if ($r->implementsInterface(\UnitEnum::class)) {
foreach ($slot::cases() as $case) {
$this->slots->add(new Slot(name: $case));
$this->slots->add(new Slot(name: $case, origin: Origin\EnumCase::createFromEnum($case)));
}
}

// Otherwise skip if it's a regular class name.
continue;
}
$this->slots->add(
$slot instanceof Slot ? $slot : new Slot(name: $slot)
$slot instanceof Slot
? $slot
: new Slot(name: $slot, origin: match (true) {
\is_string($slot) => Origin\StaticallyDefined::create(data: $slot),
$slot instanceof \UnitEnum => Origin\EnumCase::createFromEnum($slot),
}),
);
}
}
Expand Down Expand Up @@ -163,7 +169,6 @@ public function getDefinition(ObjectListInterface $case, \Reflector $r): mixed
foreach ($parametersFrom->getParameters() as $rParam) {
$paramType = $rParam->getType();
if ($paramType instanceof \ReflectionNamedType || $paramType instanceof \ReflectionUnionType) {
// @todo use the type @ $paramType->getName()
$args = ['name' => $rParam->getName()];
// Default should only be set if there is a default.
if ($rParam->isDefaultValueAvailable()) {
Expand All @@ -174,6 +179,8 @@ public function getDefinition(ObjectListInterface $case, \Reflector $r): mixed
$args['fillValueFromThemeObjectClassPropertyWhenEmpty'] = $rParam->name;
}

$args['origin'] = Origin\Parameter::fromReflection($rParam);

$slots[] = new Slot(...$args);
}
}
Expand Down
35 changes: 35 additions & 0 deletions src/Slots/Origin/EnumCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Pinto\Slots\Origin;

/**
* @internal
*/
final class EnumCase
{
private function __construct(
private readonly string $className,
private readonly string $case,
) {
}

public static function createFromEnum(\UnitEnum $unitEnum): static
{
return new static(
className: $unitEnum::class,
case: $unitEnum->name
);
}

public function enumCase(): \UnitEnum
{
try {
// @phpstan-ignore return.type
return \constant($this->className . '::' . $this->case);
} catch (\Error) {
throw new \InvalidArgumentException($this->className . '::' . $this->case . ' does not exist.');
}
}
}
41 changes: 41 additions & 0 deletions src/Slots/Origin/Parameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Pinto\Slots\Origin;

/**
* @internal
*/
final class Parameter
{
private function __construct(
private readonly string $parameterName,
private readonly string $functionName,
private readonly string $className,
) {
}

public static function fromReflection(\ReflectionParameter $r): static
{
// Serialize it as best as possible.
// The data here is not infinitely storable, e.g is reset on container resets.
return new static(
parameterName: $r->getName(),
functionName: $r->getDeclaringFunction()->getName(),
className: $r->getDeclaringClass()?->getName() ?? throw new \LogicException('unhandled'),
);
}

public function parameterReflection(): \ReflectionParameter
{
try {
return new \ReflectionParameter(
[$this->className, $this->functionName],
$this->parameterName,
);
} catch (\ReflectionException|\InvalidArgumentException $e) {
throw new \InvalidArgumentException(previous: $e);
}
}
}
28 changes: 28 additions & 0 deletions src/Slots/Origin/StaticallyDefined.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Pinto\Slots\Origin;

/**
* @internal
*/
final class StaticallyDefined
{
public function __construct(
private ?string $data = null,
) {
}

public static function create(?string $data = null): static
{
return new static(
data: $data,
);
}

public function data(): ?string
{
return $this->data;
}
}
1 change: 1 addition & 0 deletions src/Slots/Slot.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ final class Slot

public function __construct(
public readonly \UnitEnum|string $name,
public readonly Origin\Parameter|Origin\StaticallyDefined|Origin\EnumCase $origin = new Origin\StaticallyDefined(),
string $useNamedParameters = self::useNamedParameters,
public readonly mixed $defaultValue = new NoDefaultValue(),
public readonly ?string $fillValueFromThemeObjectClassPropertyWhenEmpty = null,
Expand Down
69 changes: 69 additions & 0 deletions tests/PintoSlotsOriginsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Pinto\tests;

use PHPUnit\Framework\TestCase;
use Pinto\Slots\Origin;
use Pinto\tests\fixtures\Etc\SlotEnum;
use Pinto\tests\fixtures\Objects\Slots\PintoObjectSlotsBasic;

final class PintoSlotsOriginsTest extends TestCase
{
/**
* @see Origin\EnumCase
*/
public function testEnumCase(): void
{
$origin = Origin\EnumCase::createFromEnum(SlotEnum::Slot1);
static::assertEquals(SlotEnum::Slot1, $origin->enumCase());
}

/**
* Test a serialised origin, but since gone/renamed, etc.
*/
public function testEnumCaseNonExistent(): void
{
$constructor = (new \ReflectionClass(Origin\EnumCase::class))->getConstructor() ?? throw new \LogicException('impossible');
$origin = (new \ReflectionClass(Origin\EnumCase::class))->newInstanceWithoutConstructor();
$constructor->setAccessible(true);
$constructor->invokeArgs($origin, ['class name', 'case name']);

static::expectException(\InvalidArgumentException::class);
static::expectExceptionMessage('class name::case name does not exist');
$origin->enumCase();
}

/**
* @see Origin\StaticallyDefined
*/
public function testStaticallyDefined(): void
{
$origin = Origin\StaticallyDefined::create('foo');
static::assertEquals('foo', $origin->data());
}

/**
* @see Origin\StaticallyDefined
*/
public function testParameterReflection(): void
{
$origin = Origin\Parameter::fromReflection(new \ReflectionParameter([PintoObjectSlotsBasic::class, '__construct'], 'text'));
static::assertEquals('text', $origin->parameterReflection()->getName());
}

/**
* Test a serialised origin, but since gone/renamed, etc.
*/
public function testParameterReflectionNonExistent(): void
{
$constructor = (new \ReflectionClass(Origin\Parameter::class))->getConstructor() ?? throw new \LogicException('impossible');
$origin = (new \ReflectionClass(Origin\Parameter::class))->newInstanceWithoutConstructor();
$constructor->setAccessible(true);
$constructor->invokeArgs($origin, ['parameter name', '__construct', PintoObjectSlotsBasic::class]);

static::expectException(\InvalidArgumentException::class);
$origin->parameterReflection();
}
}
Loading
Loading