Don't use named parameters in code

This commit is contained in:
Sebastian Meyer 2024-10-14 14:51:42 +02:00
parent 97fee92f63
commit 396f8dc9e0
36 changed files with 533 additions and 704 deletions

View File

@ -31,8 +31,8 @@
"ext-dom": "*", "ext-dom": "*",
"ext-libxml": "*", "ext-libxml": "*",
"ext-sqlite3": "*", "ext-sqlite3": "*",
"doctrine/dbal": "^4.1", "doctrine/dbal": "^4.2",
"doctrine/orm": "^3.2", "doctrine/orm": "^3.3",
"opencultureconsulting/basics": "^2.1", "opencultureconsulting/basics": "^2.1",
"opencultureconsulting/psr15": "^1.2", "opencultureconsulting/psr15": "^1.2",
"symfony/cache": "^6.4", "symfony/cache": "^6.4",

53
composer.lock generated
View File

@ -94,16 +94,16 @@
}, },
{ {
"name": "doctrine/dbal", "name": "doctrine/dbal",
"version": "4.1.1", "version": "4.2.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/dbal.git", "url": "https://github.com/doctrine/dbal.git",
"reference": "7a8252418689feb860ea8dfeab66d64a56a64df8" "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/7a8252418689feb860ea8dfeab66d64a56a64df8", "url": "https://api.github.com/repos/doctrine/dbal/zipball/dadd35300837a3a2184bd47d403333b15d0a9bd0",
"reference": "7a8252418689feb860ea8dfeab66d64a56a64df8", "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -116,7 +116,7 @@
"doctrine/coding-standard": "12.0.0", "doctrine/coding-standard": "12.0.0",
"fig/log-test": "^1", "fig/log-test": "^1",
"jetbrains/phpstorm-stubs": "2023.2", "jetbrains/phpstorm-stubs": "2023.2",
"phpstan/phpstan": "1.12.0", "phpstan/phpstan": "1.12.6",
"phpstan/phpstan-phpunit": "1.4.0", "phpstan/phpstan-phpunit": "1.4.0",
"phpstan/phpstan-strict-rules": "^1.6", "phpstan/phpstan-strict-rules": "^1.6",
"phpunit/phpunit": "10.5.30", "phpunit/phpunit": "10.5.30",
@ -182,7 +182,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/dbal/issues", "issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/4.1.1" "source": "https://github.com/doctrine/dbal/tree/4.2.1"
}, },
"funding": [ "funding": [
{ {
@ -198,7 +198,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-09-03T08:58:39+00:00" "time": "2024-10-10T18:01:27+00:00"
}, },
{ {
"name": "doctrine/deprecations", "name": "doctrine/deprecations",
@ -578,16 +578,16 @@
}, },
{ {
"name": "doctrine/orm", "name": "doctrine/orm",
"version": "3.2.2", "version": "3.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/orm.git", "url": "https://github.com/doctrine/orm.git",
"reference": "831a1eb7d260925528cdbb49cc1866c0357cf147" "reference": "69958152e661aa9c14e80d1ee4962863485aa60b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/orm/zipball/831a1eb7d260925528cdbb49cc1866c0357cf147", "url": "https://api.github.com/repos/doctrine/orm/zipball/69958152e661aa9c14e80d1ee4962863485aa60b",
"reference": "831a1eb7d260925528cdbb49cc1866c0357cf147", "reference": "69958152e661aa9c14e80d1ee4962863485aa60b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -609,7 +609,10 @@
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^12.0", "doctrine/coding-standard": "^12.0",
"phpbench/phpbench": "^1.0", "phpbench/phpbench": "^1.0",
"phpstan/phpstan": "1.11.1", "phpdocumentor/guides-cli": "^1.4",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "1.12.6",
"phpstan/phpstan-deprecation-rules": "^1.2",
"phpunit/phpunit": "^10.4.0", "phpunit/phpunit": "^10.4.0",
"psr/log": "^1 || ^2 || ^3", "psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2", "squizlabs/php_codesniffer": "3.7.2",
@ -660,9 +663,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/orm/issues", "issues": "https://github.com/doctrine/orm/issues",
"source": "https://github.com/doctrine/orm/tree/3.2.2" "source": "https://github.com/doctrine/orm/tree/3.3.0"
}, },
"time": "2024-08-23T10:03:52+00:00" "time": "2024-10-12T20:07:18+00:00"
}, },
{ {
"name": "doctrine/persistence", "name": "doctrine/persistence",
@ -4225,16 +4228,16 @@
}, },
{ {
"name": "phpstan/phpdoc-parser", "name": "phpstan/phpdoc-parser",
"version": "1.32.0", "version": "1.33.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git", "url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140",
"reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4266,22 +4269,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types", "description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": { "support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues", "issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0"
}, },
"time": "2024-09-26T07:23:32+00:00" "time": "2024-10-13T11:25:22+00:00"
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.12.5", "version": "1.12.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17" "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc4d2f145a88ea7141ae698effd64d9df46527ae",
"reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4326,7 +4329,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-09-26T12:45:22+00:00" "time": "2024-10-06T15:03:59+00:00"
}, },
{ {
"name": "phpstan/phpstan-doctrine", "name": "phpstan/phpstan-doctrine",

View File

@ -43,7 +43,7 @@ final class App
*/ */
public function __construct() public function __construct()
{ {
$this->requestHandler = new QueueRequestHandler(middlewares: [new Dispatcher()]); $this->requestHandler = new QueueRequestHandler([new Dispatcher()]);
} }
/** /**

View File

@ -72,22 +72,20 @@ final class Configuration
*/ */
private function __construct() private function __construct()
{ {
$configPath = Path::canonicalize(path: self::CONFIG_FILE); $configPath = Path::canonicalize(self::CONFIG_FILE);
if (!is_readable(filename: $configPath)) { if (!is_readable($configPath)) {
throw new FileNotFoundException( throw new FileNotFoundException(
message: 'Configuration file not found or not readable.', 'Configuration file not found or not readable.',
code: 500, 500,
path: $configPath null,
$configPath
); );
} }
/** @var array<TKey, TValue> */ /** @var array<TKey, TValue> */
$config = Yaml::parseFile(filename: $configPath); $config = Yaml::parseFile($configPath);
$violations = ConfigurationValidator::validate(config: $config); $violations = ConfigurationValidator::validate($config);
if ($violations->count() > 0) { if ($violations->count() > 0) {
throw new ValidationFailedException( throw new ValidationFailedException(null, $violations);
value: null,
violations: $violations
);
} }
$this->settings = $config; $this->settings = $config;
} }

View File

@ -91,13 +91,11 @@ abstract class Console extends Command
/** @var Application */ /** @var Application */
$app = $this->getApplication(); $app = $this->getApplication();
$app->doRun( $app->doRun(
input: new ArrayInput( new ArrayInput([
parameters: [
'command' => 'orm:clear-cache:result', 'command' => 'orm:clear-cache:result',
'--flush' => true '--flush' => true
] ]),
), new NullOutput()
output: new NullOutput()
); );
} }
@ -109,7 +107,7 @@ abstract class Console extends Command
protected function getPhpMemoryLimit(): int protected function getPhpMemoryLimit(): int
{ {
if (!isset($this->memoryLimit)) { if (!isset($this->memoryLimit)) {
$ini = trim(string: ini_get(option: 'memory_limit')); $ini = trim(ini_get('memory_limit'));
$limit = (int) $ini; $limit = (int) $ini;
if ($limit < 0) { if ($limit < 0) {
return -1; return -1;
@ -146,47 +144,41 @@ abstract class Console extends Command
if (array_key_exists('format', $this->arguments)) { if (array_key_exists('format', $this->arguments)) {
$formats = $this->em->getMetadataFormats(); $formats = $this->em->getMetadataFormats();
if (!$formats->containsKey(key: $this->arguments['format'])) { if (!$formats->containsKey($this->arguments['format'])) {
$output->writeln( $output->writeln([
messages: [
'', '',
sprintf( sprintf(
format: ' [ERROR] Metadata format "%s" is not supported. ', ' [ERROR] Metadata format "%s" is not supported. ',
values: $this->arguments['format'] $this->arguments['format']
), ),
'' ''
] ]);
);
return false; return false;
} }
} }
if (array_key_exists('file', $this->arguments) && !is_readable(filename: $this->arguments['file'])) { if (array_key_exists('file', $this->arguments) && !is_readable($this->arguments['file'])) {
$output->writeln( $output->writeln([
messages: [
'', '',
sprintf( sprintf(
format: ' [ERROR] File "%s" not found or not readable. ', ' [ERROR] File "%s" not found or not readable. ',
values: $this->arguments['file'] $this->arguments['file']
), ),
'' ''
] ]);
);
return false; return false;
} }
if (array_key_exists('sets', $this->arguments)) { if (array_key_exists('sets', $this->arguments)) {
$sets = $this->em->getSets(); $sets = $this->em->getSets();
$invalidSets = array_diff($this->arguments['sets'], $sets->getKeys()); $invalidSets = array_diff($this->arguments['sets'], $sets->getKeys());
if (count($invalidSets) !== 0) { if (count($invalidSets) !== 0) {
$output->writeln( $output->writeln([
messages: [
'', '',
sprintf( sprintf(
format: ' [ERROR] Sets "%s" are not supported. ', ' [ERROR] Sets "%s" are not supported. ',
values: implode('", "', $invalidSets) implode('", "', $invalidSets)
), ),
'' ''
] ]);
);
return false; return false;
} }
} }

View File

@ -84,30 +84,27 @@ class AddRecordCommand extends Console
*/ */
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int
{ {
if (!$this->validateInput(input: $input, output: $output)) { if (!$this->validateInput($input, $output)) {
return Command::INVALID; return Command::INVALID;
} }
/** @var Format */ /** @var Format */
$format = $this->em->getMetadataFormat(prefix: $this->arguments['format']); $format = $this->em->getMetadataFormat($this->arguments['format']);
$content = file_get_contents(filename: $this->arguments['file']) ?: ''; $content = file_get_contents($this->arguments['file']) ?: '';
$record = new Record( $record = new Record($this->arguments['identifier'], $format);
identifier: $this->arguments['identifier'],
format: $format
);
if (trim($content) !== '') { if (trim($content) !== '') {
$record->setContent(data: $content); $record->setContent($content);
} }
if (array_key_exists('sets', $this->arguments)) { if (array_key_exists('sets', $this->arguments)) {
foreach ($this->arguments['sets'] as $set) { foreach ($this->arguments['sets'] as $set) {
/** @var Set */ /** @var Set */
$setSpec = $this->em->getSet(spec: $set); $setSpec = $this->em->getSet($set);
$record->addSet(set: $setSpec); $record->addSet($setSpec);
} }
} }
$this->em->addOrUpdate(entity: $record); $this->em->addOrUpdate($record);
$this->em->pruneOrphanedSets(); $this->em->pruneOrphanedSets();
$this->clearResultCache(); $this->clearResultCache();

View File

@ -81,20 +81,16 @@ class AddSetCommand extends Console
*/ */
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int
{ {
if (!$this->validateInput(input: $input, output: $output)) { if (!$this->validateInput($input, $output)) {
return Command::INVALID; return Command::INVALID;
} }
if (array_key_exists('file', $this->arguments)) { if (array_key_exists('file', $this->arguments)) {
$description = file_get_contents(filename: $this->arguments['file']) ?: null; $description = file_get_contents($this->arguments['file']) ?: null;
} }
$set = new Set( $set = new Set($this->arguments['setSpec'], $this->arguments['setName'], $description ?? null);
spec: $this->arguments['setSpec'], $this->em->addOrUpdate($set);
name: $this->arguments['setName'],
description: $description ?? null
);
$this->em->addOrUpdate(entity: $set);
return Command::SUCCESS; return Command::SUCCESS;
} }

View File

@ -122,14 +122,14 @@ class CsvImportCommand extends Console
*/ */
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int
{ {
if (!$this->validateInput(input: $input, output: $output)) { if (!$this->validateInput($input, $output)) {
return Command::INVALID; return Command::INVALID;
} }
/** @var resource */ /** @var resource */
$file = fopen(filename: $this->arguments['file'], mode: 'r'); $file = fopen($this->arguments['file'], 'r');
$columnMapping = $this->getColumnNames(input: $input, output: $output, file: $file); $columnMapping = $this->getColumnNames($input, $output, $file);
if (!isset($columnMapping)) { if (!isset($columnMapping)) {
return Command::FAILURE; return Command::FAILURE;
@ -139,31 +139,25 @@ class CsvImportCommand extends Console
$progressIndicator = new ProgressIndicator($output, null, 100, ['⠏', '⠛', '⠹', '⢸', '⣰', '⣤', '⣆', '⡇']); $progressIndicator = new ProgressIndicator($output, null, 100, ['⠏', '⠛', '⠹', '⢸', '⣰', '⣤', '⣆', '⡇']);
$progressIndicator->start('Importing...'); $progressIndicator->start('Importing...');
while ($row = fgetcsv(stream: $file)) { while ($row = fgetcsv($file)) {
/** @var Format */ /** @var Format */
$format = $this->em->getMetadataFormat(prefix: $this->arguments['format']); $format = $this->em->getMetadataFormat($this->arguments['format']);
$record = new Record( $record = new Record($row[$columnMapping['idColumn']], $format);
identifier: $row[$columnMapping['idColumn']],
format: $format
);
if (strlen(trim($row[$columnMapping['contentColumn']])) > 0) { if (strlen(trim($row[$columnMapping['contentColumn']])) > 0) {
$record->setContent( $record->setContent($row[$columnMapping['contentColumn']], !$this->arguments['noValidation']);
data: $row[$columnMapping['contentColumn']],
validate: !$this->arguments['noValidation']
);
} }
if (isset($columnMapping['dateColumn'])) { if (isset($columnMapping['dateColumn'])) {
$record->setLastChanged(dateTime: new DateTime($row[$columnMapping['dateColumn']])); $record->setLastChanged(new DateTime($row[$columnMapping['dateColumn']]));
} }
if (isset($columnMapping['setColumn'])) { if (isset($columnMapping['setColumn'])) {
$sets = $row[$columnMapping['setColumn']]; $sets = $row[$columnMapping['setColumn']];
foreach (explode(',', $sets) as $set) { foreach (explode(',', $sets) as $set) {
/** @var Set */ /** @var Set */
$setSpec = $this->em->getSet(spec: trim($set)); $setSpec = $this->em->getSet(trim($set));
$record->addSet(set: $setSpec); $record->addSet($setSpec);
} }
} }
$this->em->addOrUpdate(entity: $record, bulkMode: true); $this->em->addOrUpdate($record, true);
++$count; ++$count;
$progressIndicator->advance(); $progressIndicator->advance();
@ -175,7 +169,7 @@ class CsvImportCommand extends Console
$progressIndicator->finish('All done!'); $progressIndicator->finish('All done!');
fclose(stream: $file); fclose($file);
$this->clearResultCache(); $this->clearResultCache();
@ -215,9 +209,9 @@ class CsvImportCommand extends Console
$output->writeln([ $output->writeln([
'', '',
sprintf( sprintf(
format: ' [ERROR] File "%s" does not contain valid CSV. ', ' [ERROR] File "%s" does not contain valid CSV. ',
/** @phpstan-ignore-next-line - URI is always set for fopen() resources. */ /** @phpstan-ignore-next-line - URI is always set for fopen() resources. */
values: stream_get_meta_data(stream: $file)['uri'] ?: 'unknown' stream_get_meta_data($file)['uri'] ?: 'unknown'
), ),
'' ''
]); ]);
@ -236,9 +230,9 @@ class CsvImportCommand extends Console
$output->writeln([ $output->writeln([
'', '',
sprintf( sprintf(
format: ' [ERROR] File "%s" does not contain mandatory columns. ', ' [ERROR] File "%s" does not contain mandatory columns. ',
/** @phpstan-ignore-next-line - URI is always set for fopen() resources. */ /** @phpstan-ignore-next-line - URI is always set for fopen() resources. */
values: stream_get_meta_data($file)['uri'] ?: 'unknown' stream_get_meta_data($file)['uri'] ?: 'unknown'
), ),
'' ''
]); ]);

View File

@ -71,17 +71,14 @@ class DeleteRecordCommand extends Console
*/ */
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int
{ {
if (!$this->validateInput(input: $input, output: $output)) { if (!$this->validateInput($input, $output)) {
return Command::INVALID; return Command::INVALID;
} }
$record = $this->em->getRecord( $record = $this->em->getRecord($this->arguments['identifier'], $this->arguments['format']);
identifier: $this->arguments['identifier'],
format: $this->arguments['format']
);
if (isset($record)) { if (isset($record)) {
$this->em->delete(entity: $record); $this->em->delete($record);
$this->clearResultCache(); $this->clearResultCache();
$output->writeln([ $output->writeln([
'', '',

View File

@ -79,8 +79,8 @@ class PruneDeletedRecordsCommand extends Console
$output->writeln([ $output->writeln([
'', '',
sprintf( sprintf(
format: ' [OK] %d deleted records were successfully removed! ', ' [OK] %d deleted records were successfully removed! ',
values: $deleted $deleted
), ),
'' ''
]); ]);

View File

@ -54,8 +54,8 @@ class PruneResumptionTokensCommand extends Console
$output->writeln([ $output->writeln([
'', '',
sprintf( sprintf(
format: ' [OK] %d expired resumption tokens were successfully deleted! ', ' [OK] %d expired resumption tokens were successfully deleted! ',
values: $expired $expired
), ),
'' ''
]); ]);

View File

@ -69,24 +69,20 @@ class UpdateFormatsCommand extends Console
continue; continue;
} }
try { try {
$format = new Format( $format = new Format($prefix, $format['namespace'], $format['schema']);
prefix: $prefix, $this->em->addOrUpdate($format);
namespace: $format['namespace'],
schema: $format['schema']
);
$this->em->addOrUpdate(entity: $format);
$output->writeln([ $output->writeln([
sprintf( sprintf(
format: ' [OK] Metadata format "%s" added or updated successfully! ', ' [OK] Metadata format "%s" added or updated successfully! ',
values: $prefix $prefix
) )
]); ]);
} catch (ValidationFailedException $exception) { } catch (ValidationFailedException $exception) {
$failure = true; $failure = true;
$output->writeln([ $output->writeln([
sprintf( sprintf(
format: ' [ERROR] Could not add or update metadata format "%s". ', ' [ERROR] Could not add or update metadata format "%s". ',
values: $prefix $prefix
), ),
$exception->getMessage() $exception->getMessage()
]); ]);
@ -95,11 +91,11 @@ class UpdateFormatsCommand extends Console
foreach (array_diff($inDatabase->getKeys(), array_keys($formats)) as $prefix) { foreach (array_diff($inDatabase->getKeys(), array_keys($formats)) as $prefix) {
/** @var Format */ /** @var Format */
$format = $inDatabase[$prefix]; $format = $inDatabase[$prefix];
$this->em->delete(entity: $format); $this->em->delete($format);
$output->writeln([ $output->writeln([
sprintf( sprintf(
format: ' [OK] Metadata format "%s" and all associated records deleted successfully! ', ' [OK] Metadata format "%s" and all associated records deleted successfully! ',
values: $prefix $prefix
) )
]); ]);
} }

View File

@ -46,13 +46,10 @@ abstract class Entity
*/ */
protected function validateUrl(string $url): string protected function validateUrl(string $url): string
{ {
$url = trim(string: $url); $url = trim($url);
$violations = UrlValidator::validate(url: $url); $violations = UrlValidator::validate($url);
if ($violations->count() > 0) { if ($violations->count() > 0) {
throw new ValidationFailedException( throw new ValidationFailedException(null, $violations);
value: null,
violations: $violations
);
} }
return $url; return $url;
} }
@ -69,12 +66,9 @@ abstract class Entity
*/ */
protected function validateRegEx(string $string, string $regEx): string protected function validateRegEx(string $string, string $regEx): string
{ {
$violations = RegExValidator::validate(string: $string, regEx: $regEx); $violations = RegExValidator::validate($string, $regEx);
if ($violations->count() > 0) { if ($violations->count() > 0) {
throw new ValidationFailedException( throw new ValidationFailedException(null, $violations);
value: null,
violations: $violations
);
} }
return $string; return $string;
} }
@ -90,12 +84,9 @@ abstract class Entity
*/ */
protected function validateXml(string $xml): string protected function validateXml(string $xml): string
{ {
$violations = XmlValidator::validate(xml: $xml); $violations = XmlValidator::validate($xml);
if ($violations->count() > 0) { if ($violations->count() > 0) {
throw new ValidationFailedException( throw new ValidationFailedException(null, $violations);
value: null,
violations: $violations
);
} }
return $xml; return $xml;
} }

View File

@ -124,7 +124,7 @@ final class Format extends Entity
public function setNamespace(string $namespace): void public function setNamespace(string $namespace): void
{ {
try { try {
$this->namespace = $this->validateUrl(url: $namespace); $this->namespace = $this->validateUrl($namespace);
} catch (ValidationFailedException $exception) { } catch (ValidationFailedException $exception) {
throw $exception; throw $exception;
} }
@ -142,7 +142,7 @@ final class Format extends Entity
public function setSchema(string $schema): void public function setSchema(string $schema): void
{ {
try { try {
$this->xmlSchema = $this->validateUrl(url: $schema); $this->xmlSchema = $this->validateUrl($schema);
} catch (ValidationFailedException $exception) { } catch (ValidationFailedException $exception) {
throw $exception; throw $exception;
} }
@ -160,12 +160,9 @@ final class Format extends Entity
public function __construct(string $prefix, string $namespace, string $schema) public function __construct(string $prefix, string $namespace, string $schema)
{ {
try { try {
$this->prefix = $this->validateRegEx( $this->prefix = $this->validateRegEx($prefix, '/^[A-Za-z0-9\-_\.!~\*\'\(\)]+$/');
string: $prefix, $this->setNamespace($namespace);
regEx: '/^[A-Za-z0-9\-_\.!~\*\'\(\)]+$/' $this->setSchema($schema);
);
$this->setNamespace(namespace: $namespace);
$this->setSchema(schema: $schema);
$this->records = new ArrayCollection(); $this->records = new ArrayCollection();
} catch (ValidationFailedException $exception) { } catch (ValidationFailedException $exception) {
throw $exception; throw $exception;

View File

@ -106,9 +106,9 @@ final class Record extends Entity
*/ */
public function addSet(Set $set): void public function addSet(Set $set): void
{ {
if (!$this->sets->contains(element: $set)) { if (!$this->sets->contains($set)) {
$this->sets->add(element: $set); $this->sets->add($set);
$set->addRecord(record: $this); $set->addRecord($this);
} }
} }
@ -161,7 +161,7 @@ final class Record extends Entity
*/ */
public function getSet(string $setSpec): ?Set public function getSet(string $setSpec): ?Set
{ {
return $this->sets->get(key: $setSpec); return $this->sets->get($setSpec);
} }
/** /**
@ -196,9 +196,9 @@ final class Record extends Entity
*/ */
public function removeSet(Set $set): void public function removeSet(Set $set): void
{ {
if ($this->sets->contains(element: $set)) { if ($this->sets->contains($set)) {
$this->sets->removeElement(element: $set); $this->sets->removeElement($set);
$set->removeRecord(record: $this); $set->removeRecord($this);
} }
} }
@ -218,7 +218,7 @@ final class Record extends Entity
$data = trim($data); $data = trim($data);
if ($validate) { if ($validate) {
try { try {
$data = $this->validateXml(xml: $data); $data = $this->validateXml($data);
} catch (ValidationFailedException $exception) { } catch (ValidationFailedException $exception) {
throw $exception; throw $exception;
} }
@ -268,13 +268,13 @@ final class Record extends Entity
{ {
try { try {
$this->identifier = $this->validateRegEx( $this->identifier = $this->validateRegEx(
string: $identifier, $identifier,
// xs:anyURI // xs:anyURI
regEx: '/^(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)?\/{0,2}[0-9a-zA-Z;\/?:@&=+$\\.\\-_!~*\'()%]+)?(#[0-9a-zA-Z;\/?:@&=+$\\.\\-_!~*\'()%]+)?$/' '/^(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)?\/{0,2}[0-9a-zA-Z;\/?:@&=+$\\.\\-_!~*\'()%]+)?(#[0-9a-zA-Z;\/?:@&=+$\\.\\-_!~*\'()%]+)?$/'
); );
$this->setFormat(format: $format); $this->setFormat($format);
$this->setContent(data: $data); $this->setContent($data);
$this->setLastChanged(dateTime: $lastChanged); $this->setLastChanged($lastChanged);
$this->sets = new ArrayCollection(); $this->sets = new ArrayCollection();
} catch (ValidationFailedException $exception) { } catch (ValidationFailedException $exception) {
throw $exception; throw $exception;

View File

@ -80,9 +80,9 @@ final class Set extends Entity
*/ */
public function addRecord(Record $record): void public function addRecord(Record $record): void
{ {
if (!$this->records->contains(element: $record)) { if (!$this->records->contains($record)) {
$this->records->add(element: $record); $this->records->add($record);
$record->addSet(set: $this); $record->addSet($this);
} }
} }
@ -158,9 +158,9 @@ final class Set extends Entity
*/ */
public function removeRecord(Record $record): void public function removeRecord(Record $record): void
{ {
if ($this->records->contains(element: $record)) { if ($this->records->contains($record)) {
$this->records->removeElement(element: $record); $this->records->removeElement($record);
$record->removeSet(set: $this); $record->removeSet($this);
} }
} }
@ -178,7 +178,7 @@ final class Set extends Entity
if (isset($description)) { if (isset($description)) {
$description = trim($description); $description = trim($description);
try { try {
$description = $this->validateXml(xml: $description); $description = $this->validateXml($description);
} catch (ValidationFailedException $exception) { } catch (ValidationFailedException $exception) {
throw $exception; throw $exception;
} }
@ -211,11 +211,11 @@ final class Set extends Entity
{ {
try { try {
$this->spec = $this->validateRegEx( $this->spec = $this->validateRegEx(
string: $spec, $spec,
regEx: '/^([A-Za-z0-9\-_\.!~\*\'\(\)])+(:[A-Za-z0-9\-_\.!~\*\'\(\)]+)*$/' '/^([A-Za-z0-9\-_\.!~\*\'\(\)])+(:[A-Za-z0-9\-_\.!~\*\'\(\)]+)*$/'
); );
$this->setName(name: $name); $this->setName($name);
$this->setDescription(description: $description); $this->setDescription($description);
$this->records = new ArrayCollection(); $this->records = new ArrayCollection();
} catch (ValidationFailedException $exception) { } catch (ValidationFailedException $exception) {
throw $exception; throw $exception;

View File

@ -120,7 +120,7 @@ final class Token extends Entity
$this->verb = $verb; $this->verb = $verb;
$this->parameters = serialize($parameters); $this->parameters = serialize($parameters);
$validUntil = new DateTime(); $validUntil = new DateTime();
$validUntil->add(interval: new DateInterval(duration: 'PT' . Configuration::getInstance()->tokenValid . 'S')); $validUntil->add(new DateInterval('PT' . Configuration::getInstance()->tokenValid . 'S'));
$this->validUntil = $validUntil; $this->validUntil = $validUntil;
} }
} }

View File

@ -80,7 +80,7 @@ final class EntityManager extends EntityManagerDecorator
*/ */
public function addOrUpdate(Format|Record|Set|Token $entity, bool $bulkMode = false): void public function addOrUpdate(Format|Record|Set|Token $entity, bool $bulkMode = false): void
{ {
$this->getRepository(className: get_class($entity))->addOrUpdate(entity: $entity); $this->getRepository(get_class($entity))->addOrUpdate($entity);
if (!$bulkMode) { if (!$bulkMode) {
$this->flush(); $this->flush();
} }
@ -95,7 +95,7 @@ final class EntityManager extends EntityManagerDecorator
*/ */
public function delete(Format|Record|Set|Token $entity): void public function delete(Format|Record|Set|Token $entity): void
{ {
$this->getRepository(className: get_class($entity))->delete(entity: $entity); $this->getRepository(get_class($entity))->delete($entity);
} }
/** /**
@ -107,11 +107,11 @@ final class EntityManager extends EntityManagerDecorator
{ {
$timestamp = '0000-00-00T00:00:00Z'; $timestamp = '0000-00-00T00:00:00Z';
$dql = $this->createQueryBuilder(); $dql = $this->createQueryBuilder();
$dql->select(select: $dql->expr()->min('record.lastChanged')); $dql->select($dql->expr()->min('record.lastChanged'));
$dql->from(from: Record::class, alias: 'record'); $dql->from(Record::class, 'record');
$query = $dql->getQuery()->enableResultCache(); $query = $dql->getQuery()->enableResultCache();
/** @var ?string $result */ /** @var ?string $result */
$result = $query->getOneOrNullResult(hydrationMode: AbstractQuery::HYDRATE_SCALAR_COLUMN); $result = $query->getOneOrNullResult(AbstractQuery::HYDRATE_SCALAR_COLUMN);
return $result ?? $timestamp; return $result ?? $timestamp;
} }
@ -124,7 +124,7 @@ final class EntityManager extends EntityManagerDecorator
*/ */
public function getMetadataFormat(string $prefix): ?Format public function getMetadataFormat(string $prefix): ?Format
{ {
return $this->getReference(entityName: Format::class, id: $prefix); return $this->getReference(Format::class, $prefix);
} }
/** /**
@ -138,21 +138,21 @@ final class EntityManager extends EntityManagerDecorator
{ {
$entities = []; $entities = [];
if ($recordIdentifier === null) { if ($recordIdentifier === null) {
$formats = $this->getRepository(className: Format::class)->findAll(); $formats = $this->getRepository(Format::class)->findAll();
} else { } else {
$dql = $this->createQueryBuilder(); $dql = $this->createQueryBuilder();
$dql->select(select: 'record.format') $dql->select('record.format')
->from(from: Record::class, alias: 'record') ->from(Record::class, 'record')
->where(predicates: $dql->expr()->eq('record.identifier', ':recordIdentifier')) ->where($dql->expr()->eq('record.identifier', ':recordIdentifier'))
->setParameter(key: 'recordIdentifier', value: $recordIdentifier); ->setParameter('recordIdentifier', $recordIdentifier);
$query = $dql->getQuery()->enableResultCache(); $query = $dql->getQuery()->enableResultCache();
/** @var Format[] */ /** @var Format[] */
$formats = $query->getResult(hydrationMode: AbstractQuery::HYDRATE_OBJECT); $formats = $query->getResult(AbstractQuery::HYDRATE_OBJECT);
} }
foreach ($formats as $format) { foreach ($formats as $format) {
$entities[$format->getPrefix()] = $format; $entities[$format->getPrefix()] = $format;
} }
return new ResultSet(elements: $entities); return new ResultSet($entities);
} }
/** /**
@ -165,12 +165,10 @@ final class EntityManager extends EntityManagerDecorator
*/ */
public function getRecord(string $identifier, string $format): ?Record public function getRecord(string $identifier, string $format): ?Record
{ {
return $this->getRepository(className: Record::class)->findOneBy( return $this->getRepository(Record::class)->findOneBy([
criteria: [
'identifier' => $identifier, 'identifier' => $identifier,
'format' => $this->getMetadataFormat(prefix: $format) 'format' => $this->getMetadataFormat($format)
] ]);
);
} }
/** /**
@ -198,45 +196,42 @@ final class EntityManager extends EntityManagerDecorator
$cursor = $counter * $maxRecords; $cursor = $counter * $maxRecords;
$dql = $this->createQueryBuilder(); $dql = $this->createQueryBuilder();
$dql->select(select: 'record') $dql->select('record')
->from(from: Record::class, alias: 'record', indexBy: 'record.identifier') ->from(Record::class, 'record', 'record.identifier')
->where(predicates: $dql->expr()->eq('record.format', ':metadataPrefix')) ->where($dql->expr()->eq('record.format', ':metadataPrefix'))
->setParameter( ->setParameter('metadataPrefix', $this->getMetadataFormat($metadataPrefix))
key: 'metadataPrefix', ->setFirstResult($cursor)
value: $this->getMetadataFormat(prefix: $metadataPrefix) ->setMaxResults($maxRecords);
)
->setFirstResult(firstResult: $cursor)
->setMaxResults(maxResults: $maxRecords);
if (isset($from)) { if (isset($from)) {
$dql->andWhere(where: $dql->expr()->gte('record.lastChanged', ':from')); $dql->andWhere($dql->expr()->gte('record.lastChanged', ':from'));
$dql->setParameter(key: 'from', value: new DateTime($from)); $dql->setParameter('from', new DateTime($from));
} }
if (isset($until)) { if (isset($until)) {
$dql->andWhere(where: $dql->expr()->lte('record.lastChanged', ':until')); $dql->andWhere($dql->expr()->lte('record.lastChanged', ':until'));
$dql->setParameter(key: 'until', value: new DateTime($until)); $dql->setParameter('until', new DateTime($until));
} }
if (isset($set)) { if (isset($set)) {
$dql->innerJoin( $dql->innerJoin(
join: Set::class, Set::class,
alias: 'sets', 'sets',
conditionType: Join::WITH, Join::WITH,
condition: $dql->expr()->orX( $dql->expr()->orX(
$dql->expr()->eq('sets.spec', ':setSpec'), $dql->expr()->eq('sets.spec', ':setSpec'),
$dql->expr()->like('sets.spec', ':setLike') $dql->expr()->like('sets.spec', ':setLike')
) )
); );
$dql->setParameter(key: 'setSpec', value: $set); $dql->setParameter('setSpec', $set);
$dql->setParameter(key: 'setLike', value: $set . ':%'); $dql->setParameter('setLike', $set . ':%');
} }
$query = $dql->getQuery(); $query = $dql->getQuery();
/** @var array<string, Record> */ /** @var array<string, Record> */
$queryResult = $query->getResult(); $queryResult = $query->getResult();
$result = new ResultSet(elements: $queryResult); $result = new ResultSet($queryResult);
$paginator = new Paginator(query: $query, fetchJoinCollection: true); $paginator = new Paginator($query, true);
if (count($paginator) > ($cursor + count($result))) { if (count($paginator) > ($cursor + count($result))) {
$token = new Token( $token = new Token(
verb: $verb, $verb,
parameters: [ [
'verb' => $verb, 'verb' => $verb,
'identifier' => null, 'identifier' => null,
'metadataPrefix' => $metadataPrefix, 'metadataPrefix' => $metadataPrefix,
@ -248,9 +243,9 @@ final class EntityManager extends EntityManagerDecorator
'completeListSize' => count($paginator) 'completeListSize' => count($paginator)
] ]
); );
$this->persist(object: $token); $this->persist($token);
$this->flush(); $this->flush();
$result->setResumptionToken(token: $token); $result->setResumptionToken($token);
} }
return $result; return $result;
} }
@ -265,14 +260,12 @@ final class EntityManager extends EntityManagerDecorator
*/ */
public function getResumptionToken(string $token, string $verb): ?Token public function getResumptionToken(string $token, string $verb): ?Token
{ {
$resumptionToken = $this->getRepository(className: Token::class)->findOneBy( $resumptionToken = $this->getRepository(Token::class)->findOneBy([
criteria: [
'token' => $token, 'token' => $token,
'verb' => $verb 'verb' => $verb
] ]);
);
if (isset($resumptionToken) && $resumptionToken->getValidUntil() < new DateTime()) { if (isset($resumptionToken) && $resumptionToken->getValidUntil() < new DateTime()) {
$this->delete(entity: $resumptionToken); $this->delete($resumptionToken);
return null; return null;
} }
return $resumptionToken; return $resumptionToken;
@ -287,7 +280,7 @@ final class EntityManager extends EntityManagerDecorator
*/ */
public function getSet(string $spec): ?Set public function getSet(string $spec): ?Set
{ {
return $this->getReference(entityName: Set::class, id: $spec); return $this->getReference(Set::class, $spec);
} }
/** /**
@ -303,19 +296,19 @@ final class EntityManager extends EntityManagerDecorator
$cursor = $counter * $maxRecords; $cursor = $counter * $maxRecords;
$dql = $this->createQueryBuilder(); $dql = $this->createQueryBuilder();
$dql->select(select: 'set') $dql->select('set')
->from(from: Set::class, alias: 'set', indexBy: 'set.spec') ->from(Set::class, 'set', 'set.spec')
->setFirstResult(firstResult: $cursor) ->setFirstResult($cursor)
->setMaxResults(maxResults: $maxRecords); ->setMaxResults($maxRecords);
$query = $dql->getQuery()->enableResultCache(); $query = $dql->getQuery()->enableResultCache();
/** @var array<string, Set> */ /** @var array<string, Set> */
$queryResult = $query->getResult(hydrationMode: AbstractQuery::HYDRATE_OBJECT); $queryResult = $query->getResult(AbstractQuery::HYDRATE_OBJECT);
$result = new ResultSet(elements: $queryResult); $result = new ResultSet($queryResult);
$paginator = new Paginator(query: $query); $paginator = new Paginator($query);
if (count($paginator) > ($cursor + count($result))) { if (count($paginator) > ($cursor + count($result))) {
$token = new Token( $token = new Token(
verb: 'ListSets', 'ListSets',
parameters: [ [
'verb' => 'ListSets', 'verb' => 'ListSets',
'identifier' => null, 'identifier' => null,
'metadataPrefix' => null, 'metadataPrefix' => null,
@ -327,9 +320,9 @@ final class EntityManager extends EntityManagerDecorator
'completeListSize' => count($paginator) 'completeListSize' => count($paginator)
] ]
); );
$this->persist(object: $token); $this->persist($token);
$this->flush(); $this->flush();
$result->setResumptionToken(token: $token); $result->setResumptionToken($token);
} }
return $result; return $result;
} }
@ -343,7 +336,7 @@ final class EntityManager extends EntityManagerDecorator
*/ */
public function isValidRecordIdentifier(string $identifier): bool public function isValidRecordIdentifier(string $identifier): bool
{ {
$records = $this->getRepository(className: Record::class)->findBy(criteria: ['identifier' => $identifier]); $records = $this->getRepository(Record::class)->findBy(['identifier' => $identifier]);
return (bool) count($records) > 0; return (bool) count($records) > 0;
} }
@ -355,8 +348,8 @@ final class EntityManager extends EntityManagerDecorator
public function pruneDeletedRecords(): int public function pruneDeletedRecords(): int
{ {
$dql = $this->createQueryBuilder(); $dql = $this->createQueryBuilder();
$dql->delete(delete: Record::class, alias: 'record') $dql->delete(Record::class, 'record')
->where(predicates: $dql->expr()->isNull('record.content')); ->where($dql->expr()->isNull('record.content'));
/** @var int */ /** @var int */
$deleted = $dql->getQuery()->execute(); $deleted = $dql->getQuery()->execute();
if ($deleted > 0) { if ($deleted > 0) {
@ -373,8 +366,8 @@ final class EntityManager extends EntityManagerDecorator
public function pruneExpiredTokens(): int public function pruneExpiredTokens(): int
{ {
$dql = $this->createQueryBuilder(); $dql = $this->createQueryBuilder();
$dql->delete(delete: Token::class, alias: 'token') $dql->delete(Token::class, 'token')
->where(predicates: $dql->expr()->lt('token.validUntil', new DateTime())); ->where($dql->expr()->lt('token.validUntil', new DateTime()));
/** @var int */ /** @var int */
return $dql->getQuery()->execute(); return $dql->getQuery()->execute();
} }
@ -386,12 +379,12 @@ final class EntityManager extends EntityManagerDecorator
*/ */
public function pruneOrphanedSets(): int public function pruneOrphanedSets(): int
{ {
$sets = $this->getRepository(className: Set::class)->findAll(); $sets = $this->getRepository(Set::class)->findAll();
$count = 0; $count = 0;
foreach ($sets as $set) { foreach ($sets as $set) {
if ($set->isEmpty()) { if ($set->isEmpty()) {
$count += 1; $count += 1;
$this->remove(object: $set); $this->remove($set);
} }
} }
if ($count > 0) { if ($count > 0) {
@ -406,66 +399,39 @@ final class EntityManager extends EntityManagerDecorator
private function __construct() private function __construct()
{ {
$config = new DoctrineConfiguration(); $config = new DoctrineConfiguration();
$config->setAutoGenerateProxyClasses( $config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_NEVER);
autoGenerate: ProxyFactory::AUTOGENERATE_NEVER $config->setMetadataCache(new PhpFilesAdapter('Metadata', 0, __DIR__ . '/../var/cache'));
); $config->setMetadataDriverImpl(new AttributeDriver([__DIR__ . '/Entity']));
$config->setMetadataCache( $config->setProxyDir(__DIR__ . '/../var/generated');
cache: new PhpFilesAdapter( $config->setProxyNamespace('OCC\OaiPmh2\Entity\Proxy');
namespace: 'Metadata', $config->setQueryCache(new PhpFilesAdapter('Query', 0, __DIR__ . '/../var/cache'));
directory: __DIR__ . '/../var/cache' $config->setResultCache(new PhpFilesAdapter('Result', 0, __DIR__ . '/../var/cache'));
)
);
$config->setMetadataDriverImpl(
driverImpl: new AttributeDriver(
paths: [__DIR__ . '/Entity']
)
);
$config->setProxyDir(dir: __DIR__ . '/../var/generated');
$config->setProxyNamespace(ns: 'OCC\OaiPmh2\Entity\Proxy');
$config->setQueryCache(
cache: new PhpFilesAdapter(
namespace: 'Query',
directory: __DIR__ . '/../var/cache'
)
);
$config->setResultCache(
cache: new PhpFilesAdapter(
namespace: 'Result',
directory: __DIR__ . '/../var/cache'
)
);
$config->setSchemaAssetsFilter( $config->setSchemaAssetsFilter(
schemaAssetsFilter: static function (string|AbstractAsset $assetName): bool { static function (string|AbstractAsset $assetName): bool {
if ($assetName instanceof AbstractAsset) { if ($assetName instanceof AbstractAsset) {
$assetName = $assetName->getName(); $assetName = $assetName->getName();
} }
return in_array(needle: $assetName, haystack: self::TABLES, strict: true); return in_array($assetName, self::TABLES, true);
} }
); );
$baseDir = Path::canonicalize(path: __DIR__ . '/../'); $baseDir = Path::canonicalize(__DIR__ . '/../');
$dsn = str_replace( $dsn = str_replace('%BASEDIR%', $baseDir, Configuration::getInstance()->database);
search: '%BASEDIR%', $parser = new DsnParser([
replace: $baseDir,
subject: Configuration::getInstance()->database
);
$parser = new DsnParser(
schemeMapping: [
'mariadb' => 'pdo_mysql', 'mariadb' => 'pdo_mysql',
'mssql' => 'pdo_sqlsrv', 'mssql' => 'pdo_sqlsrv',
'mysql' => 'pdo_mysql', 'mysql' => 'pdo_mysql',
'oracle' => 'pdo_oci', 'oracle' => 'pdo_oci',
'postgresql' => 'pdo_pgsql', 'postgresql' => 'pdo_pgsql',
'sqlite' => 'pdo_sqlite' 'sqlite' => 'pdo_sqlite'
] ]);
);
$conn = DriverManager::getConnection( $conn = DriverManager::getConnection(
// Generic return type of DsnParser::parse() is not correctly recognized. // Generic return type of DsnParser::parse() is not correctly recognized.
// phpcs:ignore // phpcs:ignore
params: $parser->parse(dsn: $dsn), $parser->parse(dsn: $dsn),
config: $config $config
); );
parent::__construct(new DoctrineEntityManager(conn: $conn, config: $config)); parent::__construct(new DoctrineEntityManager($conn, $config));
} }
} }

View File

@ -88,24 +88,24 @@ abstract class Middleware extends AbstractMiddleware
protected function addResumptionToken(DOMElement $node, ?Token $token): void protected function addResumptionToken(DOMElement $node, ?Token $token): void
{ {
if (isset($token) || isset($this->arguments['resumptionToken'])) { if (isset($token) || isset($this->arguments['resumptionToken'])) {
$resumptionToken = $this->preparedResponse->createElement(localName: 'resumptionToken'); $resumptionToken = $this->preparedResponse->createElement('resumptionToken');
if (isset($token)) { if (isset($token)) {
$resumptionToken->nodeValue = $token->getToken(); $resumptionToken->nodeValue = $token->getToken();
$resumptionToken->setAttribute( $resumptionToken->setAttribute(
qualifiedName: 'expirationDate', 'expirationDate',
value: $token->getValidUntil()->format(format: 'Y-m-d\TH:i:s\Z') $token->getValidUntil()->format('Y-m-d\TH:i:s\Z')
); );
$this->arguments['completeListSize'] = $token->getParameters()['completeListSize']; $this->arguments['completeListSize'] = $token->getParameters()['completeListSize'];
} }
$resumptionToken->setAttribute( $resumptionToken->setAttribute(
qualifiedName: 'completeListSize', 'completeListSize',
value: (string) $this->arguments['completeListSize'] (string) $this->arguments['completeListSize']
); );
$resumptionToken->setAttribute( $resumptionToken->setAttribute(
qualifiedName: 'cursor', 'cursor',
value: (string) ($this->arguments['counter'] * Configuration::getInstance()->maxRecords) (string) ($this->arguments['counter'] * Configuration::getInstance()->maxRecords)
); );
$node->appendChild(node: $resumptionToken); $node->appendChild($resumptionToken);
} }
} }
@ -117,14 +117,11 @@ abstract class Middleware extends AbstractMiddleware
protected function checkResumptionToken(): void protected function checkResumptionToken(): void
{ {
if (isset($this->arguments['resumptionToken'])) { if (isset($this->arguments['resumptionToken'])) {
$token = $this->em->getResumptionToken( $token = $this->em->getResumptionToken($this->arguments['resumptionToken'], $this->arguments['verb']);
token: $this->arguments['resumptionToken'],
verb: $this->arguments['verb']
);
if (isset($token)) { if (isset($token)) {
$this->arguments = array_merge($this->arguments, $token->getParameters()); $this->arguments = array_merge($this->arguments, $token->getParameters());
} else { } else {
ErrorHandler::getInstance()->withError(errorCode: 'badResumptionToken'); ErrorHandler::getInstance()->withError('badResumptionToken');
} }
} }
} }
@ -150,7 +147,7 @@ abstract class Middleware extends AbstractMiddleware
/** @var OaiRequestMetadata */ /** @var OaiRequestMetadata */
$arguments = $request->getAttributes(); $arguments = $request->getAttributes();
$this->arguments = array_merge($this->arguments, $arguments); $this->arguments = array_merge($this->arguments, $arguments);
$this->prepareResponse(request: $request); $this->prepareResponse($request);
return $request; return $request;
} }
@ -164,11 +161,7 @@ abstract class Middleware extends AbstractMiddleware
protected function processResponse(ResponseInterface $response): ResponseInterface protected function processResponse(ResponseInterface $response): ResponseInterface
{ {
if (!ErrorHandler::getInstance()->hasErrors() && isset($this->preparedResponse)) { if (!ErrorHandler::getInstance()->hasErrors() && isset($this->preparedResponse)) {
$response = $response->withBody( $response = $response->withBody(Utils::streamFor((string) $this->preparedResponse));
body: Utils::streamFor(
resource: (string) $this->preparedResponse
)
);
} }
return $response; return $response;
} }

View File

@ -65,14 +65,14 @@ class Dispatcher extends AbstractMiddleware
/** @var array<string, string> */ /** @var array<string, string> */
$arguments = $request->getQueryParams(); $arguments = $request->getQueryParams();
} elseif ($request->getMethod() === 'POST') { } elseif ($request->getMethod() === 'POST') {
if ($request->getHeaderLine(name: 'Content-Type') === 'application/x-www-form-urlencoded') { if ($request->getHeaderLine('Content-Type') === 'application/x-www-form-urlencoded') {
/** @var array<string, string> */ /** @var array<string, string> */
$arguments = (array) $request->getParsedBody(); $arguments = (array) $request->getParsedBody();
} }
} }
if ($this->validateArguments(arguments: $arguments)) { if ($this->validateArguments($arguments)) {
foreach ($arguments as $param => $value) { foreach ($arguments as $param => $value) {
$request = $request->withAttribute(name: $param, value: $value); $request = $request->withAttribute($param, $value);
} }
} }
return $request; return $request;
@ -87,17 +87,17 @@ class Dispatcher extends AbstractMiddleware
*/ */
protected function processRequest(ServerRequestInterface $request): ServerRequestInterface protected function processRequest(ServerRequestInterface $request): ServerRequestInterface
{ {
$request = $this->getRequestWithAttributes(request: $request); $request = $this->getRequestWithAttributes($request);
$errorHandler = ErrorHandler::getInstance(); $errorHandler = ErrorHandler::getInstance();
if (!$errorHandler->hasErrors()) { if (!$errorHandler->hasErrors()) {
/** @var string */ /** @var string */
$verb = $request->getAttribute(name: 'verb'); $verb = $request->getAttribute('verb');
$middleware = __NAMESPACE__ . '\\' . $verb; $middleware = __NAMESPACE__ . '\\' . $verb;
if (is_a(object_or_class: $middleware, class: Middleware::class, allow_string: true)) { if (is_a($middleware, Middleware::class, true)) {
$this->requestHandler->queue->enqueue(value: new $middleware()); $this->requestHandler->queue->enqueue(new $middleware());
} }
} }
$this->requestHandler->queue->enqueue(value: $errorHandler); $this->requestHandler->queue->enqueue($errorHandler);
return $request; return $request;
} }
@ -113,7 +113,7 @@ class Dispatcher extends AbstractMiddleware
// TODO: Add support for content compression // TODO: Add support for content compression
// https://openarchives.org/OAI/openarchivesprotocol.html#ResponseCompression // https://openarchives.org/OAI/openarchivesprotocol.html#ResponseCompression
// https://github.com/middlewares/encoder // https://github.com/middlewares/encoder
return $response->withHeader(name: 'Content-Type', value: 'text/xml'); return $response->withHeader('Content-Type', 'text/xml');
} }
/** /**
@ -131,20 +131,20 @@ class Dispatcher extends AbstractMiddleware
count(array_diff(array_keys($arguments), self::OAI_PARAMS)) !== 0 count(array_diff(array_keys($arguments), self::OAI_PARAMS)) !== 0
or !isset($arguments['verb']) or !isset($arguments['verb'])
) { ) {
ErrorHandler::getInstance()->withError(errorCode: 'badArgument'); ErrorHandler::getInstance()->withError('badArgument');
} else { } else {
match ($arguments['verb']) { match ($arguments['verb']) {
'GetRecord' => $this->validateGetRecord(arguments: $arguments), 'GetRecord' => $this->validateGetRecord($arguments),
'Identify' => $this->validateIdentify(arguments: $arguments), 'Identify' => $this->validateIdentify($arguments),
'ListIdentifiers', 'ListRecords' => $this->validateListRecords(arguments: $arguments), 'ListIdentifiers', 'ListRecords' => $this->validateListRecords($arguments),
'ListMetadataFormats' => $this->validateListFormats(arguments: $arguments), 'ListMetadataFormats' => $this->validateListFormats($arguments),
'ListSets' => $this->validateListSets(arguments: $arguments), 'ListSets' => $this->validateListSets($arguments),
default => ErrorHandler::getInstance()->withError(errorCode: 'badVerb') default => ErrorHandler::getInstance()->withError('badVerb')
}; };
if (!ErrorHandler::getInstance()->hasErrors()) { if (!ErrorHandler::getInstance()->hasErrors()) {
$this->validateMetadataPrefix(prefix: $arguments['metadataPrefix'] ?? null); $this->validateMetadataPrefix($arguments['metadataPrefix'] ?? null);
$this->validateDateTime(datetime: $arguments['from'] ?? null); $this->validateDateTime($arguments['from'] ?? null);
$this->validateDateTime(datetime: $arguments['until'] ?? null); $this->validateDateTime($arguments['until'] ?? null);
$this->validateSet($arguments['set'] ?? null); $this->validateSet($arguments['set'] ?? null);
} }
} }
@ -161,9 +161,9 @@ class Dispatcher extends AbstractMiddleware
protected function validateDateTime(?string $datetime): void protected function validateDateTime(?string $datetime): void
{ {
if (isset($datetime)) { if (isset($datetime)) {
$date = date_parse(datetime: $datetime); $date = date_parse($datetime);
if ($date['warning_count'] > 0 || $date['error_count'] > 0) { if ($date['warning_count'] > 0 || $date['error_count'] > 0) {
ErrorHandler::getInstance()->withError(errorCode: 'badArgument'); ErrorHandler::getInstance()->withError('badArgument');
} }
} }
} }
@ -182,7 +182,7 @@ class Dispatcher extends AbstractMiddleware
or !isset($arguments['identifier']) or !isset($arguments['identifier'])
or !isset($arguments['metadataPrefix']) or !isset($arguments['metadataPrefix'])
) { ) {
ErrorHandler::getInstance()->withError(errorCode: 'badArgument'); ErrorHandler::getInstance()->withError('badArgument');
} }
} }
@ -196,7 +196,7 @@ class Dispatcher extends AbstractMiddleware
protected function validateIdentify(array $arguments): void protected function validateIdentify(array $arguments): void
{ {
if (count($arguments) !== 1) { if (count($arguments) !== 1) {
ErrorHandler::getInstance()->withError(errorCode: 'badArgument'); ErrorHandler::getInstance()->withError('badArgument');
} }
} }
@ -211,7 +211,7 @@ class Dispatcher extends AbstractMiddleware
{ {
if (count($arguments) !== 1) { if (count($arguments) !== 1) {
if (!isset($arguments['identifier']) || count($arguments) !== 2) { if (!isset($arguments['identifier']) || count($arguments) !== 2) {
ErrorHandler::getInstance()->withError(errorCode: 'badArgument'); ErrorHandler::getInstance()->withError('badArgument');
} }
} }
} }
@ -233,10 +233,10 @@ class Dispatcher extends AbstractMiddleware
(isset($arguments['resumptionToken']) && count($arguments) !== 2) (isset($arguments['resumptionToken']) && count($arguments) !== 2)
or isset($arguments['identifier']) or isset($arguments['identifier'])
) { ) {
ErrorHandler::getInstance()->withError(errorCode: 'badArgument'); ErrorHandler::getInstance()->withError('badArgument');
} }
} else { } else {
ErrorHandler::getInstance()->withError(errorCode: 'badArgument'); ErrorHandler::getInstance()->withError('badArgument');
} }
} }
@ -251,7 +251,7 @@ class Dispatcher extends AbstractMiddleware
{ {
if (count($arguments) !== 1) { if (count($arguments) !== 1) {
if (!isset($arguments['resumptionToken']) || count($arguments) !== 2) { if (!isset($arguments['resumptionToken']) || count($arguments) !== 2) {
ErrorHandler::getInstance()->withError(errorCode: 'badArgument'); ErrorHandler::getInstance()->withError('badArgument');
} }
} }
} }
@ -267,8 +267,8 @@ class Dispatcher extends AbstractMiddleware
{ {
if (isset($prefix)) { if (isset($prefix)) {
$formats = EntityManager::getInstance()->getMetadataFormats(); $formats = EntityManager::getInstance()->getMetadataFormats();
if (!$formats->containsKey(key: $prefix)) { if (!$formats->containsKey($prefix)) {
ErrorHandler::getInstance()->withError(errorCode: 'cannotDisseminateFormat'); ErrorHandler::getInstance()->withError('cannotDisseminateFormat');
} }
} }
} }
@ -284,8 +284,8 @@ class Dispatcher extends AbstractMiddleware
{ {
if (isset($spec)) { if (isset($spec)) {
$sets = EntityManager::getInstance()->getSets(); $sets = EntityManager::getInstance()->getSets();
if (!$sets->containsKey(key: $spec)) { if (!$sets->containsKey($spec)) {
ErrorHandler::getInstance()->withError(errorCode: 'badArgument'); ErrorHandler::getInstance()->withError('badArgument');
} }
} }
} }

View File

@ -70,19 +70,16 @@ class ErrorHandler extends AbstractMiddleware
*/ */
protected function getResponseBody(): StreamInterface protected function getResponseBody(): StreamInterface
{ {
$response = new Response(serverRequest: $this->requestHandler->request); $response = new Response($this->requestHandler->request);
foreach (array_unique($this->errors) as $errorCode) { foreach (array_unique($this->errors) as $errorCode) {
$error = $response->createElement( $error = $response->createElement(
localName: 'error', 'error',
value: self::OAI_ERRORS[$errorCode], self::OAI_ERRORS[$errorCode],
appendToRoot: true true
);
$error->setAttribute(
qualifiedName: 'code',
value: $errorCode
); );
$error->setAttribute('code', $errorCode);
} }
return Utils::streamFor(resource: (string) $response); return Utils::streamFor((string) $response);
} }
/** /**
@ -105,7 +102,7 @@ class ErrorHandler extends AbstractMiddleware
protected function processResponse(ResponseInterface $response): ResponseInterface protected function processResponse(ResponseInterface $response): ResponseInterface
{ {
if ($this->hasErrors()) { if ($this->hasErrors()) {
$response = $response->withBody(body: $this->getResponseBody()); $response = $response->withBody($this->getResponseBody());
} }
return $response; return $response;
} }
@ -125,11 +122,11 @@ class ErrorHandler extends AbstractMiddleware
$this->errors[] = $errorCode; $this->errors[] = $errorCode;
} else { } else {
throw new DomainException( throw new DomainException(
message: sprintf( sprintf(
format: 'Valid OAI-PMH error code expected, "%s" given.', 'Valid OAI-PMH error code expected, "%s" given.',
values: $errorCode $errorCode
), ),
code: 500 500
); );
} }
return $this; return $this;

View File

@ -46,64 +46,51 @@ class GetRecord extends Middleware
protected function prepareResponse(ServerRequestInterface $request): void protected function prepareResponse(ServerRequestInterface $request): void
{ {
$oaiRecord = $this->em->getRecord( $oaiRecord = $this->em->getRecord(
identifier: (string) $this->arguments['identifier'], (string) $this->arguments['identifier'],
format: (string) $this->arguments['metadataPrefix'] (string) $this->arguments['metadataPrefix']
); );
if (!isset($oaiRecord)) { if (!isset($oaiRecord)) {
if ($this->em->isValidRecordIdentifier(identifier: (string) $this->arguments['identifier'])) { if ($this->em->isValidRecordIdentifier((string) $this->arguments['identifier'])) {
ErrorHandler::getInstance()->withError(errorCode: 'cannotDisseminateFormat'); ErrorHandler::getInstance()->withError('cannotDisseminateFormat');
} else { } else {
ErrorHandler::getInstance()->withError(errorCode: 'idDoesNotExist'); ErrorHandler::getInstance()->withError('idDoesNotExist');
} }
return; return;
} }
$response = new Response(serverRequest: $request); $response = new Response($request);
$getRecord = $response->createElement( $getRecord = $response->createElement('GetRecord', '', true);
localName: 'GetRecord',
value: '',
appendToRoot: true
);
$record = $response->createElement(localName: 'record'); $record = $response->createElement('record');
$getRecord->appendChild(node: $record); $getRecord->appendChild($record);
$header = $response->createElement(localName: 'header'); $header = $response->createElement('header');
if (!$oaiRecord->hasContent()) { if (!$oaiRecord->hasContent()) {
$header->setAttribute( $header->setAttribute('status', 'deleted');
qualifiedName: 'status',
value: 'deleted'
);
} }
$record->appendChild(node: $header); $record->appendChild($header);
$identifier = $response->createElement( $identifier = $response->createElement('identifier', $oaiRecord->getIdentifier());
localName: 'identifier', $header->appendChild($identifier);
value: $oaiRecord->getIdentifier()
);
$header->appendChild(node: $identifier);
$datestamp = $response->createElement( $datestamp = $response->createElement(
localName: 'datestamp', 'datestamp',
value: $oaiRecord->getLastChanged()->format(format: 'Y-m-d\TH:i:s\Z') $oaiRecord->getLastChanged()->format('Y-m-d\TH:i:s\Z')
); );
$header->appendChild(node: $datestamp); $header->appendChild($datestamp);
foreach ($oaiRecord->getSets() as $set) { foreach ($oaiRecord->getSets() as $set) {
$setSpec = $response->createElement( $setSpec = $response->createElement('setSpec', $set->getName());
localName: 'setSpec', $header->appendChild($setSpec);
value: $set->getName()
);
$header->appendChild(node: $setSpec);
} }
if ($oaiRecord->hasContent()) { if ($oaiRecord->hasContent()) {
$metadata = $response->createElement(localName: 'metadata'); $metadata = $response->createElement('metadata');
$record->appendChild(node: $metadata); $record->appendChild($metadata);
$data = $response->importData(data: $oaiRecord->getContent()); $data = $response->importData($oaiRecord->getContent());
$metadata->appendChild(node: $data); $metadata->appendChild($data);
} }
$this->preparedResponse = $response; $this->preparedResponse = $response;

View File

@ -47,74 +47,55 @@ class Identify extends Middleware
*/ */
protected function prepareResponse(ServerRequestInterface $request): void protected function prepareResponse(ServerRequestInterface $request): void
{ {
$response = new Response(serverRequest: $request); $response = new Response($request);
$identify = $response->createElement( $identify = $response->createElement('Identify', '', true);
localName: 'Identify',
value: '',
appendToRoot: true
);
$repositoryName = $response->createElement( $repositoryName = $response->createElement(
localName: 'repositoryName', 'repositoryName',
value: Configuration::getInstance()->repositoryName Configuration::getInstance()->repositoryName
); );
$identify->appendChild(node: $repositoryName); $identify->appendChild($repositoryName);
$uri = Uri::composeComponents( $uri = Uri::composeComponents(
scheme: $request->getUri()->getScheme(), $request->getUri()->getScheme(),
authority: $request->getUri()->getAuthority(), $request->getUri()->getAuthority(),
path: $request->getUri()->getPath(), $request->getUri()->getPath(),
query: null, null,
fragment: null null
); );
$baseURL = $response->createElement( $baseURL = $response->createElement('baseURL', $uri);
localName: 'baseURL', $identify->appendChild($baseURL);
value: $uri
);
$identify->appendChild(node: $baseURL);
$protocolVersion = $response->createElement( $protocolVersion = $response->createElement('protocolVersion', '2.0');
localName: 'protocolVersion', $identify->appendChild($protocolVersion);
value: '2.0'
);
$identify->appendChild(node: $protocolVersion);
$adminEmail = $response->createElement( $adminEmail = $response->createElement(
localName: 'adminEmail', 'adminEmail',
value: Configuration::getInstance()->adminEmail Configuration::getInstance()->adminEmail
); );
$identify->appendChild(node: $adminEmail); $identify->appendChild($adminEmail);
$earliestDatestamp = $response->createElement( $earliestDatestamp = $response->createElement(
localName: 'earliestDatestamp', 'earliestDatestamp',
value: $this->em->getEarliestDatestamp() $this->em->getEarliestDatestamp()
); );
$identify->appendChild(node: $earliestDatestamp); $identify->appendChild($earliestDatestamp);
$deletedRecord = $response->createElement( $deletedRecord = $response->createElement(
localName: 'deletedRecord', 'deletedRecord',
value: Configuration::getInstance()->deletedRecords Configuration::getInstance()->deletedRecords
); );
$identify->appendChild(node: $deletedRecord); $identify->appendChild($deletedRecord);
$granularity = $response->createElement( $granularity = $response->createElement('granularity', 'YYYY-MM-DDThh:mm:ssZ');
localName: 'granularity', $identify->appendChild($granularity);
value: 'YYYY-MM-DDThh:mm:ssZ'
);
$identify->appendChild(node: $granularity);
// TODO: Implement explicit content compression support. // TODO: Implement explicit content compression support.
// $compressionDeflate = $response->createElement( // $compressionDeflate = $response->createElement('compression', 'deflate');
// localName: 'compression', // $identify->appendChild($compressionDeflate);
// value: 'deflate'
// );
// $identify->appendChild(node: $compressionDeflate);
// $compressionGzip = $response->createElement( // $compressionGzip = $response->createElement('compression', 'gzip');
// localName: 'compression', // $identify->appendChild($compressionGzip);
// value: 'gzip'
// );
// $identify->appendChild(node: $compressionGzip);
$this->preparedResponse = $response; $this->preparedResponse = $response;
} }

View File

@ -48,75 +48,62 @@ class ListIdentifiers extends Middleware
$this->checkResumptionToken(); $this->checkResumptionToken();
$records = $this->em->getRecords( $records = $this->em->getRecords(
verb: $this->arguments['verb'], $this->arguments['verb'],
metadataPrefix: (string) $this->arguments['metadataPrefix'], (string) $this->arguments['metadataPrefix'],
counter: $this->arguments['counter'], $this->arguments['counter'],
from: $this->arguments['from'], $this->arguments['from'],
until: $this->arguments['until'], $this->arguments['until'],
set: $this->arguments['set'] $this->arguments['set']
); );
if (count($records) === 0) { if (count($records) === 0) {
ErrorHandler::getInstance()->withError(errorCode: 'noRecordsMatch'); ErrorHandler::getInstance()->withError('noRecordsMatch');
return; return;
} }
$response = new Response(serverRequest: $request); $response = new Response($request);
$list = $response->createElement( $list = $response->createElement($this->arguments['verb'], '', true);
localName: $this->arguments['verb'],
value: '',
appendToRoot: true
);
$baseNode = $list; $baseNode = $list;
foreach ($records as $oaiRecord) { foreach ($records as $oaiRecord) {
if ($this->arguments['verb'] === 'ListRecords') { if ($this->arguments['verb'] === 'ListRecords') {
$record = $response->createElement(localName: 'record'); $record = $response->createElement('record');
$list->appendChild(node: $record); $list->appendChild($record);
$baseNode = $record; $baseNode = $record;
} }
$header = $response->createElement(localName: 'header'); $header = $response->createElement('header');
$baseNode->appendChild(node: $header); $baseNode->appendChild($header);
$identifier = $response->createElement( $identifier = $response->createElement(
localName: 'identifier', 'identifier',
value: $oaiRecord->getIdentifier() $oaiRecord->getIdentifier()
); );
$header->appendChild(node: $identifier); $header->appendChild($identifier);
$datestamp = $response->createElement( $datestamp = $response->createElement(
localName: 'datestamp', 'datestamp',
value: $oaiRecord->getLastChanged()->format(format: 'Y-m-d\TH:i:s\Z') $oaiRecord->getLastChanged()->format('Y-m-d\TH:i:s\Z')
); );
$header->appendChild(node: $datestamp); $header->appendChild($datestamp);
foreach ($oaiRecord->getSets() as $oaiSet) { foreach ($oaiRecord->getSets() as $oaiSet) {
$setSpec = $response->createElement( $setSpec = $response->createElement('setSpec', $oaiSet->getName());
localName: 'setSpec', $header->appendChild($setSpec);
value: $oaiSet->getName()
);
$header->appendChild(node: $setSpec);
} }
if (!$oaiRecord->hasContent()) { if (!$oaiRecord->hasContent()) {
$header->setAttribute( $header->setAttribute('status', 'deleted');
qualifiedName: 'status',
value: 'deleted'
);
} elseif ($this->arguments['verb'] === 'ListRecords') { } elseif ($this->arguments['verb'] === 'ListRecords') {
$metadata = $response->createElement(localName: 'metadata'); $metadata = $response->createElement('metadata');
$baseNode->appendChild(node: $metadata); $baseNode->appendChild($metadata);
$data = $response->importData(data: $oaiRecord->getContent()); $data = $response->importData($oaiRecord->getContent());
$metadata->appendChild(node: $data); $metadata->appendChild($data);
} }
} }
$this->preparedResponse = $response; $this->preparedResponse = $response;
$this->addResumptionToken( $this->addResumptionToken($list, $records->getResumptionToken() ?? null);
node: $list,
token: $records->getResumptionToken() ?? null
);
} }
} }

View File

@ -45,48 +45,44 @@ class ListMetadataFormats extends Middleware
*/ */
protected function prepareResponse(ServerRequestInterface $request): void protected function prepareResponse(ServerRequestInterface $request): void
{ {
$formats = $this->em->getMetadataFormats(recordIdentifier: $this->arguments['identifier']); $formats = $this->em->getMetadataFormats($this->arguments['identifier']);
if (count($formats) === 0) { if (count($formats) === 0) {
if ( if (
!isset($this->arguments['identifier']) !isset($this->arguments['identifier'])
|| $this->em->isValidRecordIdentifier(identifier: $this->arguments['identifier']) || $this->em->isValidRecordIdentifier($this->arguments['identifier'])
) { ) {
ErrorHandler::getInstance()->withError(errorCode: 'noMetadataFormats'); ErrorHandler::getInstance()->withError('noMetadataFormats');
} else { } else {
ErrorHandler::getInstance()->withError(errorCode: 'idDoesNotExist'); ErrorHandler::getInstance()->withError('idDoesNotExist');
} }
return; return;
} }
$response = new Response(serverRequest: $request); $response = new Response($request);
$listMetadataFormats = $response->createElement( $listMetadataFormats = $response->createElement('ListMetadataFormats', '', true);
localName: 'ListMetadataFormats',
value: '',
appendToRoot: true
);
foreach ($formats as $oaiFormat) { foreach ($formats as $oaiFormat) {
$metadataFormat = $response->createElement(localName: 'metadataFormat'); $metadataFormat = $response->createElement('metadataFormat');
$listMetadataFormats->appendChild(node: $metadataFormat); $listMetadataFormats->appendChild($metadataFormat);
$metadataPrefix = $response->createElement( $metadataPrefix = $response->createElement(
localName: 'metadataPrefix', 'metadataPrefix',
value: $oaiFormat->getPrefix() $oaiFormat->getPrefix()
); );
$metadataFormat->appendChild(node: $metadataPrefix); $metadataFormat->appendChild($metadataPrefix);
$schema = $response->createElement( $schema = $response->createElement(
localName: 'schema', 'schema',
value: $oaiFormat->getSchema() $oaiFormat->getSchema()
); );
$metadataFormat->appendChild(node: $schema); $metadataFormat->appendChild($schema);
$metadataNamespace = $response->createElement( $metadataNamespace = $response->createElement(
localName: 'metadataNamespace', 'metadataNamespace',
value: $oaiFormat->getNamespace() $oaiFormat->getNamespace()
); );
$metadataFormat->appendChild(node: $metadataNamespace); $metadataFormat->appendChild($metadataNamespace);
} }
$this->preparedResponse = $response; $this->preparedResponse = $response;

View File

@ -47,50 +47,43 @@ class ListSets extends Middleware
{ {
$this->checkResumptionToken(); $this->checkResumptionToken();
$sets = $this->em->getSets(counter: $this->arguments['counter']); $sets = $this->em->getSets($this->arguments['counter']);
if (count($sets) === 0) { if (count($sets) === 0) {
ErrorHandler::getInstance()->withError(errorCode: 'noSetHierarchy'); ErrorHandler::getInstance()->withError('noSetHierarchy');
return; return;
} }
$response = new Response(serverRequest: $request); $response = new Response($request);
$list = $response->createElement( $list = $response->createElement('ListSets', '', true);
localName: 'ListSets',
value: '',
appendToRoot: true
);
foreach ($sets as $oaiSet) { foreach ($sets as $oaiSet) {
$set = $response->createElement(localName: 'set'); $set = $response->createElement('set');
$list->appendChild(node: $set); $list->appendChild($set);
$setSpec = $response->createElement( $setSpec = $response->createElement(
localName: 'setSpec', 'setSpec',
value: $oaiSet->getSpec() $oaiSet->getSpec()
); );
$set->appendChild(node: $setSpec); $set->appendChild($setSpec);
$setName = $response->createElement( $setName = $response->createElement(
localName: 'setName', 'setName',
value: $oaiSet->getName() $oaiSet->getName()
); );
$set->appendChild(node: $setName); $set->appendChild($setName);
if ($oaiSet->hasDescription()) { if ($oaiSet->hasDescription()) {
$setDescription = $response->createElement(localName: 'setDescription'); $setDescription = $response->createElement('setDescription');
$set->appendChild(node: $setDescription); $set->appendChild($setDescription);
$data = $response->importData(data: $oaiSet->getDescription()); $data = $response->importData($oaiSet->getDescription());
$setDescription->appendChild(node: $data); $setDescription->appendChild($data);
} }
} }
$this->preparedResponse = $response; $this->preparedResponse = $response;
$this->addResumptionToken( $this->addResumptionToken($list, $sets->getResumptionToken() ?? null);
node: $list,
token: $sets->getResumptionToken() ?? null
);
} }
} }

View File

@ -45,12 +45,12 @@ final class FormatRepository extends EntityRepository
*/ */
public function addOrUpdate(Format $entity): void public function addOrUpdate(Format $entity): void
{ {
$oldFormat = $this->find(id: $entity->getPrefix()); $oldFormat = $this->find($entity->getPrefix());
if (isset($oldFormat)) { if (isset($oldFormat)) {
$oldFormat->setNamespace(namespace: $entity->getNamespace()); $oldFormat->setNamespace($entity->getNamespace());
$oldFormat->setSchema(schema: $entity->getSchema()); $oldFormat->setSchema($entity->getSchema());
} else { } else {
$this->getEntityManager()->persist(object: $entity); $this->getEntityManager()->persist($entity);
} }
} }
@ -65,7 +65,7 @@ final class FormatRepository extends EntityRepository
{ {
/** @var EntityManager */ /** @var EntityManager */
$entityManager = $this->getEntityManager(); $entityManager = $this->getEntityManager();
$entityManager->remove(object: $entity); $entityManager->remove($entity);
$entityManager->flush(); $entityManager->flush();
$entityManager->pruneOrphanedSets(); $entityManager->pruneOrphanedSets();
} }

View File

@ -49,32 +49,30 @@ final class RecordRepository extends EntityRepository
{ {
/** @var EntityManager */ /** @var EntityManager */
$entityManager = $this->getEntityManager(); $entityManager = $this->getEntityManager();
$oldRecord = $this->find( $oldRecord = $this->find([
id: [
'identifier' => $entity->getIdentifier(), 'identifier' => $entity->getIdentifier(),
'format' => $entity->getFormat() 'format' => $entity->getFormat()
] ]);
);
if (isset($oldRecord)) { if (isset($oldRecord)) {
if ($entity->hasContent() || Configuration::getInstance()->deletedRecords !== 'no') { if ($entity->hasContent() || Configuration::getInstance()->deletedRecords !== 'no') {
$oldRecord->setContent(data: $entity->getContent(), validate: false); $oldRecord->setContent($entity->getContent(), false);
$oldRecord->setLastChanged(dateTime: $entity->getLastChanged()); $oldRecord->setLastChanged($entity->getLastChanged());
$newSets = $entity->getSets()->toArray(); $newSets = $entity->getSets()->toArray();
$oldSets = $oldRecord->getSets()->toArray(); $oldSets = $oldRecord->getSets()->toArray();
// Add new sets. // Add new sets.
foreach (array_diff(array: $newSets, arrays: $oldSets) as $newSet) { foreach (array_diff($newSets, $oldSets) as $newSet) {
$oldRecord->addSet(set: $newSet); $oldRecord->addSet($newSet);
} }
// Remove old sets. // Remove old sets.
foreach (array_diff(array: $oldSets, arrays: $newSets) as $oldSet) { foreach (array_diff($oldSets, $newSets) as $oldSet) {
$oldRecord->removeSet(set: $oldSet); $oldRecord->removeSet($oldSet);
} }
} else { } else {
$entityManager->remove(object: $oldRecord); $entityManager->remove($oldRecord);
} }
} else { } else {
if ($entity->hasContent() || Configuration::getInstance()->deletedRecords !== 'no') { if ($entity->hasContent() || Configuration::getInstance()->deletedRecords !== 'no') {
$entityManager->persist(object: $entity); $entityManager->persist($entity);
} }
} }
} }
@ -91,12 +89,12 @@ final class RecordRepository extends EntityRepository
/** @var EntityManager */ /** @var EntityManager */
$entityManager = $this->getEntityManager(); $entityManager = $this->getEntityManager();
if (Configuration::getInstance()->deletedRecords === 'no') { if (Configuration::getInstance()->deletedRecords === 'no') {
$entityManager->remove(object: $entity); $entityManager->remove($entity);
$entityManager->flush(); $entityManager->flush();
$entityManager->pruneOrphanedSets(); $entityManager->pruneOrphanedSets();
} else { } else {
$entity->setContent(); $entity->setContent();
$entity->setLastChanged(dateTime: new DateTime()); $entity->setLastChanged(new DateTime());
$entityManager->flush(); $entityManager->flush();
} }
} }

View File

@ -23,9 +23,7 @@ declare(strict_types=1);
namespace OCC\OaiPmh2\Repository; namespace OCC\OaiPmh2\Repository;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use OCC\OaiPmh2\Configuration;
use OCC\OaiPmh2\Entity\Set; use OCC\OaiPmh2\Entity\Set;
use OCC\OaiPmh2\ResultSet;
/** /**
* Doctrine/ORM Repository for sets. * Doctrine/ORM Repository for sets.
@ -46,12 +44,12 @@ final class SetRepository extends EntityRepository
*/ */
public function addOrUpdate(Set $entity): void public function addOrUpdate(Set $entity): void
{ {
$oldSet = $this->find(id: $entity->getSpec()); $oldSet = $this->find($entity->getSpec());
if (isset($oldSet)) { if (isset($oldSet)) {
$oldSet->setName(name: $entity->getName()); $oldSet->setName($entity->getName());
$oldSet->setDescription(description: $entity->getDescription()); $oldSet->setDescription($entity->getDescription());
} else { } else {
$this->getEntityManager()->persist(object: $entity); $this->getEntityManager()->persist($entity);
} }
} }
@ -65,7 +63,7 @@ final class SetRepository extends EntityRepository
public function delete(Set $entity): void public function delete(Set $entity): void
{ {
$entityManager = $this->getEntityManager(); $entityManager = $this->getEntityManager();
$entityManager->remove(object: $entity); $entityManager->remove($entity);
$entityManager->flush(); $entityManager->flush();
} }
} }

View File

@ -22,7 +22,6 @@ declare(strict_types=1);
namespace OCC\OaiPmh2\Repository; namespace OCC\OaiPmh2\Repository;
use DateTime;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use OCC\OaiPmh2\Entity\Token; use OCC\OaiPmh2\Entity\Token;
@ -45,7 +44,7 @@ final class TokenRepository extends EntityRepository
*/ */
public function addOrUpdate(Token $entity): void public function addOrUpdate(Token $entity): void
{ {
$this->getEntityManager()->persist(object: $entity); $this->getEntityManager()->persist($entity);
} }
/** /**
@ -58,7 +57,7 @@ final class TokenRepository extends EntityRepository
public function delete(Token $entity): void public function delete(Token $entity): void
{ {
$entityManager = $this->getEntityManager(); $entityManager = $this->getEntityManager();
$entityManager->remove(object: $entity); $entityManager->remove($entity);
$entityManager->flush(); $entityManager->flush();
} }
} }

View File

@ -56,24 +56,24 @@ final class Response
{ {
$uri = $this->serverRequest->getUri(); $uri = $this->serverRequest->getUri();
$basePath = $uri->getPath(); $basePath = $uri->getPath();
if (str_ends_with(haystack: $basePath, needle: 'index.php')) { if (str_ends_with($basePath, 'index.php')) {
$basePath = pathinfo(path: $basePath, flags: PATHINFO_DIRNAME); $basePath = pathinfo($basePath, PATHINFO_DIRNAME);
} }
$stylesheet = Uri::composeComponents( $stylesheet = Uri::composeComponents(
scheme: $uri->getScheme(), $uri->getScheme(),
authority: $uri->getAuthority(), $uri->getAuthority(),
path: rtrim(string: $basePath, characters: '/') . '/resources/stylesheet.xsl', rtrim($basePath, '/') . '/resources/stylesheet.xsl',
query: null, null,
fragment: null null
); );
$xslt = $this->dom->createProcessingInstruction( $xslt = $this->dom->createProcessingInstruction(
target: 'xml-stylesheet', 'xml-stylesheet',
data: sprintf( sprintf(
format: 'type="text/xsl" href="%s"', 'type="text/xsl" href="%s"',
values: $stylesheet $stylesheet
) )
); );
$this->dom->appendChild(node: $xslt); $this->dom->appendChild($xslt);
} }
/** /**
@ -85,27 +85,19 @@ final class Response
{ {
$uri = $this->serverRequest->getUri(); $uri = $this->serverRequest->getUri();
$baseUrl = Uri::composeComponents( $baseUrl = Uri::composeComponents(
scheme: $uri->getScheme(), $uri->getScheme(),
authority: $uri->getAuthority(), $uri->getAuthority(),
path: $uri->getPath(), $uri->getPath(),
query: null, null,
fragment: null null
);
$request = $this->createElement(
localName: 'request',
value: $baseUrl,
appendToRoot: true
); );
$request = $this->createElement('request', $baseUrl, true);
/** @var array<string, string> */ /** @var array<string, string> */
$params = $this->serverRequest->getAttributes(); $params = $this->serverRequest->getAttributes();
foreach ($params as $param => $value) { foreach ($params as $param => $value) {
$request->setAttribute( $request->setAttribute(
qualifiedName: $param, $param,
value: htmlspecialchars( htmlspecialchars($value, ENT_XML1 | ENT_COMPAT, 'UTF-8')
string: $value,
flags: ENT_XML1 | ENT_COMPAT,
encoding: 'UTF-8'
)
); );
} }
} }
@ -117,11 +109,7 @@ final class Response
*/ */
protected function appendResponseDate(): void protected function appendResponseDate(): void
{ {
$this->createElement( $this->createElement('responseDate', gmdate('Y-m-d\TH:i:s\Z'), true);
localName: 'responseDate',
value: gmdate(format: 'Y-m-d\TH:i:s\Z'),
appendToRoot: true
);
} }
/** /**
@ -131,20 +119,20 @@ final class Response
*/ */
protected function appendRootElement(): void protected function appendRootElement(): void
{ {
$this->rootNode = $this->dom->createElement(localName: 'OAI-PMH'); $this->rootNode = $this->dom->createElement('OAI-PMH');
$this->rootNode->setAttribute( $this->rootNode->setAttribute(
qualifiedName: 'xmlns', 'xmlns',
value: 'http://www.openarchives.org/OAI/2.0/' 'http://www.openarchives.org/OAI/2.0/'
); );
$this->rootNode->setAttribute( $this->rootNode->setAttribute(
qualifiedName: 'xmlns:xsi', 'xmlns:xsi',
value: 'http://www.w3.org/2001/XMLSchema-instance' 'http://www.w3.org/2001/XMLSchema-instance'
); );
$this->rootNode->setAttribute( $this->rootNode->setAttribute(
qualifiedName: 'xsi:schemaLocation', 'xsi:schemaLocation',
value: 'http://www.openarchives.org/OAI/2.0/ https://www.openarchives.org/OAI/2.0/OAI-PMH.xsd' 'http://www.openarchives.org/OAI/2.0/ https://www.openarchives.org/OAI/2.0/OAI-PMH.xsd'
); );
$this->dom->appendChild(node: $this->rootNode); $this->dom->appendChild($this->rootNode);
} }
/** /**
@ -154,7 +142,7 @@ final class Response
*/ */
protected function createDocument(): void protected function createDocument(): void
{ {
$this->dom = new DOMDocument(version: '1.0', encoding: 'UTF-8'); $this->dom = new DOMDocument('1.0', 'UTF-8');
$this->dom->preserveWhiteSpace = false; $this->dom->preserveWhiteSpace = false;
$this->addProcessingInstructions(); $this->addProcessingInstructions();
} }
@ -170,16 +158,9 @@ final class Response
*/ */
public function createElement(string $localName, string $value = '', bool $appendToRoot = false): DOMElement public function createElement(string $localName, string $value = '', bool $appendToRoot = false): DOMElement
{ {
$node = $this->dom->createElement( $node = $this->dom->createElement($localName, htmlspecialchars($value, ENT_XML1, 'UTF-8'));
localName: $localName,
value: htmlspecialchars(
string: $value,
flags: ENT_XML1,
encoding: 'UTF-8'
)
);
if ($appendToRoot) { if ($appendToRoot) {
$this->rootNode->appendChild(node: $node); $this->rootNode->appendChild($node);
} }
return $node; return $node;
} }
@ -195,17 +176,17 @@ final class Response
*/ */
public function importData(string $data): DOMNode public function importData(string $data): DOMNode
{ {
$document = new DOMDocument(version: '1.0', encoding: 'UTF-8'); $document = new DOMDocument('1.0', 'UTF-8');
$document->preserveWhiteSpace = false; $document->preserveWhiteSpace = false;
if ($document->loadXML(source: $data) === true) { if ($document->loadXML($data) === true) {
/** @var DOMElement */ /** @var DOMElement */
$rootNode = $document->documentElement; $rootNode = $document->documentElement;
$node = $this->dom->importNode(node: $rootNode, deep: true); $node = $this->dom->importNode($rootNode, true);
return $node; return $node;
} else { } else {
throw new DOMException( throw new DOMException(
message: 'Could not import the XML data. Most likely it is not well-formed.', 'Could not import the XML data. Most likely it is not well-formed.',
code: 500 500
); );
} }
} }

View File

@ -71,7 +71,7 @@ final class ResultSet extends ArrayCollection
*/ */
public function __construct(array $elements = [], Token $token = null) public function __construct(array $elements = [], Token $token = null)
{ {
parent::__construct(elements: $elements); parent::__construct($elements);
$this->resumptionToken = $token; $this->resumptionToken = $token;
} }
} }

View File

@ -43,57 +43,51 @@ class ConfigurationValidator
protected static function getValidationConstraints(): array protected static function getValidationConstraints(): array
{ {
return [ return [
new Assert\Collection( new Assert\Collection([
fields: [
'repositoryName' => [ 'repositoryName' => [
new Assert\Type(type: 'string'), new Assert\Type('string'),
new Assert\NotBlank() new Assert\NotBlank()
], ],
'adminEmail' => [ 'adminEmail' => [
new Assert\Type(type: 'string'), new Assert\Type('string'),
new Assert\Email(options: ['mode' => 'html5']), new Assert\Email(['mode' => 'html5']),
new Assert\NotBlank() new Assert\NotBlank()
], ],
'database' => [ 'database' => [
new Assert\Type(type: 'string'), new Assert\Type('string'),
new Assert\NotBlank() new Assert\NotBlank()
], ],
'metadataPrefix' => [ 'metadataPrefix' => [
new Assert\Type(type: 'array'), new Assert\Type('array'),
new Assert\All( new Assert\All([
constraints: [ new Assert\Collection([
new Assert\Collection(
fields: [
'schema' => [ 'schema' => [
new Assert\Type(type: 'string'), new Assert\Type('string'),
new Assert\Url(), new Assert\Url(),
new Assert\NotBlank() new Assert\NotBlank()
], ],
'namespace' => [ 'namespace' => [
new Assert\Type(type: 'string'), new Assert\Type('string'),
new Assert\Url(), new Assert\Url(),
new Assert\NotBlank() new Assert\NotBlank()
] ]
] ])
) ])
]
)
], ],
'deletedRecords' => [ 'deletedRecords' => [
new Assert\Type(type: 'string'), new Assert\Type('string'),
new Assert\Choice(options: ['no', 'persistent', 'transient']), new Assert\Choice(['no', 'persistent', 'transient']),
new Assert\NotBlank() new Assert\NotBlank()
], ],
'maxRecords' => [ 'maxRecords' => [
new Assert\Type(type: 'int'), new Assert\Type('int'),
new Assert\Range(options: ['min' => 1, 'max' => 100]) new Assert\Range(['min' => 1, 'max' => 100])
], ],
'tokenValid' => [ 'tokenValid' => [
new Assert\Type(type: 'int'), new Assert\Type('int'),
new Assert\Range(options: ['min' => 300, 'max' => 86400]) new Assert\Range(['min' => 300, 'max' => 86400])
] ]
] ])
)
]; ];
} }
@ -107,8 +101,8 @@ class ConfigurationValidator
public static function validate(array $config): ConstraintViolationListInterface public static function validate(array $config): ConstraintViolationListInterface
{ {
return Validation::createValidator()->validate( return Validation::createValidator()->validate(
value: $config, $config,
constraints: self::getValidationConstraints() self::getValidationConstraints()
); );
} }
} }

View File

@ -45,12 +45,10 @@ class RegExValidator
protected static function getValidationConstraints(string $regEx): array protected static function getValidationConstraints(string $regEx): array
{ {
return [ return [
new Assert\Regex( new Assert\Regex([
pattern: [
'pattern' => $regEx, 'pattern' => $regEx,
'message' => 'This value does not match the regular expression "{{ pattern }}".' 'message' => 'This value does not match the regular expression "{{ pattern }}".'
] ])
)
]; ];
} }
@ -65,8 +63,8 @@ class RegExValidator
public static function validate(string $string, string $regEx): ConstraintViolationListInterface public static function validate(string $string, string $regEx): ConstraintViolationListInterface
{ {
return Validation::createValidator()->validate( return Validation::createValidator()->validate(
value: $string, $string,
constraints: self::getValidationConstraints(regEx: $regEx) self::getValidationConstraints($regEx)
); );
} }
} }

View File

@ -58,8 +58,8 @@ class UrlValidator
public static function validate(string $url): ConstraintViolationListInterface public static function validate(string $url): ConstraintViolationListInterface
{ {
return Validation::createValidator()->validate( return Validation::createValidator()->validate(
value: $url, $url,
constraints: self::getValidationConstraints() self::getValidationConstraints()
); );
} }
} }

View File

@ -44,7 +44,7 @@ class XmlValidator
protected static function getValidationConstraints(): array protected static function getValidationConstraints(): array
{ {
return [ return [
new Assert\Type(type: 'string'), new Assert\Type('string'),
new Assert\NotBlank() new Assert\NotBlank()
]; ];
} }
@ -59,18 +59,18 @@ class XmlValidator
public static function validate(string $xml): ConstraintViolationListInterface public static function validate(string $xml): ConstraintViolationListInterface
{ {
$violations = Validation::createValidator()->validate( $violations = Validation::createValidator()->validate(
value: $xml, $xml,
constraints: self::getValidationConstraints() self::getValidationConstraints()
); );
if (simplexml_load_string(data: $xml) === false) { if (simplexml_load_string($xml) === false) {
$violations->add( $violations->add(
violation: new ConstraintViolation( new ConstraintViolation(
message: 'Value could not be parsed as XML.', 'Value could not be parsed as XML.',
messageTemplate: 'Value could not be parsed as XML.', 'Value could not be parsed as XML.',
parameters: [], [],
root: $xml, $xml,
propertyPath: null, null,
invalidValue: $xml $xml
) )
); );
} }