From 62df3b49b1b489864c918741fe0e5e3e9c284e36 Mon Sep 17 00:00:00 2001 From: Andrei Shapiro Date: Thu, 16 Dec 2021 08:47:56 +0000 Subject: [PATCH 01/11] feat: make events serializable and provide parent classes --- core/data/event/ClassDeletedEvent.php | 27 +++++++++++++-- core/data/event/ResourceDeleted.php | 46 +++++++++++++++++--------- core/kernel/classes/class.Class.php | 3 +- core/kernel/classes/class.Resource.php | 9 ++++- 4 files changed, 64 insertions(+), 21 deletions(-) diff --git a/core/data/event/ClassDeletedEvent.php b/core/data/event/ClassDeletedEvent.php index 1289acea6..78c9945b3 100644 --- a/core/data/event/ClassDeletedEvent.php +++ b/core/data/event/ClassDeletedEvent.php @@ -22,17 +22,22 @@ namespace oat\generis\model\data\event; -use core_kernel_classes_Class; +use JsonSerializable; use oat\oatbox\event\Event; +use core_kernel_classes_Class; -class ClassDeletedEvent implements Event +class ClassDeletedEvent implements Event, JsonSerializable { /** @var core_kernel_classes_Class */ private $class; - public function __construct(core_kernel_classes_Class $class) + /** @var core_kernel_classes_Class|null */ + private $parentClass; + + public function __construct(core_kernel_classes_Class $class, core_kernel_classes_Class $parentClass = null) { $this->class = $class; + $this->parentClass = $parentClass; } public function getName(): string @@ -44,4 +49,20 @@ public function getClass(): core_kernel_classes_Class { return $this->class; } + + public function jsonSerialize(): array + { + $data = [ + 'uri' => $this->class->getUri(), + ]; + + if ($this->parentClass !== null) { + $data['class'] = [ + 'uri' => $this->parentClass->getUri(), + 'label' => $this->parentClass->getLabel(), + ]; + } + + return $data; + } } diff --git a/core/data/event/ResourceDeleted.php b/core/data/event/ResourceDeleted.php index d2ac8a06b..60987de49 100755 --- a/core/data/event/ResourceDeleted.php +++ b/core/data/event/ResourceDeleted.php @@ -15,46 +15,60 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * Copyright (c) 2018 (original work) Open Assessment Technologies SA; - * - * + * Copyright (c) 2018-2021 (original work) Open Assessment Technologies SA. */ +declare(strict_types=1); + namespace oat\generis\model\data\event; +use JsonSerializable; use oat\oatbox\event\Event; +use core_kernel_classes_Resource; -/** - * Class ResourceDeleted - * @package oat\generis\model\data\event - */ -class ResourceDeleted implements Event +class ResourceDeleted implements Event, JsonSerializable { + /** @var string */ private $uri; + /** @var core_kernel_classes_Resource|null */ + private $parentClass; + /** * @param string $uri */ - function __construct($uri) + function __construct($uri, core_kernel_classes_Resource $parentClass = null) { $this->uri = $uri; + $this->parentClass = $parentClass; } - /** - * Return the URI of the deleted resource - * @return string - */ - function getId() + function getId(): string { return $this->uri; } /** - * (non-PHPdoc) - * @see \oat\oatbox\event\Event::getName() + * {@inheritdoc} */ function getName() { return __CLASS__; } + + public function jsonSerialize(): array + { + $data = [ + 'uri' => $this->uri, + ]; + + if ($this->parentClass !== null) { + $data['class'] = [ + 'uri' => $this->parentClass->getUri(), + 'label' => $this->parentClass->getLabel(), + ]; + } + + return $data; + } } diff --git a/core/kernel/classes/class.Class.php b/core/kernel/classes/class.Class.php index 9b29a0266..ec34480bc 100644 --- a/core/kernel/classes/class.Class.php +++ b/core/kernel/classes/class.Class.php @@ -434,10 +434,11 @@ public function deleteInstances($resources, $deleteReference = false) */ public function delete($deleteReference = false) { + $parentClass = array_values($this->getParentClasses())[0] ?? null; $delete = (bool)$this->getImplementation()->delete($this, $deleteReference); if ($delete) { - $this->getEventManager()->trigger(new ClassDeletedEvent($this)); + $this->getEventManager()->trigger(new ClassDeletedEvent($this, $parentClass)); } return $delete; diff --git a/core/kernel/classes/class.Resource.php b/core/kernel/classes/class.Resource.php index 3007d5c8d..ca4260ecd 100644 --- a/core/kernel/classes/class.Resource.php +++ b/core/kernel/classes/class.Resource.php @@ -578,9 +578,16 @@ public function duplicate($excludedProperties = []) */ public function delete($deleteReference = false) { + try { + $parentClass = $this->getOnePropertyValue($this->getProperty(OntologyRdf::RDF_TYPE)); + } catch (Throwable $exception) { + $parentClass = null; + } + $returnValue = $this->getImplementation()->delete($this, $deleteReference); $eventManager = $this->getServiceManager()->get(EventManager::SERVICE_ID); - $eventManager->trigger(new ResourceDeleted($this->getUri())); + $eventManager->trigger(new ResourceDeleted($this->getUri(), $parentClass)); + return (bool) $returnValue; } From e80e3955aa00ca2afc2849be19139a287b1d0551 Mon Sep 17 00:00:00 2001 From: Andrei Shapiro Date: Thu, 16 Dec 2021 09:25:19 +0000 Subject: [PATCH 02/11] refactor: use current php-function to receive the element --- core/kernel/classes/class.Class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/kernel/classes/class.Class.php b/core/kernel/classes/class.Class.php index ec34480bc..aad49b945 100644 --- a/core/kernel/classes/class.Class.php +++ b/core/kernel/classes/class.Class.php @@ -434,7 +434,7 @@ public function deleteInstances($resources, $deleteReference = false) */ public function delete($deleteReference = false) { - $parentClass = array_values($this->getParentClasses())[0] ?? null; + $parentClass = current($this->getParentClasses()) ?: null; $delete = (bool)$this->getImplementation()->delete($this, $deleteReference); if ($delete) { From 135d13a474170d26bc6e45599b6d86694ab899bb Mon Sep 17 00:00:00 2001 From: Andrei Shapiro Date: Thu, 16 Dec 2021 21:45:35 +0000 Subject: [PATCH 03/11] feat: deprecate resource and class deleters, add setters to events --- core/data/event/ClassDeletedEvent.php | 29 ++++++++++++++++++++++--- core/data/event/ResourceDeleted.php | 30 +++++++++++++++++++++++--- core/kernel/classes/class.Class.php | 12 +++++------ core/kernel/classes/class.Resource.php | 19 +++++++--------- 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/core/data/event/ClassDeletedEvent.php b/core/data/event/ClassDeletedEvent.php index 78c9945b3..c836615d3 100644 --- a/core/data/event/ClassDeletedEvent.php +++ b/core/data/event/ClassDeletedEvent.php @@ -31,13 +31,15 @@ class ClassDeletedEvent implements Event, JsonSerializable /** @var core_kernel_classes_Class */ private $class; + /** @var core_kernel_classes_Class|null */ + private $selectedClass; + /** @var core_kernel_classes_Class|null */ private $parentClass; - public function __construct(core_kernel_classes_Class $class, core_kernel_classes_Class $parentClass = null) + public function __construct(core_kernel_classes_Class $class) { $this->class = $class; - $this->parentClass = $parentClass; } public function getName(): string @@ -50,14 +52,35 @@ public function getClass(): core_kernel_classes_Class return $this->class; } + public function setSelectedClass(?core_kernel_classes_Class $class): self + { + $this->selectedClass = $class; + + return $this; + } + + public function setParentClass(?core_kernel_classes_Class $class): self + { + $this->parentClass = $class; + + return $this; + } + public function jsonSerialize(): array { $data = [ 'uri' => $this->class->getUri(), ]; + if ($this->selectedClass !== null && $this->selectedClass !== $this->class) { + $data['selected'] = [ + 'uri' => $this->selectedClass->getUri(), + 'label' => $this->selectedClass->getLabel(), + ]; + } + if ($this->parentClass !== null) { - $data['class'] = [ + $data['parent'] = [ 'uri' => $this->parentClass->getUri(), 'label' => $this->parentClass->getLabel(), ]; diff --git a/core/data/event/ResourceDeleted.php b/core/data/event/ResourceDeleted.php index 60987de49..dfe1b2d53 100755 --- a/core/data/event/ResourceDeleted.php +++ b/core/data/event/ResourceDeleted.php @@ -24,6 +24,7 @@ use JsonSerializable; use oat\oatbox\event\Event; +use core_kernel_classes_Class; use core_kernel_classes_Resource; class ResourceDeleted implements Event, JsonSerializable @@ -31,16 +32,18 @@ class ResourceDeleted implements Event, JsonSerializable /** @var string */ private $uri; + /** @var core_kernel_classes_Class|null */ + private $selectedClass; + /** @var core_kernel_classes_Resource|null */ private $parentClass; /** * @param string $uri */ - function __construct($uri, core_kernel_classes_Resource $parentClass = null) + function __construct($uri) { $this->uri = $uri; - $this->parentClass = $parentClass; } function getId(): string @@ -56,14 +59,35 @@ function getName() return __CLASS__; } + public function setSelectedClass(?core_kernel_classes_Class $class): self + { + $this->selectedClass = $class; + + return $this; + } + + public function setParentClass(?core_kernel_classes_Class $class): self + { + $this->parentClass = $class; + + return $this; + } + public function jsonSerialize(): array { $data = [ 'uri' => $this->uri, ]; + if ($this->selectedClass !== null) { + $data['selected'] = [ + 'uri' => $this->selectedClass->getUri(), + 'label' => $this->selectedClass->getLabel(), + ]; + } + if ($this->parentClass !== null) { - $data['class'] = [ + $data['parent'] = [ 'uri' => $this->parentClass->getUri(), 'label' => $this->parentClass->getLabel(), ]; diff --git a/core/kernel/classes/class.Class.php b/core/kernel/classes/class.Class.php index aad49b945..f6f8e8f5c 100644 --- a/core/kernel/classes/class.Class.php +++ b/core/kernel/classes/class.Class.php @@ -425,20 +425,20 @@ public function deleteInstances($resources, $deleteReference = false) } /** - * Short description of method delete + * @deprecated Use \oat\tao\model\resources\Repository\ClassRepository::delete() instead * - * @access public * @author Jerome Bogaerts, - * @param boolean deleteReference - * @return boolean + * + * @param bool deleteReference + * + * @return bool */ public function delete($deleteReference = false) { - $parentClass = current($this->getParentClasses()) ?: null; $delete = (bool)$this->getImplementation()->delete($this, $deleteReference); if ($delete) { - $this->getEventManager()->trigger(new ClassDeletedEvent($this, $parentClass)); + $this->getEventManager()->trigger(new ClassDeletedEvent($this)); } return $delete; diff --git a/core/kernel/classes/class.Resource.php b/core/kernel/classes/class.Resource.php index ca4260ecd..c4e441bff 100644 --- a/core/kernel/classes/class.Resource.php +++ b/core/kernel/classes/class.Resource.php @@ -569,24 +569,21 @@ public function duplicate($excludedProperties = []) } /** - * remove any assignation made to this resource, the uri is consequently + * Remove any assignation made to this resource, the uri is consequently + * + * @deprecated Use \oat\tao\model\resources\Repository\ResourceRepository::delete() instead * - * @access public * @author patrick.plichart@tudor.lu - * @param boolean deleteReference set deleteRefence to true when you need that all reference to this resource are removed. - * @return boolean + * + * @param bool deleteReference set deleteReference to true when you need that all reference to this resource are removed. + * + * @return bool */ public function delete($deleteReference = false) { - try { - $parentClass = $this->getOnePropertyValue($this->getProperty(OntologyRdf::RDF_TYPE)); - } catch (Throwable $exception) { - $parentClass = null; - } - $returnValue = $this->getImplementation()->delete($this, $deleteReference); $eventManager = $this->getServiceManager()->get(EventManager::SERVICE_ID); - $eventManager->trigger(new ResourceDeleted($this->getUri(), $parentClass)); + $eventManager->trigger(new ResourceDeleted($this->getUri())); return (bool) $returnValue; } From 2f853dc40d863db798ba563145b5fafb59865e48 Mon Sep 17 00:00:00 2001 From: Gabriel Felipe Soares Date: Fri, 17 Dec 2021 06:49:23 +0100 Subject: [PATCH 04/11] feat: add request/response to container and improve container::has method --- composer.json | 2 +- .../ContainerServiceProvider.php | 14 ++++++++++ .../ContainerServiceProviderInterface.php | 2 ++ .../LegacyServiceGateway.php | 8 +++++- core/DependencyInjection/README.md | 28 ++++--------------- .../LegacyServiceGatewayTest.php | 10 +++++++ 6 files changed, 39 insertions(+), 25 deletions(-) diff --git a/composer.json b/composer.json index c3746bed8..62ad8e1c7 100755 --- a/composer.json +++ b/composer.json @@ -72,7 +72,7 @@ "symfony/config":"5.3.*", "psr/log": "~1.0", "psr/cache": "~1.0", - "psr/container": "^1.0.0", + "psr/container": "^1.1.1", "psr/simple-cache" : "^1.0.1", "ramsey/uuid": "^3.8", "composer-runtime-api": "^2.0" diff --git a/core/DependencyInjection/ContainerServiceProvider.php b/core/DependencyInjection/ContainerServiceProvider.php index c7507c1d9..fda216d9b 100644 --- a/core/DependencyInjection/ContainerServiceProvider.php +++ b/core/DependencyInjection/ContainerServiceProvider.php @@ -22,6 +22,10 @@ namespace oat\generis\model\DependencyInjection; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; class ContainerServiceProvider implements ContainerServiceProviderInterface @@ -30,6 +34,16 @@ public function __invoke(ContainerConfigurator $configurator): void { $services = $configurator->services(); + /** + * @TODO This needs to be override in the scope of middleware task [ADF-731] + */ + $services->set(ServerRequestInterface::class, ServerRequestInterface::class) + ->public() + ->factory(ServerRequest::class . '::fromGlobals'); + + $services->set(ResponseInterface::class, Response::class) + ->public(); + $services->set(LegacyServiceGateway::class, LegacyServiceGateway::class); } } diff --git a/core/DependencyInjection/ContainerServiceProviderInterface.php b/core/DependencyInjection/ContainerServiceProviderInterface.php index 5a161a357..dc2a65b4c 100644 --- a/core/DependencyInjection/ContainerServiceProviderInterface.php +++ b/core/DependencyInjection/ContainerServiceProviderInterface.php @@ -26,5 +26,7 @@ interface ContainerServiceProviderInterface { + public const CONTAINER_SERVICE_ID = 'service_container'; + public function __invoke(ContainerConfigurator $configurator): void; } diff --git a/core/DependencyInjection/LegacyServiceGateway.php b/core/DependencyInjection/LegacyServiceGateway.php index 71b005f29..79ef8fbad 100644 --- a/core/DependencyInjection/LegacyServiceGateway.php +++ b/core/DependencyInjection/LegacyServiceGateway.php @@ -22,6 +22,7 @@ namespace oat\generis\model\DependencyInjection; +use oat\oatbox\service\ConfigurableService; use oat\oatbox\service\ServiceManager; use Psr\Container\ContainerInterface; @@ -53,6 +54,11 @@ public function get($id) */ public function has($id) { - return $this->serviceManager->has($id); + return $this->serviceManager->has($id) || $this->isConfigurableService($id); + } + + private function isConfigurableService($id): bool + { + return class_exists($id) && is_subclass_of($id, ConfigurableService::class); } } diff --git a/core/DependencyInjection/README.md b/core/DependencyInjection/README.md index 7213ba3b2..cf2aabb7c 100644 --- a/core/DependencyInjection/README.md +++ b/core/DependencyInjection/README.md @@ -57,6 +57,10 @@ return [ ] ]; ``` +## Actions/Controllers and DI container + +You can find the documentation in **tao-core extension** on +the [routing section](https://github.com/oat-sa/tao-core/tree/master/models/classes/routing). ## How is the container started? @@ -68,29 +72,7 @@ use oat\generis\model\DependencyInjection\ContainerStarter; $container = (new ContainerStarter(ServiceManager::getServiceManager()))->getContainer(); ``` - -**IMPORTANT**: This is already done on `ServiceManager->getContainer()`, so you do not need to do it. - -## Accessing the container inside a legacy controller - -You just need to use this method `$this->getPsrContainer()`. - -```php -use oat\tao\model\http\Controller; -use Zend\ServiceManager\ServiceLocatorAwareInterface; -use Zend\ServiceManager\ServiceLocatorAwareTrait; - -class MyController extends Controller implements ServiceLocatorAwareInterface -{ - use ServiceLocatorAwareTrait; - - public function myMethod(): void - { - $service = $this->getPsrContainer()->get(MyService::class); - // Other logic... - } -} -``` +**IMPORTANT**: This is already done on `ServiceManager->getContainer()`, so you do not need to do it. ## Using legacy configuration on new services diff --git a/test/unit/core/DependencyInjection/LegacyServiceGatewayTest.php b/test/unit/core/DependencyInjection/LegacyServiceGatewayTest.php index b03a92db8..5715c1730 100755 --- a/test/unit/core/DependencyInjection/LegacyServiceGatewayTest.php +++ b/test/unit/core/DependencyInjection/LegacyServiceGatewayTest.php @@ -25,6 +25,7 @@ use oat\generis\model\DependencyInjection\LegacyServiceGateway; use oat\generis\test\TestCase; use oat\oatbox\service\ServiceManager; +use oat\tao\model\routing\ControllerService; use PHPUnit\Framework\MockObject\MockObject; use stdClass; @@ -77,4 +78,13 @@ public function testHas(): void $this->assertTrue($this->subject->has('someId')); } + + public function testHasWillReturnTrueForConfigurableService(): void + { + $this->serviceManager + ->method('has') + ->willReturn(false); + + $this->assertTrue($this->subject->has(ControllerService::class)); + } } From 9d85b7014974861739ec1426390d2eae3f47923b Mon Sep 17 00:00:00 2001 From: Gabriel Felipe Soares Date: Fri, 17 Dec 2021 08:31:48 +0100 Subject: [PATCH 05/11] feat: add request/response to container and improve container::has method (#953) From fc94df3b9ce0251f0a5da45fa4b8f4909e9f7b70 Mon Sep 17 00:00:00 2001 From: Andrei Shapiro Date: Fri, 17 Dec 2021 13:50:31 +0000 Subject: [PATCH 06/11] feat: create repositories for class and resource, create context, create resource deleter --- core/Context/AbstractContext.php | 71 +++++++++++++++ core/Context/ContextInterface.php | 38 ++++++++ core/data/event/ClassDeletedEvent.php | 4 +- core/data/event/ResourceDeleted.php | 4 +- core/kernel/classes/class.Class.php | 40 +++++--- core/kernel/classes/class.Resource.php | 38 +++++--- .../Command/ResourceRepositoryCommand.php | 28 ++++++ .../Context/ResourceRepositoryContext.php | 73 +++++++++++++++ .../Contract/ResourceDeleterInterface.php | 34 +++++++ .../Contract/ResourceRepositoryInterface.php | 36 ++++++++ core/resource/Repository/ClassRepository.php | 91 +++++++++++++++++++ .../Repository/ResourceRepository.php | 90 ++++++++++++++++++ core/resource/ResourceServiceProvider.php | 68 ++++++++++++++ core/resource/Service/ResourceDeleter.php | 76 ++++++++++++++++ .../exception/ResourceDeletionException.php | 45 +++++++++ manifest.php | 19 ++-- 16 files changed, 716 insertions(+), 39 deletions(-) create mode 100644 core/Context/AbstractContext.php create mode 100644 core/Context/ContextInterface.php create mode 100644 core/resource/Command/ResourceRepositoryCommand.php create mode 100644 core/resource/Context/ResourceRepositoryContext.php create mode 100644 core/resource/Contract/ResourceDeleterInterface.php create mode 100644 core/resource/Contract/ResourceRepositoryInterface.php create mode 100644 core/resource/Repository/ClassRepository.php create mode 100644 core/resource/Repository/ResourceRepository.php create mode 100644 core/resource/ResourceServiceProvider.php create mode 100644 core/resource/Service/ResourceDeleter.php create mode 100644 core/resource/exception/ResourceDeletionException.php diff --git a/core/Context/AbstractContext.php b/core/Context/AbstractContext.php new file mode 100644 index 000000000..97a5b0e4e --- /dev/null +++ b/core/Context/AbstractContext.php @@ -0,0 +1,71 @@ + $parameterValue) { + $this->setParameter($parameter, $parameterValue); + } + } + + public function getParameter(string $parameter, $default = null) + { + $this->checkParameterSupport($parameter); + + return $this->parameters[$parameter] ?? $default; + } + + public function setParameter(string $parameter, $parameterValue): void + { + $this->checkParameterSupport($parameter); + $this->validateParameter($parameter, $parameterValue); + + $this->parameters[$parameter] = $parameterValue; + } + + abstract protected function getSupportedParameters(): array; + + /** + * @param mixed $parameterValue + */ + abstract protected function validateParameter(string $parameter, $parameterValue): void; + + private function checkParameterSupport(string $parameter): void + { + if (!in_array($parameter, $this->getSupportedParameters(), true)) { + throw new InvalidArgumentException( + sprintf( + 'Context parameter %s is not supported.', + $parameter + ) + ); + } + } +} diff --git a/core/Context/ContextInterface.php b/core/Context/ContextInterface.php new file mode 100644 index 000000000..ef15256c2 --- /dev/null +++ b/core/Context/ContextInterface.php @@ -0,0 +1,38 @@ +selectedClass !== null && $this->selectedClass !== $this->class) { - $data['selected'] = [ + $data['selectedClass'] = [ 'uri' => $this->selectedClass->getUri(), 'label' => $this->selectedClass->getLabel(), ]; } if ($this->parentClass !== null) { - $data['parent'] = [ + $data['parentClass'] = [ 'uri' => $this->parentClass->getUri(), 'label' => $this->parentClass->getLabel(), ]; diff --git a/core/data/event/ResourceDeleted.php b/core/data/event/ResourceDeleted.php index dfe1b2d53..724626043 100755 --- a/core/data/event/ResourceDeleted.php +++ b/core/data/event/ResourceDeleted.php @@ -80,14 +80,14 @@ public function jsonSerialize(): array ]; if ($this->selectedClass !== null) { - $data['selected'] = [ + $data['selectedClass'] = [ 'uri' => $this->selectedClass->getUri(), 'label' => $this->selectedClass->getLabel(), ]; } if ($this->parentClass !== null) { - $data['parent'] = [ + $data['parentClass'] = [ 'uri' => $this->parentClass->getUri(), 'label' => $this->parentClass->getLabel(), ]; diff --git a/core/kernel/classes/class.Class.php b/core/kernel/classes/class.Class.php index f6f8e8f5c..8f069f9b1 100644 --- a/core/kernel/classes/class.Class.php +++ b/core/kernel/classes/class.Class.php @@ -18,27 +18,27 @@ * Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg (under the project TAO & TAO2); * 2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung (under the project TAO-TRANSFER); * 2009-2012 (update and modification) Public Research Centre Henri Tudor (under the project TAO-SUSTAIN & TAO-DEV); - * 2017 (update and modification) Open Assessment Technologies SA (under the project TAO-PRODUCT); + * 2017-2021 (update and modification) Open Assessment Technologies SA. */ -use oat\generis\model\data\event\ClassDeletedEvent; -use oat\generis\model\data\event\ResourceCreated; use oat\generis\model\OntologyRdf; -use oat\generis\model\resource\ResourceCollection; use oat\oatbox\event\EventManager; use oat\oatbox\event\EventManagerAwareTrait; +use oat\generis\model\data\event\ResourceCreated; +use oat\generis\model\resource\ResourceCollection; +use oat\generis\model\resource\Repository\ClassRepository; +use oat\generis\model\resource\Context\ResourceRepositoryContext; +use oat\generis\model\resource\Contract\ResourceRepositoryInterface; /** * The class of rdfs:classes. It implements basic tests like isSubClassOf(Class * instances, properties and subclasses retrieval, but also enable to edit it * setSubClassOf setProperty, etc. * - * * @author patrick.plichart@tudor.lu - * @package generis + * * @see http://www.w3.org/RDF/ * @see http://www.w3.org/TR/rdf-schema/ - * */ class core_kernel_classes_Class extends core_kernel_classes_Resource { @@ -425,7 +425,7 @@ public function deleteInstances($resources, $deleteReference = false) } /** - * @deprecated Use \oat\tao\model\resources\Repository\ClassRepository::delete() instead + * @deprecated Use \oat\generis\model\resource\Repository\ClassRepository::delete() instead * * @author Jerome Bogaerts, * @@ -435,13 +435,20 @@ public function deleteInstances($resources, $deleteReference = false) */ public function delete($deleteReference = false) { - $delete = (bool)$this->getImplementation()->delete($this, $deleteReference); - - if ($delete) { - $this->getEventManager()->trigger(new ClassDeletedEvent($this)); + try { + $this->getClassRepository()->delete( + new ResourceRepositoryContext( + [ + ResourceRepositoryContext::PARAM_CLASS => $this, + ResourceRepositoryContext::PARAM_DELETE_REFERENCE => $deleteReference, + ] + ) + ); + + return true; + } catch (Throwable $exception) { + return false; } - - return $delete; } /** @@ -459,4 +466,9 @@ public function exists() // we know that the class exists. return (bool) (count($this->getParentClasses(false)) > 0); } + + private function getClassRepository(): ResourceRepositoryInterface + { + return $this->getServiceManager()->getContainer()->get(ClassRepository::class); + } } diff --git a/core/kernel/classes/class.Resource.php b/core/kernel/classes/class.Resource.php index c4e441bff..ccb333065 100644 --- a/core/kernel/classes/class.Resource.php +++ b/core/kernel/classes/class.Resource.php @@ -18,7 +18,7 @@ * Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg (under the project TAO & TAO2); * 2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung (under the project TAO-TRANSFER); * 2009-2012 (update and modification) Public Research Centre Henri Tudor (under the project TAO-SUSTAIN & TAO-DEV); - * 2017 (update and modification) Open Assessment Technologies SA (under the project TAO-PRODUCT); + * 2017-2021 (update and modification) Open Assessment Technologies SA; */ use oat\generis\model\OntologyRdf; @@ -26,22 +26,20 @@ use oat\oatbox\event\EventAggregator; use oat\oatbox\service\ServiceManager; use oat\generis\model\OntologyAwareTrait; -use oat\oatbox\event\EventManager; +use Zend\ServiceManager\ServiceLocatorInterface; use oat\generis\model\data\event\ResourceUpdated; -use oat\generis\model\data\event\ResourceDeleted; use Zend\ServiceManager\ServiceLocatorAwareInterface; -use Zend\ServiceManager\ServiceLocatorInterface; +use oat\generis\model\resource\Repository\ResourceRepository; +use oat\generis\model\resource\Context\ResourceRepositoryContext; +use oat\generis\model\resource\Contract\ResourceRepositoryInterface; /** * Resource implements rdf:resource container identified by an uri (a string). * Methods enable meta data management for this resource * - * @access public * @author patrick.plichart@tudor.lu - * @package generis + * * @see http://www.w3.org/RDF/ - - * @version v1.0 */ class core_kernel_classes_Resource extends core_kernel_classes_Container { @@ -581,11 +579,20 @@ public function duplicate($excludedProperties = []) */ public function delete($deleteReference = false) { - $returnValue = $this->getImplementation()->delete($this, $deleteReference); - $eventManager = $this->getServiceManager()->get(EventManager::SERVICE_ID); - $eventManager->trigger(new ResourceDeleted($this->getUri())); - - return (bool) $returnValue; + try { + $this->getResourceRepository()->delete( + new ResourceRepositoryContext( + [ + ResourceRepositoryContext::PARAM_RESOURCE => $this, + ResourceRepositoryContext::PARAM_DELETE_REFERENCE => $deleteReference, + ] + ) + ); + + return true; + } catch (Throwable $exception) { + return false; + } } @@ -772,4 +779,9 @@ private function onUpdate(): void $eventAggregator = $this->getServiceManager()->get(EventAggregator::SERVICE_ID); $eventAggregator->put($this->getUri(), new ResourceUpdated($this)); } + + private function getResourceRepository(): ResourceRepositoryInterface + { + return $this->getServiceManager()->getContainer()->get(ResourceRepository::class); + } } diff --git a/core/resource/Command/ResourceRepositoryCommand.php b/core/resource/Command/ResourceRepositoryCommand.php new file mode 100644 index 000000000..3f3d74930 --- /dev/null +++ b/core/resource/Command/ResourceRepositoryCommand.php @@ -0,0 +1,28 @@ +ontology = $ontology; + $this->eventManager = $eventManager; + } + + public function delete(ContextInterface $context): void + { + /** @var core_kernel_classes_Class|null $class */ + $class = $context->getParameter(ResourceRepositoryContext::PARAM_CLASS); + + if ($class === null) { + throw new InvalidArgumentException('Class was not provided for deletion.'); + } + + $deleteReference = $context->getParameter( + ResourceRepositoryContext::PARAM_DELETE_REFERENCE, + false + ); + /** @var core_kernel_classes_Class $parentClass */ + $parentClass = $context->getParameter( + ResourceRepositoryContext::PARAM_PARENT_CLASS, + current($class->getParentClasses()) ?: null + ); + + if (!$this->getImplementation()->delete($class, $deleteReference)) { + throw new RuntimeException( + sprintf( + 'Class "%s" ("%s") was not deleted.', + $class->getLabel(), + $class->getUri() + ) + ); + } + + /** @var core_kernel_classes_Class|null $selectedClass */ + $selectedClass = $context->getParameter(ResourceRepositoryContext::PARAM_SELECTED_CLASS); + $classDeletedEvent = (new ClassDeletedEvent($class)) + ->setSelectedClass($selectedClass) + ->setParentClass($parentClass); + $this->eventManager->trigger($classDeletedEvent); + } + + private function getImplementation(): core_kernel_persistence_ClassInterface + { + return $this->ontology->getRdfsInterface()->getClassImplementation(); + } +} diff --git a/core/resource/Repository/ResourceRepository.php b/core/resource/Repository/ResourceRepository.php new file mode 100644 index 000000000..a613dcd39 --- /dev/null +++ b/core/resource/Repository/ResourceRepository.php @@ -0,0 +1,90 @@ +ontology = $ontology; + $this->eventManager = $eventManager; + } + + public function delete(ContextInterface $context): void + { + /** @var core_kernel_classes_Resource|null $resource */ + $resource = $context->getParameter(ResourceRepositoryContext::PARAM_RESOURCE); + + if ($resource === null) { + throw new InvalidArgumentException('Resource was not provided for deletion.'); + } + + $deleteReference = $context->getParameter( + ResourceRepositoryContext::PARAM_DELETE_REFERENCE, + false + ); + + if (!$this->getImplementation()->delete($resource, $deleteReference)) { + throw new RuntimeException( + sprintf( + 'Resource "%s" ("%s") was not deleted.', + $resource->getLabel(), + $resource->getUri() + ) + ); + } + + /** @var core_kernel_classes_Class|null $selectedClass */ + $selectedClass = $context->getParameter(ResourceRepositoryContext::PARAM_SELECTED_CLASS); + /** @var core_kernel_classes_Class|null $selectedClass */ + $parentClass = $context->getParameter(ResourceRepositoryContext::PARAM_PARENT_CLASS); + + $resourceDeletedEvent = (new ResourceDeleted($resource->getUri())) + ->setSelectedClass($selectedClass) + ->setParentClass($parentClass); + $this->eventManager->trigger($resourceDeletedEvent); + } + + private function getImplementation(): core_kernel_persistence_ResourceInterface + { + return $this->ontology->getRdfsInterface()->getResourceImplementation(); + } +} diff --git a/core/resource/ResourceServiceProvider.php b/core/resource/ResourceServiceProvider.php new file mode 100644 index 000000000..ec8ff7c10 --- /dev/null +++ b/core/resource/ResourceServiceProvider.php @@ -0,0 +1,68 @@ +services(); + + $services + ->set(ClassRepository::class, ClassRepository::class) + ->args( + [ + service(Ontology::SERVICE_ID), + service(EventManager::SERVICE_ID), + ] + ); + + $services + ->set(ResourceRepository::class, ResourceRepository::class) + ->args( + [ + service(Ontology::SERVICE_ID), + service(EventManager::SERVICE_ID), + ] + ); + + $services + ->set(ResourceDeleter::class, ResourceDeleter::class) + ->public() + ->args( + [ + service(ResourceRepository::class), + ] + ); + } +} diff --git a/core/resource/Service/ResourceDeleter.php b/core/resource/Service/ResourceDeleter.php new file mode 100644 index 000000000..8bb19af9f --- /dev/null +++ b/core/resource/Service/ResourceDeleter.php @@ -0,0 +1,76 @@ +resourceRepository = $resourceRepository; + } + + public function delete(core_kernel_classes_Resource $resource): void + { + try { + $this->resourceRepository->delete( + new ResourceRepositoryContext( + [ + ResourceRepositoryContext::PARAM_RESOURCE => $resource, + ResourceRepositoryContext::PARAM_PARENT_CLASS => $this->getParentClass($resource), + ] + ) + ); + } catch (Throwable $exception) { + throw new ResourceDeletionException( + sprintf( + 'Unable to delete resource "%s::%s" (%s).', + $resource->getLabel(), + $resource->getUri(), + $exception->getMessage() + ), + __('Unable to delete the selected resource') + ); + } + } + + private function getParentClass(core_kernel_classes_Resource $resource): ?core_kernel_classes_Class + { + $parentClassUri = $resource->getOnePropertyValue($resource->getProperty(OntologyRdf::RDF_TYPE)); + + return $parentClassUri !== null + ? $resource->getClass($parentClassUri) + : null; + } +} diff --git a/core/resource/exception/ResourceDeletionException.php b/core/resource/exception/ResourceDeletionException.php new file mode 100644 index 000000000..43d2143b2 --- /dev/null +++ b/core/resource/exception/ResourceDeletionException.php @@ -0,0 +1,45 @@ +userMessage = $userMessage; + + parent::__construct($message, $code, $throwable); + } + + public function getUserMessage(): string + { + return $this->userMessage; + } +} diff --git a/manifest.php b/manifest.php index 921b63fc2..e36fede70 100755 --- a/manifest.php +++ b/manifest.php @@ -1,7 +1,5 @@ [ TaskQueue::class, SetupDefaultKvPersistence::class, - RegisterServices::class + RegisterServices::class, ], ], 'update' => Updater::class, 'containerServiceProviders' => [ ContainerServiceProvider::class, LogServiceProvider::class, - ] + ResourceServiceProvider::class, + ], ]; From 5e2dfcc62be7e94d4f6ebf5545d3d5cbfead6d3c Mon Sep 17 00:00:00 2001 From: Andrei Shapiro Date: Fri, 17 Dec 2021 13:52:53 +0000 Subject: [PATCH 07/11] refactor: fix deprecation message --- core/kernel/classes/class.Resource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/kernel/classes/class.Resource.php b/core/kernel/classes/class.Resource.php index ccb333065..e29bcb459 100644 --- a/core/kernel/classes/class.Resource.php +++ b/core/kernel/classes/class.Resource.php @@ -569,7 +569,7 @@ public function duplicate($excludedProperties = []) /** * Remove any assignation made to this resource, the uri is consequently * - * @deprecated Use \oat\tao\model\resources\Repository\ResourceRepository::delete() instead + * @deprecated Use \oat\generis\model\resource\Repository\ResourceRepository::delete() instead * * @author patrick.plichart@tudor.lu * From 21d8b44d9d5ed1e2b725e18f74f1d46387ed241b Mon Sep 17 00:00:00 2001 From: Andrei Shapiro Date: Fri, 17 Dec 2021 13:59:46 +0000 Subject: [PATCH 08/11] refactor: remove unnecesary class --- .../Command/ResourceRepositoryCommand.php | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 core/resource/Command/ResourceRepositoryCommand.php diff --git a/core/resource/Command/ResourceRepositoryCommand.php b/core/resource/Command/ResourceRepositoryCommand.php deleted file mode 100644 index 3f3d74930..000000000 --- a/core/resource/Command/ResourceRepositoryCommand.php +++ /dev/null @@ -1,28 +0,0 @@ - Date: Sun, 19 Dec 2021 00:22:21 +0000 Subject: [PATCH 09/11] tests: add unit tests --- core/resource/Service/ResourceDeleter.php | 16 +- .../resource/Service/ResourceDeleterTest.php | 149 ++++++++++++++++++ 2 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 test/unit/core/resource/Service/ResourceDeleterTest.php diff --git a/core/resource/Service/ResourceDeleter.php b/core/resource/Service/ResourceDeleter.php index 8bb19af9f..7347fcfa7 100644 --- a/core/resource/Service/ResourceDeleter.php +++ b/core/resource/Service/ResourceDeleter.php @@ -44,14 +44,14 @@ public function __construct(ResourceRepositoryInterface $resourceRepository) public function delete(core_kernel_classes_Resource $resource): void { try { - $this->resourceRepository->delete( - new ResourceRepositoryContext( - [ - ResourceRepositoryContext::PARAM_RESOURCE => $resource, - ResourceRepositoryContext::PARAM_PARENT_CLASS => $this->getParentClass($resource), - ] - ) - ); + $context = new ResourceRepositoryContext([ResourceRepositoryContext::PARAM_RESOURCE => $resource]); + $parentClass = $this->getParentClass($resource); + + if ($parentClass !== null) { + $context->setParameter(ResourceRepositoryContext::PARAM_PARENT_CLASS, $parentClass); + } + + $this->resourceRepository->delete($context); } catch (Throwable $exception) { throw new ResourceDeletionException( sprintf( diff --git a/test/unit/core/resource/Service/ResourceDeleterTest.php b/test/unit/core/resource/Service/ResourceDeleterTest.php new file mode 100644 index 000000000..ae33a4616 --- /dev/null +++ b/test/unit/core/resource/Service/ResourceDeleterTest.php @@ -0,0 +1,149 @@ +resourceRepository = $this->createMock(ResourceRepositoryInterface::class); + $this->sut = new ResourceDeleter($this->resourceRepository); + + $this->resource = $this->createMock(core_kernel_classes_Resource::class); + $this->resource + ->expects($this->once()) + ->method('getProperty') + ->willReturn($this->createMock(core_kernel_classes_Property::class)); + } + + public function testDeleteSuccessWithParentClass(): void + { + $this->resourceRepository + ->expects($this->once()) + ->method('delete'); + + $this->resource + ->expects($this->once()) + ->method('getOnePropertyValue') + ->willReturn('parentClassUri'); + $this->resource + ->expects($this->once()) + ->method('getClass') + ->willReturn($this->createMock(core_kernel_classes_Class::class)); + + $this->resource + ->expects($this->never()) + ->method('getLabel'); + $this->resource + ->expects($this->never()) + ->method('getUri'); + + $this->sut->delete($this->resource); + } + + public function testDeleteSuccessWithoutParentClass(): void + { + $this->resourceRepository + ->expects($this->once()) + ->method('delete'); + + $this->resource + ->expects($this->once()) + ->method('getOnePropertyValue') + ->willReturn(null); + $this->resource + ->expects($this->never()) + ->method('getClass'); + + $this->resource + ->expects($this->never()) + ->method('getLabel'); + $this->resource + ->expects($this->never()) + ->method('getUri'); + + $this->sut->delete($this->resource); + } + + public function testDeleteWithRepositoryError(): void + { + $resourceRepositoryContext = $this->createMock(ContextInterface::class); + $resourceRepositoryContext + ->expects($this->once()) + ->method('getParameter') + ->with(self::PARAM_RESOURCE) + ->willReturn(null); + + $this->resourceRepository + ->expects($this->once()) + ->with($resourceRepositoryContext) + ->method('delete'); + + $this->resource + ->expects($this->once()) + ->method('getOnePropertyValue') + ->willReturn('parentClassUri'); + $this->resource + ->expects($this->once()) + ->method('getClass') + ->willReturn($this->createMock(core_kernel_classes_Class::class)); + + $this->resource + ->expects($this->once()) + ->method('getLabel') + ->willReturn('resourceLabel'); + $this->resource + ->expects($this->once()) + ->method('getUri') + ->willReturn('resourceUri'); + + $this->expectException(ResourceDeletionException::class); + $this->expectExceptionMessage( + 'Unable to delete resource "resourceLabel::resourceUri" (Resource was not provided for deletion.).' + ); + + $this->sut->delete($this->resource); + } +} From 5573ca642a66a04ca5422a381d868ff0c082e39d Mon Sep 17 00:00:00 2001 From: Andrei Shapiro Date: Sun, 19 Dec 2021 00:34:49 +0000 Subject: [PATCH 10/11] refactor: make repositories public --- core/resource/ResourceServiceProvider.php | 2 ++ .../core/kernel/persistence/OntologyRdfsTest.php | 13 ------------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/core/resource/ResourceServiceProvider.php b/core/resource/ResourceServiceProvider.php index ec8ff7c10..1f94b0452 100644 --- a/core/resource/ResourceServiceProvider.php +++ b/core/resource/ResourceServiceProvider.php @@ -40,6 +40,7 @@ public function __invoke(ContainerConfigurator $configurator): void $services ->set(ClassRepository::class, ClassRepository::class) + ->public() ->args( [ service(Ontology::SERVICE_ID), @@ -49,6 +50,7 @@ public function __invoke(ContainerConfigurator $configurator): void $services ->set(ResourceRepository::class, ResourceRepository::class) + ->public() ->args( [ service(Ontology::SERVICE_ID), diff --git a/test/unit/core/kernel/persistence/OntologyRdfsTest.php b/test/unit/core/kernel/persistence/OntologyRdfsTest.php index e11cb4827..12f5f744e 100755 --- a/test/unit/core/kernel/persistence/OntologyRdfsTest.php +++ b/test/unit/core/kernel/persistence/OntologyRdfsTest.php @@ -109,19 +109,6 @@ public function testDuplicateInstance(Ontology $model) $this->assertNotEquals($resource, $resourceClone); } - /** - * @dataProvider getOntologies - */ - public function testDeleteInstance(Ontology $model) - { - $class = $model->getClass('http://testing#class'); - $resource = $class->createInstance('sample'); - $this->assertInstanceOf(\core_kernel_classes_Resource::class, $resource); - $this->assertTrue($resource->exists()); - $resource->delete(); - $this->assertFalse($resource->exists()); - } - public function getOntologies() { return [ From 160486a45c6c79ea6816f59e22c23eb1ce032d33 Mon Sep 17 00:00:00 2001 From: Andrei Shapiro Date: Sun, 19 Dec 2021 01:37:10 +0000 Subject: [PATCH 11/11] tests: add unit tests for events and repositories --- .../Repository/ClassRepositoryTest.php | 189 ++++++++++++++++++ .../Repository/ResourceRepositoryTest.php | 186 +++++++++++++++++ .../data/event/ClassDeletedEventTest.php | 162 +++++++++++++++ .../model/data/event/ResourceDeletedTest.php | 131 ++++++++++++ 4 files changed, 668 insertions(+) create mode 100644 test/unit/core/resource/Repository/ClassRepositoryTest.php create mode 100644 test/unit/core/resource/Repository/ResourceRepositoryTest.php create mode 100644 test/unit/model/data/event/ClassDeletedEventTest.php create mode 100644 test/unit/model/data/event/ResourceDeletedTest.php diff --git a/test/unit/core/resource/Repository/ClassRepositoryTest.php b/test/unit/core/resource/Repository/ClassRepositoryTest.php new file mode 100644 index 000000000..d7c98cdc2 --- /dev/null +++ b/test/unit/core/resource/Repository/ClassRepositoryTest.php @@ -0,0 +1,189 @@ +classImplementation = $this->createMock(core_kernel_persistence_ClassInterface::class); + + $this->rdfsInterface = $this->createMock(RdfsInterface::class); + $this->rdfsInterface + ->method('getClassImplementation') + ->willReturn($this->classImplementation); + + $this->ontology = $this->createMock(Ontology::class); + $this->ontology + ->method('getRdfsInterface') + ->willReturn($this->rdfsInterface); + + $this->eventManager = $this->createMock(EventManager::class); + + $this->sut = new ClassRepository($this->ontology, $this->eventManager); + } + + public function testDeleteSuccess(): void + { + $this->ontology + ->expects($this->once()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->once()) + ->method('getClassImplementation'); + $this->classImplementation + ->expects($this->once()) + ->method('delete') + ->willReturn(true); + $this->eventManager + ->expects($this->once()) + ->method('trigger'); + + $context = $this->createContext(4, $this->createClass()); + $this->sut->delete($context); + } + + public function testDeleteWithoutResource(): void + { + $this->ontology + ->expects($this->never()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->never()) + ->method('getClassImplementation'); + $this->classImplementation + ->expects($this->never()) + ->method('delete'); + $this->eventManager + ->expects($this->never()) + ->method('trigger'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Class was not provided for deletion.'); + + $context = $this->createContext(1, null); + $this->sut->delete($context); + } + + public function testDeleteFailure(): void + { + $this->ontology + ->expects($this->once()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->once()) + ->method('getClassImplementation'); + $this->classImplementation + ->expects($this->once()) + ->method('delete') + ->willReturn(false); + $this->eventManager + ->expects($this->never()) + ->method('trigger'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Class "classLabel" ("classUri") was not deleted.'); + + $context = $this->createContext(3, $this->createClass('classUri', 'classLabel')); + $this->sut->delete($context); + } + + private function createClass(string $uri = null, string $label = null): core_kernel_classes_Class + { + $class = $this->createMock(core_kernel_classes_Class::class); + $class + ->expects($uri !== null ? $this->once() : $this->never()) + ->method('getUri') + ->willReturn($uri); + $class + ->expects($label !== null ? $this->once() : $this->never()) + ->method('getLabel') + ->willReturn($label); + $class + ->expects($this->once()) + ->method('getParentClasses') + ->willReturn([]); + + return $class; + } + + private function createContext(int $expects, ?core_kernel_classes_Class $class): ContextInterface + { + $context = $this->createMock(ContextInterface::class); + $context + ->expects($this->exactly($expects)) + ->method('getParameter') + ->willReturnCallback( + function (string $param) use ($class) { + if ($param === self::PARAM_CLASS) { + return $class; + } + + if ($param === self::PARAM_DELETE_REFERENCE) { + return false; + } + + if (in_array($param, [self::PARAM_SELECTED_CLASS, self::PARAM_PARENT_CLASS], true)) { + return $this->createMock(core_kernel_classes_Class::class); + } + + return null; + } + ); + + return $context; + } +} diff --git a/test/unit/core/resource/Repository/ResourceRepositoryTest.php b/test/unit/core/resource/Repository/ResourceRepositoryTest.php new file mode 100644 index 000000000..4c96f1d7e --- /dev/null +++ b/test/unit/core/resource/Repository/ResourceRepositoryTest.php @@ -0,0 +1,186 @@ +resourceImplementation = $this->createMock(core_kernel_persistence_ResourceInterface::class); + + $this->rdfsInterface = $this->createMock(RdfsInterface::class); + $this->rdfsInterface + ->method('getResourceImplementation') + ->willReturn($this->resourceImplementation); + + $this->ontology = $this->createMock(Ontology::class); + $this->ontology + ->method('getRdfsInterface') + ->willReturn($this->rdfsInterface); + + $this->eventManager = $this->createMock(EventManager::class); + + $this->sut = new ResourceRepository($this->ontology, $this->eventManager); + } + + public function testDeleteSuccess(): void + { + $this->ontology + ->expects($this->once()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->once()) + ->method('getResourceImplementation'); + $this->resourceImplementation + ->expects($this->once()) + ->method('delete') + ->willReturn(true); + $this->eventManager + ->expects($this->once()) + ->method('trigger'); + + $context = $this->createContext(4, $this->createResource('resourceUri')); + $this->sut->delete($context); + } + + public function testDeleteWithoutResource(): void + { + $this->ontology + ->expects($this->never()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->never()) + ->method('getResourceImplementation'); + $this->resourceImplementation + ->expects($this->never()) + ->method('delete'); + $this->eventManager + ->expects($this->never()) + ->method('trigger'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Resource was not provided for deletion.'); + + $context = $this->createContext(1, null); + $this->sut->delete($context); + } + + public function testDeleteFailure(): void + { + $this->ontology + ->expects($this->once()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->once()) + ->method('getResourceImplementation'); + $this->resourceImplementation + ->expects($this->once()) + ->method('delete') + ->willReturn(false); + $this->eventManager + ->expects($this->never()) + ->method('trigger'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Resource "resourceLabel" ("resourceUri") was not deleted.'); + + $context = $this->createContext(2, $this->createResource('resourceUri', 'resourceLabel')); + $this->sut->delete($context); + } + + private function createResource(string $uri, string $label = null): core_kernel_classes_Resource + { + $class = $this->createMock(core_kernel_classes_Resource::class); + $class + ->expects($this->once()) + ->method('getUri') + ->willReturn($uri); + $class + ->expects($label !== null ? $this->once() : $this->never()) + ->method('getLabel') + ->willReturn($label); + + return $class; + } + + private function createContext(int $expects, ?core_kernel_classes_Resource $resource): ContextInterface + { + $context = $this->createMock(ContextInterface::class); + $context + ->expects($this->exactly($expects)) + ->method('getParameter') + ->willReturnCallback( + function (string $param) use ($resource) { + if ($param === self::PARAM_RESOURCE) { + return $resource; + } + + if ($param === self::PARAM_DELETE_REFERENCE) { + return false; + } + + if (in_array($param, [self::PARAM_SELECTED_CLASS, self::PARAM_PARENT_CLASS], true)) { + return $this->createMock(core_kernel_classes_Class::class); + } + + return null; + } + ); + + return $context; + } +} diff --git a/test/unit/model/data/event/ClassDeletedEventTest.php b/test/unit/model/data/event/ClassDeletedEventTest.php new file mode 100644 index 000000000..cce61a1dc --- /dev/null +++ b/test/unit/model/data/event/ClassDeletedEventTest.php @@ -0,0 +1,162 @@ +class = $this->createMock(core_kernel_classes_Class::class); + + $this->sut = new ClassDeletedEvent($this->class); + } + + public function testGetName(): void + { + $this->assertEquals(ClassDeletedEvent::class, $this->sut->getName()); + } + + public function testGetClass(): void + { + $this->assertEquals($this->class, $this->sut->getClass()); + } + + public function testJsonSerializeWithoutAnyData(): void + { + $this->class + ->expects($this->once()) + ->method('getUri') + ->willReturn(self::CLASS_URI); + + $this->assertEquals(['uri' => self::CLASS_URI], $this->sut->jsonSerialize()); + } + + public function testJsonSerializeWithSelectedClass(): void + { + $this->class + ->expects($this->once()) + ->method('getUri') + ->willReturn(self::CLASS_URI); + + $this->sut->setSelectedClass($this->createClass('selectedClassUri', 'selectedClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::CLASS_URI, + 'selectedClass' => [ + 'uri' => 'selectedClassUri', + 'label' => 'selectedClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithParentClass(): void + { + $this->class + ->expects($this->once()) + ->method('getUri') + ->willReturn(self::CLASS_URI); + + $this->sut->setParentClass($this->createClass('parentClassUri', 'parentClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::CLASS_URI, + 'parentClass' => [ + 'uri' => 'parentClassUri', + 'label' => 'parentClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithSelectedAndParentClasses(): void + { + $this->class + ->expects($this->once()) + ->method('getUri') + ->willReturn(self::CLASS_URI); + + $this->sut->setSelectedClass($this->createClass('selectedClassUri', 'selectedClassLabel')); + $this->sut->setParentClass($this->createClass('parentClassUri', 'parentClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::CLASS_URI, + 'selectedClass' => [ + 'uri' => 'selectedClassUri', + 'label' => 'selectedClassLabel', + ], + 'parentClass' => [ + 'uri' => 'parentClassUri', + 'label' => 'parentClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithNullableSelectedAndParentClasses(): void + { + $this->class + ->expects($this->once()) + ->method('getUri') + ->willReturn(self::CLASS_URI); + + $this->sut->setSelectedClass(null); + $this->sut->setParentClass(null); + + $this->assertEquals(['uri' => self::CLASS_URI], $this->sut->jsonSerialize()); + } + + private function createClass(string $uri, string $label): core_kernel_classes_Class + { + $class = $this->createMock(core_kernel_classes_Class::class); + $class + ->expects($this->once()) + ->method('getUri') + ->willReturn($uri); + $class + ->expects($this->once()) + ->method('getLabel') + ->willReturn($label); + + return $class; + } +} diff --git a/test/unit/model/data/event/ResourceDeletedTest.php b/test/unit/model/data/event/ResourceDeletedTest.php new file mode 100644 index 000000000..698eb682a --- /dev/null +++ b/test/unit/model/data/event/ResourceDeletedTest.php @@ -0,0 +1,131 @@ +sut = new ResourceDeleted(self::RESOURCE_URI); + } + + public function testGetName(): void + { + $this->assertEquals(ResourceDeleted::class, $this->sut->getName()); + } + + public function testGetId(): void + { + $this->assertEquals(self::RESOURCE_URI, $this->sut->getId()); + } + + public function testJsonSerializeWithoutAnyData(): void + { + $this->assertEquals(['uri' => self::RESOURCE_URI], $this->sut->jsonSerialize()); + } + + public function testJsonSerializeWithSelectedClass(): void + { + $this->sut->setSelectedClass($this->createClass('selectedClassUri', 'selectedClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::RESOURCE_URI, + 'selectedClass' => [ + 'uri' => 'selectedClassUri', + 'label' => 'selectedClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithParentClass(): void + { + $this->sut->setParentClass($this->createClass('parentClassUri', 'parentClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::RESOURCE_URI, + 'parentClass' => [ + 'uri' => 'parentClassUri', + 'label' => 'parentClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithSelectedAndParentClasses(): void + { + $this->sut->setSelectedClass($this->createClass('selectedClassUri', 'selectedClassLabel')); + $this->sut->setParentClass($this->createClass('parentClassUri', 'parentClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::RESOURCE_URI, + 'selectedClass' => [ + 'uri' => 'selectedClassUri', + 'label' => 'selectedClassLabel', + ], + 'parentClass' => [ + 'uri' => 'parentClassUri', + 'label' => 'parentClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithNullableSelectedAndParentClasses(): void + { + $this->sut->setSelectedClass(null); + $this->sut->setParentClass(null); + + $this->assertEquals(['uri' => self::RESOURCE_URI], $this->sut->jsonSerialize()); + } + + private function createClass(string $uri, string $label): core_kernel_classes_Class + { + $class = $this->createMock(core_kernel_classes_Class::class); + $class + ->expects($this->once()) + ->method('getUri') + ->willReturn($uri); + $class + ->expects($this->once()) + ->method('getLabel') + ->willReturn($label); + + return $class; + } +}