Skip to content

Commit

Permalink
Proxy for writing and reading object properties.
Browse files Browse the repository at this point in the history
  • Loading branch information
Smoren committed Jun 16, 2023
1 parent 529e957 commit b8db069
Show file tree
Hide file tree
Showing 23 changed files with 308 additions and 108 deletions.
10 changes: 8 additions & 2 deletions src/Components/NestedAccessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Smoren\Schemator\Exceptions\PathNotWritableException;
use Smoren\Schemator\Helpers\ContainerAccessHelper;
use Smoren\Schemator\Interfaces\NestedAccessorInterface;
use Smoren\Schemator\Interfaces\ProxyInterface;

/**
* @implements NestedAccessorInterface<string|string[]|null>
Expand Down Expand Up @@ -86,7 +87,12 @@ public function get($path = null, bool $strict = true)
public function set($path, $value): self
{
$source = &$this->getRef($this->getPathStack($path));
$source = $value;

if ($source instanceof ProxyInterface) {
$source->setValue($value);
} else {
$source = $value;
}

return $this;
}
Expand Down Expand Up @@ -202,7 +208,7 @@ protected function cutPathTail($path): array
*
* @param string[] $pathStack
*
* @return mixed
* @return mixed|ProxyInterface<object>
*
* @throws PathNotWritableException when path is not writable.
*/
Expand Down
6 changes: 4 additions & 2 deletions src/Helpers/ContainerAccessHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Smoren\Schemator\Helpers;

use ArrayAccess;
use Smoren\Schemator\Interfaces\ProxyInterface;
use Smoren\Schemator\Structs\ObjectPropertyProxy;
use stdClass;

/**
Expand Down Expand Up @@ -51,7 +53,7 @@ public static function get($container, $key, $defaultValue = null)
* @param TKey $key
* @param TValue|null $defaultValue
*
* @return TValue|null
* @return TValue|ProxyInterface<TValue>|null
*
* @throws \InvalidArgumentException
*/
Expand Down Expand Up @@ -286,7 +288,7 @@ protected static function getFromObject(object $container, $key, $defaultValue)
* @param TKey $key
* @param TValue|null $defaultValue
*
* @return TValue|null
* @return TValue|ProxyInterface<TValue>|null
*
* @throws \InvalidArgumentException
*/
Expand Down
8 changes: 5 additions & 3 deletions src/Helpers/ObjectAccessHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use ReflectionMethod;
use ReflectionProperty;
use Smoren\Schemator\Interfaces\ProxyInterface;
use Smoren\Schemator\Structs\ObjectPropertyProxy;
use stdClass;

/**
Expand Down Expand Up @@ -46,7 +48,7 @@ public static function getPropertyValue(object $object, string $propertyName)
* @param string $propertyName
* @param mixed $defaultValue
*
* @return mixed
* @return mixed|ProxyInterface<object>
*
* @throws \InvalidArgumentException
*/
Expand All @@ -61,8 +63,8 @@ public static function &getPropertyRef(object &$object, string $propertyName, $d
return $object->{$propertyName};
}

$className = get_class($object);
throw new \InvalidArgumentException("Property '{$className}::{$propertyName}' is not readable");
$proxy = new ObjectPropertyProxy($object, $propertyName);
return $proxy;
}

/**
Expand Down
20 changes: 20 additions & 0 deletions src/Interfaces/ProxyInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Smoren\Schemator\Interfaces;

/**
* @template T
*/
interface ProxyInterface
{
/**
* @return mixed
*/
public function getValue();

/**
* @param mixed $value
* @return void
*/
public function setValue($value): void;
}
54 changes: 54 additions & 0 deletions src/Structs/ObjectPropertyProxy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Smoren\Schemator\Structs;

use Smoren\Schemator\Helpers\ObjectAccessHelper;
use Smoren\Schemator\Interfaces\ProxyInterface;

