diff --git a/lib/SP/Domain/Common/Attributes/Hydratable.php b/lib/SP/Domain/Common/Attributes/Hydratable.php index 36cbe2e0..9828a233 100644 --- a/lib/SP/Domain/Common/Attributes/Hydratable.php +++ b/lib/SP/Domain/Common/Attributes/Hydratable.php @@ -36,7 +36,10 @@ final class Hydratable public function __construct(private readonly string $sourceProperty, array $targetClass) { - $this->targetClass = array_filter($targetClass, static fn(string $class) => class_exists($class)); + $this->targetClass = array_filter( + $targetClass, + static fn(string $class) => class_exists($class) || interface_exists($class) + ); } public function getTargetClass(): array diff --git a/lib/SP/Domain/Common/Models/SerializedModel.php b/lib/SP/Domain/Common/Models/SerializedModel.php index 9a1ead3b..05ae6495 100644 --- a/lib/SP/Domain/Common/Models/SerializedModel.php +++ b/lib/SP/Domain/Common/Models/SerializedModel.php @@ -46,8 +46,15 @@ trait SerializedModel /** @var Hydratable $instance */ $instance = $attribute->newInstance(); - if (in_array($class, $instance->getTargetClass()) && $this->{$instance->getSourceProperty()} !== null) { - return unserialize($this->{$instance->getSourceProperty()}, ['allowed_classes' => [$class]]) ?: null; + $valid = array_filter( + $instance->getTargetClass(), + static fn(string $targetClass) => is_a($class, $targetClass, true) + ); + + $property = $this->{$instance->getSourceProperty()}; + + if (count($valid) > 0 && $property !== null) { + return unserialize($property, ['allowed_classes' => [$class]]) ?: null; } } @@ -65,7 +72,12 @@ trait SerializedModel /** @var Hydratable $instance */ $instance = $attribute->newInstance(); - if (in_array($object::class, $instance->getTargetClass())) { + $valid = array_filter( + $instance->getTargetClass(), + static fn(string $targetClass) => is_a($object, $targetClass, true) + ); + + if (count($valid) > 0) { return $this->mutate([$instance->getSourceProperty() => serialize($object)]); } } diff --git a/lib/SP/Domain/Plugin/Models/PluginData.php b/lib/SP/Domain/Plugin/Models/PluginData.php index 8f2b9885..5b00e4c0 100644 --- a/lib/SP/Domain/Plugin/Models/PluginData.php +++ b/lib/SP/Domain/Plugin/Models/PluginData.php @@ -26,14 +26,17 @@ namespace SP\Domain\Plugin\Models; use SP\DataModel\EncryptedModel; use SP\Domain\Common\Attributes\Encryptable; +use SP\Domain\Common\Attributes\Hydratable; use SP\Domain\Common\Models\HydratableModel; use SP\Domain\Common\Models\Model; use SP\Domain\Common\Models\SerializedModel; +use SP\Domain\Plugin\Ports\PluginDataStorage; /** * Class PluginDataModel */ #[Encryptable('data', 'key')] +#[Hydratable('data', [PluginDataStorage::class])] final class PluginData extends Model implements HydratableModel { use SerializedModel; diff --git a/lib/SP/Domain/Plugin/Ports/PluginDataStorage.php b/lib/SP/Domain/Plugin/Ports/PluginDataStorage.php new file mode 100644 index 00000000..d2dcfb9c --- /dev/null +++ b/lib/SP/Domain/Plugin/Ports/PluginDataStorage.php @@ -0,0 +1,35 @@ +. + */ + +namespace SP\Domain\Plugin\Ports; + +/** + * Interface PluginDataStorage + * + * This interface must be implemented by all classes that are used to store plugin's data + */ +interface PluginDataStorage +{ + +} diff --git a/lib/SP/Domain/Plugin/Services/PluginOperation.php b/lib/SP/Domain/Plugin/Services/PluginOperation.php index ec95af07..11683bb9 100644 --- a/lib/SP/Domain/Plugin/Services/PluginOperation.php +++ b/lib/SP/Domain/Plugin/Services/PluginOperation.php @@ -110,7 +110,7 @@ final class PluginOperation implements PluginOperationInterface return $this->pluginDataService ->getByItemId($this->pluginName, $itemId) ->hydrate($class); - } catch (NoSuchItemException $e) { + } catch (NoSuchItemException) { return null; } } diff --git a/tests/SPT/Domain/Plugin/Services/PluginOperationTest.php b/tests/SPT/Domain/Plugin/Services/PluginOperationTest.php new file mode 100644 index 00000000..4036589d --- /dev/null +++ b/tests/SPT/Domain/Plugin/Services/PluginOperationTest.php @@ -0,0 +1,181 @@ +. + */ + +namespace SPT\Domain\Plugin\Services; + +use Defuse\Crypto\Exception\CryptoException; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\MockObject\MockObject; +use SP\Domain\Common\Services\ServiceException; +use SP\Domain\Core\Exceptions\ConstraintException; +use SP\Domain\Core\Exceptions\NoSuchPropertyException; +use SP\Domain\Core\Exceptions\QueryException; +use SP\Domain\Plugin\Models\PluginData; +use SP\Domain\Plugin\Ports\PluginDataService; +use SP\Domain\Plugin\Services\PluginOperation; +use SP\Infrastructure\Common\Repositories\NoSuchItemException; +use SP\Infrastructure\Database\QueryResult; +use SPT\Generators\PluginDataGenerator; +use SPT\Stubs\PluginDataStub; +use SPT\UnitaryTestCase; + +/** + * Class PluginOperationTest + */ +#[Group('unitary')] +class PluginOperationTest extends UnitaryTestCase +{ + + private PluginOperation $pluginOperation; + private PluginDataService|MockObject $pluginDataService; + + /** + * @throws ServiceException + * @throws ConstraintException + * @throws NoSuchPropertyException + * @throws CryptoException + * @throws QueryException + */ + public function testUpdate() + { + $pluginDataStorage = new PluginDataStub(100, 'test_data'); + + $this->pluginDataService + ->expects($this->once()) + ->method('update') + ->with( + self::callback(function (PluginData $current) use ($pluginDataStorage) { + return $current->getName() === 'test_plugin' + && $current->getItemId() === 100 + && $current->getData() === serialize($pluginDataStorage) + && $current->getKey() === null; + }) + ) + ->willReturn(1); + + $out = $this->pluginOperation->update(100, $pluginDataStorage); + + $this->assertEquals(1, $out); + } + + /** + * @throws ConstraintException + * @throws NoSuchItemException + * @throws QueryException + */ + public function testDelete() + { + $this->pluginDataService + ->expects($this->once()) + ->method('deleteByItemId') + ->with('test_plugin', 100); + + $this->pluginOperation->delete(100); + } + + /** + * @throws ServiceException + * @throws ConstraintException + * @throws NoSuchPropertyException + * @throws CryptoException + * @throws QueryException + */ + public function testCreate() + { + $pluginDataStorage = new PluginDataStub(100, 'test_data'); + + $queryResult = new QueryResult([]); + $this->pluginDataService + ->expects($this->once()) + ->method('create') + ->with( + self::callback(function (PluginData $current) use ($pluginDataStorage) { + return $current->getName() === 'test_plugin' + && $current->getItemId() === 100 + && $current->getData() === serialize($pluginDataStorage) + && $current->getKey() === null; + }) + ) + ->willReturn($queryResult->setLastId(10)); + + $out = $this->pluginOperation->create(100, $pluginDataStorage); + + $this->assertEquals(10, $out); + } + + /** + * @throws ServiceException + * @throws ConstraintException + * @throws NoSuchPropertyException + * @throws CryptoException + * @throws QueryException + */ + public function testGet() + { + $pluginDataStorage = new PluginDataStub(100, 'test_data'); + + $pluginData = PluginDataGenerator::factory() + ->buildPluginData() + ->mutate(['data' => serialize($pluginDataStorage)]); + + $this->pluginDataService + ->expects($this->once()) + ->method('getByItemId') + ->with('test_plugin', 100) + ->willReturn($pluginData); + + $out = $this->pluginOperation->get(100, PluginDataStub::class); + + $this->assertEquals($pluginDataStorage, $out); + } + + /** + * @throws ServiceException + * @throws ConstraintException + * @throws NoSuchPropertyException + * @throws CryptoException + * @throws QueryException + */ + public function testGetWithNoItem() + { + $this->pluginDataService + ->expects($this->once()) + ->method('getByItemId') + ->with('test_plugin', 100) + ->willThrowException(NoSuchItemException::error('test')); + + $out = $this->pluginOperation->get(100, PluginDataStub::class); + + $this->assertNull($out); + } + + protected function setUp(): void + { + parent::setUp(); + + $this->pluginDataService = $this->createMock(PluginDataService::class); + + $this->pluginOperation = new PluginOperation($this->pluginDataService, 'test_plugin'); + } +} diff --git a/tests/SPT/Stubs/PluginDataStub.php b/tests/SPT/Stubs/PluginDataStub.php new file mode 100644 index 00000000..8b299241 --- /dev/null +++ b/tests/SPT/Stubs/PluginDataStub.php @@ -0,0 +1,47 @@ +. + */ + +namespace SPT\Stubs; + +use SP\Domain\Plugin\Ports\PluginDataStorage; + +/** + * Class PluginDataStub + */ +final class PluginDataStub implements PluginDataStorage +{ + public function __construct(private readonly int $id, private readonly string $name) + { + } + + public function getId(): int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } +}