diff --git a/models/classes/Lists/Business/Contract/ValueCollectionRepositoryInterface.php b/models/classes/Lists/Business/Contract/ValueCollectionRepositoryInterface.php index dcb3665f6d..21d3e70ba9 100644 --- a/models/classes/Lists/Business/Contract/ValueCollectionRepositoryInterface.php +++ b/models/classes/Lists/Business/Contract/ValueCollectionRepositoryInterface.php @@ -34,13 +34,6 @@ public function findAll(ValueCollectionSearchRequest $searchRequest): ValueColle public function isApplicable(string $collectionUri): bool; - /** - * @param ValueCollection $valueCollection - * - * @return bool - * - * @throws ValueConflictException - */ public function persist(ValueCollection $valueCollection): bool; public function delete(string $valueCollectionUri): void; diff --git a/models/classes/Lists/Business/Domain/Value.php b/models/classes/Lists/Business/Domain/Value.php index 8c387a7695..ec4cea216c 100644 --- a/models/classes/Lists/Business/Domain/Value.php +++ b/models/classes/Lists/Business/Domain/Value.php @@ -25,11 +25,10 @@ namespace oat\tao\model\Lists\Business\Domain; use tao_helpers_Uri; -use JsonSerializable; -class Value implements JsonSerializable +class Value implements \JsonSerializable { - /** @var int|null */ + /** @var string|int|null */ private $id; /** @var string */ @@ -50,7 +49,7 @@ class Value implements JsonSerializable /** @var bool */ private $hasChanges = false; - public function __construct(?int $id, string $uri, string $label, string $dependencyUri = null) + public function __construct($id, string $uri, string $label, string $dependencyUri = null) { $this->id = $id; $this->uri = $uri; @@ -71,7 +70,7 @@ public function getListUri(): string return $this->listUri; } - public function getId(): ?int + public function getId() { return $this->id; } diff --git a/models/classes/Lists/Business/Domain/ValueCollectionSearchRequest.php b/models/classes/Lists/Business/Domain/ValueCollectionSearchRequest.php index f1e24cbd52..b5bab3aee3 100644 --- a/models/classes/Lists/Business/Domain/ValueCollectionSearchRequest.php +++ b/models/classes/Lists/Business/Domain/ValueCollectionSearchRequest.php @@ -78,7 +78,7 @@ public function setPropertyUri(string $propertyUri): self public function hasValueCollectionUri(): bool { - return null !== $this->valueCollectionUri; + return !empty($this->valueCollectionUri); } public function getValueCollectionUri(): string @@ -145,7 +145,9 @@ public function hasExcluded(): bool public function addExcluded(string $excluded): self { - $this->excluded[] = $excluded; + if (!empty($excluded)) { + $this->excluded[] = $excluded; + } return $this; } diff --git a/models/classes/Lists/DataAccess/Repository/RdfValueCollectionRepository.php b/models/classes/Lists/DataAccess/Repository/RdfValueCollectionRepository.php index ff87fc8eb8..675cf76f9a 100644 --- a/models/classes/Lists/DataAccess/Repository/RdfValueCollectionRepository.php +++ b/models/classes/Lists/DataAccess/Repository/RdfValueCollectionRepository.php @@ -25,25 +25,28 @@ namespace oat\tao\model\Lists\DataAccess\Repository; use common_exception_Error; -use common_persistence_SqlPersistence as SqlPersistence; use core_kernel_classes_Class as KernelClass; +use core_kernel_classes_Property as KernelProperty; use core_kernel_classes_Resource as KernelResource; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\FetchMode; -use Doctrine\DBAL\Query\QueryBuilder; +use oat\generis\model\kernel\persistence\smoothsql\search\ComplexSearchService; +use oat\generis\model\OntologyAwareTrait; use oat\generis\model\OntologyRdf; use oat\generis\model\OntologyRdfs; use oat\generis\persistence\PersistenceManager; +use oat\search\base\QueryBuilderInterface; +use oat\search\base\QueryCriterionInterface; +use oat\search\base\QueryInterface; use oat\tao\model\Lists\Business\Contract\ValueCollectionRepositoryInterface; use oat\tao\model\Lists\Business\Domain\CollectionType; use oat\tao\model\Lists\Business\Domain\Value; use oat\tao\model\Lists\Business\Domain\ValueCollection; use oat\tao\model\Lists\Business\Domain\ValueCollectionSearchRequest; use oat\tao\model\service\InjectionAwareService; -use Throwable; class RdfValueCollectionRepository extends InjectionAwareService implements ValueCollectionRepositoryInterface { + use OntologyAwareTrait; + public const SERVICE_ID = 'tao/ValueCollectionRepository'; /** @var PersistenceManager */ @@ -67,31 +70,33 @@ public function isApplicable(string $collectionUri): bool public function findAll(ValueCollectionSearchRequest $searchRequest): ValueCollection { - $query = $this->getPersistence()->getPlatForm()->getQueryBuilder(); + /** @var ComplexSearchService $search */ + $search = $this->getModel()->getSearchInterface(); + $queryBuilder = $search->query(); - $this->enrichWithInitialCondition($query); - $this->enrichWithSelect($searchRequest, $query); - $this->enrichQueryWithPropertySearchConditions($searchRequest, $query); + $query = $this->getQuery($search, $queryBuilder, $searchRequest); + $this->enrichWithLimit($searchRequest, $queryBuilder); $this->enrichQueryWithValueCollectionSearchCondition($searchRequest, $query); $this->enrichQueryWithSubject($searchRequest, $query); $this->enrichQueryWithExcludedValueUris($searchRequest, $query); $this->enrichQueryWithObjects($searchRequest, $query); - $this->enrichQueryWithOrderById($query); + $this->enrichQueryWithOrderBy($queryBuilder); $values = []; - $data = $query->execute()->fetchAll(); - $labels = $this->retrieveLabels($data); - foreach ($data as $rawValue) { + $data = $search->getGateway()->searchTriples($queryBuilder, OntologyRdfs::RDFS_LABEL); + foreach ($data as $triple) { $values[] = new Value( - (int)$rawValue['id'], - $rawValue['subject'], - $this->extractLabel($searchRequest, $labels, $rawValue['subject']) + $triple->id, + $triple->subject, + $triple->object ); } - $valueCollectionUri = $searchRequest->hasValueCollectionUri() - ? $searchRequest->getValueCollectionUri() - : $rawValue['collection_uri'] ?? null; + if ($searchRequest->hasValueCollectionUri()) { + $valueCollectionUri = $searchRequest->getValueCollectionUri(); + } else { + $valueCollectionUri = null; + } return new ValueCollection($valueCollectionUri, ...$values); } @@ -102,11 +107,7 @@ public function persist(ValueCollection $valueCollection): bool throw new ValueConflictException("Value Collection {$valueCollection->getUri()} has duplicate values."); } - $platform = $this->getPersistence()->getPlatForm(); - - $platform->beginTransaction(); - - try { + $persistValueCollectionAction = function () use ($valueCollection): void { foreach ($valueCollection as $value) { $this->verifyUriUniqueness($value); @@ -116,18 +117,20 @@ public function persist(ValueCollection $valueCollection): bool $this->update($value); } } + }; - $platform->commit(); - + try { + $platform = $this->getPersistence(); + if ($platform instanceof \common_persistence_Transactional) { + $platform->transactional($persistValueCollectionAction); + } else { + $persistValueCollectionAction(); + } return true; } catch (ValueConflictException $exception) { throw $exception; - } catch (Throwable $exception) { + } catch (\Throwable $exception) { return false; - } finally { - if (isset($exception)) { - $platform->rollBack(); - } } } @@ -180,262 +183,117 @@ private function update(Value $value): void return; } - $query = $this->getPersistence()->getPlatForm()->getQueryBuilder(); - - $expressionBuilder = $query->expr(); - - $query - ->update('statements') - ->set('object', ':label') - ->set('subject', ':uri') - ->where($expressionBuilder->eq('id', ':id')) - ->setParameters( - [ - 'id' => $value->getId(), - 'uri' => $value->getUri(), - 'label' => $value->getLabel(), - ] - ) - ->execute(); - - $this->updateRelations($value); - } + $listValue = new KernelClass($value->getOriginalUri()); - private function updateRelations(Value $value): void - { - if (!$value->hasModifiedUri()) { - return; + $listValue->setLabel($value->getLabel()); + if ($value->hasModifiedUri()) { + $listValue->updateUri($value->getUri()); } - - $this->updateValues($value); - $this->updateProperties($value); - } - - /** - * @param Value $value - */ - private function updateValues(Value $value): void - { - $query = $this->getPersistence()->getPlatForm()->getQueryBuilder(); - - $expressionBuilder = $query->expr(); - - $query - ->update('statements') - ->set('subject', ':uri') - ->where($expressionBuilder->eq('subject', ':original_uri')) - ->setParameters( - [ - 'uri' => $value->getUri(), - 'original_uri' => $value->getOriginalUri(), - ] - ) - ->execute(); } - /** - * @param Value $value - */ - private function updateProperties(Value $value): void + private function enrichQueryWithOrderBy(QueryBuilderInterface $query): void { - $query = $this->getPersistence()->getPlatForm()->getQueryBuilder(); - - $expressionBuilder = $query->expr(); - - $query - ->update('statements') - ->set('object', ':uri') - ->where($expressionBuilder->eq('object', ':original_uri')) - ->setParameters( - [ - 'uri' => $value->getUri(), - 'original_uri' => $value->getOriginalUri(), - ] - ) - ->execute(); + $query->sort([OntologyRdfs::RDFS_LABEL => 'asc']); } - private function enrichWithInitialCondition(QueryBuilder $query): QueryBuilder + private function enrichWithLimit(ValueCollectionSearchRequest $searchRequest, QueryBuilderInterface $query): void { - $expressionBuilder = $query->expr(); - - $query - ->from('statements', 'element') - ->innerJoin( - 'element', - 'statements', - 'collection', - $expressionBuilder->eq('collection.subject', 'element.subject') - ) - ->andWhere($expressionBuilder->eq('element.predicate', ':label_uri')) - ->andWhere($expressionBuilder->eq('collection.predicate', ':type_uri')) - ->setParameters( - [ - 'label_uri' => OntologyRdfs::RDFS_LABEL, - 'type_uri' => OntologyRdf::RDF_TYPE, - ] - ); - - return $query; - } - - private function enrichQueryWithOrderById(QueryBuilder $query): void - { - $query->addOrderBy('element.id'); - } - - private function enrichWithSelect(ValueCollectionSearchRequest $searchRequest, QueryBuilder $query): void - { - $query->select( - 'collection.object as collection_uri', - 'element.id', - 'element.subject', - 'element.object', - 'element.l_language as datalanguage' - ); - if ($searchRequest->hasOffset()) { - $query->setFirstResult($searchRequest->getOffset()); + $query->setOffset($searchRequest->getOffset()); } if ($searchRequest->hasLimit()) { - $query->setMaxResults($searchRequest->getLimit()); + $query->setLimit($searchRequest->getLimit()); } } - private function enrichQueryWithPropertySearchConditions( - ValueCollectionSearchRequest $searchRequest, - QueryBuilder $query - ): void { - if (!$searchRequest->hasPropertyUri()) { - return; - } - - $expressionBuilder = $query->expr(); - - $query - ->innerJoin( - 'collection', - 'statements', - 'property', - $expressionBuilder->eq('property.object', 'collection.object') - ) - ->andWhere($expressionBuilder->eq('property.subject', ':property_uri')) - ->andWhere($expressionBuilder->eq('property.predicate', ':range_uri')) - ->setParameter('property_uri', $searchRequest->getPropertyUri()) - ->setParameter('range_uri', OntologyRdfs::RDFS_RANGE); - } - private function enrichQueryWithValueCollectionSearchCondition( ValueCollectionSearchRequest $searchRequest, - QueryBuilder $query + QueryInterface $query ): void { - if (!$searchRequest->hasValueCollectionUri()) { - return; + $typeList = []; + + if ($searchRequest->hasPropertyUri()) { + $rangeProperty = new KernelProperty(OntologyRdfs::RDFS_RANGE); + $searchProperty = new KernelProperty($searchRequest->getPropertyUri()); + $typeList = $searchProperty->getPropertyValues($rangeProperty); } - $expressionBuilder = $query->expr(); + if ($searchRequest->hasValueCollectionUri()) { + $typeList[] = $searchRequest->getValueCollectionUri(); + } - $query - ->andWhere($expressionBuilder->eq('collection.object', ':collection_uri')) - ->setParameter('collection_uri', $searchRequest->getValueCollectionUri()); + if (!empty($typeList)) { + $query->add(OntologyRdf::RDF_TYPE)->in(array_unique($typeList)); + } } - private function enrichQueryWithSubject(ValueCollectionSearchRequest $searchRequest, QueryBuilder $query): void + private function enrichQueryWithSubject(ValueCollectionSearchRequest $searchRequest, QueryInterface $query): void { if (!$searchRequest->hasSubject()) { return; } - $query - ->andWhere( - $this->getPersistence()->getPlatForm()->getQueryBuilder()->expr()->like( - 'LOWER(element.object)', - ':subject' - ) - ) - ->setParameter('subject', '%' . $searchRequest->getSubject() . '%'); + $query->add(OntologyRdfs::RDFS_LABEL) + ->contains($searchRequest->getSubject()); } private function enrichQueryWithExcludedValueUris( ValueCollectionSearchRequest $searchRequest, - QueryBuilder $query + QueryInterface $query ): void { if (!$searchRequest->hasExcluded()) { return; } - $query - ->andWhere( - $this->getPersistence()->getPlatForm()->getQueryBuilder()->expr()->notIn( - 'element.subject', - ':excluded_value_uri' - ) - ) - ->setParameter('excluded_value_uri', $searchRequest->getExcluded(), Connection::PARAM_STR_ARRAY); + $query->add(QueryCriterionInterface::VIRTUAL_URI_FIELD) + ->notIn($searchRequest->getExcluded()); } private function enrichQueryWithObjects( ValueCollectionSearchRequest $searchRequest, - QueryBuilder $query + QueryInterface $query ): void { if (!$searchRequest->hasUris()) { return; } - $expressionBuilder = $query->expr(); - - $query - ->andWhere($expressionBuilder->in('element.subject', ':subjects')) - ->setParameter('subjects', $searchRequest->getUris(), Connection::PARAM_STR_ARRAY); + $query->add(QueryCriterionInterface::VIRTUAL_URI_FIELD) + ->in($searchRequest->getUris()); } - private function getPersistence(): SqlPersistence + private function getPersistence(): \common_persistence_Persistence { - /** @noinspection PhpIncompatibleReturnTypeInspection */ - return $this->persistenceManager->getPersistenceById($this->persistenceId); + $ontologyOptions = $this->getModel()->getOptions(); + return $this->persistenceManager->getPersistenceById($ontologyOptions['persistence']); } public function count(ValueCollectionSearchRequest $searchRequest): int { - $query = $this->getQueryBuilder(); + /** @var ComplexSearchService $search */ + $search = $this->getModel()->getSearchInterface(); + $queryBuilder = $search->query(); - $this->enrichWithInitialCondition($query); + $query = $this->getQuery($search, $queryBuilder, $searchRequest); $this->enrichQueryWithValueCollectionSearchCondition($searchRequest, $query); - $result = $query->select('count(element.id) AS c')->execute(); - - $row = $result->fetch(FetchMode::NUMERIC); - return (int) ($row[0] ?? 0); + return $search->getGateway()->count($queryBuilder); } - private function extractLabel( - ValueCollectionSearchRequest $searchRequest, - iterable $labels, - string $subject - ): ?string { - if (!empty($labels[$subject][$searchRequest->getDataLanguage()])) { - return $labels[$subject][$searchRequest->getDataLanguage()]; - } - - if (!empty($labels[$subject][$searchRequest->getDefaultLanguage()])) { - return $labels[$subject][$searchRequest->getDefaultLanguage()]; - } - - return ''; - } + private function getQuery( + ComplexSearchService $search, + QueryBuilderInterface $queryBuilder, + ValueCollectionSearchRequest $searchRequest + ): QueryInterface { + $search->setLanguage( + $queryBuilder, + $searchRequest->getDataLanguage(), + $searchRequest->getDefaultLanguage() + ); - private function retrieveLabels(iterable $data): array - { - $labels = []; - foreach ($data as $element) { - $labels[$element['subject']][$element['datalanguage']] = $element['object']; - } - return $labels; - } + $query = $queryBuilder->newQuery(); + $queryBuilder->setCriteria($query); - private function getQueryBuilder(): QueryBuilder - { - return $this->getPersistence()->getPlatForm()->getQueryBuilder(); + return $query; } } diff --git a/test/unit/models/classes/Lists/DataAccess/Repository/QueryStub.php b/test/unit/models/classes/Lists/DataAccess/Repository/QueryStub.php new file mode 100644 index 0000000000..81d958222a --- /dev/null +++ b/test/unit/models/classes/Lists/DataAccess/Repository/QueryStub.php @@ -0,0 +1,56 @@ +currentProperty = $property; + + return $this; + } + + public function __call($name, $arguments) + { + $value = is_array($arguments) ? (array)reset($arguments) : []; + + $this->criteriaList[] = sprintf( + '%s is %s [%s]', + $this->currentProperty, + $name, + implode(',', $value) + ); + + return $this; + } + + public function getCriteriaList(): array + { + return $this->criteriaList; + } +} diff --git a/test/unit/models/classes/Lists/DataAccess/Repository/RdfValueCollectionRepositoryTest.php b/test/unit/models/classes/Lists/DataAccess/Repository/RdfValueCollectionRepositoryTest.php index 5841fb6c9d..ce086cab11 100644 --- a/test/unit/models/classes/Lists/DataAccess/Repository/RdfValueCollectionRepositoryTest.php +++ b/test/unit/models/classes/Lists/DataAccess/Repository/RdfValueCollectionRepositoryTest.php @@ -24,109 +24,91 @@ namespace oat\tao\test\unit\model\Lists\DataAccess\Repository; -use common_persistence_sql_Platform as SqlPlatform; use common_persistence_SqlPersistence as SqlPersistence; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Driver\ResultStatement; -use Doctrine\DBAL\FetchMode; -use Doctrine\DBAL\Platforms\MySqlPlatform; -use Doctrine\DBAL\Query\Expression\ExpressionBuilder; -use Exception; -use oat\generis\model\OntologyRdf; +use oat\generis\model\data\Ontology; +use oat\generis\model\data\RdfsInterface; +use oat\generis\model\kernel\persistence\smoothsql\search\ComplexSearchService; use oat\generis\model\OntologyRdfs; use oat\generis\persistence\PersistenceManager; use oat\generis\test\MockObject; +use oat\generis\test\ServiceManagerMockTrait; use oat\generis\test\TestCase; +use oat\oatbox\event\EventAggregator; +use oat\oatbox\event\EventManager; +use oat\oatbox\service\ServiceManager; +use oat\search\base\QueryBuilderInterface; +use oat\search\base\SearchGateWayInterface; use oat\tao\model\Lists\Business\Domain\Value; use oat\tao\model\Lists\Business\Domain\ValueCollection; use oat\tao\model\Lists\Business\Domain\ValueCollectionSearchRequest; use oat\tao\model\Lists\DataAccess\Repository\RdfValueCollectionRepository; use oat\tao\model\Lists\DataAccess\Repository\ValueConflictException; -use PHPUnit\Framework\MockObject\MockObject as PhpUnitMockObject; +use oat\tao\model\search\ResultSet; +use oat\tao\test\unit\models\classes\Lists\DataAccess\Repository\QueryStub; class RdfValueCollectionRepositoryTest extends TestCase { + use ServiceManagerMockTrait; + private const PERSISTENCE_ID = 'test'; private const COLLECTION_URI = 'http://example.com'; - /** @var PersistenceManager|MockObject */ - private $persistenceManagerMock; - - /** @var SqlPersistence|MockObject */ - private $persistenceMock; - - /** @var Connection|MockObject */ - private $connectionMock; - - /** @var MySqlPlatform|MockObject */ - private $platformMock; - /** @var RdfValueCollectionRepository|MockObject */ private $sut; - /** @var string[] */ - private $conditions = []; - - /** @var array */ - private $queryParameters = []; + private QueryStub $criterion; - /** @var int[] */ - private $queryParameterTypes = []; + /** + * @var SearchGateWayInterface|MockObject + */ + private $gateway; - /** @var SqlPlatform|PhpUnitMockObject */ - private $sqlPlatformMock; + /** + * @var \core_kernel_persistence_ResourceInterface|MockObject + */ + private $resourceMock; public function setUp(): void { - $this->platformMock = $this->createMock(MySqlPlatform::class); - $this->connectionMock = $this->createPartialMock( - Connection::class, - [ - 'getDatabasePlatform', - 'getExpressionBuilder', - 'executeQuery', - 'connect', - 'beginTransaction', - 'executeStatement' - ] - ); - - $this->sqlPlatformMock = $this->getMockBuilder(SqlPlatform::class) - ->onlyMethods(['rollBack', 'commit']) - ->setConstructorArgs([$this->connectionMock]) - ->getMock(); - - $this->persistenceMock = $this->createMock(SqlPersistence::class); - $this->persistenceManagerMock = $this->createMock(PersistenceManager::class); - - $this->setUpInitialMockExpectations(); + $persistenceManagerMock = $this->mockPersistance(); + $complexSearchService = $this->mockComplexSearch(); + $ontology = $this->mockOntology($complexSearchService); + $serviceManager = $this->mockServiceLocator($ontology); $this->sut = $this->getMockBuilder(RdfValueCollectionRepository::class) - ->onlyMethods(['insert', 'verifyUriUniqueness']) - ->setConstructorArgs([$this->persistenceManagerMock, self::PERSISTENCE_ID]) + ->onlyMethods(['verifyUriUniqueness']) + ->setConstructorArgs([$persistenceManagerMock, self::PERSISTENCE_ID]) ->getMock(); + $this->sut->setServiceLocator($serviceManager); } /** - * @param ValueCollectionSearchRequest $searchRequest - * * @dataProvider findAllDataProvider */ - public function testFindAll(ValueCollectionSearchRequest $searchRequest): void + public function testFindAll(ValueCollectionSearchRequest $searchRequest, array $queryParams): void { - $result = new ValueCollection( - self::COLLECTION_URI, + $expectedResult = new ValueCollection( + $searchRequest->hasValueCollectionUri() ? $searchRequest->getValueCollectionUri() : null, new Value(1, '1', 'Element 1'), new Value(2, '2', 'Element 2') ); - $this->expectQuery($searchRequest, $result); + if ($searchRequest->hasPropertyUri()) { + $this->resourceMock + ->method('getPropertyValues') + ->with( + new \core_kernel_classes_Property($searchRequest->getPropertyUri()), + new \core_kernel_classes_Property(OntologyRdfs::RDFS_RANGE), + [] + ) + ->willReturn(['http://url']); + } - $this->assertEquals( - $result, - $this->sut->findAll($searchRequest) - ); + $this->prepareResponse($searchRequest, $expectedResult); + + $this->assertEquals($expectedResult, $this->sut->findAll($searchRequest), 'Result is incorrect'); + $this->assertEquals($queryParams, $this->criterion->getCriteriaList(), 'Query params are incorrect'); } public function testPersistDuplicates(): void @@ -139,82 +121,71 @@ public function testPersistDuplicates(): void $this->sut->persist($valueCollection); } - public function testPersistRollback(): void - { - $this->sut - ->method('insert') - ->willThrowException(new Exception()); - $this->sqlPlatformMock->expects($this->once())->method('rollback'); - - $valueCollection = new ValueCollection('http://url', new Value(null, '', '')); - - $result = $this->sut->persist($valueCollection); - - $this->assertFalse($result); - } - public function testPersistUpdateNoChanges(): void { - $this->connectionMock - ->expects(static::never()) - ->method('executeStatement'); + $this->resourceMock + ->expects(self::never()) + ->method('createInstance'); + + $this->resourceMock + ->expects(self::never()) + ->method('setPropertyValue'); - $this->connectionMock - ->expects(static::never()) - ->method('executeQuery'); + $this->resourceMock + ->expects(self::never()) + ->method('updateUri'); $value = new Value(666, 'uri', 'label'); $valueCollection = new ValueCollection('http://url', $value); - $result = $this->sut->persist($valueCollection); - - $this->assertTrue($result); + $this->assertTrue($this->sut->persist($valueCollection)); } public function testPersistUpdate(): void { - $this->connectionMock - ->expects(static::once()) - ->method('executeStatement'); + $this->resourceMock + ->expects(self::once()) + ->method('removePropertyValues'); + + $this->resourceMock + ->expects(self::once()) + ->method('setPropertyValue'); $value = new Value(666, 'uri1', 'label'); $value->setLabel('newLabel'); $valueCollection = new ValueCollection('http://url', $value); - $result = $this->sut->persist($valueCollection); - - $this->assertTrue($result); + $this->assertTrue($this->sut->persist($valueCollection)); } public function testPersistUpdateDifferentUris(): void { - $this->connectionMock - ->expects(static::exactly(3)) - ->method('executeStatement'); + $this->resourceMock + ->expects(self::once()) + ->method('updateUri'); $value = new Value(666, 'uri1', 'label'); $value->setUri('uri2'); $valueCollection = new ValueCollection('http://url', $value); - $result = $this->sut->persist($valueCollection); - - $this->assertTrue($result); + $this->assertTrue($this->sut->persist($valueCollection)); } public function testPersistInsert(): void { - $this->sut->expects($this->once())->method('insert'); + $this->resourceMock + ->expects(self::once()) + ->method('createInstance') + ->willReturn(new \core_kernel_classes_Resource('uri1')); $value = new Value(null, 'uri1', 'label'); $valueCollection = new ValueCollection('http://url', $value); - $result = $this->sut->persist($valueCollection); - - $this->assertTrue($result); + $this->assertTrue($this->sut->persist($valueCollection)); } /** @@ -222,36 +193,19 @@ public function testPersistInsert(): void */ public function testCount( int $expected, - $fetchResult, ?string $valueCollectionUri, array $queryParams ): void { - $hasValueCollectionUri = !empty($valueCollectionUri); - - $searchRequest = $this->createMock(ValueCollectionSearchRequest::class); - $searchRequest - ->expects($this->atLeastOnce()) - ->method('hasValueCollectionUri') - ->willReturn($hasValueCollectionUri); - - $searchRequest - ->expects($hasValueCollectionUri ? $this->atLeastOnce() : $this->never()) - ->method('getValueCollectionUri') - ->willReturn($valueCollectionUri); - - $statementMock = $this->createMock(ResultStatement::class); - $statementMock - ->expects($this->once()) - ->method('fetch') - ->with(FetchMode::NUMERIC) - ->willReturn($fetchResult); - - $this->connectionMock->expects($this->once()) - ->method('executeQuery') - ->with($this->createCountQuery($hasValueCollectionUri), $queryParams) - ->willReturn($statementMock); + + $searchRequest = new ValueCollectionSearchRequest(); + $searchRequest->setValueCollectionUri($valueCollectionUri); + + $this->gateway->expects(self::once()) + ->method('count') + ->willReturn($expected); $this->assertEquals($expected, $this->sut->count($searchRequest)); + $this->assertEquals($queryParams, $this->criterion->getCriteriaList()); } public function countDataProvider(): array @@ -259,41 +213,28 @@ public function countDataProvider(): array return [ 'count() with value collection uses its URI for querying' => [ 'expected' => 1, - 'fetchResult' => [1], 'valueCollectionUri' => 'http://value.collection/', 'queryParams' => [ - 'label_uri' => 'http://www.w3.org/2000/01/rdf-schema#label', - 'type_uri' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', - 'collection_uri' => 'http://value.collection/', + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type is in [http://value.collection/]', ], ], 'count() without value collection does not use its URI for querying' => [ 'expected' => 1, - 'fetchResult' => [1], 'valueCollectionUri' => '', - 'queryParams' => [ - 'label_uri' => 'http://www.w3.org/2000/01/rdf-schema#label', - 'type_uri' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', - ] + 'queryParams' => [] ], 'An empty result set is handled gracefully' => [ 'expected' => 0, - 'fetchResult' => [], 'valueCollectionUri' => 'http://value.collection/', 'queryParams' => [ - 'label_uri' => 'http://www.w3.org/2000/01/rdf-schema#label', - 'type_uri' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', - 'collection_uri' => 'http://value.collection/', + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type is in [http://value.collection/]', ], ], 'A null result set is handled gracefully' => [ 'expected' => 0, - 'fetchResult' => null, 'valueCollectionUri' => 'http://value.collection/', 'queryParams' => [ - 'label_uri' => 'http://www.w3.org/2000/01/rdf-schema#label', - 'type_uri' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', - 'collection_uri' => 'http://value.collection/', + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type is in [http://value.collection/]', ], ], ]; @@ -303,39 +244,57 @@ public function findAllDataProvider(): array { return [ 'Bare search request' => [ - (new ValueCollectionSearchRequest())->setDataLanguage('en'), + 'searchRequest' => (new ValueCollectionSearchRequest())->setDataLanguage('en'), + 'queryParams' => [], ], 'Search request with property URI' => [ - (new ValueCollectionSearchRequest()) + 'searchRequest' => (new ValueCollectionSearchRequest()) ->setPropertyUri('https://example.com') ->setDataLanguage('en'), + 'queryParams' => [ + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type is in [http://url]', + ], ], 'Search request with value collection URI' => [ - (new ValueCollectionSearchRequest()) + 'searchRequest' => (new ValueCollectionSearchRequest()) ->setValueCollectionUri(self::COLLECTION_URI) ->setDataLanguage('en'), + 'queryParams' => [ + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type is in [http://example.com]', + ], ], 'Search request with subject' => [ - (new ValueCollectionSearchRequest()) + 'searchRequest' => (new ValueCollectionSearchRequest()) ->setPropertyUri('https://example.com') ->setSubject('test') ->setDataLanguage('en'), + 'queryParams' => [ + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type is in [http://url]', + 'http://www.w3.org/2000/01/rdf-schema#label is contains [test]', + ], ], 'Search request with excluded value URIs' => [ - (new ValueCollectionSearchRequest()) + 'searchRequest' => (new ValueCollectionSearchRequest()) ->setPropertyUri('https://example.com') ->addExcluded('https://example.com#1') ->addExcluded('https://example.com#2') ->setDataLanguage('en'), + 'queryParams' => [ + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type is in [http://url]', + 'uri is notIn [https://example.com#1,https://example.com#2]', + ], ], 'Search request with limit' => [ - (new ValueCollectionSearchRequest()) + 'searchRequest' => (new ValueCollectionSearchRequest()) ->setPropertyUri('https://example.com') ->setLimit(1) ->setDataLanguage('en'), + 'queryParams' => [ + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type is in [http://url]' + ], ], 'Search request with all properties' => [ - (new ValueCollectionSearchRequest()) + 'searchRequest' => (new ValueCollectionSearchRequest()) ->setPropertyUri('https://example.com') ->setValueCollectionUri(self::COLLECTION_URI) ->setSubject('test') @@ -343,200 +302,119 @@ public function findAllDataProvider(): array ->addExcluded('https://example.com#2') ->setLimit(1) ->setDataLanguage('en'), + 'queryParams' => [ + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type is in [http://url,http://example.com]', + 'http://www.w3.org/2000/01/rdf-schema#label is contains [test]', + 'uri is notIn [https://example.com#1,https://example.com#2]', + ], ], ]; } - private function setUpInitialMockExpectations(): void + private function mockPersistance() { - $this->persistenceManagerMock + $persistenceManagerMock = $this->createMock(PersistenceManager::class); + $persistenceMock = $this->createMock(SqlPersistence::class); + + $persistenceManagerMock ->method('getPersistenceById') ->with(self::PERSISTENCE_ID) - ->willReturn($this->persistenceMock); - - $this->persistenceMock - ->method('getPlatform') - ->willReturn($this->sqlPlatformMock); - - $this->connectionMock - ->method('getDatabasePlatform') - ->willReturn($this->platformMock); - - $this->connectionMock - ->method('getExpressionBuilder') - ->willReturn(new ExpressionBuilder($this->connectionMock)); - } - - private function createQuery(ValueCollectionSearchRequest $searchRequest): string - { - $queryParts = [ - $this->createInitialQuery(), - $this->createPropertyUriCondition($searchRequest), - $this->createValueCollectionUriCondition($searchRequest), - $this->createSubjectCondition($searchRequest), - $this->createExcludedCondition($searchRequest), - $this->createCondition(), - $this->createOrderBy(), - $this->createLimit($searchRequest), - ]; - - return implode(' ', array_filter($queryParts)); - } - - private function createInitialQuery(): string - { - $this->queryParameters = [ - 'label_uri' => OntologyRdfs::RDFS_LABEL, - 'type_uri' => OntologyRdf::RDF_TYPE, - ]; - - $this->conditions = [ - '(element.predicate = :label_uri)', - 'AND (collection.predicate = :type_uri)', - ]; - - return implode( - ' ', - [ - 'SELECT collection.object as collection_uri, ' - . 'element.id, element.subject,' - . ' element.object, ' - . 'element.l_language as datalanguage', - 'FROM statements element', - 'INNER JOIN statements collection', - 'ON collection.subject = element.subject', - ] - ); - } + ->willReturn($persistenceMock); + + $persistenceMock + ->method('transactional') + ->willReturnCallback( + function (\Closure $function) { + return $function(); + } + ); - private function createCountQuery(bool $withValueCollection): string - { - return 'SELECT count(element.id) AS c FROM statements element ' . - 'INNER JOIN statements collection ON collection.subject = element.subject ' . - 'WHERE (element.predicate = :label_uri) AND ' . - '(collection.predicate = :type_uri)' . - ($withValueCollection ? ' AND (collection.object = :collection_uri)' : ''); + return $persistenceManagerMock; } - private function createPropertyUriCondition(ValueCollectionSearchRequest $searchRequest): ?string + private function prepareResponse(ValueCollectionSearchRequest $searchRequest, ValueCollection $result): void { - if (!$searchRequest->hasPropertyUri()) { - return null; - } - - $this->queryParameters['property_uri'] = $searchRequest->getPropertyUri(); - $this->queryParameters['range_uri'] = OntologyRdfs::RDFS_RANGE; + $tripleList = []; + foreach ($result as $expectedValue) { + $triple = new \core_kernel_classes_Triple(); - $this->conditions[] = 'AND (property.subject = :property_uri)'; - $this->conditions[] = 'AND (property.predicate = :range_uri)'; - - return implode( - ' ', - [ - 'INNER JOIN statements property', - 'ON property.object = collection.object', - ] - ); - } + $triple->id = $expectedValue->getId(); + $triple->subject = $expectedValue->getUri(); + $triple->object = $expectedValue->getLabel(); - private function createValueCollectionUriCondition(ValueCollectionSearchRequest $searchRequest): ?string - { - if (!$searchRequest->hasValueCollectionUri()) { - return null; + $tripleList[] = $triple; } - $this->queryParameters['collection_uri'] = $searchRequest->getValueCollectionUri(); - - $this->conditions[] = 'AND (collection.object = :collection_uri)'; - - return null; + $this->gateway->expects(self::once()) + ->method('searchTriples') + ->willReturn(new ResultSet($tripleList, count($tripleList))); } - private function createSubjectCondition(ValueCollectionSearchRequest $searchRequest): ?string + public function mockComplexSearch() { - if (!$searchRequest->hasSubject()) { - return null; - } + $this->criterion = new QueryStub(); + $this->gateway = $this->createMock(SearchGateWayInterface::class); - $this->queryParameters['subject'] = '%' . $searchRequest->getSubject() . '%'; + $queryBuilder = $this->createMock(QueryBuilderInterface::class); + $queryBuilder->method('setCriteria')->willReturnSelf(); + $queryBuilder->method('newQuery')->willReturn($this->criterion); - $this->conditions[] = 'AND (LOWER(element.object) LIKE :subject)'; + $complexSearchService = $this->createMock(ComplexSearchService::class); + $complexSearchService->method('query')->willReturn($queryBuilder); + $complexSearchService->method('getGateway')->willReturn($this->gateway); - return null; + return $complexSearchService; } - private function createExcludedCondition(ValueCollectionSearchRequest $searchRequest): ?string - { - if (!$searchRequest->hasExcluded()) { - return null; - } - - $this->queryParameters['excluded_value_uri'] = $searchRequest->getExcluded(); - $this->queryParameterTypes['excluded_value_uri'] = Connection::PARAM_STR_ARRAY; - - $this->conditions[] = 'AND (element.subject NOT IN (:excluded_value_uri))'; - - return null; - } - - private function createOrderBy(): string - { - return 'ORDER BY element.id ASC'; - } - - private function createCondition(): string - { - $conditionStatement = implode(' ', $this->conditions); - - return "WHERE $conditionStatement"; - } - - private function createLimit(ValueCollectionSearchRequest $searchRequest): ?string + /** + * @param $complexSearchService + * + * @return Ontology|MockObject + */ + public function mockOntology($complexSearchService) { - if (!$searchRequest->hasLimit()) { - return null; - } + $this->resourceMock = $this->createMock( + \core_kernel_persistence_ClassInterface::class + ); - return "LIMIT {$searchRequest->getLimit()}"; + $mockRdfs = $this->createMock(RdfsInterface::class); + $mockRdfs->method('getClassImplementation')->willReturn($this->resourceMock); + $mockRdfs->method('getResourceImplementation')->willReturn($this->resourceMock); + $mockRdfs->method('getPropertyImplementation')->willReturn($this->resourceMock); + + $ontology = $this->createMock(Ontology::class); + $ontology->method('getOptions')->willReturn(['persistence' => self::PERSISTENCE_ID]); + $ontology->method('getRdfsInterface')->willReturn($mockRdfs); + $ontology->method('getSearchInterface')->willReturn($complexSearchService); + $ontology->method('getProperty')->willReturnCallback(function ($uri) use ($ontology) { + $property = new \core_kernel_classes_Property($uri); + $property->setModel($ontology); + return $property; + }); + return $ontology; } - private function expectQuery(ValueCollectionSearchRequest $searchRequest, ValueCollection $result): void + /** + * @param $ontology + * + * @return ServiceManager|MockObject + */ + public function mockServiceLocator($ontology) { - $statementMock = $this->createMock(ResultStatement::class); - - $statementMock - ->expects(static::once()) - ->method('fetchAll') - ->willReturn( - $this->domainToRawData($result) - ); + $eventAggregator = new EventAggregator(); + $eventManager = new EventManager(); - $this->connectionMock - ->expects(static::once()) - ->method('executeQuery') - ->with( - $this->createQuery($searchRequest), - $this->queryParameters, - $this->queryParameterTypes, - null - ) - ->willReturn($statementMock); - } + $serviceManager = $this->getServiceManagerMock( + [ + Ontology::SERVICE_ID => $ontology, + EventAggregator::SERVICE_ID => $eventAggregator, + EventManager::SERVICE_ID => $eventManager, + ] + ); - private function domainToRawData(ValueCollection $valueCollection): array - { - $result = []; - - foreach ($valueCollection as $value) { - $result[] = [ - 'collection_uri' => $valueCollection->getUri(), - 'id' => (string)$value->getId(), - 'subject' => $value->getUri(), - 'object' => $value->getLabel(), - 'datalanguage' => 'en', - ]; - } + ServiceManager::setServiceManager($serviceManager); + $eventManager->setServiceLocator($serviceManager); - return $result; + return $serviceManager; } }