/**
* @template T of object
* @implements ProxyInterface<T>
*/
class ObjectPropertyProxy implements ProxyInterface
{
/**
* @var T
*/
protected object $object;
protected string $propertyName;

/**
* @param T $object
* @param string $propertyName
*/
public function __construct(object $object, string $propertyName)
{
$this->object = $object;
$this->propertyName = $propertyName;
}

/**
* @return mixed
*/
public function getValue()
{
if (!ObjectAccessHelper::hasReadableProperty($this->object, $this->propertyName)) {
$className = get_class($this->object);
throw new \BadMethodCallException("Property '{$className}::{$this->propertyName}' is not readable");
}
return ObjectAccessHelper::getPropertyValue($this->object, $this->propertyName);
}

/**
* @param mixed $value
* @return void
*/
public function setValue($value): void
{
if (!ObjectAccessHelper::hasWritableProperty($this->object, $this->propertyName)) {
$className = get_class($this->object);
throw new \BadMethodCallException("Property '{$className}::{$this->propertyName}' is not writable");
}
ObjectAccessHelper::setPropertyValue($this->object, $this->propertyName, $value);
}
}
12 changes: 6 additions & 6 deletions tests/unit/ContainerAccessHelper/DeleteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,16 @@ public function errorDataProvider(): array
[new ClassWithAccessibleProperties(), 'unknownProperty'],
[new ClassWithAccessibleProperties(), 'publicProperty'],
[new ClassWithAccessibleProperties(), 'publicProperty'],
[new ClassWithAccessibleProperties(), 'publicPropertyWithGetterAccess'],
[new ClassWithAccessibleProperties(), 'publicPropertyWithGetterAccess'],
[new ClassWithAccessibleProperties(), 'publicPropertyWithMethodsAccess'],
[new ClassWithAccessibleProperties(), 'publicPropertyWithMethodsAccess'],
[new ClassWithAccessibleProperties(), 'protectedProperty'],
[new ClassWithAccessibleProperties(), 'protectedProperty'],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithGetterAccess'],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithGetterAccess'],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithMethodsAccess'],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithMethodsAccess'],
[new ClassWithAccessibleProperties(), 'privateProperty'],
[new ClassWithAccessibleProperties(), 'privateProperty'],
[new ClassWithAccessibleProperties(), 'privatePropertyWithGetterAccess'],
[new ClassWithAccessibleProperties(), 'privatePropertyWithGetterAccess'],
[new ClassWithAccessibleProperties(), 'privatePropertyWithMethodsAccess'],
[new ClassWithAccessibleProperties(), 'privatePropertyWithMethodsAccess'],
];
}

Expand Down
6 changes: 3 additions & 3 deletions tests/unit/ContainerAccessHelper/ExistsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,11 @@ public function objectDataProvider(): array
[new ClassWithAccessibleProperties(), '0', false],
[new ClassWithAccessibleProperties(), 'unknownProperty', false],
[new ClassWithAccessibleProperties(), 'publicProperty', true],
[new ClassWithAccessibleProperties(), 'publicPropertyWithGetterAccess', true],
[new ClassWithAccessibleProperties(), 'publicPropertyWithMethodsAccess', true],
[new ClassWithAccessibleProperties(), 'protectedProperty', false],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithGetterAccess', true],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithMethodsAccess', true],
[new ClassWithAccessibleProperties(), 'privateProperty', false],
[new ClassWithAccessibleProperties(), 'privatePropertyWithGetterAccess', true],
[new ClassWithAccessibleProperties(), 'privatePropertyWithMethodsAccess', true],
];
}

Expand Down
4 changes: 2 additions & 2 deletions tests/unit/ContainerAccessHelper/GetRefTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ public function fromObjectDataProvider(): array
return [
[new ClassWithAccessibleProperties(), 'publicProperty', null, 1],
[new ClassWithAccessibleProperties(), 'publicProperty', 42, 1],
[new ClassWithAccessibleProperties(), 'publicPropertyWithGetterAccess', null, 2],
[new ClassWithAccessibleProperties(), 'publicPropertyWithGetterAccess', 42, 2],
[new ClassWithAccessibleProperties(), 'publicPropertyWithMethodsAccess', null, 2],
[new ClassWithAccessibleProperties(), 'publicPropertyWithMethodsAccess', 42, 2],
];
}

Expand Down
12 changes: 6 additions & 6 deletions tests/unit/ContainerAccessHelper/GetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,16 +166,16 @@ public function fromObjectDataProvider(): array
[new ClassWithAccessibleProperties(), 'unknownProperty', 42, 42],
[new ClassWithAccessibleProperties(), 'publicProperty', null, 1],
[new ClassWithAccessibleProperties(), 'publicProperty', 42, 1],
[new ClassWithAccessibleProperties(), 'publicPropertyWithGetterAccess', null, 2],
[new ClassWithAccessibleProperties(), 'publicPropertyWithGetterAccess', 42, 2],
[new ClassWithAccessibleProperties(), 'publicPropertyWithMethodsAccess', null, 2],
[new ClassWithAccessibleProperties(), 'publicPropertyWithMethodsAccess', 42, 2],
[new ClassWithAccessibleProperties(), 'protectedProperty', null, null],
[new ClassWithAccessibleProperties(), 'protectedProperty', 42, 42],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithGetterAccess', null, 4],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithGetterAccess', 42, 4],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithMethodsAccess', null, 4],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithMethodsAccess', 42, 4],
[new ClassWithAccessibleProperties(), 'privateProperty', null, null],
[new ClassWithAccessibleProperties(), 'privateProperty', 42, 42],
[new ClassWithAccessibleProperties(), 'privatePropertyWithGetterAccess', null, 6],
[new ClassWithAccessibleProperties(), 'privatePropertyWithGetterAccess', 42, 6],
[new ClassWithAccessibleProperties(), 'privatePropertyWithMethodsAccess', null, 6],
[new ClassWithAccessibleProperties(), 'privatePropertyWithMethodsAccess', 42, 6],
];
}

