Consolidate class naming scheme

This commit is contained in:
Sebastian Meyer 2024-01-08 22:37:09 +01:00
parent c0321ab38d
commit 647e2ac007
19 changed files with 244 additions and 220 deletions

View File

@ -25,7 +25,9 @@ namespace OCC\OaiPmh2;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Base class for all OAI-PMH console commands.
@ -33,7 +35,7 @@ use Symfony\Component\Console\Output\NullOutput;
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
* @package opencultureconsulting/oai-pmh2
*/
abstract class ConsoleCommand extends Command
abstract class Console extends Command
{
/**
* Clears the result cache.
@ -76,4 +78,43 @@ abstract class ConsoleCommand extends Command
}
return $limit;
}
/**
* Validate input arguments.
*
* @param InputInterface $input The inputs
* @param OutputInterface $output The output interface
*
* @return bool Whether the inputs validate
*/
protected function validateInput(InputInterface $input, OutputInterface $output): bool
{
/** @var array<string, string> */
$arguments = $input->getArguments();
$formats = Database::getInstance()->getMetadataFormats()->getQueryResult();
if (!in_array($arguments['format'], array_keys($formats), true)) {
$output->writeln([
'',
sprintf(
' [ERROR] Metadata format "%s" is not supported. ',
$arguments['format']
),
''
]);
return false;
}
if (!is_readable($arguments['file'])) {
$output->writeln([
'',
sprintf(
' [ERROR] File "%s" not found or not readable. ',
$arguments['file']
),
''
]);
return false;
}
return true;
}
}

View File

@ -22,10 +22,11 @@ declare(strict_types=1);
namespace OCC\OaiPmh2\Console;
use OCC\OaiPmh2\ConsoleCommand;
use OCC\OaiPmh2\Console;
use OCC\OaiPmh2\Database;
use OCC\OaiPmh2\Database\Format;
use OCC\OaiPmh2\Database\Record;
use OCC\OaiPmh2\Entity\Format;
use OCC\OaiPmh2\Entity\Record;
use OCC\OaiPmh2\Entity\Set;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@ -42,7 +43,7 @@ use Symfony\Component\Console\Output\OutputInterface;
name: 'oai:records:add',
description: 'Add or update a record in the database'
)]
class AddRecordCommand extends ConsoleCommand
class AddRecordCommand extends Console
{
/**
* Configures the current command.
@ -84,29 +85,29 @@ class AddRecordCommand extends ConsoleCommand
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var array<string, string> */
$arguments = $input->getArguments();
/** @var Format */
$format = Database::getInstance()->getEntityManager()->getReference(Format::class, $arguments['format']);
$content = file_get_contents($arguments['file']);
if ($content === false) {
$output->writeln([
'',
sprintf(
' [ERROR] File "%s" not found or not readable. ',
$arguments['file']
),
''
]);
if (!$this->validateInput($input, $output)) {
return Command::INVALID;
}
/** @var string */
$identifier = $input->getArgument('identifier');
/** @var Format */
$format = Database::getInstance()->getEntityManager()->getReference(Format::class, $input->getArgument('format'));
/** @var string */
$file = $input->getArgument('file');
/** @var string[] */
$sets = $input->getArgument('sets');
/** @var string */
$content = file_get_contents($file);
$record = new Record($arguments['identifier'], $format);
$record = new Record($identifier, $format);
if (trim($content) !== '') {
$record->setContent($content);
}
// TODO: Add full set support.
foreach ($sets as $set) {
/** @var Set */
$setSpec = Database::getInstance()->getEntityManager()->getReference(Set::class, $set);
$record->addSet($setSpec);
}
Database::getInstance()->addOrUpdateRecord($record);
Database::getInstance()->pruneOrphanSets();
@ -117,8 +118,8 @@ class AddRecordCommand extends ConsoleCommand
'',
sprintf(
' [OK] Record "%s" with metadata prefix "%s" added or updated successfully! ',
$arguments['identifier'],
$arguments['format']
$identifier,
$format->getPrefix()
),
''
]);

View File

