diff --git a/composer.json b/composer.json index a99b274..dbb1417 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "ext-libxml": "*", "ext-sqlite3": "*", "doctrine/dbal": "^3.8", - "doctrine/orm": "^2.19", + "doctrine/orm": "^3.2", "opencultureconsulting/basics": "^1.1", "opencultureconsulting/psr15": "^1.0", "symfony/cache": "^6.4", @@ -97,6 +97,9 @@ ], "psalm:check": [ "@php vendor/bin/psalm" + ], + "psalm:check-security": [ + "@php vendor/bin/psalm --taint-analysis" ] }, "scripts-descriptions": { @@ -107,6 +110,7 @@ "phpcs:check": "Runs a code check with PHP_CodeSniffer and reports problems. If a custom configuration file '.phpcs.xml' exists, it will be used instead of the default settings in '.phpcs.xml.dist'.", "phpdoc:build": "Builds the documentation from source files in ./src and additional templates in .phpdoc/. If a custom configuration file 'phpdoc.xml' exists, it will be used instead of the default settings in 'phpdoc.dist.xml'.", "phpstan:check": "Runs a code check with PHPStan static code analyzer and reports problems. If a custom configuration file 'phpstan.neon' exists, it will be used instead of the default settings in 'phpstan.dist.neon'.", - "psalm:check": "Runs a code check with Psalm static code analyzer and reports problems. If a custom configuration file 'psalm.xml' exists, it will be used instead of the default settings in 'psalm.xml.dist'." + "psalm:check": "Runs a code check with Psalm static code analyzer and reports problems. If a custom configuration file 'psalm.xml' exists, it will be used instead of the default settings in 'psalm.xml.dist'.", + "psalm:check-security": "Runs a code check with Psalm static code analyzer and reports security issues. If a custom configuration file 'psalm.xml' exists, it will be used instead of the default settings in 'psalm.xml.dist'." } } diff --git a/composer.lock b/composer.lock index bcdca6c..fec8caf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f2f983ef97bc36ea0dea83537e3affce", + "content-hash": "8fbabb7463378ac73b62ab40c5f50b20", "packages": [ { "name": "doctrine/cache", @@ -185,97 +185,6 @@ ], "time": "2024-04-18T06:56:21+00:00" }, - { - "name": "doctrine/common", - "version": "3.4.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/common.git", - "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", - "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", - "shasum": "" - }, - "require": { - "doctrine/persistence": "^2.0 || ^3.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0 || ^10.0", - "doctrine/collections": "^1", - "phpstan/phpstan": "^1.4.1", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", - "squizlabs/php_codesniffer": "^3.0", - "symfony/phpunit-bridge": "^6.1", - "vimeo/psalm": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", - "homepage": "https://www.doctrine-project.org/projects/common.html", - "keywords": [ - "common", - "doctrine", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.4.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", - "type": "tidelift" - } - ], - "time": "2024-04-16T13:35:33+00:00" - }, { "name": "doctrine/dbal", "version": "3.8.6", @@ -767,61 +676,48 @@ }, { "name": "doctrine/orm", - "version": "2.19.6", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "c1bb2ccf4b19c845f91ff7c4c01dc7cbba7f4073" + "reference": "722cea6536775206e81744542b36fa7c9a4ea3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/c1bb2ccf4b19c845f91ff7c4c01dc7cbba7f4073", - "reference": "c1bb2ccf4b19c845f91ff7c4c01dc7cbba7f4073", + "url": "https://api.github.com/repos/doctrine/orm/zipball/722cea6536775206e81744542b36fa7c9a4ea3e5", + "reference": "722cea6536775206e81744542b36fa7c9a4ea3e5", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/cache": "^1.12.1 || ^2.1.1", - "doctrine/collections": "^1.5 || ^2.1", - "doctrine/common": "^3.0.3", - "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/collections": "^2.2", + "doctrine/dbal": "^3.8.2 || ^4", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2", "doctrine/inflector": "^1.4 || ^2.0", "doctrine/instantiator": "^1.3 || ^2", - "doctrine/lexer": "^2 || ^3", - "doctrine/persistence": "^2.4 || ^3", + "doctrine/lexer": "^3", + "doctrine/persistence": "^3.3.1", "ext-ctype": "*", - "php": "^7.1 || ^8.0", + "php": "^8.1", "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/polyfill-php72": "^1.23", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "doctrine/annotations": "<1.13 || >= 3.0" + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.3.9 || ^7.0" }, "require-dev": { - "doctrine/annotations": "^1.13 || ^2", - "doctrine/coding-standard": "^9.0.2 || ^12.0", - "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "~1.4.10 || 1.11.1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "doctrine/coding-standard": "^12.0", + "phpbench/phpbench": "^1.0", + "phpstan/phpstan": "1.11.1", + "phpunit/phpunit": "^10.4.0", "psr/log": "^1 || ^2 || ^3", "squizlabs/php_codesniffer": "3.7.2", - "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "vimeo/psalm": "4.30.0 || 5.24.0" + "symfony/cache": "^5.4 || ^6.2 || ^7.0", + "vimeo/psalm": "5.24.0" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", - "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", - "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" }, - "bin": [ - "bin/doctrine" - ], "type": "library", "autoload": { "psr-4": { @@ -862,9 +758,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.19.6" + "source": "https://github.com/doctrine/orm/tree/3.2.1" }, - "time": "2024-06-26T17:24:40+00:00" + "time": "2024-06-26T21:48:58+00:00" }, { "name": "doctrine/persistence", @@ -2361,159 +2257,6 @@ ], "time": "2024-06-19T12:30:46+00:00" }, - { - "name": "symfony/polyfill-php72", - "version": "v1.30.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "10112722600777e02d2745716b70c5db4ca70442" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/10112722600777e02d2745716b70c5db4ca70442", - "reference": "10112722600777e02d2745716b70c5db4ca70442", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.30.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-06-19T12:30:46+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.30.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-05-31T15:07:36+00:00" - }, { "name": "symfony/polyfill-php83", "version": "v1.30.0", @@ -5644,6 +5387,86 @@ ], "time": "2024-05-31T14:49:08+00:00" }, + { + "name": "symfony/polyfill-php80", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, { "name": "symfony/polyfill-php81", "version": "v1.30.0", diff --git a/psalm.xml.dist b/psalm.xml.dist index 606f492..cea884b 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -10,11 +10,35 @@ findUnusedVariablesAndParams="true" > + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Configuration.php b/src/Configuration.php index 682752b..30cbab6 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -146,13 +146,13 @@ protected function loadConfigFile(): array $configPath ); } + /** @var array */ $config = Yaml::parseFile($configPath); $validator = Validation::createValidator(); $violations = $validator->validate($config, $this->getValidationConstraints()); if ($violations->count() > 0) { throw new ValidationFailedException(null, $violations); } - /** @var array */ return $config; } diff --git a/src/Console/CsvImportCommand.php b/src/Console/CsvImportCommand.php index 5386d97..8919eec 100644 --- a/src/Console/CsvImportCommand.php +++ b/src/Console/CsvImportCommand.php @@ -124,10 +124,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var array */ $arguments = $input->getArguments(); - /** @var Format */ - $format = Database::getInstance() - ->getEntityManager() - ->getReference(Format::class, $arguments['format']); /** @var bool */ $noValidation = $input->getOption('noValidation'); /** @var resource */ @@ -143,6 +139,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $progressIndicator->start('Importing...'); while ($row = fgetcsv($file)) { + /** @var Format */ + $format = Database::getInstance() + ->getEntityManager() + ->getReference(Format::class, $arguments['format']); $record = new Record($row[$columns['idColumn']], $format); if (strlen(trim($row[$columns['contentColumn']])) > 0) { $record->setContent($row[$columns['contentColumn']], !$noValidation); @@ -171,10 +171,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $progressIndicator->setMessage( 'Importing... ' . (string) $count . ' records processed. Flushing to database...' ); - Database::getInstance()->flush([Record::class]); + Database::getInstance()->flush(true); } } - Database::getInstance()->flush(); + Database::getInstance()->flush(true); Database::getInstance()->pruneOrphanSets(); $progressIndicator->finish('All done!'); diff --git a/src/Console/UpdateFormatsCommand.php b/src/Console/UpdateFormatsCommand.php index 98afc42..3d16243 100644 --- a/src/Console/UpdateFormatsCommand.php +++ b/src/Console/UpdateFormatsCommand.php @@ -54,6 +54,7 @@ class UpdateFormatsCommand extends Console */ protected function execute(InputInterface $input, OutputInterface $output): int { + /** @var array> */ $formats = Configuration::getInstance()->metadataPrefix; $this->clearResultCache(); $inDatabase = Database::getInstance() diff --git a/src/Database.php b/src/Database.php index be378be..c34a3a2 100644 --- a/src/Database.php +++ b/src/Database.php @@ -198,15 +198,14 @@ public function deleteRecord(Record $record): void /** * Flush all changes to the database. * - * @param string[] $entities Optional array of entity types to clear from entity manager - * + * @param bool $clear Should the entity manager get cleared as well? * @return void */ - public function flush(array $entities = []): void + public function flush(bool $clear = false): void { $this->entityManager->flush(); - foreach ($entities as $entity) { - $this->entityManager->clear($entity); + if ($clear) { + $this->entityManager->clear(); } } @@ -412,7 +411,6 @@ public function getResumptionToken(string $token, string $verb): ?Token */ public function getSets(int $counter = 0): Result { - $result = []; $maxRecords = Configuration::getInstance()->maxRecords; $cursor = $counter * $maxRecords; diff --git a/src/Document.php b/src/Document.php index 8a4c3d5..d504207 100644 --- a/src/Document.php +++ b/src/Document.php @@ -98,7 +98,9 @@ protected function appendRequest(): void ); $request = $this->dom->createElement('request', $baseUrl); $this->rootNode->appendChild($request); - foreach ($this->serverRequest->getAttributes() as $param => $value) { + /** @var array */ + $params = $this->serverRequest->getAttributes(); + foreach ($params as $param => $value) { $request->setAttribute( $param, htmlspecialchars($value, ENT_XML1 | ENT_COMPAT, 'UTF-8') diff --git a/src/Middleware/Dispatcher.php b/src/Middleware/Dispatcher.php index b9d88f7..04f39d9 100644 --- a/src/Middleware/Dispatcher.php +++ b/src/Middleware/Dispatcher.php @@ -59,9 +59,11 @@ protected function getRequestWithAttributes(ServerRequestInterface $request): Se { $arguments = []; if ($request->getMethod() === 'GET') { + /** @var array */ $arguments = $request->getQueryParams(); } elseif ($request->getMethod() === 'POST') { if ($request->getHeaderLine('Content-Type') === 'application/x-www-form-urlencoded') { + /** @var array */ $arguments = (array) $request->getParsedBody(); } } @@ -84,8 +86,10 @@ protected function processRequest(ServerRequestInterface $request): ServerReques { $request = $this->getRequestWithAttributes($request); if (!ErrorHandler::getInstance()->hasErrors()) { + /** @var string */ + $verb = $request->getAttribute('verb'); /** @var Middleware $middleware */ - $middleware = __NAMESPACE__ . '\\' . $request->getAttribute('verb'); + $middleware = __NAMESPACE__ . '\\' . $verb; $this->requestHandler->queue->enqueue(new $middleware()); } $this->requestHandler->queue->enqueue(ErrorHandler::getInstance()); diff --git a/src/Middleware/GetRecord.php b/src/Middleware/GetRecord.php index 43d59d2..04f841d 100644 --- a/src/Middleware/GetRecord.php +++ b/src/Middleware/GetRecord.php @@ -46,6 +46,7 @@ class GetRecord extends Middleware */ protected function prepareResponse(ServerRequestInterface $request): void { + /** @var array */ $params = $request->getAttributes(); /** @var Format */ $format = Database::getInstance()->getEntityManager()->getReference(Format::class, $params['metadataPrefix']); @@ -58,6 +59,8 @@ protected function prepareResponse(ServerRequestInterface $request): void ErrorHandler::getInstance()->withError('idDoesNotExist'); } return; + } else { + $oaiRecordContent = $oaiRecord->getContent(); } $document = new Document($request); @@ -67,7 +70,7 @@ protected function prepareResponse(ServerRequestInterface $request): void $getRecord->appendChild($record); $header = $document->createElement('header'); - if ($oaiRecord->getContent() === null) { + if (!isset($oaiRecordContent)) { $header->setAttribute('status', 'deleted'); } $record->appendChild($header); @@ -83,11 +86,11 @@ protected function prepareResponse(ServerRequestInterface $request): void $header->appendChild($setSpec); } - if ($oaiRecord->getContent() !== null) { + if (isset($oaiRecordContent)) { $metadata = $document->createElement('metadata'); $record->appendChild($metadata); - $data = $document->importData($oaiRecord->getContent()); + $data = $document->importData($oaiRecordContent); $metadata->appendChild($data); } diff --git a/src/Middleware/ListIdentifiers.php b/src/Middleware/ListIdentifiers.php index 792b6ee..93ebdad 100644 --- a/src/Middleware/ListIdentifiers.php +++ b/src/Middleware/ListIdentifiers.php @@ -52,6 +52,7 @@ protected function prepareResponse(ServerRequestInterface $request): void $completeListSize = 0; $maxRecords = Configuration::getInstance()->maxRecords; + /** @var array */ $params = $request->getAttributes(); $verb = $params['verb']; $metadataPrefix = $params['metadataPrefix'] ?? ''; @@ -99,11 +100,11 @@ protected function prepareResponse(ServerRequestInterface $request): void $until, $set ); + $newToken = $records->getResumptionToken(); if (count($records) === 0) { ErrorHandler::getInstance()->withError('noRecordsMatch'); return; - } elseif ($records->getResumptionToken() !== null) { - $newToken = $records->getResumptionToken(); + } elseif (isset($newToken)) { $completeListSize = $newToken->getParameters()['completeListSize']; } diff --git a/src/Middleware/ListSets.php b/src/Middleware/ListSets.php index 2cc34df..0d2ba2f 100644 --- a/src/Middleware/ListSets.php +++ b/src/Middleware/ListSets.php @@ -68,11 +68,11 @@ protected function prepareResponse(ServerRequestInterface $request): void } $sets = Database::getInstance()->getSets($counter); + $newToken = $sets->getResumptionToken(); if (count($sets) === 0) { ErrorHandler::getInstance()->withError('noSetHierarchy'); return; - } elseif ($sets->getResumptionToken() !== null) { - $newToken = $sets->getResumptionToken(); + } elseif (isset($newToken)) { $completeListSize = $newToken->getParameters()['completeListSize']; } diff --git a/src/Result.php b/src/Result.php index 1cf715a..f92db3f 100644 --- a/src/Result.php +++ b/src/Result.php @@ -50,12 +50,12 @@ class Result implements Countable, Iterator * * @var QueryResult */ - private array $data = []; + private array $data; /** * This holds the optional resumption token. */ - protected ?Token $resumptionToken; + protected ?Token $resumptionToken = null; /** * Get the query result.