Expand Down
6 changes: 3 additions & 3 deletions tests/unit/ContainerAccessHelper/SetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,9 @@ public function toObjectDataProvider(): array
{
return [
[new ClassWithAccessibleProperties(), 'publicProperty', 42],
[new ClassWithAccessibleProperties(), 'publicPropertyWithGetterAccess', 42],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithGetterAccess', 42],
[new ClassWithAccessibleProperties(), 'privatePropertyWithGetterAccess', 42],
[new ClassWithAccessibleProperties(), 'publicPropertyWithMethodsAccess', 42],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithMethodsAccess', 42],
[new ClassWithAccessibleProperties(), 'privatePropertyWithMethodsAccess', 42],
];
}

Expand Down
48 changes: 33 additions & 15 deletions tests/unit/Fixtures/ClassWithAccessibleProperties.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,59 @@

class ClassWithAccessibleProperties
{
/**
* @var int
*/
public int $publicProperty = 1;
public int $publicPropertyWithGetterAccess = 2;
/**
* @var int
*/
protected int $protectedProperty = 3;
protected int $protectedPropertyWithGetterAccess = 4;
/**
* @var int
*/
private int $privateProperty = 5;
private int $privatePropertyWithGetterAccess = 6;
/**
* @var int
*/
public int $publicPropertyWithMethodsAccess = 2;
/**
* @var int
*/
protected int $protectedPropertyWithMethodsAccess = 4;
/**
* @var int
*/
private int $privatePropertyWithMethodsAccess = 6;

public function getPublicPropertyWithGetterAccess(): int
public function getPublicPropertyWithMethodsAccess(): int
{
return $this->publicPropertyWithGetterAccess;
return $this->publicPropertyWithMethodsAccess;
}

public function setPublicPropertyWithGetterAccess(int $value): void
public function setPublicPropertyWithMethodsAccess(int $value): void
{
$this->publicPropertyWithGetterAccess = $value;
$this->publicPropertyWithMethodsAccess = $value;
}

public function getProtectedPropertyWithGetterAccess(): int
public function getProtectedPropertyWithMethodsAccess(): int
{
return $this->protectedPropertyWithGetterAccess;
return $this->protectedPropertyWithMethodsAccess;
}

public function setProtectedPropertyWithGetterAccess(int $value): void
public function setProtectedPropertyWithMethodsAccess(int $value): void
{
$this->protectedPropertyWithGetterAccess = $value;
$this->protectedPropertyWithMethodsAccess = $value;
}

public function getPrivatePropertyWithGetterAccess(): int
public function getPrivatePropertyWithMethodsAccess(): int
{
return $this->privatePropertyWithGetterAccess;
return $this->privatePropertyWithMethodsAccess;
}

public function setPrivatePropertyWithGetterAccess(int $value): void
public function setPrivatePropertyWithMethodsAccess(int $value): void
{
$this->privatePropertyWithGetterAccess = $value;
$this->privatePropertyWithMethodsAccess = $value;
}

protected function protectedMethod(): int
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/NestedAccessor/NestedAccessorDeleteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,9 @@ public function dataProviderForNotWritableError(): array
{
return [
[new ClassWithAccessibleProperties(), 'publicProperty'],
[new ClassWithAccessibleProperties(), 'publicPropertyWithGetterAccess'],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithGetterAccess'],
[new ClassWithAccessibleProperties(), 'privatePropertyWithGetterAccess'],
[new ClassWithAccessibleProperties(), 'publicPropertyWithMethodsAccess'],
[new ClassWithAccessibleProperties(), 'protectedPropertyWithMethodsAccess'],
[new ClassWithAccessibleProperties(), 'privatePropertyWithMethodsAccess'],
];
}
}
37 changes: 37 additions & 0 deletions tests/unit/NestedAccessor/NestedAccessorSetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Smoren\Schemator\Tests\Unit\NestedAccessor;

use Smoren\Schemator\Components\NestedAccessor;
use Smoren\Schemator\Tests\Unit\Fixtures\ClassWithAccessibleProperties;

class NestedAccessorSetTest extends \Codeception\Test\Unit
{
Expand Down Expand Up @@ -223,4 +224,40 @@ public function dataProviderForStdClass(): array
],
];
}

/**
* @dataProvider dataProviderForObject
*/
public function testObject($source, $path, $value)
{
// Given
$accessor = new NestedAccessor($source);

// When
$accessor->set($path, $value);

// Then
$this->assertEquals($value, $accessor->get($path));
}

public function dataProviderForObject(): array
{
return [
[
new ClassWithAccessibleProperties(),
'protectedPropertyWithMethodsAccess',
22,
],
[
['a' => new ClassWithAccessibleProperties()],
'a.protectedPropertyWithMethodsAccess',
23,
],
[
['a' => new ClassWithAccessibleProperties()],
'a.privatePropertyWithMethodsAccess',
24,
],
];
}
}
Loading

0 comments on commit b8db069

Please sign in to comment.