diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4da2fc2..e91b40b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,6 +37,7 @@ jobs: run: | export COVERALLS_RUN_LOCALLY=1 export COVERALLS_REPO_TOKEN=${{ secrets.coverallsToken }} - composer require "psr/log:^2" - composer require "php-coveralls/php-coveralls" + rm -fR composer.json composer.lock vendor + composer require php-coveralls/php-coveralls php vendor/bin/php-coveralls -v + diff --git a/composer.json b/composer.json index 75da6c6..547f186 100644 --- a/composer.json +++ b/composer.json @@ -12,12 +12,13 @@ } ], "require": { - "php": ">=8.0 <8.3", - "guzzlehttp/guzzle": "^7 | ^6", - "acdh-oeaw/easyrdf": ">=0.13.5", + "php": ">=8.1 <8.3", + "guzzlehttp/guzzle": "^7", "zozlak/rdf-constants": "^1", "zozlak/query-part": "^2", - "psr/log": "*" + "psr/log": "^3", + "sweetrdf/quick-rdf": "^2", + "sweetrdf/quick-rdf-io": "^1.0.6" }, "autoload": { "psr-4": { diff --git a/src/acdhOeaw/arche/lib/BinaryPayload.php b/src/acdhOeaw/arche/lib/BinaryPayload.php index b7228a4..5ddd945 100644 --- a/src/acdhOeaw/arche/lib/BinaryPayload.php +++ b/src/acdhOeaw/arche/lib/BinaryPayload.php @@ -38,13 +38,8 @@ */ class BinaryPayload { - static private int $guzzleVersion; - static public function guzzleMimetype(string $fileName): ?string { - if (!isset(self::$guzzleVersion)) { - self::$guzzleVersion = (int) InstalledVersions::getVersion('guzzlehttp/psr7'); - } - return self::$guzzleVersion >= 2 || self::$guzzleVersion === 0 ? \GuzzleHttp\Psr7\MimeType::fromFilename($fileName) : \GuzzleHttp\Psr7\mimetype_from_filename($fileName); + return \GuzzleHttp\Psr7\MimeType::fromFilename($fileName); } /** diff --git a/src/acdhOeaw/arche/lib/Config.php b/src/acdhOeaw/arche/lib/Config.php index ae7df36..22c6e64 100644 --- a/src/acdhOeaw/arche/lib/Config.php +++ b/src/acdhOeaw/arche/lib/Config.php @@ -26,7 +26,6 @@ namespace acdhOeaw\arche\lib; -use acdhOeaw\arche\lib\exception\RepoLibException; use function GuzzleHttp\json_decode; use function GuzzleHttp\json_encode; @@ -36,26 +35,43 @@ * * @author zozlak * @property Config $accessControl + * @property string $accessRestriction + * @property string $accessRestrictionAgg + * @property string $accessRole + * @property string $binarySizeCumulative * @property array $addNamespaces * @property string $address * @property string $admin * @property array $adminRoles * @property array $allowedRoles - * @property array $assignRoles + * @property array> $assignRoles * @property Config $autoAddIds * @property Config $auth * @property array $authMethods * @property string $binaryModificationDate * @property string $binaryModificationUser * @property string $binarySize + * @property string $binarySizeCumulative * @property string $class + * @property Config $classes * @property int $checkInterval + * @property bool $checkAutoCreatedResources + * @property Config $checkRanges + * @property bool $checkUnknownProperties + * @property bool $checkVocabularyValues + * @property string $clarinSet + * @property string $clarinSetProperty * @property array $classLoader + * @property string $cmdi + * @property string $cmdiPid + * @property string $collection * @property int $configDate * @property string $connStr * @property string $cors + * @property string $countCumulative * @property array $copying * @property Config $create + * @property string $createValue * @property string $creationDate * @property string $creationUser * @property array $creatorRights @@ -73,8 +89,10 @@ * @property string $delete * @property array $denyNamespaces * @property string $dir + * @property Config $doorkeeper * @property bool $enforceCompleteness * @property bool $enforceOnMetadata + * @property Config $epicPid * @property bool $exceptionOnTimeout * @property string $file * @property string $fileName @@ -86,17 +104,22 @@ * @property Config $handlers * @property string $hash * @property string $hashAlgorithm - * @property Config $headers + * @property object $headers * @property string $highlighting * @property string $host * @property Config $httpBasic * @property object $httpHeader * @property string $id * @property string $indexing + * @property string $isNewVersionOf * @property string $label * @property string $lang + * @property string $latitude + * @property string $longitude * @property string $level * @property int $levels + * @property string $license + * @property string $licenseAgg * @property Config $logging * @property quickRdf\DatasetNode $meta * @property string $metadata @@ -113,8 +136,10 @@ * @property string $modeDir * @property string $modificationDate * @property string $modificationUser - * @property array $namespaces + * @property Config $namespaces * @property array $nonRelationProperties + * @property string $licenseAgg + * @property string $ontology * @property array $options * @property int $outputTriplesCache * @property array $parameters @@ -122,14 +147,22 @@ * @property string $path * @property string $pathBase * @property string $password + * @property string $pid * @property int $port + * @property string $prefix + * @property array $properties + * @property string $pswd * @property string $publicRole * @property string $queue * @property Config|null $rabbitMq * @property string $read * @property array $relativesProperties + * @property string $resolver + * @property string $resource * @property array $resourceProperties * @property Config $rest + * @property string $rolePublic + * @property string $roleAcademic * @property Config $schema * @property string $searchCount * @property string $searchOrder @@ -147,18 +180,26 @@ * @property string $tikaLocation * @property int $timeout * @property int $lockTimeout + * @property string $searchFts + * @property string $searchFtsProperty + * @property string $searchFtsQuery * @property int $statementTimeout * @property string $tmpDir + * @property string $topCollection * @property Config $transactionController * @property string $transactionId * @property string $type * @property string $uri + * @property string $url * @property string $urlBase * @property string $user * @property string $userCol * @property string $value * @property bool $verifyCert * @property float $version + * @property string $vid + * @property string $withReferences + * @property string $wkt */ class Config { diff --git a/src/acdhOeaw/arche/lib/Repo.php b/src/acdhOeaw/arche/lib/Repo.php index c7d8aa7..d0714b9 100644 --- a/src/acdhOeaw/arche/lib/Repo.php +++ b/src/acdhOeaw/arche/lib/Repo.php @@ -28,8 +28,6 @@ use Generator; use RuntimeException; -use EasyRdf\Graph; -use EasyRdf\Resource; use GuzzleHttp\Client; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; @@ -37,12 +35,22 @@ use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Promise\RejectedPromise; use Psr\Http\Message\ResponseInterface; +use rdfInterface\DatasetInterface; +use rdfInterface\DatasetNodeInterface; +use rdfInterface\QuadInterface; +use rdfInterface\TermInterface; +use quickRdf\Dataset; +use quickRdf\DatasetNode; +use quickRdf\DataFactory as DF; +use quickRdfIo\NQuadsSerializer; +use quickRdfIo\Util as RdfIoUtil; +use termTemplates\QuadTemplate as QT; use acdhOeaw\arche\lib\exception\Conflict; use acdhOeaw\arche\lib\exception\Deleted; use acdhOeaw\arche\lib\exception\NotFound; use acdhOeaw\arche\lib\exception\AmbiguousMatch; use acdhOeaw\arche\lib\exception\RepoLibException; -use acdhOeaw\arche\lib\promise\GeneratorPromise; +use acdhOeaw\arche\lib\promise\RepoResourceGeneratorPromise; use acdhOeaw\arche\lib\promise\GraphPromise; use acdhOeaw\arche\lib\promise\ResponsePromise; use acdhOeaw\arche\lib\promise\RepoResourcePromise; @@ -244,14 +252,14 @@ public function __construct(string $baseUrl, array $guzzleOptions = []) { throw new RepoLibException("This version of arche-lib requires ARCHE version " . self::ARCHE_CORE_MIN_VERSION . " while the repository version is $version"); } $this->schema = new Schema($config->schema); - $this->headers = new Schema($config->rest->headers); + $this->headers = $config->rest->headers; $this->baseUrl = $config->rest->urlBase . $config->rest->pathBase; } /** * Creates a repository resource. * - * @param Resource $metadata resource metadata + * @param DatasetNodeInterface $metadata resource metadata * @param BinaryPayload $payload resource binary payload (can be null) * @param string $class an optional class of the resulting object representing the resource * (to be used by extension libraries) @@ -261,7 +269,7 @@ public function __construct(string $baseUrl, array $guzzleOptions = []) { * read mode denoted by the $readMode parameter * @return RepoResource */ - public function createResource(Resource $metadata, + public function createResource(DatasetNodeInterface $metadata, BinaryPayload $payload = null, string $class = null, string $readMode = RepoResourceInterface::META_RESOURCE, @@ -272,7 +280,7 @@ public function createResource(Resource $metadata, /** * Asynchronous version of createResource() * - * @param Resource $metadata + * @param DatasetNodeInterface $metadata * @param BinaryPayload $payload * @param string $class * @param string $readMode @@ -280,14 +288,15 @@ public function createResource(Resource $metadata, * @return RepoResourcePromise * @see createResource() */ - public function createResourceAsync(Resource $metadata, + public function createResourceAsync(DatasetNodeInterface $metadata, BinaryPayload $payload = null, string $class = null, string $readMode = RepoResourceInterface::META_RESOURCE, ?string $parentProperty = null): RepoResourcePromise { - $graph = new Graph(); - $metadata = $body = $metadata->copy([], '/^$/', $this->baseUrl, $graph); - $body = $graph->serialise('application/n-triples'); + $sbj = DF::namedNode($this->baseUrl); + $metadata = $metadata->map(fn(QuadInterface $x) => $x->withSubject($sbj))->withNode($sbj); + $serializer = new NQuadsSerializer(); + $body = $serializer->serialize($metadata); $headers = ['Content-Type' => 'application/n-triples']; $req = new Request('post', $this->baseUrl . 'metadata', $headers, $body); $readModeTmp = $payload === null ? $readMode : RepoResourceInterface::META_NONE; @@ -459,21 +468,17 @@ public function getResourceByIdsAsync(array $ids, string $class = null): RepoRes $req = new Request('post', $url, $headers, $body); $promise = $this->sendRequestAsync($req); $promise = $promise->then(function (ResponseInterface $resp) use ($class) { - $format = explode(';', $resp->getHeader('Content-Type')[0] ?? '')[0]; - $graph = new Graph(); - $graph->parse($resp->getBody(), $format); - $matches = $graph->resourcesMatching($this->schema->searchMatch); + $graph = new Dataset(); + $graph->add(RdfIoUtil::parse($resp, new DF())); + $matches = $graph->listSubjects(new QT(predicate: $this->schema->searchMatch))->getValues(); switch (count($matches)) { case 0: return new RejectedPromise(new NotFound()); case 1; $class = $class ?? self::$resourceClass; - return new $class($matches[0]->getUri(), $this); + return new $class($matches[0], $this); default: - $uris = implode(', ', array_map(function ($x) { - return $x->getUri(); - }, $matches)); - return new RejectedPromise(new AmbiguousMatch("Many resources match the search: $uris")); + return new RejectedPromise(new AmbiguousMatch("Many resources match the search: " . implode(', ', $matches))); } }); return new RepoResourcePromise($promise); @@ -498,17 +503,17 @@ public function getResourcesBySqlQuery(string $query, array $parameters, * @param string $query * @param array $parameters * @param SearchConfig $config - * @return GeneratorPromise + * @return RepoResourceGeneratorPromise * @see getResourcesBySqlQuery() */ public function getResourcesBySqlQueryAsync(string $query, array $parameters, - SearchConfig $config): GeneratorPromise { + SearchConfig $config): RepoResourceGeneratorPromise { $promise = $this->getGraphBySqlQueryAsync($query, $parameters, $config); - $promise = $promise->then(function (Graph $graph) use ($config): Generator { + $promise = $promise->then(function (Dataset $graph) use ($config): Generator { yield from $this->extractResourcesFromGraph($graph, $config); }); - return new GeneratorPromise($promise); + return new RepoResourceGeneratorPromise($promise); } /** @@ -528,16 +533,16 @@ public function getResourcesBySearchTerms(array $searchTerms, * * @param array $searchTerms * @param SearchConfig $config - * @return GeneratorPromise + * @return RepoResourceGeneratorPromise * @see getResourcesBySearchTerms() */ public function getResourcesBySearchTermsAsync(array $searchTerms, - SearchConfig $config): GeneratorPromise { + SearchConfig $config): RepoResourceGeneratorPromise { $promise = $this->getGraphBySearchTermsAsync($searchTerms, $config); - $promise = $promise->then(function (Graph $graph) use ($config): Generator { + $promise = $promise->then(function (Dataset $graph) use ($config): Generator { yield from $this->extractResourcesFromGraph($graph, $config); }); - return new GeneratorPromise($promise); + return new RepoResourceGeneratorPromise($promise); } /** @@ -546,10 +551,10 @@ public function getResourcesBySearchTermsAsync(array $searchTerms, * @param string $query * @param array $parameters * @param SearchConfig $config - * @return Graph + * @return DatasetInterface */ public function getGraphBySqlQuery(string $query, array $parameters, - SearchConfig $config): Graph { + SearchConfig $config): DatasetInterface { return $this->getGraphBySqlQueryAsync($query, $parameters, $config)->wait(true) ?? throw new RuntimeException('Promise returned null'); } @@ -564,19 +569,20 @@ public function getGraphBySqlQuery(string $query, array $parameters, */ public function getGraphBySqlQueryAsync(string $query, array $parameters, SearchConfig $config): GraphPromise { - $headers = [ + $headers = [ 'Accept' => 'application/n-triples', 'Content-Type' => 'application/x-www-form-urlencoded', ]; - $headers = array_merge($headers, $config->getHeaders($this)); - $body = array_merge( + $headers = array_merge($headers, $config->getHeaders($this)); + $parameters = array_map(fn($x) => $x instanceof TermInterface ? $x->getValue() : $x, $parameters); + $body = array_merge( ['sql' => $query, 'sqlParam' => $parameters], $config->toArray() ); - $body = http_build_query($body); - $req = new Request('post', $this->baseUrl . 'search', $headers, $body); - $promise = $this->sendRequestAsync($req); - $promise = $promise->then(function (ResponseInterface $resp) use ($config): Graph { + $body = http_build_query($body); + $req = new Request('post', $this->baseUrl . 'search', $headers, $body); + $promise = $this->sendRequestAsync($req); + $promise = $promise->then(function (ResponseInterface $resp) use ($config): Dataset { return $this->parseSearchResponse($resp, $config); }); return new GraphPromise($promise); @@ -586,10 +592,10 @@ public function getGraphBySqlQueryAsync(string $query, array $parameters, * * @param array $searchTerms * @param SearchConfig $config - * @return Graph + * @return Dataset */ public function getGraphBySearchTerms(array $searchTerms, - SearchConfig $config): Graph { + SearchConfig $config): Dataset { return $this->getGraphBySearchTermsAsync($searchTerms, $config)->wait(true) ?? throw new RuntimeException('Promise returned null'); } @@ -618,7 +624,7 @@ public function getGraphBySearchTermsAsync(array $searchTerms, $req = new Request('post', $this->baseUrl . 'search', $headers, $body); $promise = $this->sendRequestAsync($req); - $promise = $promise->then(function (ResponseInterface $resp) use ($config): Graph { + $promise = $promise->then(function (ResponseInterface $resp) use ($config): Dataset { return $this->parseSearchResponse($resp, $config); }); return new GraphPromise($promise); @@ -711,53 +717,21 @@ public function inTransaction(): bool { * * @param ResponseInterface $resp PSR-7 search request response * @param SearchConfig $config search configuration object - * @return Graph + * @return Dataset */ private function parseSearchResponse(ResponseInterface $resp, - SearchConfig $config): Graph { - $graph = new Graph(); - $body = (string) $resp->getBody(); - if (!empty($body)) { - $format = explode(';', $resp->getHeader('Content-Type')[0] ?? '')[0]; - $graph->parse($body, $format); - - $config->count = (int) ((string) $graph->resource($this->getBaseUrl())->getLiteral($this->getSchema()->searchCount)); - } else { - $config->count = 0; - } + SearchConfig $config): Dataset { + $graph = new Dataset(); + $graph->add(RdfIoUtil::parse($resp, new DF())); + $config->count = (int) ($graph->getObjectValue(new QT(DF::namedNode($this->getBaseUrl()), $this->getSchema()->searchCount)) ?? 0); return $graph; } - /** - * Extracts collection of RepoResource objects from the EasyRdf graph being - * parsed from a search response. - * - * @param Graph $graph graph returned by the parseSearchResponse() method - * @param SearchConfig $config search configuration object - * @return Generator - */ - private function extractResourcesFromGraph(Graph $graph, - SearchConfig $config): Generator { - $class = $config->class ?? self::$resourceClass; - $resources = $graph->resourcesMatching($this->schema->searchMatch); - - $this->sortMatchingResources($resources, $this->schema->searchOrder); - - foreach ($resources as $i) { - $i->delete($this->schema->searchMatch); - $i->delete($this->schema->searchOrder); - $obj = new $class($i->getUri(), $this); - /** @var RepoResource $obj */ - $obj->setGraph($i); - yield $obj; - } - } - private function withReadHeaders(Request $request, string $mode, ?string $parentProperty): Request { return $request-> withHeader('Accept', 'application/n-triples')-> withHeader($this->getHeaderName('metadataReadMode'), $mode)-> - withHeader($this->getHeaderName('metadataParentProperty'), $parentProperty ?? $this->schema->parent); + withHeader($this->getHeaderName('metadataParentProperty'), $parentProperty ?? $this->schema->parent->getValue()); } } diff --git a/src/acdhOeaw/arche/lib/RepoDb.php b/src/acdhOeaw/arche/lib/RepoDb.php index a9fedae..cbacb43 100644 --- a/src/acdhOeaw/arche/lib/RepoDb.php +++ b/src/acdhOeaw/arche/lib/RepoDb.php @@ -30,8 +30,9 @@ use PDO; use PDOException; use PDOStatement; -use EasyRdf\Graph; -use EasyRdf\Literal; +use quickRdf\Dataset; +use quickRdf\DataFactory as DF; +use termTemplates\QuadTemplate as QT; use zozlak\RdfConstants as RDF; use zozlak\queryPart\QueryPart; use acdhOeaw\arche\lib\RepoResourceInterface AS RRI; @@ -55,7 +56,7 @@ class RepoDb implements RepoInterface { * * @var string */ - static public $resourceClass = '\acdhOeaw\arche\lib\RepoResourceDb'; + static public $resourceClass = RepoResourceDb::class; /** * Creates a repository object instance from a given configuration file. @@ -151,12 +152,12 @@ public function getResourceByIds(array $ids, string $class = null): RepoResource * * @param array $searchTerms * @param SearchConfig $config - * @return Generator + * @return Generator */ public function getResourcesBySearchTerms(array $searchTerms, SearchConfig $config): Generator { $graph = $this->getGraphBySearchTerms($searchTerms, $config); - yield from $this->parseSearchGraph($graph, $config); + yield from $this->extractResourcesFromGraph($graph, $config); } /** @@ -165,25 +166,25 @@ public function getResourcesBySearchTerms(array $searchTerms, * @param string $query * @param array $parameters * @param SearchConfig $config - * @return Generator + * @return Generator */ public function getResourcesBySqlQuery(string $query, array $parameters, SearchConfig $config): Generator { $graph = $this->getGraphBySqlQuery($query, $parameters, $config); - yield from $this->parseSearchGraph($graph, $config); + yield from $this->extractResourcesFromGraph($graph, $config); } /** * * @param array $searchTerms * @param SearchConfig $config - * @return Graph + * @return Dataset */ public function getGraphBySearchTerms(array $searchTerms, - SearchConfig $config): Graph { + SearchConfig $config): Dataset { $query = $this->getPdoStatementBySearchTerms($searchTerms, $config); $graph = $this->parsePdoStatement($query); - $config->count = (int) ((string) $graph->resource($this->getBaseUrl())->getLiteral($this->schema->searchCount)); + $config->count = (int) ($graph->getObjectValue(new QT(DF::namedNode($this->getBaseUrl()), $this->getSchema()->searchCount)) ?? 0); return $graph; } @@ -192,13 +193,13 @@ public function getGraphBySearchTerms(array $searchTerms, * @param string $query * @param array $parameters * @param SearchConfig $config - * @return Graph + * @return Dataset */ public function getGraphBySqlQuery(string $query, array $parameters, - SearchConfig $config): Graph { + SearchConfig $config): Dataset { $query = $this->getPdoStatementBySqlQuery($query, $parameters, $config); $graph = $this->parsePdoStatement($query); - $config->count = (int) ((string) $graph->resource($this->getBaseUrl())->getLiteral($this->schema->searchCount)); + $config->count = (int) ($graph->getObjectValue(new QT(DF::namedNode($this->getBaseUrl()), $this->getSchema()->searchCount)) ?? 0); return $graph; } @@ -453,9 +454,9 @@ private function getFtsQuery(SearchConfig $cfg): array { FROM fts "; $param = [ - $this->schema->searchFts, RDF::XSD_STRING, - $this->schema->searchFtsProperty, - $this->schema->searchFtsQuery, RDF::XSD_STRING, + $this->schema->searchFts->getValue(), RDF::XSD_STRING, + $this->schema->searchFtsProperty->getValue(), + $this->schema->searchFtsQuery->getValue(), RDF::XSD_STRING, ]; } return [new QueryPart($withQuery, $withParam), new QueryPart($query, $param)]; @@ -541,57 +542,33 @@ private function getPagingQuery(SearchConfig $config): QueryPart { * Parses SQL query results containing resources metadata into an RDF graph. * * @param PDOStatement $query - * @return Graph + * @return Dataset */ - public function parsePdoStatement(PDOStatement $query): Graph { + public function parsePdoStatement(PDOStatement $query): Dataset { $idProp = $this->schema->id; $baseUrl = $this->getBaseUrl(); - $graph = new Graph(); + $graph = new Dataset(); while ($triple = $query->fetchObject()) { - $triple->id = $baseUrl . $triple->id; - $resource = $graph->resource($triple->id); + $sbj = DF::namedNode($baseUrl . $triple->id); + $pred = $triple->type === 'ID' ? $idProp : DF::namedNode($triple->property); switch ($triple->type) { case 'ID': - $resource->addResource($idProp, $triple->value); + case 'URI': + $obj = DF::namedNode($triple->value); break; case 'REL': - $resource->addResource($triple->property, $baseUrl . $triple->value); - break; - case 'URI': - $resource->addResource($triple->property, $triple->value); + $obj = DF::namedNode($baseUrl . $triple->value); break; case 'GEOM': $triple->type = RDF::XSD_STRING; default: - $type = empty($triple->lang) & $triple->type !== RDF::XSD_STRING ? $triple->type : null; - $literal = new Literal($triple->value, !empty($triple->lang) ? $triple->lang : null, $type); - $resource->add($triple->property, $literal); + $obj = DF::literal($triple->value, $triple->lang, $triple->type); } + $graph->add(DF::quad($sbj, $pred, $obj)); } return $graph; } - /** - * - * @param Graph $graph - * @param SearchConfig $config - * @return Generator - */ - private function parseSearchGraph(Graph $graph, SearchConfig $config): Generator { - $class = $config->class ?? RepoResourceDb::class; - - $resources = $graph->resourcesMatching($this->schema->searchMatch); - $this->sortMatchingResources($resources, $this->schema->searchOrder); - foreach ($resources as $i) { - $i->delete($this->schema->searchMatch); - $i->delete($this->schema->searchOrder); - $obj = new $class($i->getUri(), $this); - /** @var RepoResourceDb $obj */ - $obj->setGraph($i); - yield $obj; - } - } - /** * @param string $mode * @param string|null $parent diff --git a/src/acdhOeaw/arche/lib/RepoInterface.php b/src/acdhOeaw/arche/lib/RepoInterface.php index daa0549..ab33820 100644 --- a/src/acdhOeaw/arche/lib/RepoInterface.php +++ b/src/acdhOeaw/arche/lib/RepoInterface.php @@ -26,8 +26,8 @@ namespace acdhOeaw\arche\lib; -use EasyRdf\Graph; use Generator; +use rdfInterface\DatasetInterface; use acdhOeaw\arche\lib\SearchTerm; /** @@ -107,18 +107,18 @@ public function getResourcesBySearchTerms(array $searchTerms, * @param string $query * @param array $parameters * @param SearchConfig $config - * @return Graph + * @return DatasetInterface */ public function getGraphBySqlQuery(string $query, array $parameters, - SearchConfig $config): Graph; + SearchConfig $config): DatasetInterface; /** * Returns RDF metadata graph of the search results. * * @param array $searchTerms * @param SearchConfig $config - * @return Graph + * @return DatasetInterface */ public function getGraphBySearchTerms(array $searchTerms, - SearchConfig $config): Graph; + SearchConfig $config): DatasetInterface; } diff --git a/src/acdhOeaw/arche/lib/RepoResource.php b/src/acdhOeaw/arche/lib/RepoResource.php index 8b4496f..1683580 100644 --- a/src/acdhOeaw/arche/lib/RepoResource.php +++ b/src/acdhOeaw/arche/lib/RepoResource.php @@ -27,12 +27,16 @@ namespace acdhOeaw\arche\lib; use RuntimeException; -use EasyRdf\Graph; -use EasyRdf\Serialiser\Ntriples; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use GuzzleHttp\Promise\PromiseInterface; use Psr\Http\Message\ResponseInterface; +use quickRdf\DataFactory as DF; +use quickRdf\Dataset; +use quickRdf\DatasetNode; +use quickRdfIo\NQuadsSerializer; +use quickRdfIo\Util as RdfIoUtil; +use termTemplates\QuadTemplate as QT; use acdhOeaw\arche\lib\exception\RepoLibException; use acdhOeaw\arche\lib\promise\ResponsePromise; @@ -86,9 +90,9 @@ public function __construct(string $url, RepoInterface $repo) { if (!$repo instanceof Repo) { throw new RepoLibException('The RepoResource object can be created only with a Repo repository connection handle'); } - $this->url = $url; - $this->repo = $repo; - $this->repoInt = $repo; + $this->repo = $repo; + $this->repoInt = $repo; + $this->metadata = new DatasetNode(DF::namedNode($url)); } /** @@ -107,7 +111,7 @@ public function getContent(): ResponseInterface { * @see getContent() */ public function getContentAsync(): ResponsePromise { - $request = new Request('get', $this->url); + $request = new Request('get', (string) $this->getUri()); return $this->repo->sendRequestAsync($request); } @@ -150,7 +154,7 @@ public function updateContentAsync(BinaryPayload $content, ?string $parentProperty = null, array $resourceProperties = [], array $relativesProperties = []): PromiseInterface { - $request = new Request('put', $this->url); + $request = new Request('put', (string) $this->getUri()); $request = $content->attachTo($request); $request = $this->withReadHeaders($request, $readMode, $parentProperty, $resourceProperties, $relativesProperties); $promise = $this->repo->sendRequestAsync($request); @@ -167,7 +171,7 @@ public function updateContentAsync(BinaryPayload $content, */ public function hasBinaryContent(): bool { $this->loadMetadata(); - return (int) ((string) $this->metadata?->getLiteral($this->repo->getSchema()->binarySize)) > 0; + return $this->metadata->any(new QT(predicate: $this->repo->getSchema()->binarySize)); } /** @@ -217,8 +221,8 @@ public function updateMetadataAsync(string $updateMode = self::UPDATE_MERGE, 'Content-Type' => 'application/n-triples', $this->repo->getHeaderName('metadataWriteMode') => $updateMode, ]; - $body = $this->metadata?->getGraph()->serialise('application/n-triples'); - $req = new Request('patch', $this->url . '/metadata', $headers, $body); + $body = (new NQuadsSerializer())->serialize($this->metadata); + $req = new Request('patch', (string) $this->getUri() . '/metadata', $headers, $body); $req = $this->withReadHeaders($req, $readMode, $parentProperty, $resourceProperties, $relativesProperties); $promise = $this->repo->sendRequestAsync($req); $promise = $promise->then(function (ResponseInterface $resp): void { @@ -244,20 +248,17 @@ public function updateMetadataAsync(string $updateMode = self::UPDATE_MERGE, public function delete(bool $tombstone = false, bool $references = false, string $recursiveProperty = ''): array { $result = $this->deleteAsync($tombstone, $references, $recursiveProperty)->wait(); - $g = new Graph(); + $g = new Dataset(); if (!is_array($result)) { $result = [$result]; } - foreach ($result as $i) { - $g->parse((string) $i->getBody()); - } $deleted = []; - foreach ($g->resources() as $i) { - if (count($i->propertyUris()) > 0) { - $deleted[] = $i->getUri(); + foreach ($result as $i) { + foreach (RdfIoUtil::parse($i, new DF()) as $triple) { + $deleted[$triple->getSubject()->getValue()] = ''; } } - return $deleted; + return array_keys($deleted); } /** @@ -281,31 +282,28 @@ public function deleteAsync(bool $tombstone = false, if (!empty($recursiveProperty)) { $headers[$this->repo->getHeaderName('metadataParentProperty')] = $recursiveProperty; } - $req = new Request('delete', $this->getUri(), $headers); + $req = new Request('delete', (string) $this->getUri(), $headers); $promise = $this->repo->sendRequestAsync($req); - $this->metadata = null; + $this->metadata = $this->metadata->withDataset(new Dataset()); if ($tombstone) { $promise = $promise->then(function (ResponseInterface $resp): array { - $format = explode(';', $resp->getHeader('Content-Type')[0] ?? '')[0]; - $idProp = Ntriples::escapeIri($this->repo->getSchema()->id); - - $graph = new Graph(); - $graph->parse((string) $resp->getBody(), $format); $respPromises = []; - foreach ($graph->resources() as $i) { - if (count($i->propertyUris()) > 0) { - $req = new Request('delete', $i->getUri() . '/tombstone'); - $resp = $this->repo->sendRequest($req); - if ($resp->getStatusCode() === 204) { - // fake response of a delete without tombstone removal - $uri = Ntriples::escapeIri($i->getUri()); - $resp = $resp-> - withBody(\GuzzleHttp\Psr7\Utils::streamFor("<$uri> <$idProp> <$uri> ."))-> - withHeader('Content-Type', 'application/n-triples'); - } - $respPromises[] = $resp; + $deleted = []; + foreach (RdfIoUtil::parse($resp, new DF()) as $quad) { + $deleted[$quad->getSubject()->getValue()] = $quad->getSubject(); + } + foreach ($deleted as $i) { + $req = new Request('delete', $i->getValue() . '/tombstone'); + $resp = $this->repo->sendRequest($req); + if ($resp->getStatusCode() === 204) { + // fake response of a delete without tombstone removal + $body = NQuadsSerializer::serializeQuad(DF::quad($i, $this->repo->getSchema()->id, $i)); + $resp = $resp-> + withBody(\GuzzleHttp\Psr7\Utils::streamFor($body))-> + withHeader('Content-Type', 'application/n-triples'); } + $respPromises[] = $resp; } return $respPromises; }); @@ -353,8 +351,8 @@ public function loadMetadataAsync(bool $force = false, ?string $parentProperty = null, array $resourceProperties = [], array $relativesProperties = []): ?PromiseInterface { - if ($this->metadata === null || $force) { - $req = new Request('get', $this->url . '/metadata'); + if (count($this->metadata) === 0 || $force) { + $req = new Request('get', (string) $this->getUri() . '/metadata'); $req = $this->withReadHeaders($req, $mode, $parentProperty, $resourceProperties, $relativesProperties); $promise = $this->repo->sendRequestAsync($req); $promise = $promise->then(function (ResponseInterface $resp): void { @@ -415,7 +413,7 @@ public function mergeAsync(string $targetResId, $request = $this->withReadHeaders($request, $readMode, $parentProperty, $resourceProperties, $relativesProperties); $promise = $this->repo->sendRequestAsync($request); $promise = $promise->then(function (ResponseInterface $resp) use ($targetRes): void { - $this->url = $targetRes->getUri(); + $this->metadata = $this->metadata->withNode(DF::namedNode($targetRes->getUri())); $this->parseMetadata($resp); }); return $promise; @@ -428,19 +426,18 @@ public function mergeAsync(string $targetResId, * @return void */ private function parseMetadata(ResponseInterface $resp): void { - $format = explode(';', $resp->getHeader('Content-Type')[0] ?? '')[0]; - $graph = new Graph(); + $graph = new Dataset(); switch ($resp->getStatusCode()) { case 200: case 201: - $graph->parse($resp->getBody(), $format); + $graph->add(RdfIoUtil::parse($resp, new DF())); break; case 204: break; default: throw new RepoLibException("Invalid response status code: " . $resp->getStatusCode() . " with body: " . $resp->getBody()); } - $this->metadata = $graph->resource($this->url); + $this->metadata = $this->metadata->withDataset($graph); $this->metaSynced = true; } @@ -460,7 +457,7 @@ private function withReadHeaders(Request $request, string $mode, $request = $request-> withHeader('Accept', 'application/n-triples')-> withHeader($this->repo->getHeaderName('metadataReadMode'), $mode)-> - withHeader($this->repo->getHeaderName('metadataParentProperty'), $parentProperty ?? $this->repo->getSchema()->parent); + withHeader($this->repo->getHeaderName('metadataParentProperty'), $parentProperty ?? $this->repo->getSchema()->parent->getValue()); if (count($resourceProperties) > 0) { $request = $request->withHeader($this->repo->getHeaderName('resourceProperties'), implode(',', $resourceProperties)); } diff --git a/src/acdhOeaw/arche/lib/RepoResourceDb.php b/src/acdhOeaw/arche/lib/RepoResourceDb.php index 893ac7d..93af54a 100644 --- a/src/acdhOeaw/arche/lib/RepoResourceDb.php +++ b/src/acdhOeaw/arche/lib/RepoResourceDb.php @@ -27,6 +27,8 @@ namespace acdhOeaw\arche\lib; use PDOStatement; +use quickRdf\DataFactory as DF; +use quickRdf\DatasetNode; use acdhOeaw\arche\lib\exception\RepoLibException; /** @@ -54,10 +56,10 @@ public function __construct(string $urlOrId, RepoInterface $repo) { if (!is_numeric($urlOrId)) { $urlOrId = preg_replace('/^.*[^0-9]/', '', $urlOrId); } - $this->id = (int) $urlOrId; - $this->repo = $repo; - $this->repoInt = $repo; - $this->url = $this->repo->getBaseUrl() . $this->id; + $this->id = (int) $urlOrId; + $this->repo = $repo; + $this->repoInt = $repo; + $this->metadata = new DatasetNode(DF::namedNode($this->repo->getBaseUrl() . $this->id)); } /** @@ -84,12 +86,11 @@ public function loadMetadata(bool $force = false, string $parentProperty = null, array $resourceProperties = [], array $relativesProperties = []): void { - if (!$force && $this->metadata !== null) { - return; + if ($force || count($this->metadata) === 0) { + $stmt = $this->getMetadataStatement($mode, $parentProperty, $resourceProperties, $relativesProperties); + $graph = $this->repo->parsePdoStatement($stmt); + $this->metadata = $this->metadata->withDataset($graph); } - $stmt = $this->getMetadataStatement($mode, $parentProperty, $resourceProperties, $relativesProperties); - $graph = $this->repo->parsePdoStatement($stmt); - $this->metadata = $graph->resource($this->getUri()); } /** diff --git a/src/acdhOeaw/arche/lib/RepoResourceInterface.php b/src/acdhOeaw/arche/lib/RepoResourceInterface.php index d283c0a..97b3681 100644 --- a/src/acdhOeaw/arche/lib/RepoResourceInterface.php +++ b/src/acdhOeaw/arche/lib/RepoResourceInterface.php @@ -26,7 +26,9 @@ namespace acdhOeaw\arche\lib; -use EasyRdf\Resource; +use rdfInterface\DatasetInterface; +use rdfInterface\DatasetNodeInterface; +use rdfInterface\TermInterface; /** * @@ -106,15 +108,11 @@ public function __construct(string $url, RepoInterface $repo); /** * Returns the repository resource URL. - * - * @return string */ - public function getUri(): string; + public function getUri(): TermInterface; /** * Returns repository connection object associated with the given resource object. - * - * @return RepoInterface */ public function getRepo(): RepoInterface; @@ -157,10 +155,10 @@ public function loadMetadata(bool $force = false, * A reference to the metadata is returned meaning adjusting the returned object * automatically affects the resource metadata. * - * @return Resource + * @return DatasetNodeInterface * @see getMetadata() */ - public function getGraph(): Resource; + public function getGraph(): DatasetNodeInterface; /** * Returns resource metadata. @@ -172,26 +170,26 @@ public function getGraph(): Resource; * does not automatically affect the resource metadata. * Use the setMetadata() method to write back the changes you made. * - * @return Resource + * @return DatasetNodeInterface * @see setMetadata() * @see getGraph() */ - public function getMetadata(): Resource; + public function getMetadata(): DatasetNodeInterface; /** - * Replaces resource metadata with a given RDF resource graph. A reference + * Replaces resource metadata with a given RDF graph. A reference * to the provided metadata is stored meaning future modifications of the * $metadata object automatically affect the resource metadata. * * New metadata are not automatically written back to the repository. * Use the updateMetadata() method to write them back. * - * @param Resource $resource + * @param DatasetInterface $metadata * @return void * @see updateMetadata() * @see setMetadata() */ - public function setGraph(Resource $resource): void; + public function setGraph(DatasetInterface $metadata): void; /** * Replaces resource metadata with a given RDF resource graph. A deep copy @@ -201,11 +199,11 @@ public function setGraph(Resource $resource): void; * New metadata are not automatically written back to the repository. * Use the `updateMetadata()` method to write them back. * - * @param Resource $metadata + * @param DatasetNodeInterface $metadata * @see updateMetadata() * @see setGraph() */ - public function setMetadata(Resource $metadata): void; + public function setMetadata(DatasetNodeInterface $metadata): void; /** * Naivly checks if the resource is of a given class. diff --git a/src/acdhOeaw/arche/lib/RepoResourceTrait.php b/src/acdhOeaw/arche/lib/RepoResourceTrait.php index 251935f..abf7864 100644 --- a/src/acdhOeaw/arche/lib/RepoResourceTrait.php +++ b/src/acdhOeaw/arche/lib/RepoResourceTrait.php @@ -26,8 +26,12 @@ namespace acdhOeaw\arche\lib; -use EasyRdf\Resource; -use acdhOeaw\arche\lib\exception\RepoLibException; +use rdfInterface\DatasetInterface; +use rdfInterface\DatasetNodeInterface; +use rdfInterface\TermInterface; +use rdfInterface\QuadInterface; +use quickRdf\DataFactory as DF; +use termTemplates\QuadTemplate as QT; use zozlak\RdfConstants as RDF; /** @@ -38,24 +42,19 @@ */ trait RepoResourceTrait { - private ?Resource $metadata = null; + private DatasetNodeInterface $metadata; private bool $metaSynced; private RepoInterface $repoInt; - private string $url; /** * Returns the repository resource URL. - * - * @return string */ - public function getUri(): string { - return $this->url; + public function getUri(): TermInterface { + return $this->metadata->getNode(); } /** * Returns repository connection object associated with the given resource object. - * - * @return RepoInterface */ public function getRepo(): RepoInterface { return $this->repoInt; @@ -64,16 +63,12 @@ public function getRepo(): RepoInterface { /** * Returns an array with all repository resource identifiers. * - * @return string[] + * @return array */ public function getIds(): array { $idProp = $this->repoInt->getSchema()->id; $this->loadMetadata(); - $ids = []; - foreach ($this->metadata?->allResources($idProp) ?? [] as $i) { - $ids[] = (string) $i; - } - return $ids; + return $this->metadata->listObjects(new QT(predicate: $idProp))->getValues(); } /** @@ -83,11 +78,7 @@ public function getIds(): array { */ public function getClasses(): array { $this->loadMetadata(); - $ret = []; - foreach ($this->metadata?->allResources(RDF::RDF_TYPE) ?? [] as $i) { - $ret[] = $i->getUri(); - } - return $ret; + return $this->metadata->listObjects(new QT(predicate: DF::namedNode(RDF::RDF_TYPE)))->getValues(); } /** @@ -100,14 +91,14 @@ public function getClasses(): array { * does not automatically affect the resource metadata. * Use the setMetadata() method to write back the changes you made. * - * @return Resource + * @return DatasetNodeInterface * @see setMetadata() * @see setGraph() * @see getGraph() */ - public function getMetadata(): Resource { + public function getMetadata(): DatasetNodeInterface { $this->loadMetadata(); - return $this->metadata?->copy() ?? throw new RepoLibException('Metadata not loaded'); + return $this->metadata->copy(); } /** @@ -119,13 +110,13 @@ public function getMetadata(): Resource { * A reference to the metadata is returned meaning adjusting the returned object * automatically affects the resource metadata. * - * @return Resource + * @return DatasetNodeInterface * @see setGraph() * @see getMetadata() */ - public function getGraph(): Resource { + public function getGraph(): DatasetNodeInterface { $this->loadMetadata(); - return $this->metadata ?? throw new RepoLibException('Metadata not loaded'); + return $this->metadata; } /** @@ -133,33 +124,36 @@ public function getGraph(): Resource { * of the provided metadata is stored meaning future modifications of the * $metadata object don't affect the resource metadata. * - * New metadata are not automatically written back to the repository. + * New metadata is not automatically written back to the repository. * Use the `updateMetadata()` method to write them back. * - * @param Resource $metadata + * @param DatasetNodeInterface $metadata * @see updateMetadata() * @see setGraph() */ - public function setMetadata(Resource $metadata): void { - $this->metadata = $metadata->copy([], '/^$/', $this->getUri()); + public function setMetadata(DatasetNodeInterface $metadata): void { + $sbj = $this->metadata->getNode(); + $metadata = $metadata->withDataset($metadata->getDataset()->copy()); + $metadata->forEach(fn(QuadInterface $x) => $x->withSubject($sbj)); + $this->metadata = $this->metadata->withDataset($metadata->getDataset()); $this->metaSynced = false; } /** - * Replaces resource metadata with a given RDF resource graph. A reference + * Replaces resource metadata with a given RDF graph. A reference * to the provided metadata is stored meaning future modifications of the * $metadata object automatically affect the resource metadata. * * New metadata are not automatically written back to the repository. * Use the updateMetadata() method to write them back. * - * @param Resource $resource + * @param DatasetInterface $metadata * @return void * @see updateMetadata() * @see setMetadata() */ - public function setGraph(Resource $resource): void { - $this->metadata = $resource; + public function setGraph(DatasetInterface $metadata): void { + $this->metadata = $this->metadata->withDataset($metadata); $this->metaSynced = false; } diff --git a/src/acdhOeaw/arche/lib/RepoTrait.php b/src/acdhOeaw/arche/lib/RepoTrait.php index 9c330ca..281c73b 100644 --- a/src/acdhOeaw/arche/lib/RepoTrait.php +++ b/src/acdhOeaw/arche/lib/RepoTrait.php @@ -26,8 +26,14 @@ namespace acdhOeaw\arche\lib; +use Generator; +use rdfInterface\DatasetNodeInterface; +use quickRdf\DataFactory as DF; +use quickRdf\Dataset; +use quickRdf\DatasetNode; +use quickRdf\NamedNode; +use termTemplates\QuadTemplate as QT; use Psr\Log\AbstractLogger; -use EasyRdf\Resource; use acdhOeaw\arche\lib\exception\RepoLibException; /** @@ -45,7 +51,7 @@ trait RepoTrait { /** * An object providing mappings of repository REST API parameters to HTTP headers used by a given repository instance. */ - private Schema $headers; + private object $headers; /** * An object providing mappings of repository concepts to RDF properties used to denote them by a given repository instance. @@ -106,15 +112,45 @@ public function setQueryLog(AbstractLogger $log): void { } /** + * Extracts collection of RepoResource objects from the EasyRdf graph being + * parsed from a search response. * - * @param array $resources - * @param string $searchOrderProp + * @param Dataset $graph graph returned by the parseSearchResponse() method + * @param SearchConfig $config search configuration object + * @return Generator + */ + private function extractResourcesFromGraph(Dataset $graph, + SearchConfig $config): Generator { + $class = $config->class ?? static::$resourceClass; + + $resources = []; + foreach ($graph->listSubjects(new QT(predicate: $this->schema->searchMatch)) as $sbj) { + $resources[] = (new DatasetNode($sbj))->withDataset($graph); + } + $this->sortMatchingResources($resources, $this->schema->searchOrder); + $graph->delete(new QT(predicate: $this->schema->searchMatch)); + $graph->delete(new QT(predicate: $this->schema->searchOrder)); + + foreach ($resources as $i) { + $obj = new $class($i->getNode()->getValue(), $this); + /** @var RepoResource $obj */ + $obj->setGraph($graph); + yield $obj; + } + } + + /** + * + * @param array $resources + * @param NamedNode $searchOrderProp * @return void */ private function sortMatchingResources(array &$resources, - string $searchOrderProp): void { - usort($resources, function (Resource $a, Resource $b) use ($searchOrderProp) { - return $a->getLiteral($searchOrderProp) <=> $b->getLiteral($searchOrderProp); + NamedNode $searchOrderProp): void { + $tmpl = new QT(predicate: $searchOrderProp); + usort($resources, function (DatasetNodeInterface $a, + DatasetNodeInterface $b) use ($tmpl) { + return $a->getObjectValue($tmpl) <=> $b->getObjectValue($tmpl); }); } } diff --git a/src/acdhOeaw/arche/lib/Schema.php b/src/acdhOeaw/arche/lib/Schema.php index be2c908..e308fef 100644 --- a/src/acdhOeaw/arche/lib/Schema.php +++ b/src/acdhOeaw/arche/lib/Schema.php @@ -26,63 +26,132 @@ namespace acdhOeaw\arche\lib; -use function GuzzleHttp\json_decode; -use function GuzzleHttp\json_encode; +use quickRdf\DataFactory as DF; +use quickRdf\NamedNode; /** - * A container for configuration properties. + * An immutable container for RDF property mappings schema. * - * Assures deep copies of complex configuration objects being returned to prevent - * surprising configuration modifications. - * * @author zozlak - * @property string $id - * @property string $label - * @property string $parent - * @property string $delete - * @property string $binarySize - * @property string $hash - * @property string $mime - * @property string $fileName - * @property string $searchCount - * @property string $searchFts - * @property string $searchMatch - * @property string $searchOrder - * @property string $searchOrderValue - * @property string $creationDate - * @property string $creationUser - * @property string $modificationDate - * @property string $modificationUser - * @property string $binaryModificationDate - * @property string $binaryModificationUser - * @property object $test + * @property NamedNode $accessRestriction + * @property NamedNode $accessRestrictionAgg + * @property NamedNode $accessRole + * @property NamedNode $binaryModificationDate + * @property NamedNode $binaryModificationUser + * @property NamedNode $binarySize + * @property NamedNode $binarySizeCumulative + * @property NamedNode $class + * @property object $classes + * @property NamedNode $cmdi + * @property NamedNode $cmdiPid + * @property NamedNode $countCumulative + * @property NamedNode $creationDate + * @property NamedNode $creationUser + * @property NamedNode $dateStart + * @property NamedNode $dateEnd + * @property NamedNode $delete + * @property Schema $dissService + * @property NamedNode $fileName + * @property NamedNode $hash + * @property NamedNode $hasService + * @property NamedNode $id + * @property NamedNode $info + * @property Schema $ingest + * @property NamedNode $isNewVersionOf + * @property NamedNode $label + * @property NamedNode $latitude + * @property NamedNode $license + * @property NamedNode $licenseAgg + * @property NamedNode $location + * @property NamedNode $longitude + * @property NamedNode $matchProperty + * @property NamedNode $matchRequired + * @property NamedNode $matchValue + * @property NamedNode $mime + * @property NamedNode $modificationDate + * @property NamedNode $modificationUser + * @property Schema $namespaces + * @property NamedNode $ontology + * @property NamedNode $parent + * @property NamedNode $parameterClass + * @property NamedNode $parameterDefaultValue + * @property NamedNode $parameterRdfProperty + * @property NamedNode $pid + * @property NamedNode $returnFormat + * @property NamedNode $revProxy + * @property NamedNode $searchCount + * @property NamedNode $searchFts + * @property NamedNode $searchFtsQuery + * @property NamedNode $searchFtsProperty + * @property NamedNode $searchMatch + * @property NamedNode $searchOrder + * @property NamedNode $searchOrderValue + * @property float $searchWeight + * @property Schema $test + * @property NamedNode $url + * @property NamedNode $version + * @property NamedNode $vid + * @property NamedNode $wkt */ -class Schema { +class Schema implements \Iterator { - private object $schema; + private array $schema; /** * Creates the Schema object. * - * @param object $schema object with configuration properties + * @param object|array $schema object with configuration properties */ - public function __construct(object $schema) { - $this->schema = $schema; + public function __construct(object | array $schema) { + if (is_object($schema)) { + $schema = get_object_vars($schema); + } + foreach ($schema as $k => $v) { + if (is_object($v) || is_array($v)) { + $this->schema[$k] = new Schema($v); + } else { + $this->schema[$k] = DF::namedNode((string) $v); + } + } } /** * Magic method implementing accessing properties. * * @param string $name configuration property to be returned - * @return mixed + * @return Schema|NamedNode|null */ - public function __get(string $name): mixed { - if (!isset($this->schema->$name)) { - return null; - } elseif (is_object($this->schema->$name)) { - return json_decode(json_encode($this->schema->$name)); - } else { - return $this->schema->$name; - } + public function __get(string $name): Schema | NamedNode | null { + return $this->schema[$name] ?? null; + } + + /** + * + * @param string $name + * @param mixed $value + * @throws \BadMethodCallException + */ + public function __set(string $name, mixed $value) { + throw new \BadMethodCallException(); + } + + public function current(): mixed { + return current($this->schema); + } + + public function key(): mixed { + return key($this->schema); + } + + public function next(): void { + next($this->schema); + } + + public function rewind(): void { + reset($this->schema); + } + + public function valid(): bool { + return current($this->schema) !== false; } } diff --git a/src/acdhOeaw/arche/lib/SearchConfig.php b/src/acdhOeaw/arche/lib/SearchConfig.php index 42182a5..787a1c9 100644 --- a/src/acdhOeaw/arche/lib/SearchConfig.php +++ b/src/acdhOeaw/arche/lib/SearchConfig.php @@ -318,7 +318,7 @@ public function toQuery(): string { return http_build_query($this->toArray()); } - public function getTsHeadlineOptions(int $offset): string { + public function getTsHeadlineOptions(int $offset = 0): string { $options = ''; foreach (self::$highlightParam as $i) { $ii = 'fts' . $i; diff --git a/src/acdhOeaw/arche/lib/SmartSearch.php b/src/acdhOeaw/arche/lib/SmartSearch.php index 78f7e3d..ad8579b 100644 --- a/src/acdhOeaw/arche/lib/SmartSearch.php +++ b/src/acdhOeaw/arche/lib/SmartSearch.php @@ -28,7 +28,6 @@ use Generator; use PDO; -use PDOStatement; use Psr\Log\AbstractLogger; use zozlak\queryPart\QueryPart; use zozlak\RdfConstants as RDF; diff --git a/src/acdhOeaw/arche/lib/TripleValue.php b/src/acdhOeaw/arche/lib/TripleValue.php index a2ff6b4..dcc0cbb 100644 --- a/src/acdhOeaw/arche/lib/TripleValue.php +++ b/src/acdhOeaw/arche/lib/TripleValue.php @@ -33,7 +33,7 @@ */ class TripleValue implements \Stringable { - static public function fromDbRow(object $row) { + static public function fromDbRow(object $row): TripleValue { $triple = new TripleValue(); $triple->type = $row->type; $triple->lang = (string) $row->lang; diff --git a/src/acdhOeaw/arche/lib/promise/GraphPromise.php b/src/acdhOeaw/arche/lib/promise/GraphPromise.php index 1178ddf..8c42d8f 100644 --- a/src/acdhOeaw/arche/lib/promise/GraphPromise.php +++ b/src/acdhOeaw/arche/lib/promise/GraphPromise.php @@ -26,7 +26,7 @@ namespace acdhOeaw\arche\lib\promise; -use EasyRdf\Graph; +use quickRdf\Dataset; use GuzzleHttp\Promise\PromiseInterface; /** @@ -59,9 +59,9 @@ public function reject($reason): void { /** * * @param bool $unwrap - * @return Graph|null + * @return Dataset | null */ - public function wait($unwrap = true): ?Graph { + public function wait($unwrap = true): Dataset | null { return $this->promise->wait($unwrap); } } diff --git a/src/acdhOeaw/arche/lib/promise/GeneratorPromise.php b/src/acdhOeaw/arche/lib/promise/RepoResourceGeneratorPromise.php similarity index 92% rename from src/acdhOeaw/arche/lib/promise/GeneratorPromise.php rename to src/acdhOeaw/arche/lib/promise/RepoResourceGeneratorPromise.php index 4b3406b..c74d22f 100644 --- a/src/acdhOeaw/arche/lib/promise/GeneratorPromise.php +++ b/src/acdhOeaw/arche/lib/promise/RepoResourceGeneratorPromise.php @@ -28,13 +28,14 @@ use Generator; use GuzzleHttp\Promise\PromiseInterface; +use acdhOeaw\arche\lib\RepoResource; /** * Description of ResponsePromise * * @author zozlak */ -class GeneratorPromise implements PromiseInterface { +class RepoResourceGeneratorPromise implements PromiseInterface { use PromiseTrait; @@ -59,7 +60,7 @@ public function reject($reason): void { /** * * @param bool $unwrap - * @return Generator|null + * @return Generator|null */ public function wait($unwrap = true): ?Generator { return $this->promise->wait(true); diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index 6ceb439..e3de969 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -41,24 +41,24 @@ public function testCopy(): void { // TODO - not sure if this is a preferred behavior but it works like that // since ever and it wasn't an issue yet - $this->assertNull($cfg1->foo); - $this->assertNull($cfg2->foo); - $cfg1->foo = 'bar'; - $this->assertEquals('bar', $cfg1->foo); - $this->assertEquals('bar', $cfg2->foo); + $this->assertNull($cfg1->uri); + $this->assertNull($cfg2->uri); + $cfg1->uri = 'bar'; + $this->assertEquals('bar', $cfg1->uri); + $this->assertEquals('bar', $cfg2->uri); } public function testSerialization(): void { $cfg = new Config(new \stdClass()); - $cfg->foo = 'bar'; + $cfg->uri = 'bar'; $cfgProps = $cfg->asObject(); $this->assertInstanceOf(\stdClass::class, $cfgProps); - $this->assertEquals('bar', $cfgProps->foo ?? null); + $this->assertEquals('bar', $cfgProps->uri ?? null); $cfgProps = $cfg->asArray(); $this->assertIsArray($cfgProps); - $this->assertEquals('bar', $cfgProps['foo'] ?? null); + $this->assertEquals('bar', $cfgProps['uri'] ?? null); $yaml = $cfg->asYaml(); $this->assertEquals(yaml_emit($cfgProps), $yaml); diff --git a/tests/RepoResourceTest.php b/tests/RepoResourceTest.php index 641b200..f805384 100644 --- a/tests/RepoResourceTest.php +++ b/tests/RepoResourceTest.php @@ -26,11 +26,10 @@ namespace acdhOeaw\arche\lib\tests; -use zozlak\RdfConstants as C; +use zozlak\RdfConstants as RDF; +use termTemplates\PredicateTemplate as PT; use acdhOeaw\arche\lib\BinaryPayload; -use acdhOeaw\arche\lib\Repo; use acdhOeaw\arche\lib\RepoResource; -use acdhOeaw\arche\lib\exception\AmbiguousMatch; use acdhOeaw\arche\lib\exception\Deleted; use acdhOeaw\arche\lib\exception\NotFound; @@ -46,15 +45,14 @@ public function setUp(): void { self::$repo->begin(); $meta1 = $this->getMetadata([ - C::RDF_TYPE => ['https://class/1', 'https://class/2'], - self::$schema->id => ['https://an.unique.id/1', 'https://an.unique.id/2'], - self::$schema->label => 'sample label for the first resource', + RDF::RDF_TYPE => ['https://class/1', 'https://class/2'], + 'id' => ['https://an.unique.id/1', 'https://an.unique.id/2'], + 'label' => 'sample label for the first resource', 'https://date.prop' => '2019-01-01', 'https://number.prop' => 150, 'https://lorem.ipsum' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed iaculis nisl enim, malesuada tempus nisl ultrices ut. Duis egestas at arcu in blandit. Nulla eget sem urna. Sed hendrerit enim ut ultrices luctus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur non dolor non neque venenatis aliquet vitae venenatis est.', ]); $res1 = self::$repo->createResource($meta1); - $this->noteResource($res1); self::$repo->commit(); } @@ -120,17 +118,17 @@ public function testUpdateMetadata(): void { $meta = $this->getMetadata([$p1 => 'v1', $p2 => 'v2', $p3 => 'v3']); self::$repo->begin(); $res = self::$repo->createResource($meta); - $this->assertEquals('v1', (string) $res->getMetadata()->get($p1)); - $this->assertEquals('v2', (string) $res->getMetadata()->get($p2)); - $this->assertEquals('v3', (string) $res->getMetadata()->get($p3)); + $this->assertEquals('v1', $res->getMetadata()->getObjectValue(new PT($p1))); + $this->assertEquals('v2', $res->getMetadata()->getObjectValue(new PT($p2))); + $this->assertEquals('v3', $res->getMetadata()->getObjectValue(new PT($p3))); - $meta = $this->getMetadata([$p3 => 'v33', $p4 => 'v4', $pd => $p1]); + $meta = $this->getMetadata([$p3 => 'v33', $p4 => 'v4', $pd->getValue() => $p1]); $res->setMetadata($meta); $res->updateMetadata(); - $this->assertEquals(null, $res->getMetadata()->get($p1)); - $this->assertEquals('v2', (string) $res->getMetadata()->get($p2)); - $this->assertEquals('v33', (string) $res->getMetadata()->get($p3)); - $this->assertEquals('v4', (string) $res->getMetadata()->get($p4)); + $this->assertEquals(null, $res->getMetadata()->getObjectValue(new PT($p1))); + $this->assertEquals('v2', $res->getMetadata()->getObjectValue(new PT($p2))); + $this->assertEquals('v33', $res->getMetadata()->getObjectValue(new PT($p3))); + $this->assertEquals('v4', $res->getMetadata()->getObjectValue(new PT($p4))); self::$repo->rollback(); } @@ -139,9 +137,8 @@ public function testDelete(): void { self::$repo->begin(); $id = 'https://a.b/' . rand(); - $meta1 = $this->getMetadata([self::$schema->id => $id]); + $meta1 = $this->getMetadata(['id' => $id]); $res1 = self::$repo->createResource($meta1); - $this->noteResource($res1); $res1->delete(false, false); self::$repo->commit(); @@ -154,9 +151,8 @@ public function testDeleteTombstone(): void { self::$repo->begin(); $id = 'https://a.b/' . rand(); - $meta1 = $this->getMetadata([self::$schema->id => $id]); + $meta1 = $this->getMetadata(['id' => $id]); $res1 = self::$repo->createResource($meta1); - $this->noteResource($res1); $res1->delete(true, false); self::$repo->commit(); @@ -166,17 +162,14 @@ public function testDeleteTombstone(): void { } public function testDeleteWithConflict(): void { - $relProp = 'https://some.prop'; self::$repo->begin(); $id = 'https://a.b/' . rand(); - $meta1 = $this->getMetadata([self::$schema->id => $id]); + $meta1 = $this->getMetadata(['id' => $id]); $res1 = self::$repo->createResource($meta1); - $this->noteResource($res1); - $meta2 = $this->getMetadata([$relProp => $res1->getUri()]); + $meta2 = $this->getMetadata(['parent' => $res1->getUri()]); $res2 = self::$repo->createResource($meta2); - $this->noteResource($res2); $this->expectExceptionCode(409); $res1->delete(true, false); @@ -189,19 +182,17 @@ public function testDeleteWithReferences(): void { self::$repo->begin(); $id = 'https://a.b/' . rand(); - $meta1 = $this->getMetadata([self::$schema->id => $id]); + $meta1 = $this->getMetadata(['id' => $id]); $res1 = self::$repo->createResource($meta1); - $this->noteResource($res1); - $meta2 = $this->getMetadata([$relProp => $res1->getUri()]); + $meta2 = $this->getMetadata(['parent' => $res1->getUri()]); $res2 = self::$repo->createResource($meta2); - $this->noteResource($res2); $res1->delete(true, true); self::$repo->commit(); $res2->loadMetadata(true); - $this->assertNull($res2->getMetadata()->getResource($relProp)); + $this->assertFalse($res2->getGraph()->any(new PT($relProp))); $this->expectExceptionCode(404); $res1->loadMetadata(true); } @@ -211,13 +202,11 @@ public function testDeleteRecursively(): void { self::$repo->begin(); $id = 'https://a.b/' . rand(); - $meta1 = $this->getMetadata([self::$schema->id => $id]); + $meta1 = $this->getMetadata(['id' => $id]); $res1 = self::$repo->createResource($meta1); - $this->noteResource($res1); $meta2 = $this->getMetadata([$relProp => $res1->getUri()]); $res2 = self::$repo->createResource($meta2); - $this->noteResource($res2); $res1->delete(false, false, $relProp); self::$repo->commit(); @@ -242,19 +231,16 @@ public function testDeleteComplex(): void { self::$repo->begin(); $id = 'https://a.b/' . rand(); - $meta1 = $this->getMetadata([self::$schema->id => $id]); + $meta1 = $this->getMetadata(['id' => $id]); $res1 = self::$repo->createResource($meta1); - $this->noteResource($res1); $meta2 = $this->getMetadata([$relProp => $res1->getUri()]); $res2 = self::$repo->createResource($meta2); - $this->noteResource($res2); $meta3 = $this->getMetadata([ $otherProp => [$res1->getUri(), $res2->getUri()] ]); $res3 = self::$repo->createResource($meta3); - $this->noteResource($res3); $res1->delete(true, true, $relProp); self::$repo->commit(); @@ -272,7 +258,7 @@ public function testDeleteComplex(): void { $this->assertEquals(404, $e->getCode()); } $res3->loadMetadata(true); - $this->assertNull($res3->getMetadata()->getResource($otherProp)); + $this->assertFalse($res3->getMetadata()->any(new PT($otherProp))); } public function testMerge(): void { @@ -284,29 +270,25 @@ public function testMerge(): void { $id1 = 'https://a.b/' . rand(); $meta1 = $this->getMetadata([ - $idProp => $id1, - $prop1 => 'foo1', - $prop2 => 'bar1', + 'id' => $id1, + $prop1 => 'foo1', + $prop2 => 'bar1', ]); $res1 = self::$repo->createResource($meta1); - $this->noteResource($res1); $id2 = 'https://a.b/' . rand(); $meta2 = $this->getMetadata([ - $idProp => $id2, - $prop2 => 'bar2', - $prop3 => 'baz2', + 'id' => $id2, + $prop2 => 'bar2', + $prop3 => 'baz2', ]); $res2 = self::$repo->createResource($meta2); $res2url = $res2->getUri(); - $this->noteResource($res2); $res2->merge($id1); $meta = $res2->getMetadata(); $this->assertEquals($res1->getUri(), $res2->getUri()); - $ids = array_map(function ($x) { - return $x->getUri(); - }, $meta->all($idProp)); + $ids = $meta->listObjects(new PT($idProp))->getValues(); $this->assertContains($id1, $ids); $this->assertContains($id2, $ids); $this->assertCount(3, $ids); diff --git a/tests/RepoTest.php b/tests/RepoTest.php index 2cea510..791bd57 100644 --- a/tests/RepoTest.php +++ b/tests/RepoTest.php @@ -26,14 +26,14 @@ namespace acdhOeaw\arche\lib\tests; -use EasyRdf\Literal; use GuzzleHttp\Exception\ClientException; use zozlak\RdfConstants as RDF; +use quickRdf\DataFactory as DF; +use termTemplates\PredicateTemplate as PT; use acdhOeaw\arche\lib\BinaryPayload; use acdhOeaw\arche\lib\Repo; use acdhOeaw\arche\lib\RepoResource; use acdhOeaw\arche\lib\exception\AmbiguousMatch; -use acdhOeaw\arche\lib\exception\Deleted; use acdhOeaw\arche\lib\exception\NotFound; use acdhOeaw\arche\lib\exception\ExceptionUtil; @@ -88,42 +88,41 @@ public function testTransactionExpired(): void { public function testCreateBinaryResource(): void { $labelProp = self::$schema->label; - $metadata = $this->getMetadata([$labelProp => 'sampleTitle']); + $labelTmpl = new PT($labelProp); + $metadata = $this->getMetadata(['label' => 'sampleTitle']); $binary = new BinaryPayload(null, __FILE__); self::$repo->begin(); $res1 = self::$repo->createResource($metadata, $binary); - $this->noteResource($res1); $this->assertEquals(file_get_contents(__FILE__), (string) $res1->getContent()->getBody(), 'file content mismatch'); - $this->assertEquals('sampleTitle', (string) $res1->getMetadata()->getLiteral($labelProp)); + $this->assertEquals('sampleTitle', $res1->getMetadata()->getObjectValue($labelTmpl)); self::$repo->commit(); $res2 = new RepoResource($res1->getUri(), self::$repo); $this->assertEquals(file_get_contents(__FILE__), (string) $res2->getContent()->getBody(), 'file content mismatch'); - $this->assertEquals('sampleTitle', (string) $res2->getMetadata()->getLiteral($labelProp)); + $this->assertEquals('sampleTitle', $res2->getMetadata()->GetObjectValue($labelTmpl)); } public function testCreateResource(): void { $labelProp = self::$schema->label; - $metadata = $this->getMetadata([$labelProp => 'sampleTitle']); + $labelTmpl = new PT($labelProp); + $metadata = $this->getMetadata(['label'=> 'sampleTitle']); self::$repo->begin(); $res1 = self::$repo->createResource($metadata); - $this->noteResource($res1); - $this->assertEquals('sampleTitle', (string) $res1->getMetadata()->getLiteral($labelProp)); + $this->assertEquals('sampleTitle', (string) $res1->getMetadata()->GetObjectValue($labelTmpl)); self::$repo->commit(); $res2 = new RepoResource($res1->getUri(), self::$repo); - $this->assertEquals('sampleTitle', (string) $res2->getMetadata()->getLiteral($labelProp)); + $this->assertEquals('sampleTitle', (string) $res2->getMetadata()->GetObjectValue($labelTmpl)); } public function testSearchById(): void { $idProp = self::$schema->id; $id = 'https://a.b/' . rand(); - $meta = $this->getMetadata([$idProp => $id]); + $meta = $this->getMetadata(['id' => $id]); self::$repo->begin(); $res1 = self::$repo->createResource($meta); - $this->noteResource($res1); self::$repo->commit(); $res2 = self::$repo->getResourceById($id); @@ -139,13 +138,11 @@ public function testSearchByIdsAmigous(): void { $idProp = self::$schema->id; $id1 = 'https://a.b/' . rand(); $id2 = 'https://a.b/' . rand(); - $meta1 = $this->getMetadata([$idProp => $id1]); - $meta2 = $this->getMetadata([$idProp => $id2]); + $meta1 = $this->getMetadata(['id' => $id1]); + $meta2 = $this->getMetadata(['id' => $id2]); self::$repo->begin(); $res1 = self::$repo->createResource($meta1); - $this->noteResource($res1); $res2 = self::$repo->createResource($meta2); - $this->noteResource($res2); self::$repo->commit(); $this->expectException(AmbiguousMatch::class); @@ -153,17 +150,18 @@ public function testSearchByIdsAmigous(): void { } public function testMap(): void { - $prop = 'http://foo/bar'; - $valueOk = rand(); - $metaOk = $this->getMetadata([$prop => $valueOk]); - $metaBad = $this->getMetadata([$prop => new Literal('baz', null, RDF::XSD_DATE)]); + $prop = 'http://foo/bar'; + $propTmpl = new PT($prop); + $valueOk = rand(); + $metaOk = $this->getMetadata([$prop => $valueOk]); + $metaBad = $this->getMetadata([$prop => DF::literal('baz', null, RDF::XSD_DATE)]); self::$repo->begin(); // REJEST_SKIP $results = self::$repo->map([$metaOk, $metaBad], fn($meta) => self::$repo->createResourceAsync($meta), 1, Repo::REJECT_SKIP); $this->assertCount(1, $results); $this->assertInstanceOf(RepoResource::class, $results[0]); - $this->assertEquals($valueOk, (string) $results[0]->getGraph()->get($prop)); + $this->assertEquals($valueOk, (string) $results[0]->getGraph()->getObjectValue($propTmpl)); // REJECT_INCLUDE $results = self::$repo->map([$metaOk, $metaBad], fn($meta) => self::$repo->createResourceAsync($meta), 1, Repo::REJECT_INCLUDE); @@ -178,7 +176,7 @@ public function testMap(): void { } $this->assertNotNull($rejected); $this->assertNotNull($fulfilled); - $this->assertEquals($valueOk, (string) $fulfilled->getGraph()->get($prop)); + $this->assertEquals($valueOk, (string) $fulfilled->getGraph()->getObjectValue($propTmpl)); $this->assertEquals(400, $rejected->getResponse()->getStatusCode()); $this->assertStringContainsString('Wrong property value', (string) $rejected->getResponse()->getBody()); diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index 5e44091..f30e3b6 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -25,6 +25,7 @@ */ namespace acdhOeaw\arche\lib\tests; + use acdhOeaw\arche\lib\Schema; /** @@ -35,16 +36,15 @@ class SchemaTest extends \PHPUnit\Framework\TestCase { public function testComplexStructure(): void { - $tmp = json_encode(['test' => ['b' => 'c']]); - if (is_string($tmp)) { - $schema = new Schema(json_decode($tmp)); - $x = $schema->test; - if (isset($x->b)) { - $x->b = 'd'; - } - $y = $schema->test; - $this->assertEquals((object) ['b' => 'd'], $x); - $this->assertEquals((object) ['b' => 'c'], $y); + $schema = new Schema(json_decode('{"test": {"mime": "c"}}')); + $x = $schema->test; + $this->assertTrue(\quickRdf\DataFactory::namedNode('c')->equals($x->mime)); + $this->assertNull($x->hash); + try { + $x->mime = 'foo'; + $this->assertTrue(false); + } catch (\BadMethodCallException $ex) { + $this->assertTrue(true); } } } diff --git a/tests/SearchTest.php b/tests/SearchTest.php index 897a049..7753184 100644 --- a/tests/SearchTest.php +++ b/tests/SearchTest.php @@ -26,10 +26,12 @@ namespace acdhOeaw\arche\lib\tests; +use quickRdf\DataFactory as DF; +use termTemplates\QuadTemplate as QT; use acdhOeaw\arche\lib\RepoResource; use acdhOeaw\arche\lib\SearchConfig; use acdhOeaw\arche\lib\SearchTerm; -use zozlak\RdfConstants as C; +use zozlak\RdfConstants as RDF; /** * Description of SearchTest @@ -41,29 +43,26 @@ class SearchTest extends TestBase { public function setUp(): void { parent::setUp(); - $relProp = self::$repo->getSchema()->parent; self::$repo->begin(); $meta1 = $this->getMetadata([ - self::$schema->id => 'https://an.unique.id', - self::$schema->label => 'sample label for the first resource', + 'id' => 'https://an.unique.id', + 'label' => 'sample label for the first resource', 'https://number.prop' => 150, 'https://lorem.ipsum' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed iaculis nisl enim, malesuada tempus nisl ultrices ut. Duis egestas at arcu in blandit. Nulla eget sem urna. Sed hendrerit enim ut ultrices luctus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur non dolor non neque venenatis aliquet vitae venenatis est.', + 'https://date.prop' => DF::literal('2019-01-01', null, RDF::XSD_DATE), ]); - $meta1->add('https://date.prop', new \EasyRdf\Literal('2019-01-01', null, C::XSD_DATE)); $res1 = self::$repo->createResource($meta1); - $this->noteResource($res1); $meta2 = $this->getMetadata([ - self::$schema->id => 'https://res2.id', - $relProp => $res1->getUri(), - self::$schema->label => 'a more original title for a resource', + 'id' => 'https://res2.id', + 'parent' => $res1->getUri(), + 'label' => 'a more original title for a resource', 'https://number.prop' => 20, 'https://lorem.ipsum' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur non dolor non neque venenatis aliquet vitae venenatis est. Aenean eleifend ipsum eu placerat sagittis. Aenean ullamcorper dignissim enim, ut congue turpis tristique eu.', + 'https://date.prop' => DF::literal('2019-02-01', null, RDF::XSD_DATE), ]); - $meta2->add('https://date.prop', new \EasyRdf\Literal('2019-02-01', null, C::XSD_DATE)); - $res2 = self::$repo->createResource($meta2); - $this->noteResource($res2); + self::$repo->createResource($meta2); self::$repo->commit(); } @@ -76,15 +75,15 @@ public function testSearchBySqlQuery(): void { $param = [self::$schema->label]; $result = iterator_to_array(self::$repo->getResourcesBySqlQuery($query, $param, new SearchConfig())); $this->assertEquals(1, count($result)); - $this->assertEquals('a more original title for a resource', (string) $result[0]->getMetadata()->getLiteral(self::$schema->label)); + $this->assertEquals('a more original title for a resource', (string) $result[0]->getMetadata()->getObjectValue(new QT(predicate: self::$schema->label))); } /** * @group search */ public function testSearchBySqlQueryEmpty(): void { - $result = self::$repo->getResourcesBySqlQuery("SELECT -1 AS id WHERE false", [ - ], new SearchConfig()); + $param = []; + $result = self::$repo->getResourcesBySqlQuery("SELECT -1 AS id WHERE false", $param, new SearchConfig()); $this->assertEquals([], iterator_to_array($result)); } @@ -92,20 +91,21 @@ public function testSearchBySqlQueryEmpty(): void { * @group search */ public function testSearchBySearchTerms(): void { - $term1 = new SearchTerm('https://number.prop', 30, '<=', C::XSD_DECIMAL); - $term2 = new SearchTerm('https://date.prop', '2019-01-15', '>=', C::XSD_DATE); - $term3 = new SearchTerm('https://lorem.ipsum', 'ipsum', '@@'); - $result = iterator_to_array(self::$repo->getResourcesBySearchTerms([$term1, - $term2, $term3], new SearchConfig())); + $terms = [ + new SearchTerm('https://number.prop', 30, '<=', RDF::XSD_DECIMAL), + new SearchTerm('https://date.prop', '2019-01-15', '>=', RDF::XSD_DATE), + new SearchTerm('https://lorem.ipsum', 'ipsum', '@@'), + ]; + $result = iterator_to_array(self::$repo->getResourcesBySearchTerms($terms, new SearchConfig())); $this->assertEquals(1, count($result)); - $this->assertEquals('a more original title for a resource', (string) $result[0]->getMetadata()->getLiteral(self::$schema->label)); + $this->assertEquals('a more original title for a resource', (string) $result[0]->getMetadata()->getObjectValue(new QT(predicate: self::$schema->label))); } /** * @group search */ public function testSearchWrongDataType(): void { - $term1 = new SearchTerm('https://number.prop', 30, '<=', C::XSD_STRING); + $term1 = new SearchTerm('https://number.prop', 30, '<=', RDF::XSD_STRING); $result = iterator_to_array(self::$repo->getResourcesBySearchTerms([$term1], new SearchConfig())); $this->assertEquals(2, count($result)); } @@ -114,6 +114,11 @@ public function testSearchWrongDataType(): void { * @group search */ public function testSearchFtsHighlight(): void { + $ftsValueTmpl = new QT(predicate: self::$schema->searchFts->getValue() . '1'); + $ftsPropTmpl = new QT(predicate: self::$schema->searchFtsProperty->getValue() . '1'); + $ftsQueryTmpl = new QT(predicate: self::$schema->searchFtsQuery->getValue() . '1'); + $dateTmpl = new QT(predicate: 'https://date.prop'); + $term = new SearchTerm('https://lorem.ipsum', 'ipsum', '@@'); $config = new SearchConfig(); $config->readFtsConfigFromTerms([$term]); @@ -125,32 +130,35 @@ public function testSearchFtsHighlight(): void { $config->ftsFragmentDelimiter = '|'; $result = self::$repo->getResourcesBySearchTerms([$term], $config); - $result = iterator_to_array($result); - $this->assertEquals(2, count($result)); - $ftsValueProp = self::$repo->getSchema()->searchFts; - $ftsPropProp = self::$repo->getSchema()->searchFtsProperty; - $ftsQueryProp = self::$repo->getSchema()->searchFtsQuery; - $ftsHighlight1 = (string) $result[0]->getMetadata()->getLiteral($ftsValueProp . '1'); - $ftsHighlight2 = (string) $result[1]->getMetadata()->getLiteral($ftsValueProp . '1'); - $date1 = (string) $result[0]->getMetadata()->getLiteral('https://date.prop'); - $date2 = (string) $result[1]->getMetadata()->getLiteral('https://date.prop'); + $meta = array_map(fn($x) => $x->getMetadata(), iterator_to_array($result)); + $this->assertEquals(2, count($meta)); + $ftsHighlight1 = $meta[0]->getObjectValue($ftsValueTmpl); + $ftsHighlight2 = $meta[1]->getObjectValue($ftsValueTmpl); + $date1 = $meta[0]->getObjectValue($dateTmpl); + $date2 = $meta[1]->getObjectValue($dateTmpl); $expected = [ '2019-01-01' => 'Lorem #ipsum# dolor', '2019-02-01' => 'Lorem #ipsum# dolor|eleifend #ipsum#', ]; $this->assertEquals($expected[$date1], $ftsHighlight1); $this->assertEquals($expected[$date2], $ftsHighlight2); - $this->assertEquals('https://lorem.ipsum', (string) $result[0]->getMetadata()->get($ftsPropProp . '1')); - $this->assertEquals('https://lorem.ipsum', (string) $result[1]->getMetadata()->get($ftsPropProp . '1')); - $this->assertEquals('ipsum', (string) $result[0]->getMetadata()->getLiteral($ftsQueryProp . '1')); - $this->assertEquals('ipsum', (string) $result[1]->getMetadata()->getLiteral($ftsQueryProp . '1')); + $this->assertEquals('https://lorem.ipsum', $meta[0]->getObjectValue($ftsPropTmpl)); + $this->assertEquals('https://lorem.ipsum', $meta[1]->getObjectValue($ftsPropTmpl)); + $this->assertEquals('ipsum', $meta[0]->getObjectValue($ftsQueryTmpl)); + $this->assertEquals('ipsum', $meta[1]->getObjectValue($ftsQueryTmpl)); } /** * @group search */ public function testSearchFtsHighlight2(): void { - $term = new SearchTerm('https://lorem.ipsum', 'ipsum', '@@'); + $ftsValue1Tmpl = new QT(predicate: self::$schema->searchFts->getValue() . '1'); + $ftsValue2Tmpl = new QT(predicate: self::$schema->searchFts->getValue() . '2'); + $ftsQuery1Tmpl = new QT(predicate: self::$schema->searchFtsQuery->getValue() . '1'); + $ftsQuery2Tmpl = new QT(predicate: self::$schema->searchFtsQuery->getValue() . '2'); + $dateTmpl = new QT(predicate: 'https://date.prop'); + + $terms = [new SearchTerm('https://lorem.ipsum', 'ipsum', '@@')]; $config = new SearchConfig(); $config->ftsQuery = ['ipsum', 'dolor']; $config->ftsStartSel = ['#', '@']; @@ -160,28 +168,26 @@ public function testSearchFtsHighlight2(): void { $config->ftsMaxFragments = [2, 2]; $config->ftsFragmentDelimiter = ['|', '~']; - $result = iterator_to_array(self::$repo->getResourcesBySearchTerms([ - $term], $config)); - $this->assertEquals(2, count($result)); - $ftsValueProp = self::$repo->getSchema()->searchFts; - $ftsQueryProp = self::$repo->getSchema()->searchFtsQuery; - $date1 = (string) $result[0]->getMetadata()->getLiteral('https://date.prop'); - $date2 = (string) $result[1]->getMetadata()->getLiteral('https://date.prop'); + $result = self::$repo->getResourcesBySearchTerms($terms, $config); + $meta = array_map(fn($x) => $x->getMetadata(), iterator_to_array($result)); + $this->assertEquals(2, count($meta)); + $date1 = $meta[0]->getObjectValue($dateTmpl); + $date2 = $meta[1]->getObjectValue($dateTmpl); $ftsHighlight1 = [ - (string) $result[0]->getMetadata()->getLiteral($ftsValueProp . '1'), - (string) $result[0]->getMetadata()->getLiteral($ftsValueProp . '2'), + $meta[0]->getObjectValue($ftsValue1Tmpl), + $meta[0]->getObjectValue($ftsValue2Tmpl), ]; $ftsHighlight2 = [ - (string) $result[1]->getMetadata()->getLiteral($ftsValueProp . '1'), - (string) $result[1]->getMetadata()->getLiteral($ftsValueProp . '2'), + $meta[1]->getObjectValue($ftsValue1Tmpl), + $meta[1]->getObjectValue($ftsValue2Tmpl), ]; $ftsQuery1 = [ - (string) $result[0]->getMetadata()->getLiteral($ftsQueryProp . '1'), - (string) $result[0]->getMetadata()->getLiteral($ftsQueryProp . '2'), + $meta[0]->getObjectValue($ftsQuery1Tmpl), + $meta[0]->getObjectValue($ftsQuery2Tmpl), ]; $ftsQuery2 = [ - (string) $result[1]->getMetadata()->getLiteral($ftsQueryProp . '1'), - (string) $result[1]->getMetadata()->getLiteral($ftsQueryProp . '2'), + $meta[1]->getObjectValue($ftsQuery1Tmpl), + $meta[1]->getObjectValue($ftsQuery2Tmpl), ]; $order1 = $ftsQuery1[0] === 'ipsum' ? [0, 1] : [1, 0]; $order2 = $ftsQuery2[0] === 'ipsum' ? [0, 1] : [1, 0]; @@ -201,24 +207,28 @@ public function testSearchFtsHighlight2(): void { * @group search */ public function testSearchRelatives(): void { + $dateTmpl = new QT(predicate: 'https://date.prop'); + $query = "SELECT id FROM metadata WHERE property = ? AND value = ?"; $param = ['https://date.prop', '2019-02-01']; $config = new SearchConfig(); $config->metadataMode = RepoResource::META_RELATIVES; - $config->metadataParentProperty = self::$repo->getSchema()->parent; + $config->metadataParentProperty = self::$schema->parent; - $result = iterator_to_array(self::$repo->getResourcesBySqlQuery($query, $param, $config)); + $result = iterator_to_array(self::$repo->getResourcesBySqlQuery($query, $param, $config)); $this->assertEquals(1, count($result)); - $meta = $result[0]->getGraph(); - $this->assertEquals('2019-02-01', (string) $meta->getLiteral('https://date.prop')); - $parentProp = self::$repo->getSchema()->parent; - $this->assertEquals('2019-01-01', (string) $meta->getResource($parentProp)?->getLiteral('https://date.prop')); + $meta = $result[0]->getGraph(); + $this->assertEquals('2019-02-01', $meta->getObjectValue($dateTmpl)); + $parent = $meta->getObject(new QT(predicate: self::$schema->parent)); + $this->assertEquals('2019-01-01', $meta->getDataset()->getObjectValue(new QT($parent, $dateTmpl->getPredicate()))); } /** * @group search */ public function testSearchPaging(): void { + $dateTmpl = new QT(predicate: 'https://date.prop'); + $query = "SELECT id FROM metadata WHERE property = ? ORDER BY id"; $param = ['https://date.prop']; $config = new SearchConfig(); @@ -228,7 +238,7 @@ public function testSearchPaging(): void { $result = iterator_to_array(self::$repo->getResourcesBySqlQuery($query, $param, $config)); $this->assertEquals(1, count($result)); $meta = $result[0]->getGraph(); - $this->assertEquals('2019-01-01', (string) $meta->getLiteral('https://date.prop')); + $this->assertEquals('2019-01-01', $meta->getObjectValue($dateTmpl)); $this->assertEquals(2, $config->count); $config->offset = 1; @@ -236,7 +246,7 @@ public function testSearchPaging(): void { $result = iterator_to_array(self::$repo->getResourcesBySqlQuery($query, $param, $config)); $this->assertEquals(1, count($result)); $meta = $result[0]->getGraph(); - $this->assertEquals('2019-02-01', (string) $meta->getLiteral('https://date.prop')); + $this->assertEquals('2019-02-01', $meta->getObjectValue($dateTmpl)); $this->assertEquals(2, $config->count); } @@ -245,20 +255,21 @@ public function testSearchPaging(): void { */ public function testSearchOrder(): void { $dateProp = 'https://date.prop'; + $dateTmpl = new QT(predicate: $dateProp); $query = "SELECT id FROM metadata WHERE property = ? ORDER BY id"; $param = [$dateProp]; $config = new SearchConfig(); $config->orderBy = [$dateProp]; $results = iterator_to_array(self::$repo->getResourcesBySqlQuery($query, $param, $config)); - $first = (string) $results[0]->getGraph()->get($dateProp); - $second = (string) $results[1]->getGraph()->get($dateProp); + $first = $results[0]->getGraph()->getObjectValue($dateTmpl); + $second = $results[1]->getGraph()->getObjectValue($dateTmpl); $this->assertGreaterThan($first, $second); $config->orderBy = ["^$dateProp"]; $results = iterator_to_array(self::$repo->getResourcesBySqlQuery($query, $param, $config)); - $first = (string) $results[0]->getGraph()->get($dateProp); - $second = (string) $results[1]->getGraph()->get($dateProp); + $first = $results[0]->getGraph()->getObjectValue($dateTmpl); + $second = $results[1]->getGraph()->getObjectValue($dateTmpl); $this->assertLessThan($first, $second); } @@ -269,7 +280,7 @@ public function testSearchOr(): void { $terms = [new SearchTerm(['https://date.prop', 'https://number.prop'], 20)]; $result = iterator_to_array(self::$repo->getResourcesBySearchTerms($terms, new SearchConfig())); $this->assertEquals(1, count($result)); - $this->assertEquals('a more original title for a resource', (string) $result[0]->getMetadata()->getLiteral(self::$schema->label)); + $this->assertEquals('a more original title for a resource', $result[0]->getMetadata()->getObjectValue(new QT(predicate: self::$schema->label))); } /** @@ -278,10 +289,10 @@ public function testSearchOr(): void { * @group search */ public function testSearchRelation(): void { - $terms = [new SearchTerm(self::$repo->getSchema()->parent, 'https://an.unique.id')]; + $terms = [new SearchTerm(self::$schema->parent, 'https://an.unique.id')]; $result = iterator_to_array(self::$repo->getResourcesBySearchTerms($terms, new SearchConfig())); $this->assertEquals(1, count($result)); - $this->assertEquals('a more original title for a resource', (string) $result[0]->getMetadata()->getLiteral(self::$schema->label)); + $this->assertEquals('a more original title for a resource', $result[0]->getMetadata()->getObjectValue(new QT(predicate: self::$schema->label))); } /** @@ -290,11 +301,11 @@ public function testSearchRelation(): void { */ public function testSearchInverseRelation(): void { $terms = [new SearchTerm( - SearchTerm::PROPERTY_NEGATE . self::$repo->getSchema()->parent, + SearchTerm::PROPERTY_NEGATE . self::$schema->parent, 'https://res2.id' )]; $result = iterator_to_array(self::$repo->getResourcesBySearchTerms($terms, new SearchConfig())); $this->assertEquals(1, count($result)); - $this->assertEquals('sample label for the first resource', (string) $result[0]->getMetadata()->getLiteral(self::$schema->label)); + $this->assertEquals('sample label for the first resource', $result[0]->getMetadata()->getObjectValue(new QT(predicate: self::$schema->label))); } } diff --git a/tests/TestBase.php b/tests/TestBase.php index 3fe09ad..45d8cbe 100644 --- a/tests/TestBase.php +++ b/tests/TestBase.php @@ -26,15 +26,12 @@ namespace acdhOeaw\arche\lib\tests; -use EasyRdf\Graph; -use EasyRdf\Resource; use GuzzleHttp\Exception\ClientException; +use quickRdf\DataFactory as DF; +use quickRdf\DatasetNode; use acdhOeaw\arche\lib\Config; use acdhOeaw\arche\lib\Repo; -use acdhOeaw\arche\lib\RepoResource; use acdhOeaw\arche\lib\Schema; -use acdhOeaw\arche\lib\exception\Deleted; -use acdhOeaw\arche\lib\exception\NotFound; /** * Description of TestBase @@ -50,79 +47,68 @@ class TestBase extends \PHPUnit\Framework\TestCase { static public function setUpBeforeClass(): void { $cfgFile = __DIR__ . '/config.yaml'; self::$config = Config::fromYaml($cfgFile); - self::$schema = new Schema(self::$config->schema); self::$repo = Repo::factory($cfgFile); + self::$schema = self::$repo->getSchema(); } static public function tearDownAfterClass(): void { } - /** - * - * @var array - */ - private array $resources; - public function setUp(): void { - $this->resources = []; + exec("docker exec -u www-data arche psql -c 'TRUNCATE resources CASCADE' 2>&1 > /dev/null"); + if (file_exists(__DIR__ . '/../log/rest.log')) { + unlink(__DIR__ . '/../log/rest.log'); + } + if (file_exists(__DIR__ . '/../log/tx.log')) { + unlink(__DIR__ . '/../log/tx.log'); + } } public function tearDown(): void { - try { - self::$repo->rollback(); - } catch (ClientException $e) { - - } - self::$repo->begin(); - foreach ($this->resources as $i) { + if (self::$repo->inTransaction()) { try { - $i->delete(true, true, self::$schema->parent); - } catch (Deleted $e) { - - } catch (NotFound $e) { + self::$repo->rollback(); + } catch (ClientException) { } } - self::$repo->commit(); - } - - protected function noteResource(RepoResource $res): void { - $this->resources[] = $res; } /** * * @param array $properties - * @return Resource + * @return DatasetNode */ - protected function getMetadata(array $properties): Resource { - $graph = new Graph(); - $res = $graph->newBNode(); + protected function getMetadata(array $properties): DatasetNode { + $res = DF::blankNode(); + $graph = new DatasetNode($res); foreach ($properties as $p => $v) { switch ($p) { case 'id': $p = self::$schema->id; break; case 'rel': + case 'parent': $p = self::$schema->parent; break; case 'title': case 'label': $p = self::$schema->label; break; + default: + $p = DF::namedNode($p); } if (!is_array($v)) { $v = [$v]; } foreach ($v as $i) { - if (preg_match('|^https?://|', $i)) { - $res->addResource($p, $i); - } else { - $res->addLiteral($p, $i); + if (!is_object($i)) { + $i = preg_match('|^https?://|', $i) ? DF::namedNode($i) : DF::literal($i); } + $graph->add(DF::quad($res, $p, $i)); } } - return $res; + return $graph; } }