Skip to content
Open
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: 2 additions & 0 deletions src/Exception/PropertyNotAccessibleException.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ final class PropertyNotAccessibleException extends RuntimeException
public function __construct(
public readonly string $class,
public readonly string $property,
public readonly ?\Throwable $previous = null,
) {
parent::__construct(
message: \sprintf(self::MESSAGE, $class, $property),
previous: $previous,
);
}
}
28 changes: 25 additions & 3 deletions src/RouteProvider/PropertyAccess/PurgatoryPropertyAccessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Sofascore\PurgatoryBundle\RouteProvider\PropertyAccess;

use Sofascore\PurgatoryBundle\Exception\PropertyNotAccessibleException;
use Sofascore\PurgatoryBundle\Exception\ValueNotIterableException;
use Symfony\Component\PropertyAccess\Exception\AccessException;
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
Expand All @@ -25,17 +26,36 @@ public function __construct(
/**
* @param object|array<array-key, mixed> $objectOrArray
* @param string|PropertyPathInterface $propertyPath
*
* @throws PropertyNotAccessibleException
* @throws ValueNotIterableException
*/
public function getValue($objectOrArray, $propertyPath): mixed
{
if (!str_contains((string) $propertyPath, self::DELIMITER)) {
return $this->propertyAccessor->getValue($objectOrArray, $propertyPath);
try {
return $this->propertyAccessor->getValue($objectOrArray, $propertyPath);
} catch (\InvalidArgumentException|AccessException|UnexpectedTypeException $exception) {
throw new PropertyNotAccessibleException(
\is_array($objectOrArray) ? 'array' : $objectOrArray::class,
(string) $propertyPath,
$exception,
);
}
}

/** @var array{0: string, 1: string} $propertyPathParts */
$propertyPathParts = explode(separator: self::DELIMITER, string: (string) $propertyPath, limit: 2);

$collection = $this->propertyAccessor->getValue($objectOrArray, $propertyPathParts[0]);
try {
$collection = $this->propertyAccessor->getValue($objectOrArray, $propertyPathParts[0]);
} catch (\InvalidArgumentException|AccessException|UnexpectedTypeException $exception) {
throw new PropertyNotAccessibleException(
\is_array($objectOrArray) ? 'array' : $objectOrArray::class,
$propertyPathParts[0],
$exception,
);
}

if (!is_iterable($collection)) {
throw new ValueNotIterableException($collection, $propertyPathParts[0]);
Expand All @@ -60,6 +80,8 @@ public function getValue($objectOrArray, $propertyPath): mixed
/**
* @param object|array<array-key, mixed> $objectOrArray
* @param string|PropertyPathInterface $propertyPath
*
* @param-out object|array<array-key, mixed> $objectOrArray
*/
public function setValue(&$objectOrArray, $propertyPath, mixed $value): void
{
Expand Down Expand Up @@ -89,7 +111,7 @@ public function isReadable($objectOrArray, $propertyPath): bool
$this->getValue($objectOrArray, $propertyPath);

return true;
} catch (AccessException|UnexpectedTypeException) {
} catch (PropertyNotAccessibleException) {
return false;
}
}
Expand Down
1 change: 1 addition & 0 deletions tests/RouteProvider/PropertyAccess/Fixtures/Foo.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Foo
public function __construct(
public readonly int $id,
public readonly Collection $children,
private readonly ?string $privateProperty = 'value',
) {
}

Expand Down
190 changes: 190 additions & 0 deletions tests/RouteProvider/PropertyAccess/PurgatoryPropertyAccessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Sofascore\PurgatoryBundle\Exception\PropertyNotAccessibleException;
use Sofascore\PurgatoryBundle\Exception\ValueNotIterableException;
use Sofascore\PurgatoryBundle\RouteProvider\PropertyAccess\PurgatoryPropertyAccessor;
use Sofascore\PurgatoryBundle\Tests\RouteProvider\PropertyAccess\Fixtures\Foo;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;

#[CoversClass(PurgatoryPropertyAccessor::class)]
Expand Down Expand Up @@ -130,6 +132,60 @@ public static function traversableProvider(): iterable
];
}

public function testReadPathPropertyNotExist(): void
{
self::assertFalse($this->purgatoryPropertyAccessor->isReadable(
objectOrArray: new Foo(
id: 1,
children: new ArrayCollection([]),
),
propertyPath: 'nonExistentProperty',
));
}

public function testReadPathPropertyNotAccessible(): void
{
self::assertFalse($this->purgatoryPropertyAccessor->isReadable(
objectOrArray: new Foo(
id: 1,
children: new ArrayCollection([]),
),
propertyPath: 'privateProperty',
));
}

public function testReadTraversableChildPropertyNotExist(): void
{
self::assertFalse($this->purgatoryPropertyAccessor->isReadable(
objectOrArray: new Foo(
id: 1,
children: new ArrayCollection([
new Foo(
id: 1,
children: new ArrayCollection([]),
),
]),
),
propertyPath: 'children[*].nonExistentProperty',
));
}