@ -23,10 +23,11 @@ declare(strict_types=1);
namespace OCC\OaiPmh2\Console;
use DateTime;
use OCC\OaiPmh2\ConsoleCommand;
use OCC\OaiPmh2\Console;
use OCC\OaiPmh2\Database;
use OCC\OaiPmh2\Database\Format;
use OCC\OaiPmh2\Database\Record;
use OCC\OaiPmh2\Entity\Format;
use OCC\OaiPmh2\Entity\Record;
use OCC\OaiPmh2\Entity\Set;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressIndicator;
@ -45,7 +46,7 @@ use Symfony\Component\Console\Output\OutputInterface;
name: 'oai:records:import:csv',
description: 'Import records from a CSV file'
)]
class CsvImportCommand extends ConsoleCommand
class CsvImportCommand extends Console
{
/**
* Configures the current command.
@ -93,7 +94,7 @@ class CsvImportCommand extends ConsoleCommand
'setColumn',
's',
InputOption::VALUE_OPTIONAL,
'Name of the CSV column which holds the records\' sets list.',
'Name of the CSV column which holds the comma-separated list of the records\' sets.',
'sets'
);
$this->addOption(
@ -148,7 +149,12 @@ class CsvImportCommand extends ConsoleCommand
if (strlen(trim($row[$columns['contentColumn']])) > 0) {
$record->setContent($row[$columns['contentColumn']], !$noValidation);
}
// TODO: Complete support for sets.
$sets = $row[$columns['setColumn']] ?? '';
foreach (explode(',', trim($sets)) as $set) {
/** @var Set */
$setSpec = Database::getInstance()->getEntityManager()->getReference(Set::class, $set);
$record->addSet($setSpec);
}
Database::getInstance()->addOrUpdateRecord($record, true);
++$count;
@ -230,43 +236,4 @@ class CsvImportCommand extends ConsoleCommand
}
return $columns;
}
/**
* Validate input arguments.
*
* @param InputInterface $input The inputs
* @param OutputInterface $output The output interface
*
* @return bool Whether the inputs validate
*/
protected function validateInput(InputInterface $input, OutputInterface $output): bool
{
/** @var array<string, string> */
$arguments = $input->getArguments();
$formats = Database::getInstance()->getMetadataFormats()->getQueryResult();
if (!in_array($arguments['format'], array_keys($formats), true)) {
$output->writeln([
'',
sprintf(
' [ERROR] Metadata format "%s" is not supported. ',
$arguments['format']
),
''
]);
return false;
}
if (!is_readable($arguments['file'])) {
$output->writeln([
'',
sprintf(
' [ERROR] File "%s" not found or not readable. ',
$arguments['file']
),
''
]);
return false;
}
return true;
}
}

View File

@ -22,10 +22,10 @@ declare(strict_types=1);
namespace OCC\OaiPmh2\Console;
use OCC\OaiPmh2\ConsoleCommand;
use OCC\OaiPmh2\Console;
use OCC\OaiPmh2\Database;
use OCC\OaiPmh2\Database\Format;
use OCC\OaiPmh2\Database\Record;
use OCC\OaiPmh2\Entity\Format;
use OCC\OaiPmh2\Entity\Record;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@ -42,7 +42,7 @@ use Symfony\Component\Console\Output\OutputInterface;
name: 'oai:records:delete',
description: 'Delete a record from database'
)]
class DeleteRecordCommand extends ConsoleCommand
class DeleteRecordCommand extends Console
{
/**
* Configures the current command.

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
namespace OCC\OaiPmh2\Console;
use OCC\OaiPmh2\Configuration;
use OCC\OaiPmh2\ConsoleCommand;
use OCC\OaiPmh2\Console;
use OCC\OaiPmh2\Database;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
@ -41,7 +41,7 @@ use Symfony\Component\Console\Output\OutputInterface;
name: 'oai:records:prune',
description: 'Prune deleted records from database'
)]
class PruneDeletedRecordsCommand extends ConsoleCommand
class PruneDeletedRecordsCommand extends Console
{
/**
* Configures the current command.

View File

@ -22,7 +22,7 @@ declare(strict_types=1);
namespace OCC\OaiPmh2\Console;
use OCC\OaiPmh2\ConsoleCommand;
use OCC\OaiPmh2\Console;
use OCC\OaiPmh2\Database;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
@ -39,7 +39,7 @@ use Symfony\Component\Console\Output\OutputInterface;
name: 'oai:tokens:prune',
description: 'Prune expired resumption tokens from database'
)]
class PruneResumptionTokensCommand extends ConsoleCommand
class PruneResumptionTokensCommand extends Console
{
/**
* Executes the current command.

View File

@ -23,9 +23,9 @@ declare(strict_types=1);
namespace OCC\OaiPmh2\Console;
use OCC\OaiPmh2\Configuration;
use OCC\OaiPmh2\ConsoleCommand;
use OCC\OaiPmh2\Console;
use OCC\OaiPmh2\Database;
use OCC\OaiPmh2\Database\Format;
use OCC\OaiPmh2\Entity\Format;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@ -42,7 +42,7 @@ use Symfony\Component\Validator\Exception\ValidationFailedException;
name: 'oai:formats:update',
description: 'Update metadata formats in database from configuration'
)]
class UpdateFormatsCommand extends ConsoleCommand
class UpdateFormatsCommand extends Console
{
/**
* Executes the current command.

View File

@ -34,11 +34,11 @@ use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Tools\Pagination\Paginator;
use OCC\Basics\Traits\Singleton;
use OCC\OaiPmh2\Database\Format;
use OCC\OaiPmh2\Database\Record;
use OCC\OaiPmh2\Database\Result;
use OCC\OaiPmh2\Database\Set;
use OCC\OaiPmh2\Database\Token;
use OCC\OaiPmh2\Entity\Format;
use OCC\OaiPmh2\Entity\Record;
use OCC\OaiPmh2\Entity\Set;
use OCC\OaiPmh2\Entity\Token;
use OCC\OaiPmh2\Result;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
use Symfony\Component\Filesystem\Path;
@ -485,10 +485,10 @@ class Database
)
);
$configuration->setMetadataDriverImpl(
new AttributeDriver([__DIR__ . '/Database'])
new AttributeDriver([__DIR__ . '/Entity'])
);
$configuration->setProxyDir(__DIR__ . '/../var/generated');
$configuration->setProxyNamespace('OCC\OaiPmh2\Database\Proxy');
$configuration->setProxyNamespace('OCC\OaiPmh2\Entity\Proxy');
$configuration->setQueryCache(
new PhpFilesAdapter(
'Query',

View File

@ -28,7 +28,6 @@ use DOMException;
use DOMNode;
use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
/**
* An OAI-PMH XML response object.

112
src/Entity.php Normal file
View File

@ -0,0 +1,112 @@
<?php
/**
* OAI-PMH 2.0 Data Provider
* Copyright (C) 2023 Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace OCC\OaiPmh2;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Exception\ValidationFailedException;
use Symfony\Component\Validator\Validation;
/**
* Base class for all Doctrine/ORM entities.
*
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
* @package opencultureconsulting/oai-pmh2
*/
abstract class Entity
{
/**
* Check if a string does not contain any whitespaces.
*
* @param string $string The string
*
* @return string The validated string
*
* @throws ValidationFailedException
*/
protected function validateNoWhitespace(string $string): string
{
$string = trim($string);
$validator = Validation::createValidator();
$violations = $validator->validate(
$string,
[
new Assert\Regex([
'pattern' => '/\s/',
'match' => false,
'message' => 'This value contains whitespaces.'
]),
new Assert\NotBlank()
]
);
if ($violations->count() > 0) {
throw new ValidationFailedException(null, $violations);
}
return $string;
}
/**
* Check if a string is a valid URI.
*
* @param string $uri The URI
*
* @return string The validated URI
*
* @throws ValidationFailedException
*/
protected function validateUri(string $uri): string
{
$uri = trim($uri);
$validator = Validation::createValidator();
$violations = $validator->validate($uri, new Assert\Url());
if ($violations->count() > 0) {
throw new ValidationFailedException(null, $violations);
}
return $uri;
}
/**
* Check if a string is well-formed XML.
*
* @param string $xml The XML string
*
* @return string The validated XML string
*
* @throws ValidationFailedException
*/
protected function validateXml(string $xml): string
{
$validator = Validation::createValidator();
$violations = $validator->validate(
$xml,
[
new Assert\Type('string'),
new Assert\NotBlank()
]
);
if ($violations->count() > 0
or simplexml_load_string($xml) === false) {
throw new ValidationFailedException(null, $violations);
}
return $xml;
}
}

View File

@ -20,12 +20,11 @@
declare(strict_types=1);
namespace OCC\OaiPmh2\Database;
namespace OCC\OaiPmh2\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use OCC\OaiPmh2\Entity;
use Symfony\Component\Validator\Exception\ValidationFailedException;
use Symfony\Component\Validator\Validation;
/**
* Doctrine/ORM Entity for formats.
@ -35,7 +34,7 @@ use Symfony\Component\Validator\Validation;
*/
#[ORM\Entity]
#[ORM\Table(name: 'formats')]
class Format
class Format extends Entity
{
/**
* The unique metadata prefix.
@ -98,7 +97,7 @@ class Format
public function setNamespace(string $namespace): void
{
try {
$this->namespace = $this->validateUrl($namespace);
$this->namespace = $this->validateUri($namespace);
} catch (ValidationFailedException $exception) {
throw $exception;
}
@ -116,62 +115,12 @@ class Format
public function setSchema(string $schema): void
{
try {
$this->xmlSchema = $this->validateUrl($schema);
$this->xmlSchema = $this->validateUri($schema);
} catch (ValidationFailedException $exception) {
throw $exception;
}
}
/**
* Validate metadata prefix.
*
* @param string $prefix The metadata prefix
*
* @return string The validated prefix
*
* @throws ValidationFailedException
*/
protected function validatePrefix(string $prefix): string
{
$prefix = trim($prefix);
$validator = Validation::createValidator();
$violations = $validator->validate(
$prefix,
[
new Assert\Regex([
'pattern' => '/\s/',
'match' => false,
'message' => 'This value contains whitespaces.'
]),
new Assert\NotBlank()
]
);
if ($violations->count() > 0) {
throw new ValidationFailedException(null, $violations);
}
return $prefix;
}
/**
* Validate namespace and schema URLs.
*
* @param string $url The namespace or schema URL
*
* @return string The validated URL
*
* @throws ValidationFailedException
*/
protected function validateUrl(string $url): string
{
$url = trim($url);
$validator = Validation::createValidator();
$violations = $validator->validate($url, new Assert\Url());
if ($violations->count() > 0) {
throw new ValidationFailedException(null, $violations);
}
return $url;
}
/**
* Get new entity of format.
*
@ -184,7 +133,7 @@ class Format
public function __construct(string $prefix, string $namespace, string $schema)
{
try {
$this->prefix = $this->validatePrefix($prefix);
$this->prefix = $this->validateNoWhitespace($prefix);
$this->setNamespace($namespace);
$this->setSchema($schema);
} catch (ValidationFailedException $exception) {

View File

@ -20,15 +20,14 @@
declare(strict_types=1);
namespace OCC\OaiPmh2\Database;
namespace OCC\OaiPmh2\Entity;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use OCC\OaiPmh2\Entity;
use Symfony\Component\Validator\Exception\ValidationFailedException;
use Symfony\Component\Validator\Validation;
/**
* Doctrine/ORM Entity for records.
@ -38,7 +37,7 @@ use Symfony\Component\Validator\Validation;
*/
#[ORM\Entity]
#[ORM\Table(name: 'records')]
class Record
class Record extends Entity
{
/**
* The record identifier.
@ -197,7 +196,7 @@ class Record
$data = trim($data);
if ($validate) {
try {
$data = $this->validate($data);
$data = $this->validateXml($data);
} catch (ValidationFailedException $exception) {
throw $exception;
}
@ -233,32 +232,6 @@ class Record
$this->lastChanged = $dateTime;
}
/**
* Validate XML content.
*
* @param string $xml The XML string
*
* @return string The validated XML string
*
* @throws ValidationFailedException
*/
protected function validate(string $xml): string
{
$validator = Validation::createValidator();
$violations = $validator->validate(
$xml,
[
new Assert\Type('string'),
new Assert\NotBlank()
]
);
if ($violations->count() > 0
or simplexml_load_string($xml) === false) {
throw new ValidationFailedException(null, $violations);
}
return $xml;
}
/**
* Get new entity of record.
*
@ -272,7 +245,7 @@ class Record
public function __construct(string $identifier, Format $format, ?string $data = null, ?DateTime $lastChanged = null)
{
try {
$this->identifier = $identifier;
$this->identifier = $this->validateNoWhitespace($identifier);
$this->setFormat($format);
$this->setContent($data);
$this->setLastChanged($lastChanged);

View File

@ -20,14 +20,13 @@
declare(strict_types=1);
namespace OCC\OaiPmh2\Database;
namespace OCC\OaiPmh2\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use OCC\OaiPmh2\Entity;
use Symfony\Component\Validator\Exception\ValidationFailedException;
use Symfony\Component\Validator\Validation;
/**
* Doctrine/ORM Entity for sets.
@ -37,7 +36,7 @@ use Symfony\Component\Validator\Validation;
*/
#[ORM\Entity]
#[ORM\Table(name: 'sets')]
class Set
class Set extends Entity
{
/**
* The unique set spec.
@ -152,40 +151,16 @@ class Set
* @param string $description The description
*
* @return void
*/
public function setDescription(string $description): void
{
$this->description = trim($description);
}
/**
* Validate set spec.
*
* @param string $spec The set spec
*
* @return string The validated spec
*
* @throws ValidationFailedException
*/
protected function validate(string $spec): string
public function setDescription(string $description): void
{
$spec = trim($spec);
$validator = Validation::createValidator();
$violations = $validator->validate(
$spec,
[
new Assert\Regex([
'pattern' => '/\s/',
'match' => false,
'message' => 'This value contains whitespaces.'
]),
new Assert\NotBlank()
]
);
if ($violations->count() > 0) {
throw new ValidationFailedException(null, $violations);
try {
$this->description = $this->validateXml($description);
} catch (ValidationFailedException $exception) {
throw $exception;
}
return $spec;
}
/**
@ -200,7 +175,7 @@ class Set
public function __construct(string $spec, string $name, string $description = '')
{
try {
$this->spec = $this->validate($spec);
$this->spec = $this->validateNoWhitespace($spec);
$this->name = trim($name);
$this->setDescription($description);
$this->records = new ArrayCollection();

View File

@ -20,12 +20,13 @@
declare(strict_types=1);
namespace OCC\OaiPmh2\Database;
namespace OCC\OaiPmh2\Entity;
use DateInterval;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use OCC\OaiPmh2\Configuration;
use OCC\OaiPmh2\Entity;
/**
* Doctrine/ORM Entity for resumption tokens.
@ -35,7 +36,7 @@ use OCC\OaiPmh2\Configuration;
*/
#[ORM\Entity]
#[ORM\Table(name: 'tokens')]
class Token
class Token extends Entity
{
/**
* The resumption token.

View File

@ -23,8 +23,8 @@ declare(strict_types=1);
namespace OCC\OaiPmh2\Middleware;
use OCC\OaiPmh2\Database;
use OCC\OaiPmh2\Database\Format;
use OCC\OaiPmh2\Document;
use OCC\OaiPmh2\Entity\Format;
use OCC\OaiPmh2\Middleware;
use Psr\Http\Message\ServerRequestInterface;

View File

@ -25,8 +25,8 @@ namespace OCC\OaiPmh2\Middleware;
use DateTime;
use OCC\OaiPmh2\Configuration;
use OCC\OaiPmh2\Database;
use OCC\OaiPmh2\Database\Record;
use OCC\OaiPmh2\Document;
use OCC\OaiPmh2\Entity\Record;
use OCC\OaiPmh2\Middleware;
use Psr\Http\Message\ServerRequestInterface;

View File

@ -23,8 +23,8 @@ declare(strict_types=1);
namespace OCC\OaiPmh2\Middleware;
use OCC\OaiPmh2\Database;
use OCC\OaiPmh2\Database\Format;
use OCC\OaiPmh2\Document;
use OCC\OaiPmh2\Entity\Format;
use OCC\OaiPmh2\Middleware;
use Psr\Http\Message\ServerRequestInterface;

View File

@ -24,9 +24,8 @@ namespace OCC\OaiPmh2\Middleware;
use OCC\OaiPmh2\Configuration;
use OCC\OaiPmh2\Database;
use OCC\OaiPmh2\Database\Set;
use OCC\OaiPmh2\Database\Token;
use OCC\OaiPmh2\Document;
use OCC\OaiPmh2\Entity\Set;
use OCC\OaiPmh2\Middleware;
use Psr\Http\Message\ServerRequestInterface;
@ -92,8 +91,11 @@ class ListSets extends Middleware
$set->appendChild($setName);
if ($oaiSet->getDescription() !== '') {
$setDescription = $document->createElement('setDescription', $oaiSet->getDescription());
$setDescription = $document->createElement('setDescription');
$set->appendChild($setDescription);
$description = $document->importData($oaiSet->getDescription());
$setDescription->appendChild($description);
}
}

View File

@ -20,12 +20,16 @@
declare(strict_types=1);
namespace OCC\OaiPmh2\Database;
namespace OCC\OaiPmh2;
use Countable;
use Iterator;
use OCC\Basics\InterfaceTraits\Countable as CountableTrait;
use OCC\Basics\InterfaceTraits\Iterator as IteratorTrait;
use OCC\OaiPmh2\Entity\Format;
use OCC\OaiPmh2\Entity\Record;
use OCC\OaiPmh2\Entity\Set;
use OCC\OaiPmh2\Entity\Token;
/**
* A database result set with optional resumption token.