public function testReadTraversableChildPropertyNotAccessible(): void
{
self::assertFalse($this->purgatoryPropertyAccessor->isReadable(
objectOrArray: new Foo(
id: 1,
children: new ArrayCollection([
new Foo(
id: 1,
children: new ArrayCollection([]),
),
]),
),
propertyPath: 'children[*].privateProperty',
));
}

public function testNotTraversable(): void
{
$this->expectException(ValueNotIterableException::class);
Expand All @@ -143,4 +199,138 @@ public function testNotTraversable(): void
propertyPath: 'id[*].id',
);
}

public function testPropertyNotAccessible(): void
{
$this->expectException(PropertyNotAccessibleException::class);
$this->expectExceptionObject(new PropertyNotAccessibleException(
class: Foo::class,
property: 'privateProperty',
previous: new NoSuchPropertyException(
message: 'Can\'t get a way to read the property "privateProperty" in class "Sofascore\PurgatoryBundle\Tests\RouteProvider\PropertyAccess\Fixtures\Foo".',
),
));

$this->purgatoryPropertyAccessor->getValue(
objectOrArray: new Foo(
id: 1,
children: new ArrayCollection([]),
),
propertyPath: 'privateProperty',
);
}

public function testTraversablePropertyNotAccessible(): void
{
$this->expectException(PropertyNotAccessibleException::class);
$this->expectExceptionObject(new PropertyNotAccessibleException(
class: Foo::class,
property: 'privateProperty',
previous: new NoSuchPropertyException(
message: 'Can\'t get a way to read the property "privateProperty" in class "Sofascore\PurgatoryBundle\Tests\RouteProvider\PropertyAccess\Fixtures\Foo".',
),
));

$this->purgatoryPropertyAccessor->getValue(
objectOrArray: new Foo(
id: 1,
children: new ArrayCollection([]),
),
propertyPath: 'privateProperty[*].values',
);
}

public function testTraversableChildPropertyNotAccessible(): void
{
$this->expectException(PropertyNotAccessibleException::class);
$this->expectExceptionObject(new PropertyNotAccessibleException(
class: Foo::class,
property: 'privateProperty',
previous: new NoSuchPropertyException(
message: 'Can\'t get a way to read the property "privateProperty" in class "Sofascore\PurgatoryBundle\Tests\RouteProvider\PropertyAccess\Fixtures\Foo".',
),
));

$this->purgatoryPropertyAccessor->getValue(
objectOrArray: new Foo(
id: 1,
children: new ArrayCollection(
[
new Foo(
id: 1,
children: new ArrayCollection([]),
),
],
),
),
propertyPath: 'children[*].privateProperty',
);
}

public function testPropertyNotExist(): void
{
$this->expectException(PropertyNotAccessibleException::class);
$this->expectExceptionObject(new PropertyNotAccessibleException(
class: Foo::class,
property: 'nonExistentProperty',
previous: new NoSuchPropertyException(
message: 'Can\'t get a way to read the property "nonExistentProperty" in class "Sofascore\PurgatoryBundle\Tests\RouteProvider\PropertyAccess\Fixtures\Foo".',
),
));

$this->purgatoryPropertyAccessor->getValue(
objectOrArray: new Foo(
id: 1,
children: new ArrayCollection([]),
),
propertyPath: 'nonExistentProperty',
);
}

public function testTraversablePropertyNotExist(): void
{
$this->expectException(PropertyNotAccessibleException::class);
$this->expectExceptionObject(new PropertyNotAccessibleException(
class: Foo::class,
property: 'nonExistentProperty',
previous: new NoSuchPropertyException(
message: 'Can\'t get a way to read the property "nonExistentProperty" in class "Sofascore\PurgatoryBundle\Tests\RouteProvider\PropertyAccess\Fixtures\Foo".',
),
));

$this->purgatoryPropertyAccessor->getValue(
objectOrArray: new Foo(
id: 1,
children: new ArrayCollection([]),
),
propertyPath: 'nonExistentProperty[*].values',
);
}

public function testTraversableChildPropertyNotExist(): void
{
$this->expectException(PropertyNotAccessibleException::class);
$this->expectExceptionObject(new PropertyNotAccessibleException(
class: Foo::class,
property: 'nonExistentProperty',
previous: new NoSuchPropertyException(
message: 'Can\'t get a way to read the property "nonExistentProperty" in class "Sofascore\PurgatoryBundle\Tests\RouteProvider\PropertyAccess\Fixtures\Foo".',
),
));

$this->purgatoryPropertyAccessor->getValue(
objectOrArray: new Foo(
id: 1,
children: new ArrayCollection(
[
new Foo(
id: 1,
children: new ArrayCollection([]),
),
],
),
),
propertyPath: 'children[*].nonExistentProperty',
);
}
}