diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..050d624e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +.gitattributes export-ignore +/.github/ export-ignore +.gitignore export-ignore +/.php_cs.dist export-ignore +/phpstan.neon.dist export-ignore +/phpstan.tests.neon.dist export-ignore +/phpunit.xml.dist export-ignore +/stubs/ export-ignore +/tests/ export-ignore diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 00000000..cc83b173 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,55 @@ +name: Static analysis + +on: + push: + branches: + - '[0-9]+.x' + - '[0-9]+.[0-9]+' + - '[0-9]+.[0-9]+.x' + pull_request: + +jobs: + phpstan-src: + name: PHPStan src + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: PHPStan + uses: docker://oskarstark/phpstan-ga + with: + args: analyze --no-progress + + phpstan-tests: + name: PHPStan tests + runs-on: ubuntu-latest + env: + REQUIRE_DEV: "true" + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + composer update --no-progress + + - name: PHPStan + uses: docker://oskarstark/phpstan-ga + with: + args: analyze --no-progress -c phpstan.tests.neon.dist + + php-cs-fixer: + name: PHP-CS-Fixer + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: PHP-CS-Fixer + uses: docker://oskarstark/php-cs-fixer-ga + with: + args: --dry-run --diff diff --git a/.github/workflows/test-application.yaml b/.github/workflows/test-application.yaml new file mode 100644 index 00000000..cc3ec50b --- /dev/null +++ b/.github/workflows/test-application.yaml @@ -0,0 +1,81 @@ +name: Test application + +on: + pull_request: + push: + branches: + - '[0-9]+.x' + - '[0-9]+.[0-9]+' + - '[0-9]+.[0-9]+.x' + +jobs: + test: + name: "PHP ${{ matrix.php-version }} ${{ matrix.dependencies }} ${{ matrix.dev-dependencies && 'dev' }}" + runs-on: ubuntu-20.04 + + strategy: + fail-fast: false + matrix: + include: + - php-version: '8.0' + dependencies: 'lowest' + - php-version: '8.1' + - php-version: '8.2' + - php-version: '8.3' + - php-version: '8.3' + + steps: + - name: Checkout project + uses: actions/checkout@v3 + + - name: Install and configure PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: 'composer:v2' + + - name: Allow unstable dependencies + if: matrix.dev-dependencies == true + run: composer config minimum-stability dev + + - name: Install dependencies with Composer + uses: ramsey/composer-install@v2 + with: + dependency-versions: ${{ matrix.dependencies }} + composer-options: --prefer-dist + + - name: Execute test cases + run: vendor/bin/phpunit + + php-windows: + name: "PHP Windows ${{ matrix.php-version }} ${{ matrix.dependencies }} ${{ matrix.dev-dependencies && 'dev' }}" + runs-on: windows-latest + + strategy: + fail-fast: false + matrix: + include: + - php-version: '8.1' + + steps: + - name: Checkout project + uses: actions/checkout@v3 + + - name: Install and configure PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: 'composer:v2' + + - name: Allow unstable dependencies + if: matrix.dev-dependencies == true + run: composer config minimum-stability dev + + - name: Install dependencies with Composer + uses: ramsey/composer-install@v2 + with: + dependency-versions: ${{ matrix.dependencies }} + composer-options: --prefer-dist + + - name: Execute test cases + run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index bb6966fc..35e5af6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ cli-config.php - +.php-cs-fixer.cache vendor/ composer.phar -composer.lock \ No newline at end of file +composer.lock diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 00000000..92bc7484 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,18 @@ +in(__DIR__.'/src') + ->in(__DIR__.'/tests') + ->name('*.php') +; + +$config = new PhpCsFixer\Config(); + +return $config + ->setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + 'single_line_throw' => false, + ]) + ->setFinder($finder) +; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b688730d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -language: php - -php: - - 5.3 - - 5.4 - - 5.5 - - 5.6 - - hhvm - -env: - - PACKAGE_VERSION=high - -matrix: - include: - - php: 5.3.3 - env: PACKAGE_VERSION=low - -before_script: - - composer selfupdate - - if [[ "$PACKAGE_VERSION" == "high" ]]; then composer update --prefer-source; fi - - if [[ "$PACKAGE_VERSION" == "low" ]]; then composer update --prefer-lowest --prefer-source; fi - -script: phpunit -c tests/phpunit.xml.dist - -notifications: - irc: "irc.freenode.org#jackalope" diff --git a/CHANGELOG.md b/CHANGELOG.md index 49650207..c560bbc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,133 @@ Changelog ========= +2.x +--- + +2.0.2 +----- + +* Return type for `PHPCR\Util\QOM\QueryBuilder::getQuery` is not nullable. + +2.0.1 +----- + +* The SQL generator now escapes single quotes `'`. This avoids SQL injection risks. If you escaped + strings manually (by duplicating the `'`) you will need to stop doing that as otherwise the query + will be run with duplicated single quotes. + +2.0.0 +----- + +* Support Symfony 7 +* Drop support for Symfony 2 +* Remove deprecated code, clean up workarounds for Symfony 2. +* Drop support for PHP 7, test with PHP 8.3 +* Adjusted commands to have the return type declarations. + +1.x +--- + +1.8.1 +----- +* Codestyle fixes. + +1.8.0 +----- + +* Fixed handling of QOM fulltext search expression which should never be string but a `QOM\StaticOperandInterface`. +* Fixed EOF detection while parsing CND with PHP 7+. + +1.7.0 +----- + +* Introduced phpstan and fixed a couple of undeclared properties and other findings. +* Test with PHP 8.2 +* Drop support for PHP 7.1 + +1.6.3 +----- + +* Fix more deprecations with PHP 8.1. Round floats when using them as a timestamp, rather than the implicit conversion that floored them. + +1.6.2 +----- + +* Fix deprecations of PHP 8.1 + +1.6.1 +----- + +* Fixed handling of windows style newlines in SQL2 parsing. + +1.6.0 +----- + +* Refactored SQL2 parsing to be more efficient. + +1.5.3 +----- + +* Compatible with Symfony 6 +* Compatible with PHP 8.1 + +1.5.2 +----- + +* Fix SQL 2 scanner delimiter detection to handle that tokens don't necessarily have whitespace between them. + +1.5.1 +----- + +* Fix issues with PHPUnit 9 + +1.5.0 +----- + +* Support PHP 8 +* Drop support for PHP 5.6 and 7.0 + +1.4.1 +----- + +* Support Symfony 5 +* Test with PHP 7.3 and 7.4 + +1.4.0 +----- + +* Added option to phpcr:workspace:import command to be able to specify the UUID behavior on collisions during import + +1.3.2 +----- + +* Support Symfony 4 + +1.3.1 +----- + +* Support for PHP 7.2 + +1.3.0 +----- + +* Support for PHP 5.6/7.0/7.1 +* **2017-11-18**: Removed hhvm test + +1.2.7 +----- + +* **2015-07-13**: Added Symfony 3 compatibility for the console commands. If you use + the commands, update your `cli-config.php` according to `cli-config.php.dist` to set + the question helper if it is available. + +1.2.0 +----- + +* **2014-10-24**: Fixed SQL2 handling, notably precedency when generating SQL2 and parsing of literals. +* **2014-10-05**: Added PathHelper::getLocalNodeName +* **2014-09-01**: Added PathHelper::relativizePath + 1.1.1 ----- diff --git a/README.md b/README.md index 4754dd66..609eb5c0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PHPCR Utilities -[![Build Status](https://secure.travis-ci.org/phpcr/phpcr-utils.png?branch=1.1)](http://travis-ci.org/phpcr/phpcr-utils) +[![Build Status](https://github.com/phpcr/phpcr-utils/actions/workflows/test-application.yaml/badge.svg)](https://github.com/phpcr/phpcr-utils/actions) [![Latest Stable Version](https://poser.pugx.org/phpcr/phpcr-utils/version.png)](https://packagist.org/packages/phpcr/phpcr-utils) [![Total Downloads](https://poser.pugx.org/phpcr/phpcr-utils/d/total.png)](https://packagist.org/packages/phpcr/phpcr-utils) @@ -66,6 +66,11 @@ valid UUID. We recommend all implementations to use this implementation to guarantee consistent behaviour. +**Note** + +You can use [ramsey/uuid](https://github.com/ramsey/uuid) library to generate UUIDs. In this case, +install it using Composer and generating UUIDs will be taken over by `ramsey/uuid`. + ### QOM QueryBuilder The ``QueryBuilder`` is a fluent query builder with method names matching the diff --git a/bin/phpcr b/bin/phpcr index d3a9bb1d..9b361f7a 100755 --- a/bin/phpcr +++ b/bin/phpcr @@ -1,13 +1,20 @@ #!/usr/bin/env php setCatchExceptions(true); $cli->setHelperSet($helperSet); -$cli->addCommands(array( - new \PHPCR\Util\Console\Command\NodeDumpCommand(), - new \PHPCR\Util\Console\Command\NodeMoveCommand(), - new \PHPCR\Util\Console\Command\NodeRemoveCommand(), - new \PHPCR\Util\Console\Command\NodeTouchCommand(), - new \PHPCR\Util\Console\Command\NodesUpdateCommand(), - new \PHPCR\Util\Console\Command\NodeTypeListCommand(), - new \PHPCR\Util\Console\Command\NodeTypeRegisterCommand(), - new \PHPCR\Util\Console\Command\WorkspaceCreateCommand(), - new \PHPCR\Util\Console\Command\WorkspaceDeleteCommand(), - new \PHPCR\Util\Console\Command\WorkspaceExportCommand(), - new \PHPCR\Util\Console\Command\WorkspaceImportCommand(), - new \PHPCR\Util\Console\Command\WorkspacePurgeCommand(), - new \PHPCR\Util\Console\Command\WorkspaceQueryCommand(), -)); + +$cli->addCommands([ + new Command\NodeDumpCommand(), + new Command\NodeMoveCommand(), + new Command\NodeRemoveCommand(), + new Command\NodeTouchCommand(), + new Command\NodesUpdateCommand(), + new Command\NodeTypeListCommand(), + new Command\NodeTypeRegisterCommand(), + new Command\WorkspaceCreateCommand(), + new Command\WorkspaceDeleteCommand(), + new Command\WorkspaceExportCommand(), + new Command\WorkspaceImportCommand(), + new Command\WorkspacePurgeCommand(), + new Command\WorkspaceQueryCommand(), +]); + $cli->run(); diff --git a/cli-config.php.dist b/cli-config.php.dist index f605ee10..271ff1a7 100644 --- a/cli-config.php.dist +++ b/cli-config.php.dist @@ -28,8 +28,13 @@ if (isset($argv[1]) $session = $repository->login($credentials, $workspace); $helperSet = new \Symfony\Component\Console\Helper\HelperSet(array( - 'dialog' => new \Symfony\Component\Console\Helper\DialogHelper(), 'phpcr' => new \PHPCR\Util\Console\Helper\PhpcrHelper($session), 'phpcr_console_dumper' => new \PHPCR\Util\Console\Helper\PhpcrConsoleDumperHelper(), )); + + if (class_exists('Symfony\Component\Console\Helper\QuestionHelper')) { + $helperSet->set(new \Symfony\Component\Console\Helper\QuestionHelper(), 'question'); + } else { + $helperSet->set(new \Symfony\Component\Console\Helper\DialogHelper(), 'dialog'); + } } diff --git a/composer.json b/composer.json index 0e6ec723..843324f6 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "type": "library", "description": "PHP Content Repository implementation independant utilities", "keywords": ["phpcr", "contentrepository", "cli"], - "homepage": "http://phpcr.github.com/", + "homepage": "http://phpcr.github.io/", "license": [ "MIT", "Apache-2.0" @@ -27,18 +27,26 @@ } ], "require": { - "php": ">=5.3.3", + "php": "^8.0", "phpcr/phpcr": "~2.1.0", - "symfony/console": "2.*,>=2.0.5" + "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "require-dev": { + "ramsey/uuid": "^3.5", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0", + "phpstan/phpstan": "^1.9", + "friendsofphp/php-cs-fixer": "^3.40" + }, + "suggest": { + "ramsey/uuid": "A library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID)." + }, + "conflict": { + "jackalope/jackalope-jackrabbit": "<1.2.1" }, "autoload": { "psr-0": { "PHPCR\\Util": "src" } }, - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - } + "bin": ["bin/phpcr"] } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..6fc94366 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,16 @@ +parameters: + level: 5 + paths: + - src/ + + ignoreErrors: + # phpstan does not understand that the empty arrays are only the default + - + message: "#^Empty array passed to foreach\\.$#" + count: 5 + path: src/PHPCR/Util/Console/Helper/PhpcrHelper.php + # only formulated in phpdoc that the return value must be countable + - + message: "#expects array|Countable, Iterator given\\.$#" + count: 1 + path: src/PHPCR/Util/Console/Command/NodesUpdateCommand.php diff --git a/phpstan.tests.neon.dist b/phpstan.tests.neon.dist new file mode 100644 index 00000000..d7889e1f --- /dev/null +++ b/phpstan.tests.neon.dist @@ -0,0 +1,20 @@ +parameters: + level: 7 + paths: + - tests/ + + excludePaths: + analyse: + - tests/*/Fixtures/* + + ignoreErrors: + # too pedantic for tests + - + message: "#^Parameter \\#1 \\.\\.\\.\\$arrays of function array_merge expects array, array\\\\|false given\\.$#" + count: 1 + path: tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php + + - + message: "#^Parameter \\#3 \\.\\.\\.\\$arrays of function array_merge expects array, array\\\\|false given\\.$#" + count: 1 + path: tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..42bb21e2 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,15 @@ + + + + + + ./tests + + + diff --git a/src/PHPCR/Util/CND/Exception/ParserException.php b/src/PHPCR/Util/CND/Exception/ParserException.php index 85707d67..3cb73bdc 100644 --- a/src/PHPCR/Util/CND/Exception/ParserException.php +++ b/src/PHPCR/Util/CND/Exception/ParserException.php @@ -1,32 +1,32 @@ - * * @license http://www.apache.org/licenses Apache License Version 2.0, January 2004 * @license http://opensource.org/licenses/MIT MIT License - * */ class ParserException extends \Exception { - public function __construct(TokenQueue $queue, $msg) + public function __construct(TokenQueue $queue, string $msg) { $token = $queue->peek(); $msg = sprintf("PARSER ERROR: %s. Current token is [%s, '%s'] at line %s, column %s", $msg, GenericToken::getTypeName($token->getType()), $token->getData(), $token->getLine(), $token->getRow()); // construct a lookup of the next tokens $lookup = ''; - for ($i = 1; $i <= 5; $i++) { + for ($i = 1; $i <= 5; ++$i) { if ($queue->isEof()) { break; } $token = $queue->get(); - $lookup .= $token->getData() . ' '; + $lookup .= $token->getData().' '; } $msg .= "\nBuffer lookup: \"$lookup\""; diff --git a/src/PHPCR/Util/CND/Exception/ScannerException.php b/src/PHPCR/Util/CND/Exception/ScannerException.php index cd4872e9..5b1c2449 100644 --- a/src/PHPCR/Util/CND/Exception/ScannerException.php +++ b/src/PHPCR/Util/CND/Exception/ScannerException.php @@ -1,5 +1,7 @@ */ class ScannerException extends \Exception { - public function __construct(ReaderInterface $reader, $msg) + public function __construct(ReaderInterface $reader, string $msg) { $msg = sprintf( "SCANNER ERROR: %s at line %s, column %s.\nCurrent buffer \"%s\"", diff --git a/src/PHPCR/Util/CND/Parser/AbstractParser.php b/src/PHPCR/Util/CND/Parser/AbstractParser.php index ff09486d..638013fb 100644 --- a/src/PHPCR/Util/CND/Parser/AbstractParser.php +++ b/src/PHPCR/Util/CND/Parser/AbstractParser.php @@ -1,13 +1,16 @@ */ abstract class AbstractParser { - /** - * The token queue - * - * @var TokenQueue - */ - protected $tokenQueue; + protected TokenQueue $tokenQueue; /** * Check the next token without consuming it and return true if it matches the given type and data. * If the data is not provided (equal to null) then only the token type is checked. * Return false otherwise. - * - * @param int $type The expected token type - * @param null|string $data The expected data or null - * @param bool $ignoreCase whether to do string comparisons case insensitive or sensitive - * - * @return boolean */ - protected function checkToken($type, $data = null, $ignoreCase = false) + protected function checkToken(int $type, ?string $data = null, bool $ignoreCase = false): bool { if ($this->tokenQueue->isEof()) { return false; @@ -54,7 +45,7 @@ protected function checkToken($type, $data = null, $ignoreCase = false) if ($data && $token->getData() !== $data) { if ($ignoreCase && is_string($data) && is_string($token->getData())) { - return strcasecmp($data, $token->getData()); + return 0 !== strcasecmp($data, $token->getData()); } return false; @@ -66,12 +57,9 @@ protected function checkToken($type, $data = null, $ignoreCase = false) /** * Check if the token data is one of the elements of the data array. * - * @param int $type - * @param array $data - * - * @return boolean + * @param string[] $data */ - protected function checkTokenIn($type, array $data, $ignoreCase = false) + protected function checkTokenIn(int $type, array $data, bool $ignoreCase = false): bool { foreach ($data as $d) { if ($this->checkToken($type, $d, $ignoreCase)) { @@ -87,20 +75,18 @@ protected function checkTokenIn($type, array $data, $ignoreCase = false) * otherwise throw an exception. * * @param int $type The expected token type - * @param null|string $data The expected token data or null - * - * @return Token + * @param string|null $data The expected token data or null * * @throws ParserException */ - protected function expectToken($type, $data = null) + protected function expectToken(int $type, ?string $data = null): Token { $token = $this->tokenQueue->peek(); if (!$this->checkToken($type, $data)) { throw new ParserException($this->tokenQueue, sprintf("Expected token [%s, '%s']", Token::getTypeName($type), $data)); } - + \assert($token instanceof GenericToken); $this->tokenQueue->next(); return $token; @@ -111,11 +97,9 @@ protected function expectToken($type, $data = null) * return false. * * @param int $type The expected token type - * @param null|string $data The expected token data or null - * - * @return boolean|Token + * @param string|null $data The expected token data or null */ - protected function checkAndExpectToken($type, $data = null) + protected function checkAndExpectToken(int $type, ?string $data = null): false|Token { if ($this->checkToken($type, $data)) { $token = $this->tokenQueue->peek(); diff --git a/src/PHPCR/Util/CND/Parser/CndParser.php b/src/PHPCR/Util/CND/Parser/CndParser.php index 94c5b011..33dd38d5 100644 --- a/src/PHPCR/Util/CND/Parser/CndParser.php +++ b/src/PHPCR/Util/CND/Parser/CndParser.php @@ -1,19 +1,22 @@ * @author David Buchmann */ -class CndParser extends AbstractParser +final class CndParser extends AbstractParser { // node type attributes - private $ORDERABLE = array('o', 'ord', 'orderable');//, 'variant' => true); - private $MIXIN = array('m', 'mix', 'mixin');//, 'variant' => true); - private $ABSTRACT = array('a', 'abs', 'abstract');//, 'variant' => true); - private $NOQUERY = array('noquery', 'nq');//, 'variant' => false); - private $QUERY = array('query', 'q');//, 'variant' => false); - private $PRIMARYITEM = array('primaryitem', '!');//, 'variant' => false); + private array $ORDERABLE = ['o', 'ord', 'orderable']; // , 'variant' => true); + private array $MIXIN = ['m', 'mix', 'mixin']; // , 'variant' => true); + private array $ABSTRACT = ['a', 'abs', 'abstract']; // , 'variant' => true); + private array $NOQUERY = ['noquery', 'nq']; // , 'variant' => false); + private array $QUERY = ['query', 'q']; // , 'variant' => false); + private array $PRIMARYITEM = ['primaryitem', '!']; // , 'variant' => false); // common for properties and child definitions - private $PRIMARY = array('!', 'pri', 'primary'); //, 'variant' => true), - private $AUTOCREATED = array('a', 'aut', 'autocreated'); //, 'variant' => true), - private $MANDATORY = array('m', 'man', 'mandatory'); //, 'variant' => true), - private $PROTECTED = array('p', 'pro', 'protected'); //, 'variant' => true), - private $OPV = array('COPY', 'VERSION', 'INITIALIZE', 'COMPUTE', 'IGNORE', 'ABORT'); + private array $PRIMARY = ['!', 'pri', 'primary']; // , 'variant' => true), + private array $AUTOCREATED = ['a', 'aut', 'autocreated']; // , 'variant' => true), + private array $MANDATORY = ['m', 'man', 'mandatory']; // , 'variant' => true), + private array $PROTECTED = ['p', 'pro', 'protected']; // , 'variant' => true), + private array $OPV = ['COPY', 'VERSION', 'INITIALIZE', 'COMPUTE', 'IGNORE', 'ABORT']; // property type attributes - private $MULTIPLE = array('*', 'mul', 'multiple'); //, 'variant' => true), - private $QUERYOPS = array('qop', 'queryops'); //, 'variant' => true), // Needs special handling ! - private $NOFULLTEXT = array('nof', 'nofulltext'); //, 'variant' => true), - private $NOQUERYORDER = array('nqord', 'noqueryorder'); //, 'variant' => true), + private array $MULTIPLE = ['*', 'mul', 'multiple']; // , 'variant' => true), + private array $QUERYOPS = ['qop', 'queryops']; // , 'variant' => true), // Needs special handling ! + private array $NOFULLTEXT = ['nof', 'nofulltext']; // , 'variant' => true), + private array $NOQUERYORDER = ['nqord', 'noqueryorder']; // , 'variant' => true), // child node attributes // multiple is actually a jackrabbit specific synonym for sns // http://www.mail-archive.com/users@jackrabbit.apache.org/msg19268.html - private $SNS = array('*', 'sns', 'multiple'); //, 'variant' => true), + private array $SNS = ['*', 'sns', 'multiple']; // , 'variant' => true), - /** - * @var NodeTypeManagerInterface - */ - private $ntm; + private NodeTypeManagerInterface $ntm; /** - * @var array + * @var string[] */ - protected $namespaces = array(); + private array $namespaces = []; /** - * @var array + * @var NodeTypeDefinitionInterface[] */ - protected $nodeTypes = array(); + private array $nodeTypes = []; - /** - * @param NodeTypeManagerInterface $ntm - */ public function __construct(NodeTypeManagerInterface $ntm) { $this->ntm = $ntm; @@ -89,10 +85,9 @@ public function __construct(NodeTypeManagerInterface $ntm) * * @param string $filename absolute path to the CND file to read * - * @return array with the namespaces map and the nodeTypes which is a - * hashmap of typename = > NodeTypeDefinitionInterface + * @return array{namespaces: string[], nodeTypes: array} */ - public function parseFile($filename) + public function parseFile(string $filename): array { $reader = new FileReader($filename); @@ -104,23 +99,24 @@ public function parseFile($filename) * * @param string $cnd string with CND content * - * @return array with the namespaces map and the nodeTypes which is a - * hashmap of typename = > NodeTypeDefinitionInterface + * @return array{namespaces: string[], nodeTypes: array} */ - public function parseString($cnd) + public function parseString(string $cnd): array { $reader = new BufferReader($cnd); return $this->parse($reader); } - private function parse(ReaderInterface $reader) + /** + * @return array{namespaces: string[], nodeTypes: array} + */ + private function parse(ReaderInterface $reader): array { $scanner = new GenericScanner(new DefaultScannerContextWithoutSpacesAndComments()); $this->tokenQueue = $scanner->scan($reader); while (!$this->tokenQueue->isEof()) { - while ($this->checkToken(Token::TK_SYMBOL, '<')) { $this->parseNamespaceMapping(); } @@ -128,13 +124,12 @@ private function parse(ReaderInterface $reader) if (!$this->tokenQueue->isEof()) { $this->parseNodeType(); } - } - return array( + return [ 'namespaces' => $this->namespaces, 'nodeTypes' => $this->nodeTypes, - ); + ]; } /** @@ -147,7 +142,7 @@ private function parse(ReaderInterface $reader) * Prefix ::= String * Uri ::= String */ - protected function parseNamespaceMapping() + private function parseNamespaceMapping(): void { $this->expectToken(Token::TK_SYMBOL, '<'); $prefix = $this->parseCndString(); @@ -167,7 +162,7 @@ protected function parseNamespaceMapping() * [NodeTypeAttribute {NodeTypeAttribute}] * {PropertyDef | ChildNodeDef} */ - protected function parseNodeType() + private function parseNodeType(): void { $nodeType = $this->ntm->createNodeTypeTemplate(); $this->parseNodeTypeName($nodeType); @@ -188,7 +183,7 @@ protected function parseNodeType() * * NodeTypeName ::= '[' String ']' */ - protected function parseNodeTypeName(NodeTypeTemplateInterface $nodeType) + private function parseNodeTypeName(NodeTypeTemplateInterface $nodeType): void { $this->expectToken(Token::TK_SYMBOL, '['); $name = $this->parseCndString(); @@ -205,12 +200,12 @@ protected function parseNodeTypeName(NodeTypeTemplateInterface $nodeType) * * Supertypes ::= '>' (StringList | '?') */ - protected function parseSupertypes(NodeTypeTemplateInterface $nodeType) + private function parseSupertypes(NodeTypeTemplateInterface $nodeType): void { $this->expectToken(Token::TK_SYMBOL, '>'); if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { - $nodeType->setDeclaredSuperTypeNames(array('?')); + $nodeType->setDeclaredSuperTypeNames(['?']); } else { $nodeType->setDeclaredSuperTypeNames($this->parseCndStringList()); } @@ -248,7 +243,7 @@ protected function parseSupertypes(NodeTypeTemplateInterface $nodeType) * Query ::= ('noquery' | 'nq') | ('query' | 'q' ) * PrimaryItem ::= ('primaryitem'| '!')(String | '?') */ - protected function parseNodeTypeAttributes(NodeTypeTemplateInterface $nodeType) + private function parseNodeTypeAttributes(NodeTypeTemplateInterface $nodeType): void { while (true) { if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->ORDERABLE)) { @@ -285,11 +280,11 @@ protected function parseNodeTypeAttributes(NodeTypeTemplateInterface $nodeType) } /** - * Parse both the children propery and nodes definitions + * Parse both the children propery and nodes definitions. * * {PropertyDef | ChildNodeDef} */ - protected function parseChildrenAndAttributes(NodeTypeTemplateInterface $nodeType) + private function parseChildrenAndAttributes(NodeTypeTemplateInterface $nodeType): void { while (true) { if ($this->checkToken(Token::TK_SYMBOL, '-')) { @@ -315,7 +310,7 @@ protected function parseChildrenAndAttributes(NodeTypeTemplateInterface $nodeTyp * [ValueConstraints] * PropertyName ::= '-' String */ - protected function parsePropDef(NodeTypeTemplateInterface $nodeType) + private function parsePropDef(NodeTypeTemplateInterface $nodeType): void { $this->expectToken(Token::TK_SYMBOL, '-'); @@ -353,7 +348,7 @@ protected function parsePropDef(NodeTypeTemplateInterface $nodeType) // Next token is '<' and two token later it's not '=', i.e. not 'tokenQueue->peek(); $next2 = $this->tokenQueue->peek(2); - if ($next1 && $next1->getData() === '<' && (!$next2 || $next2->getData() !== '=')) { + if ($next1 && '<' === $next1->getData() && (!$next2 || '=' !== $next2->getData())) { $this->parseValueConstraints($property); } } @@ -369,13 +364,13 @@ protected function parsePropDef(NodeTypeTemplateInterface $nodeType) * 'DECIMAL' | 'URI' | 'UNDEFINED' | '*' | * '?') ')' */ - protected function parsePropertyType(PropertyDefinitionTemplateInterface $property) + private function parsePropertyType(PropertyDefinitionTemplateInterface $property): void { - $types = array("STRING", "BINARY", "LONG", "DOUBLE", "BOOLEAN", "DATE", "NAME", "PATH", - "REFERENCE", "WEAKREFERENCE", "DECIMAL", "URI", "UNDEFINED", "*", "?"); + $types = ['STRING', 'BINARY', 'LONG', 'DOUBLE', 'BOOLEAN', 'DATE', 'NAME', 'PATH', + 'REFERENCE', 'WEAKREFERENCE', 'DECIMAL', 'URI', 'UNDEFINED', '*', '?', ]; - if (! $this->checkTokenIn(Token::TK_IDENTIFIER, $types, true)) { - throw new ParserException($this->tokenQueue, sprintf("Invalid property type: %s", $this->tokenQueue->get()->getData())); + if (!$this->checkTokenIn(Token::TK_IDENTIFIER, $types, true)) { + throw new ParserException($this->tokenQueue, sprintf('Invalid property type: %s', $this->tokenQueue->get()->getData())); } $data = $this->tokenQueue->get()->getData(); @@ -389,14 +384,14 @@ protected function parsePropertyType(PropertyDefinitionTemplateInterface $proper * The default values, if any, are listed after a '='. The attribute is a * list in order to accommodate multi-value properties. The absence of this * element indicates that there is no static default value reportable. A '?' - * indicates that this attribute is a variant + * indicates that this attribute is a variant. * * DefaultValues ::= '=' (StringList | '?') */ - protected function parseDefaultValue(PropertyDefinitionTemplateInterface $property) + private function parseDefaultValue(PropertyDefinitionTemplateInterface $property): void { if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { - $list = array('?'); + $list = ['?']; } else { $list = $this->parseCndStringList(); } @@ -407,16 +402,16 @@ protected function parseDefaultValue(PropertyDefinitionTemplateInterface $proper /** * The value constraints, if any, are listed after a '<'. The absence of * this element indicates that no value constraints reportable within the - * value constraint syntax. A '?' indicates that this attribute is a variant + * value constraint syntax. A '?' indicates that this attribute is a variant. * * ValueConstraints ::= '<' (StringList | '?') */ - protected function parseValueConstraints(PropertyDefinitionTemplateInterface $property) + private function parseValueConstraints(PropertyDefinitionTemplateInterface $property): void { $this->expectToken(Token::TK_SYMBOL, '<'); if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { - $list = array('?'); + $list = ['?']; } else { $list = $this->parseCndStringList(); } @@ -478,7 +473,7 @@ protected function parseValueConstraints(PropertyDefinitionTemplateInterface $pr * NoFullText ::= ('nofulltext' | 'nof') ['?'] * NoQueryOrder ::= ('noqueryorder' | 'nqord') ['?'] */ - protected function parsePropertyAttributes(NodeTypeTemplateInterface $parentType, PropertyDefinitionTemplateInterface $property) + private function parsePropertyAttributes(NodeTypeTemplateInterface $parentType, PropertyDefinitionTemplateInterface $property): void { $opvSeen = false; while (true) { @@ -502,7 +497,7 @@ protected function parsePropertyAttributes(NodeTypeTemplateInterface $parentType $property->setQueryOrderable(false); } elseif ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->OPV)) { if ($opvSeen) { - throw new ParserException($this->tokenQueue, 'More than one on parent version action specified on property ' . $property->getName()); + throw new ParserException($this->tokenQueue, 'More than one on parent version action specified on property '.$property->getName()); } $token = $this->tokenQueue->get(); $property->setOnParentVersion(OnParentVersionAction::valueFromName($token->getData())); @@ -532,7 +527,7 @@ protected function parsePropertyAttributes(NodeTypeTemplateInterface $parentType * RequiredTypes ::= '(' (StringList | '?') ')' * DefaultType ::= '=' (String | '?') */ - protected function parseChildNodeDef(NodeTypeTemplateInterface $nodeType) + private function parseChildNodeDef(NodeTypeTemplateInterface $nodeType): void { $this->expectToken(Token::TK_SYMBOL, '+'); $childType = $this->ntm->createNodeDefinitionTemplate(); @@ -598,10 +593,10 @@ protected function parseChildNodeDef(NodeTypeTemplateInterface $nodeType) * 'IGNORE' | 'ABORT' | ('OPV' '?') * Sns ::= ('sns' | '*') ['?'] */ - protected function parseChildNodeAttributes( + private function parseChildNodeAttributes( NodeTypeTemplateInterface $parentType, NodeDefinitionTemplateInterface $childType - ) { + ): void { while (true) { if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->PRIMARY)) { $parentType->setPrimaryItemName($childType->getName()); @@ -626,15 +621,15 @@ protected function parseChildNodeAttributes( } /** - * Parse a string list + * Parse a string list. * * StringList ::= String {',' String} * - * @return array + * @return string[] */ - protected function parseCndStringList() + private function parseCndStringList(): array { - $strings = array(); + $strings = []; $strings[] = $this->parseCndString(); while ($this->checkAndExpectToken(Token::TK_SYMBOL, ',')) { @@ -645,7 +640,7 @@ protected function parseCndStringList() } /** - * Parse a string + * Parse a string. * * String ::= QuotedString | UnquotedString * QuotedString ::= SingleQuotedString | DoubleQuotedString @@ -662,10 +657,8 @@ protected function parseCndStringList() * Char ::= "\t" | "\r" | "\n" | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] * * TODO: check \n, \r, \t are valid in CND strings! - * - * @return string */ - protected function parseCndString() + private function parseCndString(): string { $string = ''; $lastType = null; @@ -681,7 +674,7 @@ protected function parseCndString() $type = $token->getType(); $data = $token->getData(); - if ($type === Token::TK_STRING) { + if (Token::TK_STRING === $type) { $string = substr($data, 1, -1); $this->tokenQueue->next(); @@ -689,13 +682,13 @@ protected function parseCndString() } // If it's not an identifier or a symbol allowed in a string, break - if ($type !== Token::TK_IDENTIFIER && $type !== Token::TK_SYMBOL - || ($type === Token::TK_SYMBOL && $data !== '_' && $data !== ':')) { + if (Token::TK_IDENTIFIER !== $type && Token::TK_SYMBOL !== $type + || (Token::TK_SYMBOL === $type && '_' !== $data && ':' !== $data)) { break; } // Detect spaces (an identifier cannot be followed by an identifier as it would have been read as a single token) - if ($type === Token::TK_IDENTIFIER && $lastType === Token::TK_IDENTIFIER) { + if (Token::TK_IDENTIFIER === $type && Token::TK_IDENTIFIER === $lastType) { break; } @@ -705,7 +698,7 @@ protected function parseCndString() $lastType = $type; } - if ($string === '') { + if ('' === $string) { throw new ParserException($this->tokenQueue, sprintf("Expected CND string, found '%s': ", $this->tokenQueue->peek()->getData())); } @@ -721,37 +714,25 @@ protected function parseCndString() * (('''Operator {','Operator}''') | '?') * Operator ::= '=' | '<>' | '<' | '<=' | '>' | '>=' | 'LIKE' * - * @return array + * @return array */ - protected function parseQueryOpsAttribute() + private function parseQueryOpsAttribute(): array { if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { // this denotes a variant, whatever that is throw new ParserException($this->tokenQueue, 'TODO: understand what "variant" means'); } - $ops = array(); + $ops = []; do { - $op = $this->parseQueryOperator(); $ops[] = $op; - } while ($op && $this->checkAndExpectToken(Token::TK_SYMBOL, ',')); - if (empty($ops)) { - // There must be at least an operator if this attribute is not variant - throw new ParserException($this->tokenQueue, 'Operator expected'); - } - return $ops; } - /** - * Parse a query operator. - * - * @return bool|string - */ - protected function parseQueryOperator() + private function parseQueryOperator(): bool|string { $token = $this->tokenQueue->peek(); $data = $token->getData(); @@ -762,10 +743,10 @@ protected function parseQueryOperator() switch ($data) { case '<': - $op = ($nextData === '>' ? '>=' : ($nextData === '=' ? '<=' : '<')); + $op = ('>' === $nextData ? '>=' : ('=' === $nextData ? '<=' : '<')); break; case '>': - $op = ($nextData === '=' ? '>=' : '>'); + $op = ('=' === $nextData ? '>=' : '>'); break; case '=': $op = '='; @@ -776,9 +757,9 @@ protected function parseQueryOperator() } // Consume the correct number of tokens - if ($op === 'LIKE' || strlen($op) === 1) { + if ('LIKE' === $op || 1 === strlen($op)) { $this->tokenQueue->next(); - } elseif (strlen($op) === 2) { + } elseif (2 === strlen($op)) { $this->tokenQueue->next(); $this->tokenQueue->next(); } diff --git a/src/PHPCR/Util/CND/Reader/BufferReader.php b/src/PHPCR/Util/CND/Reader/BufferReader.php index a7dc98c7..1a500d05 100644 --- a/src/PHPCR/Util/CND/Reader/BufferReader.php +++ b/src/PHPCR/Util/CND/Reader/BufferReader.php @@ -1,68 +1,42 @@ * @author Nikola Petkanski */ class BufferReader implements ReaderInterface { - /** - * @var string - */ - protected $eofMarker; + private string $eofMarker; - /** - * @var string - */ - protected $buffer; + protected string $buffer; - /** - * @var int - */ - protected $startPos; + protected int $startPos; - /** - * @var int - */ - protected $forwardPos; + protected int $forwardPos; - /** - * @var int - */ - protected $curLine; + protected int $curLine; - /** - * @var int - */ - protected $curCol; + protected int $curCol; - /** - * @var int - */ - protected $nextCurLine; + protected int $nextCurLine; - /** - * @var int - */ - protected $nextCurCol; + protected int $nextCurCol; - /** - * @param string $buffer - */ - public function __construct($buffer) + public function __construct(string $buffer) { $this->eofMarker = chr(1); - $this->buffer = str_replace("\r\n", "\n", $buffer) . $this->eofMarker; + $this->buffer = str_replace("\r\n", "\n", $buffer).$this->eofMarker; $this->reset(); } - public function reset() + public function reset(): void { $this->startPos = 0; @@ -71,90 +45,77 @@ public function reset() $this->nextCurLine = $this->nextCurCol = 1; } - /** - * @return string - */ - public function getEofMarker() + public function getEofMarker(): string { return $this->eofMarker; } - /** - * @return int - */ - public function getCurrentLine() + public function getCurrentLine(): int { return $this->curLine; } - /** - * @return int - */ - public function getCurrentColumn() + public function getCurrentColumn(): int { return $this->curCol; } /** - * Return the literal delimited by start and end position - * @return string + * Return the literal delimited by start and end position. */ - public function current() + public function current(): string { return substr($this->buffer, $this->startPos, $this->forwardPos - $this->startPos); } - public function currentChar() + public function currentChar(): string { return substr($this->buffer, $this->forwardPos, 1); } - /** - * @return boolean - */ - public function isEof() + public function isEof(): bool { - return $this->currentChar() === $this->getEofMarker() - || $this->currentChar() === false + $currentChar = $this->currentChar(); + + // substr after end of string returned false in PHP 5 and returns '' since PHP 7 + return in_array($currentChar, [$this->getEofMarker(), false, ''], true) || $this->startPos > strlen($this->buffer) || $this->forwardPos > strlen($this->buffer); } /** - * Advance the forward position and return the literal delimited by start and end position - * - * @return string + * Advance the forward position and return the literal delimited by start and end position. */ - public function forward() + public function forward(): string { if ($this->forwardPos < strlen($this->buffer)) { - $this->forwardPos++; - $this->nextCurCol++; + ++$this->forwardPos; + ++$this->nextCurCol; } - if ($this->current() === "\n") { - $this->nextCurLine++; + if ("\n" === $this->current()) { + ++$this->nextCurLine; $this->nextCurCol = 1; } return $this->current(); } - public function forwardChar() + public function forwardChar(): string { $this->forward(); return $this->currentChar(); } - public function rewind() + public function rewind(): void { $this->forwardPos = $this->startPos; $this->nextCurLine = $this->curLine; $this->nextCurCol = $this->curCol; } - public function consume() + public function consume(): string { $current = $this->current(); @@ -167,5 +128,4 @@ public function consume() return $current; } - } diff --git a/src/PHPCR/Util/CND/Reader/FileReader.php b/src/PHPCR/Util/CND/Reader/FileReader.php index 8bddb0a5..08d74353 100644 --- a/src/PHPCR/Util/CND/Reader/FileReader.php +++ b/src/PHPCR/Util/CND/Reader/FileReader.php @@ -1,27 +1,23 @@ * @author Nikola Petkanski */ -class FileReader extends BufferReader +final class FileReader extends BufferReader { - /** - * @var string - */ - protected $filePath; + private string $path; /** - * @param string $path - * * @throws \InvalidArgumentException */ - public function __construct($path) + public function __construct(string $path) { if (!file_exists($path)) { throw new \InvalidArgumentException(sprintf("Invalid file '%s'", $path)); @@ -32,10 +28,7 @@ public function __construct($path) parent::__construct(file_get_contents($path)); } - /** - * @return string - */ - public function getPath() + public function getPath(): string { return $this->path; } diff --git a/src/PHPCR/Util/CND/Reader/ReaderInterface.php b/src/PHPCR/Util/CND/Reader/ReaderInterface.php index c633b6b7..307c6f8e 100644 --- a/src/PHPCR/Util/CND/Reader/ReaderInterface.php +++ b/src/PHPCR/Util/CND/Reader/ReaderInterface.php @@ -1,65 +1,49 @@ */ interface ReaderInterface { - /** - * @return string - */ - public function getEofMarker(); + public function getEofMarker(): string; - /** - * @return string with just one character - */ - public function currentChar(); + public function currentChar(): string; - /** - * @return boolean - */ - public function isEof(); + public function isEof(): bool; - /** - * @return int - */ - public function getCurrentLine(); + public function getCurrentLine(): int; + + public function getCurrentColumn(): int; /** - * @return int + * Return the literal delimited by start and end position. */ - public function getCurrentColumn(); + public function current(): string; /** - * Return the literal delimited by start and end position - * - * @return string + * Advance the forward position and return the literal delimited by start and end position. */ - public function current(); + public function forward(): string; /** - * Advance the forward position and return the literal delimited by start and end position - * @return string + * Forward one character and return the character at the new position. */ - public function forward(); - - public function forwardChar(); + public function forwardChar(): string; /** - * Rewind the forward position to the start position + * Rewind the forward position to the start position. */ - public function rewind(); + public function rewind(): void; /** * Return the literal delimited by start and end position, then set the - * start position to the end position - * - * @return string + * start position to the end position. */ - public function consume(); + public function consume(): string; } diff --git a/src/PHPCR/Util/CND/Scanner/AbstractScanner.php b/src/PHPCR/Util/CND/Scanner/AbstractScanner.php index 5e598bc8..d0081efd 100644 --- a/src/PHPCR/Util/CND/Scanner/AbstractScanner.php +++ b/src/PHPCR/Util/CND/Scanner/AbstractScanner.php @@ -1,5 +1,7 @@ */ abstract class AbstractScanner { - /** - * @var TokenQueue - */ - private $queue; + private TokenQueue $queue; - protected $context; + protected Context\ScannerContext $context; public function __construct(Context\ScannerContext $context) { @@ -25,20 +23,14 @@ public function __construct(Context\ScannerContext $context) $this->context = $context; } - public function resetQueue() + public function resetQueue(): void { $this->queue = new TokenQueue(); } - /** - * @param Token $token - * - * @return Token|void - */ - public function applyFilters(Token $token) + public function applyFilters(Token $token): ?Token { foreach ($this->context->getTokenFilters() as $filter) { - $token = $filter->filter($token); if (null === $token) { @@ -49,12 +41,12 @@ public function applyFilters(Token $token) return $token; } - protected function getQueue() + protected function getQueue(): TokenQueue { return $this->queue; } - protected function addToken(ReaderInterface $reader, Token $token) + protected function addToken(ReaderInterface $reader, Token $token): void { $token->setLine($reader->getCurrentLine()); $token->setRow($reader->getCurrentColumn()); diff --git a/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php index 1242664d..8d90fa5a 100644 --- a/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php +++ b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php @@ -1,18 +1,19 @@ */ class DefaultScannerContext extends ScannerContext { public function __construct() { - $this->addWhitespace(" "); + $this->addWhitespace(' '); $this->addWhitespace("\t"); $this->addStringDelimiter('\''); @@ -22,10 +23,10 @@ public function __construct() $this->addBlockCommentDelimiter('/*', '*/'); - $symbols = array( + $symbols = [ '<', '>', '+', '*', '%', '&', '/', '(', ')', '=', '?', '#', '|', '!', '~', '[', ']', '{', '}', '$', ',', ';', ':', '.', '-', '_', '\\', - ); + ]; foreach ($symbols as $symbol) { $this->addSymbol($symbol); } diff --git a/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php index cd0daedf..0403d8cd 100644 --- a/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php +++ b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php @@ -1,5 +1,7 @@ */ class DefaultScannerContextWithoutSpacesAndComments extends DefaultScannerContext @@ -20,5 +21,4 @@ public function __construct() $this->addTokenFilter(new TokenFilter\NoWhitespacesFilter()); $this->addTokenFilter(new TokenFilter\NoCommentsFilter()); } - } diff --git a/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php b/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php index 42be8443..92f1f5f6 100644 --- a/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php +++ b/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php @@ -1,5 +1,7 @@ */ class ScannerContext { /** - * Characters to be considered as white spaces - * @var array + * Characters to be considered as white spaces. + * + * @var string[] */ - protected $whitespaces = array(); + protected array $whitespaces = []; /** * Characters to be considered as paired string delimiters. @@ -24,129 +26,110 @@ class ScannerContext * These characters will not be used as symbols, thus if you remove any from this list, * you must add it to the $symbols array to be taken in account as a symbol. * - * @var array + * @var string[] */ - protected $stringDelimiters = array(); + protected array $stringDelimiters = []; /** - * Line comments start + * Line comments start. * - * @var array + * @var string[] */ - protected $lineCommentDelimiters = array(); + protected array $lineCommentDelimiters = []; /** - * Block comments delimiters + * Block comments delimiters. * - * @var array + * @var string[] */ - protected $blockCommentDelimiters = array(); + protected array $blockCommentDelimiters = []; /** * Characters to be considered as symbols. * * String delimiters must not appear in this array. * - * @var array + * @var string[] */ - protected $symbols = array(); + protected array $symbols = []; /** * @var TokenFilterInterface[] */ - protected $tokenFilters = array(); + protected array $tokenFilters = []; - /** - * @param string $startDelim - * @param string $endDelim - */ - public function addBlockCommentDelimiter($startDelim, $endDelim) + public function addBlockCommentDelimiter(string $startDelim, string $endDelim): void { $this->blockCommentDelimiters[$startDelim] = $endDelim; } /** - * @return array + * @return string[] */ - public function getBlockCommentDelimiters() + public function getBlockCommentDelimiters(): array { return $this->blockCommentDelimiters; } - /** - * @param string $delim - */ - public function addLineCommentDelimiter($delim) + public function addLineCommentDelimiter(string $delim): void { $this->lineCommentDelimiters[] = $delim; } /** - * @return array + * @return string[] */ - public function getLineCommentDelimiters() + public function getLineCommentDelimiters(): array { return $this->lineCommentDelimiters; } - /** - * @param string $delim - */ - public function addStringDelimiter($delim) + public function addStringDelimiter(string $delim): void { - if (!in_array($delim, $this->stringDelimiters)) { + if (!in_array($delim, $this->stringDelimiters, true)) { $this->stringDelimiters[] = $delim; } } /** - * @return array + * @return string[] */ - public function getStringDelimiters() + public function getStringDelimiters(): array { return $this->stringDelimiters; } - /** - * @param string $symbol - */ - public function addSymbol($symbol) + public function addSymbol(string $symbol): void { - if (!in_array($symbol, $this->symbols)) { + if (!in_array($symbol, $this->symbols, true)) { $this->symbols[] = $symbol; } } /** - * @return array + * @return string[] */ - public function getSymbols() + public function getSymbols(): array { return $this->symbols; } - /** - * @param array $whitespace - */ - public function addWhitespace($whitespace) + public function addWhitespace(string $whitespace): void { - if (!in_array($whitespace, $this->whitespaces)) { + if (!in_array($whitespace, $this->whitespaces, true)) { $this->whitespaces[] = $whitespace; } } /** - * @return array + * @return string[] */ - public function getWhitespaces() + public function getWhitespaces(): array { return $this->whitespaces; } - /** - * @param TokenFilterInterface $filter - */ - public function addTokenFilter(TokenFilterInterface $filter) + public function addTokenFilter(TokenFilterInterface $filter): void { $this->tokenFilters[] = $filter; } @@ -154,7 +137,7 @@ public function addTokenFilter(TokenFilterInterface $filter) /** * @return TokenFilterInterface[] */ - public function getTokenFilters() + public function getTokenFilters(): array { return $this->tokenFilters; } diff --git a/src/PHPCR/Util/CND/Scanner/GenericScanner.php b/src/PHPCR/Util/CND/Scanner/GenericScanner.php index 3dd563c3..8161eb3a 100644 --- a/src/PHPCR/Util/CND/Scanner/GenericScanner.php +++ b/src/PHPCR/Util/CND/Scanner/GenericScanner.php @@ -1,9 +1,11 @@ * @author Nikola Petkanski */ @@ -21,18 +22,13 @@ class GenericScanner extends AbstractScanner { /** * Scan the given reader and construct a TokenQueue composed of GenericToken. - * - * @param ReaderInterface $reader - * - * @return TokenQueue */ - public function scan(ReaderInterface $reader) + public function scan(ReaderInterface $reader): TokenQueue { $this->resetQueue(); while (!$reader->isEof()) { - $tokenFound = false; - $tokenFound = $tokenFound || $this->consumeComments($reader); + $tokenFound = $this->consumeComments($reader); $tokenFound = $tokenFound || $this->consumeNewLine($reader); $tokenFound = $tokenFound || $this->consumeSpaces($reader); $tokenFound = $tokenFound || $this->consumeString($reader); @@ -48,23 +44,17 @@ public function scan(ReaderInterface $reader) $this->addToken($reader, $token); } } - } return $this->getQueue(); } /** - * Detect and consume whitespaces - * - * @param ReaderInterface $reader - * - * @return boolean + * Detect and consume whitespaces. */ - protected function consumeSpaces(ReaderInterface $reader) + protected function consumeSpaces(ReaderInterface $reader): bool { if (in_array($reader->currentChar(), $this->context->getWhitespaces())) { - $char = $reader->forwardChar(); while (in_array($char, $this->context->getWhitespaces())) { $char = $reader->forwardChar(); @@ -82,20 +72,15 @@ protected function consumeSpaces(ReaderInterface $reader) } /** - * Detect and consume newlines - * - * @param ReaderInterface $reader - * - * @return boolean + * Detect and consume newlines. */ - protected function consumeNewLine(ReaderInterface $reader) + protected function consumeNewLine(ReaderInterface $reader): bool { - if ($reader->currentChar() === "\n") { - + if ("\n" === $reader->currentChar()) { $token = new GenericToken(GenericToken::TK_NEWLINE, "\n"); $this->addToken($reader, $token); - while ($reader->forward() === "\n") { + while ("\n" === $reader->forward()) { $reader->consume(); $reader->forward(); } @@ -107,25 +92,14 @@ protected function consumeNewLine(ReaderInterface $reader) return false; } - /** - * Detect and consume strings - * - * @param ReaderInterface $reader - * - * @return boolean - * - * @throws ScannerException - */ - protected function consumeString(ReaderInterface $reader) + protected function consumeString(ReaderInterface $reader): bool { $curDelimiter = $reader->currentChar(); - if (in_array($curDelimiter, $this->context->getStringDelimiters())) { - + if (in_array($curDelimiter, $this->context->getStringDelimiters(), true)) { $char = $reader->forwardChar(); while ($char !== $curDelimiter) { - - if ($char === "\n") { - throw new ScannerException($reader, "Newline detected in string"); + if ("\n" === $char) { + throw new ScannerException($reader, 'Newline detected in string'); } $char = $reader->forwardChar(); @@ -141,14 +115,7 @@ protected function consumeString(ReaderInterface $reader) return false; } - /** - * Detect and consume comments - * - * @param ReaderInterface $reader - * - * @return boolean - */ - protected function consumeComments(ReaderInterface $reader) + protected function consumeComments(ReaderInterface $reader): bool { if ($this->consumeBlockComments($reader)) { return true; @@ -157,36 +124,24 @@ protected function consumeComments(ReaderInterface $reader) return $this->consumeLineComments($reader); } - /** - * Detect and consume block comments - * - * @param ReaderInterface $reader - * - * @return boolean - * - * @throws ScannerException - */ - protected function consumeBlockComments(ReaderInterface $reader) + protected function consumeBlockComments(ReaderInterface $reader): bool { $nextChar = $reader->currentChar(); foreach ($this->context->getBlockCommentDelimiters() as $beginDelim => $endDelim) { - if ($nextChar === $beginDelim[0]) { - + $beginDelimLength = strlen($beginDelim); // Lookup the start delimiter - for ($i = 1; $i <= strlen($beginDelim); $i++) { + for ($i = 1; $i <= $beginDelimLength; ++$i) { $reader->forward(); } if ($reader->current() === $beginDelim) { - // Start delimiter found, let's try to find the end delimiter $nextChar = $reader->forwardChar(); - while (! $reader->isEof()) { - + while (!$reader->isEof()) { if ($nextChar === $endDelim[0]) { - - for ($i = 1; $i <= strlen($endDelim); $i++) { + $endDelimLength = strlen($endDelim); + for ($i = 1; $i <= $endDelimLength; ++$i) { $reader->forward(); } @@ -202,76 +157,52 @@ protected function consumeBlockComments(ReaderInterface $reader) } // End of file reached and no end delimiter found, error - throw new ScannerException($reader, "Unterminated block comment"); - - } else { - - // Start delimiter not found, rewind the looked up characters - $reader->rewind(); - - return false; + throw new ScannerException($reader, 'Unterminated block comment'); } - } + // Start delimiter not found, rewind the looked up characters + $reader->rewind(); + return false; + } } return false; - } - /** - * Detect and consume line comments - * - * @param ReaderInterface $reader - * - * @return boolean - */ - protected function consumeLineComments(ReaderInterface $reader) + protected function consumeLineComments(ReaderInterface $reader): bool { $nextChar = $reader->currentChar(); foreach ($this->context->getLineCommentDelimiters() as $delimiter) { - if ($delimiter && $nextChar === $delimiter[0]) { - - for ($i = 1; $i <= strlen($delimiter); $i++) { + $delimiterLength = strlen($delimiter); + for ($i = 1; $i <= $delimiterLength; ++$i) { $reader->forward(); } if ($reader->current() === $delimiter) { - // consume to end of line $char = $reader->currentChar(); - while (!$reader->isEof() && $char !== "\n") { + while (!$reader->isEof() && "\n" !== $char) { $char = $reader->forwardChar(); } $token = new GenericToken(GenericToken::TK_COMMENT, $reader->consume()); $this->addToken($reader, $token); return true; - - } else { - - // Rewind the looked up characters - $reader->rewind(); - - return false; } + // Rewind the looked up characters + $reader->rewind(); + + return false; } } return false; } - /** - * Detect and consume identifiers - * - * @param ReaderInterface $reader - * - * @return boolean - */ - protected function consumeIdentifiers(ReaderInterface $reader) + protected function consumeIdentifiers(ReaderInterface $reader): bool { $nextChar = $reader->currentChar(); @@ -289,18 +220,11 @@ protected function consumeIdentifiers(ReaderInterface $reader) return false; } - /** - * Detect and consume symbols - * - * @param ReaderInterface $reader - * - * @return boolean - */ - protected function consumeSymbols(ReaderInterface $reader) + protected function consumeSymbols(ReaderInterface $reader): bool { $found = false; $nextChar = $reader->currentChar(); - while (in_array($nextChar, $this->context->getSymbols())) { + while (in_array($nextChar, $this->context->getSymbols(), true)) { $found = true; $token = new GenericToken(GenericToken::TK_SYMBOL, $nextChar); $this->addToken($reader, $token); diff --git a/src/PHPCR/Util/CND/Scanner/GenericToken.php b/src/PHPCR/Util/CND/Scanner/GenericToken.php index 9bd27260..9f88af81 100644 --- a/src/PHPCR/Util/CND/Scanner/GenericToken.php +++ b/src/PHPCR/Util/CND/Scanner/GenericToken.php @@ -1,42 +1,41 @@ */ class GenericToken extends Token { - const TK_WHITESPACE = 0; - const TK_NEWLINE = 1; - const TK_STRING = 2; - const TK_COMMENT = 3; - const TK_IDENTIFIER = 4; - const TK_KEYWORD = 5; - const TK_SYMBOL = 6; - const TK_UNKNOWN = 99; + public const TK_WHITESPACE = 0; + public const TK_NEWLINE = 1; + public const TK_STRING = 2; + public const TK_COMMENT = 3; + public const TK_IDENTIFIER = 4; + public const TK_KEYWORD = 5; + public const TK_SYMBOL = 6; + public const TK_UNKNOWN = 99; - public static function getTypeName($type) + public static function getTypeName(int $type): string { - switch ($type) { - case self::TK_WHITESPACE: return 'Whitespace'; - case self::TK_NEWLINE: return 'Newline'; - case self::TK_STRING: return 'String'; - case self::TK_COMMENT: return 'Comment'; - case self::TK_IDENTIFIER: return 'Identifier'; - case self::TK_KEYWORD: return 'Keyword'; - case self::TK_SYMBOL: return 'Symbol'; - } - - return 'Unknown'; + return match ($type) { + self::TK_WHITESPACE => 'Whitespace', + self::TK_NEWLINE => 'Newline', + self::TK_STRING => 'String', + self::TK_COMMENT => 'Comment', + self::TK_IDENTIFIER => 'Identifier', + self::TK_KEYWORD => 'Keyword', + self::TK_SYMBOL => 'Symbol', + default => 'Unknown', + }; } public function __toString() { - return sprintf("TOKEN(%s, '%s', %s, %s)", self::getTypeName($this->getType()), trim($this->data), $this->line, $this->row); + return sprintf("TOKEN(%s, '%s', %s, %s)", self::getTypeName($this->getType()), trim($this->getData()), $this->getLine(), $this->getRow()); } - } diff --git a/src/PHPCR/Util/CND/Scanner/Token.php b/src/PHPCR/Util/CND/Scanner/Token.php index ab08388e..10813282 100644 --- a/src/PHPCR/Util/CND/Scanner/Token.php +++ b/src/PHPCR/Util/CND/Scanner/Token.php @@ -1,5 +1,7 @@ */ class Token { - /** - * The type of token - * - * @var int - */ - public $type; - - /** - * The token raw data - * - * @var string - */ - public $data; - - /** - * The line where the token appears - * - * @var int - */ - protected $line; - - /** - * The column where the token appears - * - * @var int - */ - protected $row; - - /** - * Constructor - * - * @param int $type - * @param string $data - * @param int $line - * @param int $row - */ - public function __construct($type = 0, $data = '', $line = 0, $row = 0) - { - $this->type = $type; - $this->data = $data; - $this->line = $line; - $this->row = $row; + public function __construct( + private int $type = 0, + /** + * The token raw data. + */ + private string $data = '', + private int $line = 0, + private int $row = 0 + ) { } - /** - * @return string - */ - public function getData() + public function getData(): string { return $this->data; } - /** - * @return int - */ - public function getType() + public function getType(): int { return $this->type; } @@ -79,36 +41,23 @@ public function __toString() return sprintf("TOKEN(%s, '%s', %s, %s)", $this->type, trim($this->data), $this->line, $this->row); } - /** - * @param int $line - */ - public function setLine($line) + public function setLine(int $line): void { $this->line = $line; } - /** - * @return int - */ - public function getLine() + public function getLine(): int { return $this->line; } - /** - * @param int $row - */ - public function setRow($row) + public function setRow(int $row): void { $this->row = $row; } - /** - * @return int - */ - public function getRow() + public function getRow(): int { return $this->row; } - } diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php index dd3684b7..08e0724c 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php @@ -1,5 +1,7 @@ */ class NoCommentsFilter extends TokenTypeFilter diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoNewlinesFilter.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoNewlinesFilter.php index f45aae1e..0736b543 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoNewlinesFilter.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoNewlinesFilter.php @@ -1,5 +1,7 @@ */ class NoNewlinesFilter extends TokenTypeFilter diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoWhitespacesFilter.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoWhitespacesFilter.php index 80fe8468..72e4c379 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoWhitespacesFilter.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoWhitespacesFilter.php @@ -1,5 +1,7 @@ */ class NoWhitespacesFilter extends TokenTypeFilter diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php index fcca3290..51306265 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php @@ -1,5 +1,7 @@ */ class TokenFilterChain implements TokenFilterInterface @@ -15,21 +16,16 @@ class TokenFilterChain implements TokenFilterInterface /** * @var TokenFilterInterface[] */ - protected $filters; + protected array $filters; - public function addFilter(TokenFilterInterface $filter) + public function addFilter(TokenFilterInterface $filter): void { $this->filters[] = $filter; } - /** - * @param Token $token - * @return Token | null - */ - public function filter(Token $token) + public function filter(Token $token): ?Token { foreach ($this->filters as $filter) { - $token = $filter->filter($token); if (!$token) { diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php index a468480c..d924f092 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php @@ -1,5 +1,7 @@ */ interface TokenFilterInterface { - /** - * @abstract - * @param Token $token - * @return Token | null - */ - public function filter(Token $token); + public function filter(Token $token): ?Token; } diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenTypeFilter.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenTypeFilter.php index 2eb5ed91..79cb8416 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenTypeFilter.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenTypeFilter.php @@ -1,5 +1,7 @@ */ class TokenTypeFilter implements TokenFilterInterface { /** - * The filtered out token type - * @var int + * The filtered out token type. */ - protected $type; + protected int $type; - public function __construct($tokenType) + public function __construct(int $tokenType) { $this->type = $tokenType; } - /** - * @param Token $token - * @return Token | null - */ - public function filter(Token $token) + public function filter(Token $token): ?Token { if ($token->getType() === $this->type) { return null; diff --git a/src/PHPCR/Util/CND/Scanner/TokenQueue.php b/src/PHPCR/Util/CND/Scanner/TokenQueue.php index eff51b47..08bf6020 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenQueue.php +++ b/src/PHPCR/Util/CND/Scanner/TokenQueue.php @@ -1,41 +1,45 @@ */ class TokenQueue implements \IteratorAggregate { /** - * @var array + * @var Token[] */ - protected $tokens; + protected mixed $tokens; - public function __construct($tokens = array()) + /** + * @param Token[] $tokens + */ + public function __construct(array $tokens = []) { $this->tokens = $tokens; } - public function add(Token $token) + public function add(Token $token): void { $this->tokens[] = $token; } - public function reset() + public function reset(): Token { return reset($this->tokens); } - public function isEof() + public function isEof(): bool { - return current($this->tokens) === false; + return false === current($this->tokens); } - public function peek($offset = 0) + public function peek($offset = 0): Token|false { if (!$offset) { return current($this->tokens); @@ -50,23 +54,23 @@ public function peek($offset = 0) return $this->tokens[key($this->tokens) + $offset]; } - public function get($count = 1) + public function get($count = 1): ?Token { $item = null; - for ($i = 1; $i <= $count; $i++) { + for ($i = 1; $i <= $count; ++$i) { $item = $this->peek(); $this->next(); } - return $item; + return $item ?: null; } - public function next() + public function next(): Token|false { return next($this->tokens); } - public function getIterator() + public function getIterator(): \ArrayIterator { return new \ArrayIterator($this->tokens); } diff --git a/src/PHPCR/Util/CND/Writer/CndWriter.php b/src/PHPCR/Util/CND/Writer/CndWriter.php index ef8ce79a..796f3997 100644 --- a/src/PHPCR/Util/CND/Writer/CndWriter.php +++ b/src/PHPCR/Util/CND/Writer/CndWriter.php @@ -1,15 +1,16 @@ */ class CndWriter { /** - * @var NamespaceRegistryInterface + * @var array hashmap of prefix => namespace uri */ - private $ns; + private array $namespaces = []; - /** @var array hashmap of prefix => namespace uri */ - private $namespaces = array(); - - /** - * @param NodeTypeManagerInterface $ntm - */ - public function __construct(NamespaceRegistryInterface $ns) - { - $this->ns = $ns; + public function __construct( + private NamespaceRegistryInterface $ns + ) { } /** @@ -49,16 +43,16 @@ public function __construct(NamespaceRegistryInterface $ns) * @param NodeTypeTemplateInterface[] $nodeTypes * * @return string with declarations for all non-system namespaces and for - * all node types in that array. + * all node types in that array */ - public function writeString(array $nodeTypes) + public function writeString(array $nodeTypes): string { $cnd = ''; foreach ($nodeTypes as $nodeType) { $cnd .= $this->writeNodeType($nodeType); } - return $this->writeNamespaces() . $cnd; + return $this->writeNamespaces().$cnd; } /** @@ -68,7 +62,7 @@ public function writeString(array $nodeTypes) * Prefix ::= String * Uri ::= String */ - protected function writeNamespaces() + protected function writeNamespaces(): string { $ns = ''; foreach ($this->namespaces as $prefix => $uri) { @@ -78,15 +72,15 @@ protected function writeNamespaces() return $ns; } - private function checkNamespace($name) + private function checkNamespace($name): void { - if (false === strpos($name, ':')) { + if (!str_contains($name, ':')) { return; } - list($prefix) = explode(':', $name); + [$prefix] = explode(':', $name); // namespace registry will throw exception if namespace prefix not found - $this->namespaces[$prefix] = "'" . $this->ns->getURI($prefix) . "'"; + $this->namespaces[$prefix] = "'".$this->ns->getURI($prefix)."'"; } /** @@ -98,15 +92,15 @@ private function checkNamespace($name) * [NodeTypeAttribute {NodeTypeAttribute}] * {PropertyDef | ChildNodeDef} */ - protected function writeNodeType(NodeTypeDefinitionInterface $nodeType) + protected function writeNodeType(NodeTypeDefinitionInterface $nodeType): string { $this->checkNamespace($nodeType->getName()); - $s = '[' . $nodeType->getName() . ']'; + $s = '['.$nodeType->getName().']'; if ($superTypes = $nodeType->getDeclaredSupertypeNames()) { foreach ($superTypes as $superType) { $this->checkNamespace($superType); } - $s .= ' > ' . implode(', ', $superTypes); + $s .= ' > '.implode(', ', $superTypes); } $s .= "\n"; @@ -123,11 +117,9 @@ protected function writeNodeType(NodeTypeDefinitionInterface $nodeType) } $attributes .= $nodeType->isQueryable() ? 'query ' : 'noquery '; if ($nodeType->getPrimaryItemName()) { - $attributes .= 'primaryitem ' . $nodeType->getPrimaryItemName() . ' '; - } - if ($attributes) { - $s .= trim($attributes) . "\n"; + $attributes .= 'primaryitem '.$nodeType->getPrimaryItemName().' '; } + $s .= trim($attributes)."\n"; $s .= $this->writeProperties($nodeType->getDeclaredPropertyDefinitions()); @@ -136,7 +128,10 @@ protected function writeNodeType(NodeTypeDefinitionInterface $nodeType) return $s; } - private function writeProperties($properties) + /** + * @param PropertyDefinitionInterface[]|null $properties + */ + private function writeProperties(?array $properties): string { if (null === $properties) { // getDeclaredPropertyDefinitions is allowed to return null on @@ -146,17 +141,16 @@ private function writeProperties($properties) $s = ''; - /** @var $property PropertyDefinitionInterface */ foreach ($properties as $property) { $this->checkNamespace($property->getName()); - $s .= '- ' . $property->getName(); + $s .= '- '.$property->getName(); if ($property->getRequiredType()) { - $s .= ' (' . PropertyType::nameFromValue($property->getRequiredType()) . ')'; + $s .= ' ('.PropertyType::nameFromValue($property->getRequiredType()).')'; } $s .= "\n"; if ($property->getDefaultValues()) { - $s .= "= '" . implode("', '", $property->getDefaultValues()) . "'\n"; + $s .= "= '".implode("', '", $property->getDefaultValues())."'\n"; } $attributes = ''; if ($property->isMandatory()) { @@ -174,30 +168,33 @@ private function writeProperties($properties) if ($property->getAvailableQueryOperators()) { $attributes .= implode("', '", $property->getAvailableQueryOperators()); } - if (! $property->isFullTextSearchable()) { + if (!$property->isFullTextSearchable()) { $attributes .= 'nofulltext '; } - if (! $property->isQueryOrderable()) { + if (!$property->isQueryOrderable()) { $attributes .= 'noqueryorder '; } if (OnParentVersionAction::COPY !== $property->getOnParentVersion()) { - $attributes .= OnParentVersionAction::nameFromValue($property->getOnParentVersion()) . ' '; + $attributes .= OnParentVersionAction::nameFromValue($property->getOnParentVersion()).' '; } if ($attributes) { - $s .= trim($attributes) . "\n"; + $s .= trim($attributes)."\n"; } if ($property->getValueConstraints()) { - $s .= "< '" . implode("', '", $property->getValueConstraints()) . "'\n"; + $s .= "< '".implode("', '", $property->getValueConstraints())."'\n"; } } return $s; } - private function writeChildren($children) + /** + * @param NodeDefinitionInterface[]|null $children + */ + private function writeChildren(?array $children): string { if (null === $children) { // getDeclaredChildNodeDefinitions is allowed to return null on @@ -207,20 +204,19 @@ private function writeChildren($children) $s = ''; - /** @var $child NodeDefinitionInterface */ foreach ($children as $child) { $this->checkNamespace($child->getName()); - $s .= '+ ' . $child->getName(); + $s .= '+ '.$child->getName(); if ($child->getRequiredPrimaryTypeNames()) { foreach ($child->getRequiredPrimaryTypeNames() as $typeName) { $this->checkNamespace($typeName); } - $s .= ' (' . implode(', ', $child->getRequiredPrimaryTypeNames()) . ')'; + $s .= ' ('.implode(', ', $child->getRequiredPrimaryTypeNames()).')'; } if ($child->getDefaultPrimaryTypeName()) { $this->checkNamespace($child->getDefaultPrimaryTypeName()); - $s .= "\n= " . $child->getDefaultPrimaryTypeName(); + $s .= "\n= ".$child->getDefaultPrimaryTypeName(); } $s .= "\n"; @@ -234,15 +230,15 @@ private function writeChildren($children) if ($child->isProtected()) { $attributes .= 'protected '; } - if (OnParentVersionAction::COPY != $child->getOnParentVersion()) { - $attributes .= OnParentVersionAction::nameFromValue($child->getOnParentVersion()) . ' '; + if (OnParentVersionAction::COPY !== $child->getOnParentVersion()) { + $attributes .= OnParentVersionAction::nameFromValue($child->getOnParentVersion()).' '; } if ($child->allowsSameNameSiblings()) { $attributes .= 'sns '; } if ($attributes) { - $s .= trim($attributes) . "\n"; + $s .= trim($attributes)."\n"; } } diff --git a/src/PHPCR/Util/Console/Command/BaseCommand.php b/src/PHPCR/Util/Console/Command/BaseCommand.php index d579c95e..756dab9a 100644 --- a/src/PHPCR/Util/Console/Command/BaseCommand.php +++ b/src/PHPCR/Util/Console/Command/BaseCommand.php @@ -1,15 +1,14 @@ getPhpcrHelper()->getSession(); } - /** - * @return PhpcrHelper - */ - protected function getPhpcrHelper() + protected function getPhpcrHelper(): PhpcrHelper { - return $this->getHelperSet()->get('phpcr'); + $helper = $this->getHelper('phpcr'); + if (!$helper instanceof PhpcrHelper) { + throw new \RuntimeException('phpcr must be the PhpcrHelper'); + } + + return $helper; + } + + protected function getPhpcrConsoleDumperHelper(): PhpcrConsoleDumperHelper + { + $helper = $this->getHelper('phpcr_console_dumper'); + if (!$helper instanceof PhpcrConsoleDumperHelper) { + throw new \RuntimeException('phpcr_console_dumper must be the PhpcrConsoleDumperHelper'); + } + + return $helper; } - /** - * @return PhpcrConsoleDumperHelper - */ - protected function getPhpcrConsoleDumperHelper() + protected function getQuestionHelper(): QuestionHelper { - return $this->getHelperSet()->get('phpcr_console_dumper'); + $helper = $this->getHelper('question'); + if (!$helper instanceof QuestionHelper) { + throw new \RuntimeException('question must be the QuestionHelper'); + } + + return $helper; } } diff --git a/src/PHPCR/Util/Console/Command/BaseNodeManipulationCommand.php b/src/PHPCR/Util/Console/Command/BaseNodeManipulationCommand.php index 6531f12f..59f04bb0 100644 --- a/src/PHPCR/Util/Console/Command/BaseNodeManipulationCommand.php +++ b/src/PHPCR/Util/Console/Command/BaseNodeManipulationCommand.php @@ -1,5 +1,7 @@ addOption('set-prop', 'p', + $this->addOption( + 'set-prop', + 'p', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Set node property on nodes use foo=bar' ); - $this->addOption('remove-prop', 'r', + + $this->addOption( + 'remove-prop', + 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Remove property from nodes' ); - $this->addOption('add-mixin', null, + + $this->addOption( + 'add-mixin', + null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add a mixin to the nodes' ); - $this->addOption('remove-mixin', null, + + $this->addOption( + 'remove-mixin', + null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Remove mixin from the nodes' ); - $this->addOption('apply-closure', null, + + $this->addOption( + 'apply-closure', + null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Apply a closure to each node, closures are passed PHPCR\Session and PHPCR\NodeInterface' ); diff --git a/src/PHPCR/Util/Console/Command/NodeDumpCommand.php b/src/PHPCR/Util/Console/Command/NodeDumpCommand.php index 94d8a400..52b9e9a4 100644 --- a/src/PHPCR/Util/Console/Command/NodeDumpCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeDumpCommand.php @@ -1,63 +1,62 @@ * @author Daniel Leech */ class NodeDumpCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { $this ->setName('phpcr:node:dump') ->addOption('sys-nodes', null, InputOption::VALUE_NONE, 'Also dump system nodes (recommended to use with a depth limit)') ->addOption('props', null, InputOption::VALUE_NONE, 'Also dump properties of the nodes') ->addOption('identifiers', null, InputOption::VALUE_NONE, 'Also output node UUID') - ->addOption('depth', null, InputOption::VALUE_OPTIONAL, 'Limit how many level of children to show', "-1") - ->addOption('max_line_length', null, InputOption::VALUE_OPTIONAL, 'Limit the maximum characters per property', "120") + ->addOption('depth', null, InputOption::VALUE_OPTIONAL, 'Limit how many level of children to show', '-1') + ->addOption('max_line_length', null, InputOption::VALUE_OPTIONAL, 'Limit the maximum characters per property', '120') ->addOption('ref-format', 'uuid', InputOption::VALUE_REQUIRED, 'Set the way references should be displayed when dumping reference properties - either "uuid" (default) or "path"') ->addArgument('identifier', InputArgument::OPTIONAL, 'Root path to dump', '/') ->setDescription('Dump subtrees of the content repository') - ->setHelp(<<dump command recursively outputs the name of the node specified -by the identifier argument and its subnodes in a yaml-like style. - -If the props option is used the nodes properties are -displayed as yaml arrays. - -By default the command filters out system nodes and properties (i.e. nodes and -properties with names starting with 'jcr:'), the --sys-nodes option -allows to turn this filter off. -HERE - ) - ; + ->setHelp( + <<<'HERE' + The dump command recursively outputs the name of the node specified + by the identifier argument and its subnodes in a yaml-like style. + + If the props option is used the nodes properties are + displayed as yaml arrays. + + By default the command filters out system nodes and properties (i.e. nodes and + properties with names starting with 'jcr:'), the --sys-nodes option + allows to turn this filter off. + HERE + ); } /** - * {@inheritDoc} + * @throws InvalidArgumentException + * @throws \Exception + * @throws RepositoryException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $session = $this->getPhpcrSession(); $dumperHelper = $this->getPhpcrConsoleDumperHelper(); @@ -66,14 +65,14 @@ protected function execute(InputInterface $input, OutputInterface $output) $identifier = $input->getArgument('identifier'); // whether to dump node uuid - $options = array(); + $options = []; $options['dump_uuids'] = $input->hasParameterOption('--identifiers'); $options['ref_format'] = $input->getOption('ref-format'); $options['show_props'] = $input->hasParameterOption('--props'); $options['show_sys_nodes'] = $input->hasParameterOption('--sys-nodes'); $options['max_line_length'] = $input->getOption('max_line_length'); - if (null !== $options['ref_format'] && !in_array($options['ref_format'], array('uuid', 'path'))) { + if (null !== $options['ref_format'] && !in_array($options['ref_format'], ['uuid', 'path'])) { throw new \Exception('The ref-format option must be set to either "path" or "uuid"'); } @@ -86,7 +85,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $node = $session->getNode($identifier); } - $walker->traverse($node, $input->getOption('depth')); + $walker->traverse($node, (int) $input->getOption('depth')); } catch (RepositoryException $e) { if ($e instanceof PathNotFoundException || $e instanceof ItemNotFoundException) { $output->writeln("Path '$identifier' does not exist"); @@ -99,5 +98,4 @@ protected function execute(InputInterface $input, OutputInterface $output) return 0; } - } diff --git a/src/PHPCR/Util/Console/Command/NodeMoveCommand.php b/src/PHPCR/Util/Console/Command/NodeMoveCommand.php index 36fbf597..e6b67303 100644 --- a/src/PHPCR/Util/Console/Command/NodeMoveCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeMoveCommand.php @@ -1,49 +1,48 @@ */ class NodeMoveCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { $this ->setName('phpcr:node:move') ->addArgument('source', InputArgument::REQUIRED, 'Path of node to move') ->addArgument('destination', InputArgument::REQUIRED, 'Destination for node') ->setDescription('Moves a node from one path to another') - ->setHelp(<<setHelp( + <<<'EOF' + This command simply moves a node from one path (the source path) + to another (the destination path), it can also be considered + as a rename command. - $ php bin/phpcr phpcr:move /foobar /barfoo + $ php bin/phpcr phpcr:move /foobar /barfoo -Note that the parent node of the destination path must already exist. -EOF - ) - ; + Note that the parent node of the destination path must already exist. + EOF + ); } /** - * {@inheritDoc} + * @throws InvalidArgumentException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $session = $this->getPhpcrSession(); @@ -52,7 +51,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln(sprintf( 'Moving %s to %s', - $sourcePath, $destPath + $sourcePath, + $destPath )); $session->move($sourcePath, $destPath); @@ -60,5 +60,4 @@ protected function execute(InputInterface $input, OutputInterface $output) return 0; } - } diff --git a/src/PHPCR/Util/Console/Command/NodeRemoveCommand.php b/src/PHPCR/Util/Console/Command/NodeRemoveCommand.php index a32b96dd..362df924 100644 --- a/src/PHPCR/Util/Console/Command/NodeRemoveCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeRemoveCommand.php @@ -1,15 +1,15 @@ * @author Daniel Leech */ class NodeRemoveCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { parent::configure(); @@ -36,26 +32,27 @@ protected function configure() ->addArgument('path', InputArgument::REQUIRED, 'Path of the node to purge') ->addOption('force', null, InputOption::VALUE_NONE, 'Use to bypass the confirmation dialog') ->addOption('only-children', null, InputOption::VALUE_NONE, 'Use to only purge children of specified path') - ->setHelp(<<phpcr:node:remove command will remove the given node or the -children of the given node according to the options given. + ->setHelp( + <<<'EOF' + The phpcr:node:remove command will remove the given node or the + children of the given node according to the options given. -Remove specified node and its children: + Remove specified node and its children: - $ php bin/phpcr phpcr:node:remove /cms/content/blog + $ php bin/phpcr phpcr:node:remove /cms/content/blog -Remove only the children of the specified node + Remove only the children of the specified node - $ php bin/phpcr phpcr:node:remove /cms/content/blog --only-children -EOF - ) - ; + $ php bin/phpcr phpcr:node:remove /cms/content/blog --only-children + EOF + ); } /** - * {@inheritDoc} + * @throws CliInvalidArgumentException + * @throws \InvalidArgumentException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $session = $this->getPhpcrSession(); @@ -73,8 +70,6 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!$force) { - /** @var $dialog DialogHelper */ - $dialog = $this->getHelperSet()->get('dialog'); $workspaceName = $session->getWorkspace()->getName(); if ($onlyChildren) { @@ -86,10 +81,8 @@ protected function execute(InputInterface $input, OutputInterface $output) 'Are you sure you want to recursively delete the path "%s" '. 'from workspace "%s"'; } - - $force = $dialog->askConfirmation($output, sprintf( - ''.$question.' Y/N ?', $path, $workspaceName, false - )); + $confirmationQuestion = new ConfirmationQuestion(sprintf(''.$question.' Y/N ?', $path, $workspaceName), false); + $force = $this->getQuestionHelper()->ask($input, $output, $confirmationQuestion); } if (!$force) { @@ -103,7 +96,6 @@ protected function execute(InputInterface $input, OutputInterface $output) if ($onlyChildren) { $baseNode = $session->getNode($path, 0); - /** @var $childNode NodeInterface */ foreach ($baseNode->getNodes() as $childNode) { $childNodePath = $childNode->getPath(); $childNode->remove(); diff --git a/src/PHPCR/Util/Console/Command/NodeTouchCommand.php b/src/PHPCR/Util/Console/Command/NodeTouchCommand.php index 6901b7a4..7d554afd 100644 --- a/src/PHPCR/Util/Console/Command/NodeTouchCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeTouchCommand.php @@ -1,30 +1,29 @@ */ class NodeTouchCommand extends BaseNodeManipulationCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { parent::configure(); @@ -37,39 +36,43 @@ protected function configure() 'Path at which to create the new node' ) ->addOption( - 'type', 't', + 'type', + 't', InputOption::VALUE_OPTIONAL, 'Node type, default nt:unstructured', 'nt:unstructured' ) - ->addOption('dump', 'd', + ->addOption( + 'dump', + 'd', InputOption::VALUE_NONE, 'Dump a string reperesentation of the created / modified node.' ) ->setDescription('Create or modify a node') - ->setHelp(<<setHelp( + <<<'HERE' + This command allows you to create or modify a node at the specified path. -For example:: + For example:: - $ ./bin/phpcr phpcr:touch /foobar --type=my:nodetype --set-prop=foo=bar + $ ./bin/phpcr phpcr:touch /foobar --type=my:nodetype --set-prop=foo=bar -Will create the node "/foobar" and set (or create) the "foo" property -with a value of "bar". + Will create the node "/foobar" and set (or create) the "foo" property + with a value of "bar". -You can execute the command again to further modify the node. Here we add -the property "bar" and remove the property "foo". We also add the dump option -to output a string reperesentation of the node. + You can execute the command again to further modify the node. Here we add + the property "bar" and remove the property "foo". We also add the dump option + to output a string reperesentation of the node. - $ ./bin/phpcr phpcr:touch /foobar --type=my:nodetype --set-prop=bar=myvalue --remove-prop=foo --dump -HERE -); + $ ./bin/phpcr phpcr:touch /foobar --type=my:nodetype --set-prop=bar=myvalue --remove-prop=foo --dump + HERE + ); } /** - * {@inheritDoc} + * @throws InvalidArgumentException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $helper = $this->getPhpcrHelper(); $session = $this->getPhpcrSession(); @@ -97,10 +100,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $nodeType )); - if ($nodeType != $type) { + if ($nodeType !== $type) { $output->writeln(sprintf( 'You have specified node type "%s" but the existing node is of type "%s"', - $type, $nodeType + $type, + $nodeType )); return 1; @@ -121,19 +125,21 @@ protected function execute(InputInterface $input, OutputInterface $output) } $output->writeln(sprintf( - 'Creating node: %s [%s]', $path, $type + 'Creating node: %s [%s]', + $path, + $type )); $node = $parentNode->addNode($nodeName, $type); } - $helper->processNode($output, $node, array( + $helper->processNode($output, $node, [ 'setProp' => $setProp, 'removeProp' => $removeProp, 'addMixins' => $addMixins, 'removeMixins' => $removeMixins, 'dump' => $dump, - )); + ]); $session->save(); diff --git a/src/PHPCR/Util/Console/Command/NodeTypeListCommand.php b/src/PHPCR/Util/Console/Command/NodeTypeListCommand.php index 2c9f80c7..88c6dbba 100644 --- a/src/PHPCR/Util/Console/Command/NodeTypeListCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeTypeListCommand.php @@ -1,5 +1,7 @@ */ class NodeTypeListCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { $this ->setName('phpcr:node-type:list') ->setDescription('List all available node types in the repository') - ->setHelp(<<setHelp( + <<<'EOT' + This command lists all of the available node types and their subtypes + in the PHPCR repository. + EOT + ); } - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $session = $this->getPhpcrSession(); $ntm = $session->getWorkspace()->getNodeTypeManager(); diff --git a/src/PHPCR/Util/Console/Command/NodeTypeRegisterCommand.php b/src/PHPCR/Util/Console/Command/NodeTypeRegisterCommand.php index d7ed4256..7950ee4b 100644 --- a/src/PHPCR/Util/Console/Command/NodeTypeRegisterCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeTypeRegisterCommand.php @@ -1,77 +1,73 @@ * @author Daniel Leech */ class NodeTypeRegisterCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { $this ->setName('phpcr:node-type:register') ->setDescription('Register node types in the PHPCR repository') ->addArgument('cnd-file', InputArgument::IS_ARRAY, 'Register namespaces and node types from a "Compact Node Type Definition" .cnd file(s)') ->addOption('allow-update', null, InputOption::VALUE_NONE, 'Overwrite existig node type') - ->setHelp(<<setHelp( + <<<'EOT' + Register node types in the PHPCR repository. -This command allows to register node types in the repository that are defined -in a CND (Compact Namespace and Node Type Definition) file as defined in the JCR-283 -specification. + This command allows to register node types in the repository that are defined + in a CND (Compact Namespace and Node Type Definition) file as defined in the JCR-283 + specification. -Custom node types can be used to define the structure of content repository -nodes, like allowed properties and child nodes together with the namespaces -and their prefix used for the names of node types and properties. + Custom node types can be used to define the structure of content repository + nodes, like allowed properties and child nodes together with the namespaces + and their prefix used for the names of node types and properties. -This command allows you to specify multiple files and/or folders: + This command allows you to specify multiple files and/or folders: - $ php app/console phpcr:node-type:register /path/to/nodetype1.cnd /path/to/a/folder + $ php app/console phpcr:node-type:register /path/to/nodetype1.cnd /path/to/a/folder -When a folder is specified all files within the folder that have the .cnd -extension will be treated as node definition files. + When a folder is specified all files within the folder that have the .cnd + extension will be treated as node definition files. -If you use --allow-update existing node type definitions will be overwritten -in the repository. -EOT - ) - ; + If you use --allow-update existing node type definitions will be overwritten + in the repository. + EOT + ); } /** - * {@inheritDoc} + * @throws \InvalidArgumentException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $definitions = $input->getArgument('cnd-file'); - if (count($definitions) == 0) { - throw new \InvalidArgumentException( - 'At least one definition (i.e. file or folder) must be specified' - ); + if (0 === count($definitions)) { + throw new \InvalidArgumentException('At least one definition (i.e. file or folder) must be specified'); } $allowUpdate = $input->getOption('allow-update'); @@ -84,7 +80,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $cnd = file_get_contents($filePath); $this->updateFromCnd($output, $session, $cnd, $allowUpdate); $output->writeln(sprintf('Node type definition: %s', $filePath)); - $count++; + ++$count; } $output->writeln(sprintf('%d node definition(s) registered', $count)); @@ -98,11 +94,11 @@ protected function execute(InputInterface $input, OutputInterface $output) * @param OutputInterface $output the console output stream * @param SessionInterface $session the PHPCR session to talk to * @param string $cnd the compact namespace and node type definition in string form - * @param bool $allowUpdate whether to allow updating existing node types. + * @param bool $allowUpdate whether to allow updating existing node types * - * @throws \PHPCR\RepositoryException on other errors + * @throws RepositoryException on other errors */ - protected function updateFromCnd(OutputInterface $output, SessionInterface $session, $cnd, $allowUpdate) + protected function updateFromCnd(OutputInterface $output, SessionInterface $session, string $cnd, bool $allowUpdate): void { $ntm = $session->getWorkspace()->getNodeTypeManager(); @@ -114,6 +110,7 @@ protected function updateFromCnd(OutputInterface $output, SessionInterface $sess $output->write(PHP_EOL.'If you want to override the existing definition call this command with the '); $output->write('--allow-update option.'.PHP_EOL); } + throw $e; } } @@ -122,16 +119,17 @@ protected function updateFromCnd(OutputInterface $output, SessionInterface $sess * Return a list of node type definition file paths from * the given definition files or folders. * - * @param array $definitions List of files of folders + * @param string[] $definitions List of files or folders * - * @return array Array of full paths to all the type node definition files. + * @return string[] list of available node type definition files + * + * @throws \InvalidArgumentException */ - protected function getFilePaths($definitions) + protected function getFilePaths(array $definitions): array { - $filePaths = array(); + $filePaths = []; foreach ($definitions as $definition) { - if (is_dir($definition)) { $dirHandle = opendir($definition); @@ -164,12 +162,8 @@ protected function getFilePaths($definitions) return $filePaths; } - protected function fileIsNodeType($filename) + protected function fileIsNodeType(string $filename): bool { - if (substr($filename, -4) == '.cnd') { - return true; - } - - return false; + return str_ends_with($filename, '.cnd'); } } diff --git a/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php index 26d50768..eeb4d94f 100644 --- a/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php +++ b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php @@ -1,11 +1,16 @@ */ class NodesUpdateCommand extends BaseNodeManipulationCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { parent::configure(); @@ -29,61 +30,66 @@ protected function configure() $this->setName('phpcr:nodes:update') ->addOption( - 'query', null, + 'query', + null, InputOption::VALUE_REQUIRED, 'Query used to select the nodes' ) ->addOption( - 'query-language', 'l', + 'query-language', + 'l', InputOption::VALUE_OPTIONAL, 'The query language (e.g. sql, jcr_sql2)', 'jcr-sql2' ) ->addOption( - 'persist-counter', 'c', + 'persist-counter', + 'c', InputOption::VALUE_OPTIONAL, 'Save the session every x requests', '100' ) ->setDescription('Command to manipulate the nodes in the workspace.') - ->setHelp(<<phpcr:nodes:update can manipulate the properties of nodes -found using the given query. + ->setHelp( + <<phpcr:nodes:update can manipulate the properties of nodes + found using the given query. -For example, to set the property foo to bar on all unstructured nodes: + For example, to set the property foo to bar on all unstructured nodes: - php bin/phpcr phpcr:nodes:update --query="SELECT * FROM [nt:unstructured]" --set-prop=foo=bar + php bin/phpcr phpcr:nodes:update --query="SELECT * FROM [nt:unstructured]" --set-prop=foo=bar -Or to update only nodes matching a certain criteria: + Or to update only nodes matching a certain criteria: - php bin/phpcr phpcr:nodes:update \ - --query="SELECT * FROM [nt:unstructured] WHERE [phpcr:class]=\"Some\\Class\\Here\"" \ - --add-mixin=mix:mimetype + php bin/phpcr phpcr:nodes:update \ + --query="SELECT * FROM [nt:unstructured] WHERE [phpcr:class]=\"Some\\Class\\Here\"" \ + --add-mixin=mix:mimetype -The options for manipulating nodes are the same as with the -node:touch command and -can be repeated to update multiple properties. + The options for manipulating nodes are the same as with the + node:touch command and + can be repeated to update multiple properties. -If you have an advanced use case you can use the --apply-closure option: + If you have an advanced use case you can use the --apply-closure option: - php bin/phpcr phpcr:nodes:update \ - --query="SELECT * FROM [nt:unstructured] WHERE [phpcr:class]=\"Some\\Class\\Here\"" \ - --apply-closure="\\\$session->doSomething(); \\\$node->setProperty('foo', 'bar');" + php bin/phpcr phpcr:nodes:update \ + --query="SELECT * FROM [nt:unstructured] WHERE [phpcr:class]=\"Some\\Class\\Here\"" \ + --apply-closure="\\\$session->doSomething(); \\\$node->setProperty('foo', 'bar');" -For each node in the result set, the closure will be passed the current -PHPCR\SessionInterface implementation and the node (PHPCR\NodeInterface) as \$session and \$node. -HERE -); + For each node in the result set, the closure will be passed the current + PHPCR\SessionInterface implementation and the node (PHPCR\NodeInterface) as \$session and \$node. + HERE + ); } /** - * {@inheritDoc} + * @throws CliInvalidArgumentException + * @throws \InvalidArgumentException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $query = $input->getOption('query'); $queryLanguage = strtoupper($input->getOption('query-language')); - $persistCounter = intval($input->getOption('persist-counter')); + $persistCounter = (int) $input->getOption('persist-counter'); $setProp = $input->getOption('set-prop'); $removeProp = $input->getOption('remove-prop'); $addMixins = $input->getOption('add-mixin'); @@ -99,42 +105,40 @@ protected function execute(InputInterface $input, OutputInterface $output) ); } - if (strtoupper(substr($query, 0, 6) != 'SELECT')) { - throw new \InvalidArgumentException(sprintf( - 'Query doesn\'t look like a SELECT query: "%s"', - $query - )); + if (0 !== stripos($query, 'SELECT')) { + throw new \InvalidArgumentException("Query doesn't look like a SELECT query: '$query'"); } $query = $helper->createQuery($queryLanguage, $query); $result = $query->execute(); if (!$noInteraction) { - if (false === $this->getAction($output, $result)) { + if (false === $this->shouldExecute($input, $output, $result)) { return 0; } } $persistIn = $persistCounter; + /** @var RowInterface $row */ foreach ($result as $i => $row) { $output->writeln(sprintf( - "Updating node: [%d] %s.", + 'Updating node: [%d] %s.', $i, $row->getPath() )); $node = $row->getNode(); - $helper->processNode($output, $node, array( + $helper->processNode($output, $node, [ 'setProp' => $setProp, 'removeProp' => $removeProp, 'addMixins' => $addMixins, 'removeMixins' => $removeMixins, 'applyClosures' => $applyClosures, - )); + ]); - $persistIn--; + --$persistIn; if (0 === $persistIn) { $output->writeln('Saving nodes processed so far...'); $session->save(); @@ -149,31 +153,31 @@ protected function execute(InputInterface $input, OutputInterface $output) return 0; } - protected function getAction($output, $result) + private function shouldExecute(InputInterface $input, OutputInterface $output, QueryResultInterface $result): bool { - /** @var $dialog DialogHelper */ - $dialog = $this->getHelperSet()->get('dialog'); - $response = strtoupper($dialog->ask($output, sprintf( + $question = new ConfirmationQuestion(sprintf( 'About to update %d nodes. Enter "Y" to continue, "N" to cancel or "L" to list.', count($result->getRows()) - ), false)); + )); - if ($response == 'L') { + $response = $this->getQuestionHelper()->ask($input, $output, $question); + if ('L' === $response) { + /** @var RowInterface $row */ foreach ($result as $i => $row) { $output->writeln(sprintf(' - [%d] %s', $i, $row->getPath())); } - return $this->getAction($output, $result); + return $this->shouldExecute($input, $output, $result); } - if ($response == 'N') { + if ('N' === $response) { return false; } - if ($response == 'Y') { + if ('Y' === $response) { return true; } - return $this->getAction($output, $result); + return $this->shouldExecute($input, $output, $result); } } diff --git a/src/PHPCR/Util/Console/Command/WorkspaceCreateCommand.php b/src/PHPCR/Util/Console/Command/WorkspaceCreateCommand.php index 465ef9af..07f6c554 100644 --- a/src/PHPCR/Util/Console/Command/WorkspaceCreateCommand.php +++ b/src/PHPCR/Util/Console/Command/WorkspaceCreateCommand.php @@ -1,47 +1,47 @@ * @author David Buchmann */ class WorkspaceCreateCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { $this ->setName('phpcr:workspace:create') ->addArgument('name', InputArgument::REQUIRED, 'Name of the workspace to create') - ->setDescription('Create a workspace in the configured repository') - ->setHelp(<<workspace:create command creates a workspace with the specified name. -It will fail if a workspace with that name already exists or if the repository implementation -does not support the workspace creation operation. -EOT + ->addOption( + 'ignore-existing', + null, + InputOption::VALUE_NONE, + 'If set, an existing workspace will return a success code' ) - ; + ->setDescription('Create a workspace in the configured repository') + ->setHelp( + <<<'EOT' + The workspace:create command creates a workspace with the specified name. + It will fail if a workspace with that name already exists or if the repository implementation + does not support the workspace creation operation. + EOT + ); } - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $session = $this->getPhpcrSession(); @@ -60,12 +60,12 @@ protected function execute(InputInterface $input, OutputInterface $output) return 1; } - if (array_search($workspaceName, $workspace->getAccessibleWorkspaceNames())) { + if (in_array($workspaceName, $workspace->getAccessibleWorkspaceNames(), true)) { $output->writeln( sprintf('This repository already has a workspace called "%s"', $workspaceName) ); - return 2; + return $input->getOption('ignore-existing') ? 0 : 2; } $workspace->createWorkspace($workspaceName); diff --git a/src/PHPCR/Util/Console/Command/WorkspaceDeleteCommand.php b/src/PHPCR/Util/Console/Command/WorkspaceDeleteCommand.php index 42ebc01f..23662768 100644 --- a/src/PHPCR/Util/Console/Command/WorkspaceDeleteCommand.php +++ b/src/PHPCR/Util/Console/Command/WorkspaceDeleteCommand.php @@ -1,50 +1,43 @@ */ class WorkspaceDeleteCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { $this ->setName('phpcr:workspace:delete') ->addArgument('name', InputArgument::REQUIRED, 'Name of the workspace to delete') ->addOption('force', null, InputOption::VALUE_NONE, 'Use to bypass the confirmation dialog') ->setDescription('Delete a workspace from the configured repository') - ->setHelp(<<workspace:delete command deletes the workspace with the specified name if it -exists. If the workspace with that name does not yet exist, the command will not fail. -However, if the workspace does exist but the repository implementation does not support -the delete operation, the command will fail. -EOT - ) - ; + ->setHelp( + <<<'EOT' + The workspace:delete command deletes the workspace with the specified name if it + exists. If the workspace with that name does not yet exist, the command will not fail. + However, if the workspace does exist but the repository implementation does not support + the delete operation, the command will fail. + EOT + ); } - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $session = $this->getPhpcrSession(); @@ -53,7 +46,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $workspace = $session->getWorkspace(); $repo = $session->getRepository(); - if (! in_array($workspaceName, $workspace->getAccessibleWorkspaceNames())) { + if (!in_array($workspaceName, $workspace->getAccessibleWorkspaceNames())) { $output->writeln("Workspace '$workspaceName' does not exist."); return 0; @@ -71,12 +64,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $force = $input->getOption('force'); if (!$force) { - /** @var $dialog DialogHelper */ - $dialog = $this->getHelperSet()->get('dialog'); - $force = $dialog->askConfirmation($output, sprintf( + $confirmationQuestion = new ConfirmationQuestion(sprintf( 'Are you sure you want to delete workspace "%s" Y/N ?', $workspaceName ), false); + $force = $this->getQuestionHelper()->ask($input, $output, $confirmationQuestion); } if (!$force) { $output->writeln('Aborted'); diff --git a/src/PHPCR/Util/Console/Command/WorkspaceExportCommand.php b/src/PHPCR/Util/Console/Command/WorkspaceExportCommand.php index 654126a8..25009189 100644 --- a/src/PHPCR/Util/Console/Command/WorkspaceExportCommand.php +++ b/src/PHPCR/Util/Console/Command/WorkspaceExportCommand.php @@ -1,29 +1,25 @@ */ class WorkspaceExportCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { parent::configure(); @@ -31,24 +27,21 @@ protected function configure() ->setName('phpcr:workspace:export') ->addArgument('filename', InputArgument::REQUIRED, 'The xml file to export to') ->addOption('path', 'p', InputOption::VALUE_OPTIONAL, 'Path of the node to export', '/') - ->addOption('skip_binary', null, InputOption::VALUE_OPTIONAL, 'Set to "yes" to skip binaries', "no") - ->addOption('recurse', null, InputOption::VALUE_OPTIONAL, 'Set to "no" to prevent recursion', "yes") + ->addOption('skip_binary', null, InputOption::VALUE_OPTIONAL, 'Set to "yes" to skip binaries', 'no') + ->addOption('recurse', null, InputOption::VALUE_OPTIONAL, 'Set to "no" to prevent recursion', 'yes') ->setDescription('Export nodes from the repository, either to the JCR system view format or the document view format') - ->setHelp(<<export command uses the PHPCR SessionInterface::exportSystemView -method to export parts of the repository into an XML document. + ->setHelp( + <<<'EOF' + The export command uses the PHPCR SessionInterface::exportSystemView + method to export parts of the repository into an XML document. -If the path option is set, given path is exported. -Otherwise the entire repository is exported. -EOF - ) - ; + If the path option is set, given path is exported. + Otherwise the entire repository is exported. + EOF + ); } - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $session = $this->getPhpcrSession(); $repo = $session->getRepository(); @@ -67,7 +60,7 @@ protected function execute(InputInterface $input, OutputInterface $output) return 2; } - $session->exportSystemView($path, $stream, $input->getOption('skip_binary') === 'yes', $input->getOption('recurse') === 'no'); + $session->exportSystemView($path, $stream, 'yes' === $input->getOption('skip_binary'), 'no' === $input->getOption('recurse')); $output->writeln(sprintf( 'Successfully exported workspace "%s", path "%s" to file "%s".', diff --git a/src/PHPCR/Util/Console/Command/WorkspaceImportCommand.php b/src/PHPCR/Util/Console/Command/WorkspaceImportCommand.php index ea537dc8..b9b0047c 100644 --- a/src/PHPCR/Util/Console/Command/WorkspaceImportCommand.php +++ b/src/PHPCR/Util/Console/Command/WorkspaceImportCommand.php @@ -1,29 +1,32 @@ */ class WorkspaceImportCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + public const UUID_BEHAVIOR = [ + 'new' => ImportUUIDBehaviorInterface::IMPORT_UUID_CREATE_NEW, + 'remove' => ImportUUIDBehaviorInterface::IMPORT_UUID_COLLISION_REMOVE_EXISTING, + 'replace' => ImportUUIDBehaviorInterface::IMPORT_UUID_COLLISION_REPLACE_EXISTING, + 'throw' => ImportUUIDBehaviorInterface::IMPORT_UUID_COLLISION_THROW, + ]; + + protected function configure(): void { parent::configure(); @@ -31,25 +34,34 @@ protected function configure() ->setName('phpcr:workspace:import') ->addArgument('filename', null, 'The xml file to import') ->addOption('parentpath', 'p', InputOption::VALUE_OPTIONAL, 'Repository path to the parent where to import the file contents', '/') + ->addOption('uuid-behavior', null, InputOption::VALUE_REQUIRED, 'How to handle UUID collisions during the import', 'new') ->setDescription('Import xml data into the repository, either in JCR system view format or arbitrary xml') - ->setHelp(<<import command uses the PHPCR SessionInterface::importXml method -to import an XML document into the repository. If the document is in the JCR -system view format, it is interpreted according to the spec, otherwise it is -treated as document view format, meaning XML elements are translated to nodes -and XML attributes into properties. - -If the parentpath option is set, the document is imported to that -path. Otherwise the document is imported at the repository root. -EOF - ) - ; + ->setHelp( + <<<'EOF' + The import command uses the PHPCR SessionInterface::importXml method + to import an XML document into the repository. If the document is in the JCR + system view format, it is interpreted according to the spec, otherwise it is + treated as document view format, meaning XML elements are translated to nodes + and XML attributes into properties. + + If the parentpath option is set, the document is imported to that + path. Otherwise the document is imported at the repository root. + + The optional uuid-behavior option describes how UUIDs should be + handled. The following options are available: + + * new recreate a new uuid for each imported node; + * remove on collision, remove the old node from the repository and + put the imported data in the tree; + * replace on collision, replace the existing node with the one being + imported. All children of the imported node also go to the new path; + * throw throw an exception on uuid collision. + + EOF + ); } - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $filename = $input->getArgument('filename'); $parentPath = $input->getOption('parentpath'); @@ -62,11 +74,15 @@ protected function execute(InputInterface $input, OutputInterface $output) return 1; } - $session->importXml( - $parentPath, - $filename, - ImportUUIDBehaviorInterface::IMPORT_UUID_CREATE_NEW - ); + $uuidBehavior = $input->getOption('uuid-behavior'); + if (!array_key_exists($uuidBehavior, self::UUID_BEHAVIOR)) { + $output->writeln(sprintf('UUID-Behavior "%s" is not supported', $uuidBehavior)); + $output->writeln(sprintf('Supported behaviors are %s', implode(', ', array_keys(self::UUID_BEHAVIOR)))); + + return 1; + } + + $session->importXML($parentPath, $filename, self::UUID_BEHAVIOR[$uuidBehavior]); $session->save(); $output->writeln(sprintf( diff --git a/src/PHPCR/Util/Console/Command/WorkspaceListCommand.php b/src/PHPCR/Util/Console/Command/WorkspaceListCommand.php index 2b4a996a..3a5d6317 100644 --- a/src/PHPCR/Util/Console/Command/WorkspaceListCommand.php +++ b/src/PHPCR/Util/Console/Command/WorkspaceListCommand.php @@ -1,46 +1,40 @@ */ class WorkspaceListCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { $this ->setName('phpcr:workspace:list') ->setDescription('List all available workspaces in the configured repository') - ->setHelp(<<workspace:list command lists all avaialable workspaces. -EOT - ) - ; + ->setHelp( + <<<'EOT' + The workspace:list command lists all avaialable workspaces. + EOT + ); } - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $session = $this->getPhpcrSession(); $workspaces = $session->getWorkspace()->getAccessibleWorkspaceNames(); - $output->writeln("The following ".count($workspaces)." workspaces are available:"); + $output->writeln('The following '.count($workspaces).' workspaces are available:'); foreach ($workspaces as $workspace) { $output->writeln($workspace); } diff --git a/src/PHPCR/Util/Console/Command/WorkspacePurgeCommand.php b/src/PHPCR/Util/Console/Command/WorkspacePurgeCommand.php index 43c627bf..ac2dc07d 100644 --- a/src/PHPCR/Util/Console/Command/WorkspacePurgeCommand.php +++ b/src/PHPCR/Util/Console/Command/WorkspacePurgeCommand.php @@ -1,15 +1,14 @@ */ class WorkspacePurgeCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { parent::configure(); @@ -33,30 +28,26 @@ protected function configure() ->setName('phpcr:workspace:purge') ->setDescription('Remove all nodes from a workspace') ->addOption('force', null, InputOption::VALUE_NONE, 'Use to bypass the confirmation dialog') - ->setHelp(<<phpcr:workspace:purge command removes all nodes except the -system nodes and all non-system properties of the root node from the workspace. -EOF - ) - ; + ->setHelp( + <<<'EOF' + The phpcr:workspace:purge command removes all nodes except the + system nodes and all non-system properties of the root node from the workspace. + EOF + ); } - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $session = $this->getPhpcrSession(); $force = $input->getOption('force'); $workspaceName = $session->getWorkspace()->getName(); if (!$force) { - /** @var $dialog DialogHelper */ - $dialog = $this->getHelperSet()->get('dialog'); - $force = $dialog->askConfirmation($output, sprintf( + $confirmationQuestion = new ConfirmationQuestion(sprintf( 'Are you sure you want to purge workspace "%s" Y/N ?', $workspaceName ), false); + $force = $this->getQuestionHelper()->ask($input, $output, $confirmationQuestion); } if (!$force) { diff --git a/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php b/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php index c1ee06f2..2b9944e8 100644 --- a/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php +++ b/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php @@ -1,11 +1,12 @@ * @author Daniel Leech */ class WorkspaceQueryCommand extends BaseCommand { - /** - * {@inheritDoc} - */ - protected function configure() + protected function configure(): void { parent::configure(); @@ -33,16 +30,13 @@ protected function configure() ->addOption('limit', null, InputOption::VALUE_OPTIONAL, 'The query limit', 0) ->addOption('offset', null, InputOption::VALUE_OPTIONAL, 'The query offset', 0) ->setDescription('Execute a JCR SQL2 statement') - ->setHelp("The query command executes a JCR query statement on the content repository"); + ->setHelp('The query command executes a JCR query statement on the content repository'); } - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $sql = $input->getArgument('query'); - $language = strtoupper($input->getOption('language')); + $language = $input->getOption('language'); $limit = $input->getOption('limit'); $offset = $input->getOption('offset'); @@ -65,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln("Results:\n"); foreach ($result as $i => $row) { - $output->writeln("\n".($i+1).'. Row (Path: '. $row->getPath() .', Score: '. $row->getScore() .'):'); + $output->writeln("\n".($i + 1).'. Row (Path: '.$row->getPath().', Score: '.$row->getScore().'):'); foreach ($row as $column => $value) { $output->writeln("$column: ".var_export($value, true)); } diff --git a/src/PHPCR/Util/Console/Helper/PhpcrConsoleDumperHelper.php b/src/PHPCR/Util/Console/Helper/PhpcrConsoleDumperHelper.php index 35b96ce0..e09443ed 100644 --- a/src/PHPCR/Util/Console/Helper/PhpcrConsoleDumperHelper.php +++ b/src/PHPCR/Util/Console/Helper/PhpcrConsoleDumperHelper.php @@ -1,31 +1,32 @@ false, 'ref_format' => 'uuid', 'show_props' => false, 'show_sys_nodes' => false, - ), $options); + ], $options); $propVisitor = null; $nodeVisitor = new ConsoleDumperNodeVisitor($output, $options['dump_uuids']); @@ -44,10 +45,7 @@ public function getTreeWalker(OutputInterface $output, $options) return $treeWalker; } - /** - * {@inheritDoc} - */ - public function getName() + public function getName(): string { return 'phpcr_console_dumper'; } diff --git a/src/PHPCR/Util/Console/Helper/PhpcrHelper.php b/src/PHPCR/Util/Console/Helper/PhpcrHelper.php index 67ae4615..f447df5b 100644 --- a/src/PHPCR/Util/Console/Helper/PhpcrHelper.php +++ b/src/PHPCR/Util/Console/Helper/PhpcrHelper.php @@ -1,54 +1,36 @@ session = $session; } - /** - * Get the session - * - * @return SessionInterface - */ - public function getSession() + public function getSession(): SessionInterface { return $this->session; } - /** - * {@inheritDoc} - */ - public function getName() + public function getName(): string { return 'phpcr'; } @@ -58,26 +40,27 @@ public function getName() * * Provides common processing for both touch and update commands. * - * @param OutputInterface $output used for status updates. - * @param NodeInterface $node the node to manipulate. - * @param array $operations to execute on that node. + * @param OutputInterface $output used for status updates + * @param NodeInterface $node the node to manipulate + * @param array $operations to execute on that node */ - public function processNode(OutputInterface $output, NodeInterface $node, array $operations) + public function processNode(OutputInterface $output, NodeInterface $node, array $operations): void { - $operations = array_merge(array( - 'setProp' => array(), - 'removeProp' => array(), - 'addMixins' => array(), - 'removeMixins' => array(), - 'applyClosures' => array(), + $operations = array_merge([ + 'setProp' => [], + 'removeProp' => [], + 'addMixins' => [], + 'removeMixins' => [], + 'applyClosures' => [], 'dump' => false, - ), $operations); + ], $operations); foreach ($operations['setProp'] as $set) { $parts = explode('=', $set); $output->writeln(sprintf( ' > Setting property %s to %s', - $parts[0], $parts[1] + $parts[0], + $parts[1] )); $node->setProperty($parts[0], $parts[1]); } @@ -115,7 +98,9 @@ public function processNode(OutputInterface $output, NodeInterface $node, array ); } else { $closureString = $closure; - $closure = create_function('$session, $node', $closure); + $closure = function (SessionInterface $session, NodeInterface $node) use ($closureString): void { + eval($closureString); + }; $output->writeln(sprintf( ' > Applying closure: %s', strlen($closureString) > 75 ? substr($closureString, 0, 72).'...' : $closureString @@ -127,13 +112,13 @@ public function processNode(OutputInterface $output, NodeInterface $node, array if ($operations['dump']) { $output->writeln('Node dump: '); - /** @var $property PropertyInterface */ foreach ($node->getProperties() as $property) { $value = $property->getValue(); if (!is_string($value)) { $value = print_r($value, true); } - $output->writeln(sprintf(' - %s = %s', + $output->writeln(sprintf( + ' - %s = %s', $property->getName(), $value )); @@ -147,37 +132,36 @@ public function processNode(OutputInterface $output, NodeInterface $node, array * * @param string $language Language type - SQL, SQL2 * @param string $sql JCR Query string - * - * @return \PHPCR\Query\QueryInterface */ - public function createQuery($language, $sql) + public function createQuery(string $language, string $sql): QueryInterface { - $this->validateQueryLanguage($language); + $language = $this->validateQueryLanguage($language); $session = $this->getSession(); $qm = $session->getWorkspace()->getQueryManager(); - $language = strtoupper($language); - $query = $qm->createQuery($sql, $language); - return $query; + return $qm->createQuery($sql, $language); } /** * Check if this is a supported query language. * - * @param string $language Language name. - * - * @throws \Exception if the language is not supported. + * @throws \Exception if the language is not supported */ - protected function validateQueryLanguage($language) + protected function validateQueryLanguage(string $language): string { $qm = $this->getSession()->getWorkspace()->getQueryManager(); $langs = $qm->getSupportedQueryLanguages(); - if (!in_array($language, $langs)) { - throw new \Exception(sprintf( - 'Query language "%s" not supported, available query languages: %s', - $language, implode(',', $langs) - )); + foreach ($langs as $lang) { + if (0 === strcasecmp($lang, $language)) { + return $lang; + } } + + throw new \Exception(sprintf( + 'Query language "%s" not supported, available query languages: %s', + $language, + implode(',', $langs) + )); } } diff --git a/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperItemVisitor.php b/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperItemVisitor.php index 0984e4b9..ecdd293b 100644 --- a/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperItemVisitor.php +++ b/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperItemVisitor.php @@ -1,47 +1,33 @@ */ abstract class ConsoleDumperItemVisitor implements ItemVisitorInterface { /** - * Target for printing information - * - * @var OutputInterface + * Current depth in the tree. */ - protected $output; + protected int $level = 0; - /** - * Current depth in the tree - * - * @var int - */ - protected $level = 0; - - /** - * Instantiate the console dumper visitor - * - * @param OutputInterface $output - */ - public function __construct(OutputInterface $output) - { - $this->output = $output; + public function __construct( + protected OutputInterface $output + ) { } /** - * Set the current depth level for indention - * - * @param int $level + * Set the current depth level for indention. */ - public function setLevel($level) + public function setLevel(int $level): void { $this->level = $level; } diff --git a/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperNodeVisitor.php b/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperNodeVisitor.php index 76a8693f..e1f81530 100644 --- a/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperNodeVisitor.php +++ b/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperNodeVisitor.php @@ -1,56 +1,65 @@ */ class ConsoleDumperNodeVisitor extends ConsoleDumperItemVisitor { /** - * Whether to print the UUIDs or not - * - * @var bool + * Whether to print the UUIDs or not. */ - protected $identifiers; + protected bool $identifiers; /** - * Instantiate the console dumper visitor - * - * @param OutputInterface $output - * @param bool $identifiers whether to output the node UUID + * Show the full path for the node. + */ + protected bool $showFullPath = false; + + /** + * @param bool $identifiers whether to output the node UUIDs */ - public function __construct(OutputInterface $output, $identifiers = false) + public function __construct(OutputInterface $output, bool $identifiers = false) { parent::__construct($output); $this->identifiers = $identifiers; } + public function setShowFullPath(bool $showFullPath): void + { + $this->showFullPath = $showFullPath; + } + /** * Print information about the visited node. * - * @param ItemInterface $item the node to visit + * @throws \Exception */ - public function visit(ItemInterface $item) + public function visit(ItemInterface $item): void { - if (! $item instanceof NodeInterface) { - throw new \Exception("Internal error: did not expect to visit a non-node object: $item"); + if (!$item instanceof NodeInterface) { + throw new \Exception('Internal error: did not expect to visit a non-node object: '.$item::class); } - $name = $item->getName(); - - if ($item->getDepth() == 0) { + if (0 === $item->getDepth()) { $name = 'ROOT'; + } elseif ($this->showFullPath) { + $name = $item->getPath(); + } else { + $name = $item->getName(); } $out = str_repeat(' ', $this->level) - . '' . $name . ''; + .''.$name.''; if ($this->identifiers) { $identifier = $item->getIdentifier(); if ($identifier) { diff --git a/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperPropertyVisitor.php b/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperPropertyVisitor.php index fa4986d8..4a4f41c3 100644 --- a/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperPropertyVisitor.php +++ b/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperPropertyVisitor.php @@ -1,81 +1,74 @@ */ class ConsoleDumperPropertyVisitor extends ConsoleDumperItemVisitor { /** - * Limit to cap lines at to avoid garbled output on long property values - * - * @var int + * Limit to cap lines at to avoid garbled output on long property values. */ - protected $maxLineLength; + protected int $maxLineLength; - /** - * Show the full path for each reference - */ - protected $expandReferences; + private string $refFormat; /** - * Instantiate property visitor - * - * @param OutputInterface $output - * @param int $maxLineLength + * @param array $options */ - public function __construct(OutputInterface $output, $options = array()) + public function __construct(OutputInterface $output, array $options = []) { - $options = array_merge(array( + $options = array_merge([ 'max_line_length' => 120, 'ref_format' => 'uuid', - ), $options); + ], $options); parent::__construct($output); - $this->maxLineLength = $options['max_line_length']; - $this->refFormat = $options['ref_format']; + $this->maxLineLength = (int) $options['max_line_length']; + $this->refFormat = (string) $options['ref_format']; } /** - * Print information about this property + * Print information about this property. * - * @param ItemInterface $item the property to visit + * @throws \Exception */ - public function visit(ItemInterface $item) + public function visit(ItemInterface $item): void { - if (! $item instanceof PropertyInterface) { - throw new \Exception(sprintf('Internal error: did not expect to visit a non-property object: %s', is_object($item) ? get_class($item) : $item)); + if (!$item instanceof PropertyInterface) { + throw new \Exception(sprintf('Internal error: did not expect to visit a non-property object: %s', $item::class)); } $value = $item->getString(); - if (! is_string($value)) { + if (!is_string($value)) { $value = print_r($value, true); } if (strlen($value) > $this->maxLineLength) { - $value = substr($value, 0, $this->maxLineLength) . '...'; + $value = substr($value, 0, $this->maxLineLength).'...'; } - $referrers = array(); + $referrers = []; - if (in_array($item->getType(), array( + if (in_array($item->getType(), [ PropertyType::WEAKREFERENCE, - PropertyType::REFERENCE - ))) { - $referenceStrings = array(); + PropertyType::REFERENCE, + ], true)) { + $referenceStrings = []; - if ('path' == $this->refFormat) { + if ('path' === $this->refFormat) { $references = (array) $item->getValue(); foreach ($references as $reference) { @@ -88,9 +81,9 @@ public function visit(ItemInterface $item) $value = ''; } - $value = str_replace(array("\n", "\t"), '', $value); + $value = str_replace(["\n", "\t"], '', $value); - $this->output->writeln(str_repeat(' ', $this->level + 1) . '- ' . $item->getName() . ' = ' . $value); + $this->output->writeln(str_repeat(' ', $this->level + 1).'- '.$item->getName().' = '.$value); if (isset($referenceStrings)) { foreach ($referenceStrings as $referenceString) { @@ -103,5 +96,4 @@ public function visit(ItemInterface $item) } } } - } diff --git a/src/PHPCR/Util/Console/Helper/TreeDumper/SystemNodeFilter.php b/src/PHPCR/Util/Console/Helper/TreeDumper/SystemNodeFilter.php index 860fb5c4..64a7e796 100644 --- a/src/PHPCR/Util/Console/Helper/TreeDumper/SystemNodeFilter.php +++ b/src/PHPCR/Util/Console/Helper/TreeDumper/SystemNodeFilter.php @@ -1,10 +1,13 @@ * @author David Buchmann */ class NodeHelper { /** - * Do not create an instance of this class + * Do not create an instance of this class. */ private function __construct() { } /** - * Create a node and it's parents, if necessary. Like mkdir -p. + * Create a node and it's parents, if necessary. + * + * Like mkdir -p. * * @param SessionInterface $session the PHPCR session to create the path * @param string $path full path, like /content/jobs/data * * @return NodeInterface the last node of the path, i.e. data + * + * @throws \InvalidArgumentException + * @throws RepositoryException + * @throws PathNotFoundException + * @throws ItemExistsException + * @throws LockException + * @throws ConstraintViolationException + * @throws VersionException */ - public static function createPath(SessionInterface $session, $path) + public static function createPath(SessionInterface $session, string $path): NodeInterface { $current = $session->getRootNode(); - $segments = preg_split('#/#', $path, null, PREG_SPLIT_NO_EMPTY); + $segments = preg_split('#/#', $path, -1, PREG_SPLIT_NO_EMPTY); foreach ($segments as $segment) { if ($current->hasNode($segment)) { $current = $current->getNode($segment); @@ -63,74 +78,66 @@ public static function createPath(SessionInterface $session, $path) * node which you are not allowed to remove. * * @param SessionInterface $session the session to remove all children of - * the root node + * the root node + * + * @throws RepositoryException * * @see isSystemItem */ - public static function purgeWorkspace(SessionInterface $session) + public static function purgeWorkspace(SessionInterface $session): void { $root = $session->getRootNode(); - /** @var $property PropertyInterface */ foreach ($root->getProperties() as $property) { - if (! self::isSystemItem($property)) { + if (!self::isSystemItem($property)) { $property->remove(); } } - /** @var $node NodeInterface */ foreach ($root->getNodes() as $node) { - if (! self::isSystemItem($node)) { + if (!self::isSystemItem($node)) { $node->remove(); } } } - /** - * Kept as alias of purgeWorkspace for BC compatibility - * - * @deprecated - */ - public static function deleteAllNodes(SessionInterface $session) - { - self::purgeWorkspace($session); - } - /** * Determine whether this item is to be considered a system item that you * usually want to hide and that should not be removed when purging the * repository. * - * @param ItemInterface $item + * @throws RepositoryException */ - public static function isSystemItem(ItemInterface $item) + public static function isSystemItem(ItemInterface $item): bool { if ($item->getDepth() > 1) { return false; } $name = $item->getName(); - return strpos($name, 'jcr:') === 0 || strpos($name, 'rep:') === 0; + return str_starts_with($name, 'jcr:') || str_starts_with($name, 'rep:'); } /** - * Helper method to implement NodeInterface::addNodeAutoNamed + * Helper method to implement NodeInterface::addNodeAutoNamed. * * This method only checks for valid namespaces. All other exceptions must * be thrown by the addNodeAutoNamed implementation. * - * @param string[] $usedNames list of child names that is currently used and may not be chosen. - * @param string[] $namespaces namespace prefix to uri map of all currently known namespaces. - * @param string $defaultNamespace namespace prefix to use if the hint does not specify. - * @param string $nameHint the name hint according to the API definition + * @param string[] $usedNames list of child names that is currently used and may not be chosen + * @param string[] $namespaces namespace prefix to uri map of all currently known namespaces + * @param string $defaultNamespace namespace prefix to use if the hint does not specify + * @param string|null $nameHint the name hint according to the API definition * * @return string A valid node name for this node + * @return string A valid node name for this node * - * @throws NamespaceException if a namespace prefix is provided in the - * $nameHint which does not exist and this implementation performs - * this validation immediately. + * @throws RepositoryException + * @throws NamespaceException if a namespace prefix is provided in the + * $nameHint which does not exist and this implementation performs + * this validation immediately */ - public static function generateAutoNodeName($usedNames, $namespaces, $defaultNamespace, $nameHint = null) + public static function generateAutoNodeName(array $usedNames, array $namespaces, string $defaultNamespace, ?string $nameHint = null): string { $usedNames = array_flip($usedNames); @@ -138,7 +145,7 @@ public static function generateAutoNodeName($usedNames, $namespaces, $defaultNam * null: The new node name will be generated entirely by the repository. */ if (null === $nameHint) { - return self::generateWithPrefix($usedNames, $defaultNamespace . ':'); + return self::generateWithPrefix($usedNames, $defaultNamespace.':'); } /* @@ -146,24 +153,24 @@ public static function generateAutoNodeName($usedNames, $namespaces, $defaultNam * be in the empty namespace and the local part of the name will be * generated by the repository. */ - if ('' === $nameHint || ':' == $nameHint || '{}' == $nameHint) { + if ('' === $nameHint || ':' === $nameHint || '{}' === $nameHint) { return self::generateWithPrefix($usedNames, ''); } - /* - * "somePrefix:" where somePrefix is a syntactically - * valid namespace prefix - */ - if (':' == $nameHint[strlen($nameHint)-1] - && substr_count($nameHint, ':') === 1 + /* + * "somePrefix:" where somePrefix is a syntactically + * valid namespace prefix + */ + if (':' === $nameHint[strlen($nameHint) - 1] + && 1 === substr_count($nameHint, ':') && preg_match('#^[a-zA-Z][a-zA-Z0-9]*:$#', $nameHint) ) { $prefix = substr($nameHint, 0, -1); - if (! isset($namespaces[$prefix])) { + if (!isset($namespaces[$prefix])) { throw new NamespaceException("Invalid nameHint '$nameHint'"); } - return self::generateWithPrefix($usedNames, $prefix . ':'); + return self::generateWithPrefix($usedNames, $prefix.':'); } /* @@ -171,16 +178,16 @@ public static function generateAutoNodeName($usedNames, $namespaces, $defaultNam * namespace URI */ if (strlen($nameHint) > 2 - && '{' == $nameHint[0] - && '}' == $nameHint[strlen($nameHint)-1] + && '{' === $nameHint[0] + && '}' === $nameHint[strlen($nameHint) - 1] && filter_var(substr($nameHint, 1, -1), FILTER_VALIDATE_URL) ) { $prefix = array_search(substr($nameHint, 1, -1), $namespaces); - if (! $prefix) { + if (!$prefix) { throw new NamespaceException("Invalid nameHint '$nameHint'"); } - return self::generateWithPrefix($usedNames, $prefix . ':'); + return self::generateWithPrefix($usedNames, $prefix.':'); } /* @@ -193,15 +200,15 @@ public static function generateAutoNodeName($usedNames, $namespaces, $defaultNam * constructed from the hint may vary across implementations. */ if (1 === substr_count($nameHint, ':')) { - list($prefix, $name) = explode(':', $nameHint); + [$prefix, $name] = explode(':', $nameHint); if (preg_match('#^[a-zA-Z][a-zA-Z0-9]*$#', $prefix) && preg_match('#^[a-zA-Z][a-zA-Z0-9]*$#', $name) ) { - if (! isset($namespaces[$prefix])) { + if (!isset($namespaces[$prefix])) { throw new NamespaceException("Invalid nameHint '$nameHint'"); } - return self::generateWithPrefix($usedNames, $prefix . ':', $name); + return self::generateWithPrefix($usedNames, $prefix.':', $name); } } @@ -214,17 +221,17 @@ public static function generateAutoNodeName($usedNames, $namespaces, $defaultNam * as a basis. The way in which the local name is constructed from the hint * may vary across implementations. */ - $matches = array(); + $matches = []; if (preg_match('#^\\{([^\\}]+)\\}([a-zA-Z][a-zA-Z0-9]*)$#', $nameHint, $matches)) { $ns = $matches[1]; $name = $matches[2]; - $prefix = array_search($ns, $namespaces); - if (! $prefix) { + $prefix = array_search($ns, $namespaces, true); + if (!$prefix) { throw new NamespaceException("Invalid nameHint '$nameHint'"); } - return self::generateWithPrefix($usedNames, $prefix . ':', $name); + return self::generateWithPrefix($usedNames, $prefix.':', $name); } throw new RepositoryException("Invalid nameHint '$nameHint'"); @@ -237,13 +244,11 @@ public static function generateAutoNodeName($usedNames, $namespaces, $defaultNam * @param string[] $usedNames names that are forbidden * @param string $prefix the prefix including the colon at the end * @param string $namepart start for the localname - * - * @return string */ - private static function generateWithPrefix($usedNames, $prefix, $namepart = '') + private static function generateWithPrefix(array $usedNames, string $prefix, string $namepart = ''): string { do { - $name = $prefix . $namepart . mt_rand(); + $name = $prefix.$namepart.mt_rand(); } while (isset($usedNames[$name])); return $name; @@ -264,17 +269,17 @@ private static function generateWithPrefix($usedNames, $prefix, $namepart = '') * @param array $new new order * * @return array the keys are elements to move, values the destination to - * move before or null to move to the end. + * move before or null to move to the end */ - public static function calculateOrderBefore(array $old, array $new) + public static function calculateOrderBefore(array $old, array $new): array { - $reorders = array(); + $reorders = []; - //check for deleted items + // check for deleted items $newIndex = array_flip($new); foreach ($old as $key => $value) { - if (!isset($newIndex[$value])) { + if (!array_key_exists($value, $newIndex)) { unset($old[$key]); } } @@ -286,25 +291,25 @@ public static function calculateOrderBefore(array $old, array $new) $len = count($new) - 1; $oldIndex = array_flip($old); - //go backwards on the new node order and arrange them this way - for ($i = $len; $i >= 0; $i--) { - //get the name of the child node + // go backwards on the new node order and arrange them this way + for ($i = $len; $i >= 0; --$i) { + // get the name of the child node $current = $new[$i]; - //check if it's not the last node + // check if it's not the last node if (isset($new[$i + 1])) { // get the name of the next node $next = $new[$i + 1]; - //if in the old order $c and next are not neighbors already, do the reorder command - if ($oldIndex[$current] + 1 != $oldIndex[$next]) { + // if in the old order $c and next are not neighbors already, do the reorder command + if ($oldIndex[$current] + 1 !== $oldIndex[$next]) { $reorders[$current] = $next; - $old = self::orderBeforeArray($current,$next,$old); + $old = self::orderBeforeArray($current, $next, $old); $oldIndex = array_flip($old); } } else { - //check if it's not already at the end of the nodes - if ($oldIndex[$current] != $len) { + // check if it's not already at the end of the nodes + if ($oldIndex[$current] !== $len) { $reorders[$current] = null; - $old = self::orderBeforeArray($current,null,$old); + $old = self::orderBeforeArray($current, null, $old); $oldIndex = array_flip($old); } } @@ -317,40 +322,38 @@ public static function calculateOrderBefore(array $old, array $new) * Move the element $name of $list to right before $destination, * validating existence of all elements. * - * @param string $name name of the element to move - * @param string $destination name of the element $srcChildRelPath has - * to be ordered before, null to move to the end - * @param array $list the array of names + * @param string $name name of the element to move + * @param string|null $destination name of the element $srcChildRelPath has + * to be ordered before, null to move to the end + * @param array $list the array of names * * @return array The updated $nodes array with new order * - * @throws \PHPCR\ItemNotFoundException if $srcChildRelPath or $destChildRelPath are not found in $nodes + * @throws ItemNotFoundException if $srcChildRelPath or $destChildRelPath are not found in $nodes */ - public static function orderBeforeArray($name, $destination, $list) + public static function orderBeforeArray(string $name, ?string $destination, array $list): array { // reindex the array so there are no gaps $list = array_values($list); - $oldpos = array_search($name, $list); + $oldpos = array_search($name, $list, true); if (false === $oldpos) { - throw new ItemNotFoundException("$name is not a child of this node"); } - if ($destination == null) { + if (null === $destination) { // null means move to end unset($list[$oldpos]); $list[] = $name; } else { // insert before element $destination - $newpos = array_search($destination, $list); - if ($newpos === false) { - + $newpos = array_search($destination, $list, true); + if (false === $newpos) { throw new ItemNotFoundException("$destination is not a child of this node"); } if ($oldpos < $newpos) { // we first unset, the position will change by one - $newpos--; + --$newpos; } unset($list[$oldpos]); array_splice($list, $newpos, 0, $name); diff --git a/src/PHPCR/Util/PathHelper.php b/src/PHPCR/Util/PathHelper.php index b6c6282c..ffa1464f 100644 --- a/src/PHPCR/Util/PathHelper.php +++ b/src/PHPCR/Util/PathHelper.php @@ -1,26 +1,29 @@ */ class PathHelper { /** - * Do not create an instance of this class + * Do not create an instance of this class. */ // @codeCoverageIgnoreStart private function __construct() { } + // @codeCoverageIgnoreEnd /** @@ -31,29 +34,42 @@ private function __construct() * * Non-normalized paths are considered invalid, i.e. /node/. is /node and /my/node/.. is /my * - * @param string $path The path to validate - * @param bool $destination whether this is a destination path (by copy or - * move), meaning [] is not allowed. If your implementation does not - * support same name siblings, just always pass true for this - * @param bool $throw whether to throw an exception on validation errors + * @param string $path The path to validate + * @param bool $destination whether this is a destination path (by copy or + * move), meaning [] is not allowed. If your implementation does not + * support same name siblings, just always pass true for this + * @param bool $throw whether to throw an exception on validation errors + * @param bool|array $namespacePrefixes List of all known namespace prefixes. + * If specified, this method validates that the path contains no unknown prefixes. * * @return bool true if valid, false if not valid and $throw was false * * @throws RepositoryException if the path contains invalid characters and $throw is true */ - public static function assertValidAbsolutePath($path, $destination = false, $throw = true) + public static function assertValidAbsolutePath(string $path, bool $destination = false, bool $throw = true, bool|array $namespacePrefixes = false): bool { - if ((!is_string($path) && !is_numeric($path)) - || strlen($path) == 0 + if ('' === $path || '/' !== $path[0] - || strlen($path) > 1 && '/' === $path[strlen($path) - 1] || preg_match('-//|/\./|/\.\./-', $path) + || (strlen($path) > 1 && '/' === $path[strlen($path) - 1]) ) { return self::error("Invalid path '$path'", $throw); } if ($destination && ']' === $path[strlen($path) - 1]) { return self::error("Destination path may not end with index: '$path'", $throw); } + if ($namespacePrefixes) { + $matches = []; + preg_match_all('#/(?P[^/:]+):#', $path, $matches); + $unknown = array_diff(array_unique($matches['prefixes']), $namespacePrefixes); + if (count($unknown)) { + if (!$throw) { + return false; + } + + throw new NamespaceException(sprintf('Unknown namespace prefix(es) (%s) in path %s', implode(' and ', $unknown), $path)); + } + } return true; } @@ -69,8 +85,8 @@ public static function assertValidAbsolutePath($path, $destination = false, $thr * encode and decode characters that are not natively allowed by a storage * engine. * - * @param string $name The name to check - * @param boolean $throw whether to throw an exception on validation errors. + * @param string $name The name to check + * @param bool $throw whether to throw an exception on validation errors * * @return bool true if valid, false if not valid and $throw was false * @@ -78,9 +94,9 @@ public static function assertValidAbsolutePath($path, $destination = false, $thr * * @see http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local%20Names */ - public static function assertValidLocalName($name, $throw = true) + public static function assertValidLocalName(string $name, bool $throw = true): bool { - if ('.' == $name || '..' == $name) { + if ('.' === $name || '..' === $name) { return self::error("Name may not be parent or self identifier: $name", $throw); } @@ -102,23 +118,20 @@ public static function assertValidLocalName($name, $throw = true) * * Note: A well-formed input path implies a well-formed and normalized path returned. * - * @param string $path The path to normalize. + * @param string $path the path to normalize * @param bool $destination whether this is a destination path (by copy or - * move), meaning [] is not allowed in validation. - * @param bool $throw whether to throw an exception if validation fails or - * just to return false. + * move), meaning [] is not allowed in validation + * @param bool $throw whether to throw an exception if validation fails or + * just to return false * - * @return string The normalized path or false if $throw was false and the path invalid + * @return false|string The normalized path or false if $throw was false and the path invalid * * @throws RepositoryException if the path is not a valid absolute path and - * $throw is true + * $throw is true */ - public static function normalizePath($path, $destination = false, $throw = true) + public static function normalizePath(string $path, bool $destination = false, bool $throw = true): false|string { - if (!is_string($path) && !is_numeric($path)) { - return self::error('Expected string but got ' . gettype($path), $throw); - } - if (strlen($path) === 0) { + if ('' === $path) { return self::error('Path must not be of zero length', $throw); } @@ -130,7 +143,7 @@ public static function normalizePath($path, $destination = false, $throw = true) return self::error("Not an absolute path '$path'", $throw); } - $finalParts= array(); + $finalParts = []; $parts = explode('/', $path); foreach ($parts as $pathPart) { @@ -148,12 +161,11 @@ public static function normalizePath($path, $destination = false, $throw = true) break; } } - $normalizedPath = count($finalParts) > 1 ? + $normalizedPath = count($finalParts) > 1 ? implode('/', $finalParts) : - '/' // first element is always the empty-name root element. this might have been a path like /x/.. - ; + '/'; // first element is always the empty-name root element. this might have been a path like /x/.. - if (! self::assertValidAbsolutePath($normalizedPath, $destination, $throw)) { + if (!self::assertValidAbsolutePath($normalizedPath, $destination, $throw)) { return false; } @@ -167,25 +179,19 @@ public static function normalizePath($path, $destination = false, $throw = true) * @param string $path A relative or absolute path * @param string $context The absolute path context to make $path absolute if needed * @param bool $destination whether this is a destination path (by copy or - * move), meaning [] is not allowed in validation. - * @param bool $throw whether to throw an exception if validation fails or - * just to return false. + * move), meaning [] is not allowed in validation + * @param bool $throw whether to throw an exception if validation fails or + * just to return false * - * @return string The normalized, absolute path or false if $throw was - * false and the path invalid + * @return false|string The normalized, absolute path or false if $throw was + * false and the path invalid * * @throws RepositoryException if the path can not be made into a valid - * absolute path and $throw is true + * absolute path and $throw is true */ - public static function absolutizePath($path, $context, $destination = false, $throw = true) + public static function absolutizePath(string $path, string $context, bool $destination = false, bool $throw = true): false|string { - if (!is_string($path) && !is_numeric($path)) { - return self::error('Expected string path but got ' . gettype($path), $throw); - } - if (!is_string($context)) { - return self::error('Expected string context but got ' . gettype($context), $throw); - } - if (strlen($path) === 0) { + if ('' === $path) { return self::error('Path must not be of zero length', $throw); } @@ -196,6 +202,28 @@ public static function absolutizePath($path, $context, $destination = false, $th return self::normalizePath($path, $destination, $throw); } + /** + * Make an absolute path relative to $context. + * + * ie. $context . '/' . PathHelper::relativePath($path, $context) === $path + * + * Input paths are assumed to be normalized. + * + * @param string $path The absolute path to a node + * @param string $context The absolute path to an ancestor of $path + * @param bool $throw whether to throw exceptions on invalid data + * + * @return false|string The relative path from $context to $path + */ + public static function relativizePath(string $path, string $context, bool $throw = true): false|string + { + if (!str_starts_with($path, $context)) { + return self::error("$path is not within $context", $throw); + } + + return ltrim(substr($path, strlen($context)), '/'); + } + /** * Get the parent path of a valid absolute path. * @@ -203,7 +231,7 @@ public static function absolutizePath($path, $context, $destination = false, $th * * @return string the path with the last segment removed */ - public static function getParentPath($path) + public static function getParentPath(string $path): string { if ('/' === $path) { return '/'; @@ -226,14 +254,17 @@ public static function getParentPath($path) * @param string $path a valid absolute path, like /content/jobs/data * * @return string the name, that is the string after the last "/" + * + * @throws RepositoryException */ - public static function getNodeName($path) + public static function getNodeName(string $path): string { $strrpos = strrpos($path, '/'); if (false === $strrpos) { self::error(sprintf( - 'Path "%s" must be an absolute path', $path + 'Path "%s" must be an absolute path', + $path ), true); } @@ -241,13 +272,33 @@ public static function getNodeName($path) } /** - * Get the depth of the path, ignore trailing slashes, root starts counting at 0 + * Return the localname of the node at the given path. + * The local name is the node name minus the namespace. + * + * @param string $path a valid absolute path + * + * @throws RepositoryException + */ + public static function getLocalNodeName(string $path): string + { + $nodeName = self::getNodeName($path); + $localName = strstr($nodeName, ':'); + + if (false !== $localName) { + return substr($localName, 1); + } + + return $nodeName; + } + + /** + * Get the depth of the path, ignore trailing slashes, root starts counting at 0. * * @param string $path a valid absolute path, like /content/jobs/data * - * @return integer with the path depth + * @return int with the path depth */ - public static function getPathDepth($path) + public static function getPathDepth(string $path): int { return substr_count(rtrim($path, '/'), '/'); } @@ -256,14 +307,14 @@ public static function getPathDepth($path) * If $throw is true, throw a RepositoryException with $msg. Otherwise * return false. * - * @param string $msg the exception message to use in case of throw being true - * @param boolean $throw whether to throw the exception or return false + * @param string $msg the exception message to use in case of throw being true + * @param bool $throw whether to throw the exception or return false * - * @return boolean false + * @return false * * @throws RepositoryException */ - private static function error($msg, $throw) + private static function error(string $msg, bool $throw): bool { if ($throw) { throw new RepositoryException($msg); diff --git a/src/PHPCR/Util/QOM/BaseQomToSqlQueryConverter.php b/src/PHPCR/Util/QOM/BaseQomToSqlQueryConverter.php index 6e55318f..928ac90f 100644 --- a/src/PHPCR/Util/QOM/BaseQomToSqlQueryConverter.php +++ b/src/PHPCR/Util/QOM/BaseQomToSqlQueryConverter.php @@ -1,8 +1,11 @@ generator = $generator; @@ -33,20 +26,16 @@ public function __construct(BaseSqlGenerator $generator) * Query ::= 'SELECT' columns * 'FROM' Source * ['WHERE' Constraint] - * ['ORDER BY' orderings] - * - * @param QOM\QueryObjectModelInterface $query - * - * @return string + * ['ORDER BY' orderings]. */ - public function convert(QOM\QueryObjectModelInterface $query) + public function convert(QOM\QueryObjectModelInterface $query): string { $columns = $this->convertColumns($query->getColumns()); $source = $this->convertSource($query->getSource()); $constraint = ''; $orderings = ''; - if ($query->getConstraint() !== null) { + if (null !== $query->getConstraint()) { $constraint = $this->convertConstraint($query->getConstraint()); } @@ -58,45 +47,31 @@ public function convert(QOM\QueryObjectModelInterface $query) } /** - * Convert a source. This is different between SQL1 and SQL2 - * - * @param QOM\SourceInterface $source - * - * @return string + * Convert a source. This is different between SQL1 and SQL2. */ - abstract protected function convertSource(QOM\SourceInterface $source); + abstract protected function convertSource(QOM\SourceInterface $source): string; /** * Convert a constraint. This is different between SQL1 and SQL2. - * - * @param QOM\ConstraintInterface $constraint - * - * @return string */ - abstract protected function convertConstraint(QOM\ConstraintInterface $constraint); + abstract protected function convertConstraint(QOM\ConstraintInterface $constraint): string; /** * Convert dynamic operand. This is different between SQL1 and SQL2. - * @param QOM\DynamicOperandInterface $operand - * - * @return mixed */ - abstract protected function convertDynamicOperand(QOM\DynamicOperandInterface $operand); + abstract protected function convertDynamicOperand(QOM\DynamicOperandInterface $operand): mixed; /** * Selector ::= nodeTypeName ['AS' selectorName] - * nodeTypeName ::= Name - * - * @param QOM\SelectorInterface $selector - * @return string + * nodeTypeName ::= Name. */ - protected function convertSelector(QOM\SelectorInterface $selector) + protected function convertSelector(QOM\SelectorInterface $selector): string { return $this->generator->evalSelector($selector->getNodeTypeName(), $selector->getSelectorName()); } /** - * Comparison ::= DynamicOperand Operator StaticOperand + * Comparison ::= DynamicOperand Operator StaticOperand. * * Operator ::= EqualTo | NotEqualTo | LessThan | * LessThanOrEqualTo | GreaterThan | @@ -108,11 +83,8 @@ protected function convertSelector(QOM\SelectorInterface $selector) * GreaterThan ::= '>' * GreaterThanOrEqualTo ::= '>=' * Like ::= 'LIKE' - * - * @param QOM\ComparisonInterface $comparison - * @return string */ - protected function convertComparison(QOM\ComparisonInterface $comparison) + protected function convertComparison(QOM\ComparisonInterface $comparison): string { $operand1 = $this->convertDynamicOperand($comparison->getOperand1()); $operand2 = $this->convertStaticOperand($comparison->getOperand2()); @@ -126,19 +98,17 @@ protected function convertComparison(QOM\ComparisonInterface $comparison) * selectorName'.'propertyName 'IS NOT NULL' | * propertyName 'IS NOT NULL' If only one * selector exists in - * this query + * this query. * * Note: The negation, 'NOT x IS NOT NULL' * can be written 'x IS NULL' - * - * @param QOM\PropertyExistenceInterface $constraint - * @return string */ - protected function convertPropertyExistence(QOM\PropertyExistenceInterface $constraint) + protected function convertPropertyExistence(QOM\PropertyExistenceInterface $constraint): string { return $this->generator->evalPropertyExistence( $constraint->getSelectorName(), - $constraint->getPropertyName()); + $constraint->getPropertyName() + ); } /** @@ -148,12 +118,9 @@ protected function convertPropertyExistence(QOM\PropertyExistenceInterface $cons * FullTextSearchExpression ')' * // If only one selector exists in this query, * explicit specification of the selectorName - * preceding the propertyName is optional - * - * @param QOM\FullTextSearchInterface $constraint - * @return string + * preceding the propertyName is optional. */ - protected function convertFullTextSearch(QOM\FullTextSearchInterface $constraint) + protected function convertFullTextSearch(QOM\FullTextSearchInterface $constraint): string { $searchExpression = $this->convertFullTextSearchExpression($constraint->getFullTextSearchExpression()); @@ -161,24 +128,32 @@ protected function convertFullTextSearch(QOM\FullTextSearchInterface $constraint } /** - * FullTextSearchExpression ::= BindVariable | ''' FullTextSearchLiteral ''' + * FullTextSearchExpression ::= BindVariable | ''' FullTextSearchLiteral '''. * - * @param string $expr - * @return string + * @param string|StaticOperandInterface $expr */ - protected function convertFullTextSearchExpression($expr) + protected function convertFullTextSearchExpression($expr): string { if ($expr instanceof QOM\BindVariableValueInterface) { - return $this->convertBindVariable($expr); + return $this->convertBindVariable($expr->getBindVariableName()); + } + if ($expr instanceof QOM\LiteralInterface) { + $literal = $expr->getLiteralValue(); + } elseif (is_string($expr)) { + // this should not happen, the interface for full text search declares the return type to be StaticOperandInterface + // however, without type checks, jackalope 1.0 got this wrong and returned a string. + $literal = $expr; + } else { + throw new \InvalidArgumentException('Unknown full text search expression type '.$expr::class); } - $expr = $this->generator->evalFullText($expr); + $literal = $this->generator->evalFullText($literal); - return "'$expr'"; + return "'$literal'"; } /** - * StaticOperand ::= Literal | BindVariableValue + * StaticOperand ::= Literal | BindVariableValue. * * Literal ::= CastLiteral | UncastLiteral * CastLiteral ::= 'CAST(' UncastLiteral ' AS ' PropertyType ')' @@ -192,10 +167,9 @@ protected function convertFullTextSearchExpression($expr) * BindVariableValue ::= '$'bindVariableName * bindVariableName ::= Prefix * - * @param QOM\StaticOperandInterface $operand - * @return string + * @throws \InvalidArgumentException */ - protected function convertStaticOperand(QOM\StaticOperandInterface $operand) + protected function convertStaticOperand(StaticOperandInterface $operand): string { if ($operand instanceof QOM\BindVariableValueInterface) { return $this->convertBindVariable($operand->getBindVariableName()); @@ -205,20 +179,18 @@ protected function convertStaticOperand(QOM\StaticOperandInterface $operand) } // This should not happen, but who knows... - throw new \InvalidArgumentException("Invalid operand"); + throw new \InvalidArgumentException('Invalid operand'); } /** - * PropertyValue ::= [selectorName'.'] propertyName // If only one selector exists - * - * @param QOM\PropertyValueInterface $value - * @return string + * PropertyValue ::= [selectorName'.'] propertyName // If only one selector exists. */ - protected function convertPropertyValue(QOM\PropertyValueInterface $value) + protected function convertPropertyValue(QOM\PropertyValueInterface $value): string { return $this->generator->evalPropertyValue( $value->getPropertyName(), - $value->getSelectorName()); + $value->getSelectorName() + ); } /** @@ -226,15 +198,13 @@ protected function convertPropertyValue(QOM\PropertyValueInterface $value) * Ordering ::= DynamicOperand [Order] * Order ::= Ascending | Descending * Ascending ::= 'ASC' - * Descending ::= 'DESC' + * Descending ::= 'DESC'. * - * @param QOM\OrderingInterface[] $orderings - * @return string + * @param QOM\OrderingInterface[] $orderings */ - protected function convertOrderings(array $orderings) + protected function convertOrderings(array $orderings): string { - $list = array(); - /** @var $ordering QOM\OrderingInterface */ + $list = []; foreach ($orderings as $ordering) { $order = $this->generator->evalOrder($ordering->getOrder()); $operand = $this->convertDynamicOperand($ordering->getOperand()); @@ -247,38 +217,26 @@ protected function convertOrderings(array $orderings) /** * Path ::= '[' quotedPath ']' | '[' simplePath ']' | simplePath * quotedPath ::= A JCR Path that contains non-SQL-legal characters - * simplePath ::= A JCR Name that contains only SQL-legal characters - * - * @param string $path - * - * @return string + * simplePath ::= A JCR Name that contains only SQL-legal characters. */ - protected function convertPath($path) + protected function convertPath(string $path): string { return $this->generator->evalPath($path); } /** * BindVariableValue ::= '$'bindVariableName - * bindVariableName ::= Prefix - * - * @param string $var - * - * @return string + * bindVariableName ::= Prefix. */ - protected function convertBindVariable($var) + protected function convertBindVariable(string $var): string { return $this->generator->evalBindVariable($var); } /** - * Literal ::= CastLiteral | UncastLiteral - * - * @param mixed $literal - * - * @return string + * Literal ::= CastLiteral | UncastLiteral. */ - protected function convertLiteral($literal) + protected function convertLiteral(mixed $literal): string { return $this->generator->evalLiteral($literal); } @@ -290,15 +248,14 @@ protected function convertLiteral($literal) * (selectorName'.*') // If only one selector exists * selectorName ::= Name * propertyName ::= Name - * columnName ::= Name + * columnName ::= Name. * - * @param QOM\ColumnInterface[] $columns - * @return string + * @param QOM\ColumnInterface[] $columns */ - protected function convertColumns(array $columns) + protected function convertColumns(array $columns): string { - $list = array(); - /** @var $column QOM\ColumnInterface */ + $list = []; + foreach ($columns as $column) { $selector = $column->getSelectorName(); $property = $column->getPropertyName(); @@ -308,5 +265,4 @@ protected function convertColumns(array $columns) return $this->generator->evalColumns($list); } - } diff --git a/src/PHPCR/Util/QOM/BaseSqlGenerator.php b/src/PHPCR/Util/QOM/BaseSqlGenerator.php index 1cbb3922..365d625f 100644 --- a/src/PHPCR/Util/QOM/BaseSqlGenerator.php +++ b/src/PHPCR/Util/QOM/BaseSqlGenerator.php @@ -1,10 +1,11 @@ 'ASC', + Constants::JCR_ORDER_DESCENDING => 'DESC', + default => '', + }; } /** * BindVariableValue ::= '$'bindVariableName - * bindVariableName ::= Prefix - * - * @param $var - * - * @return string + * bindVariableName ::= Prefix. */ - public function evalBindVariable($var) + public function evalBindVariable(string $var): string { - return '$' . $var; + return '$'.$var; } /** * Escape the illegal characters for inclusion in a fulltext statement. Escape Character is \\. * - * @param string $string - * * @return string Escaped String * * @see http://jackrabbit.apache.org/api/1.4/org/apache/jackrabbit/util/Text.html #escapeIllegalJcrChars */ - public function evalFullText($string) + public function evalFullText(string $string): string { - $illegalCharacters = array( + $illegalCharacters = [ '!' => '\\!', '(' => '\\(', ':' => '\\:', '^' => '\\^', '[' => '\\[', ']' => '\\]', '{' => '\\{', '}' => '\\}', '\"' => '\\\"', '?' => '\\?', "'" => "''", - ); + ]; return strtr($string, $illegalCharacters); } /** - * Literal ::= CastLiteral | UncastLiteral - * - * @param mixed $literal - * - * @return string + * Literal ::= CastLiteral | UncastLiteral. */ - public function evalLiteral($literal) + public function evalLiteral(mixed $literal): string { if ($literal instanceof \DateTime) { $string = $this->valueConverter->convertType($literal, PropertyType::STRING); @@ -267,26 +214,45 @@ public function evalLiteral($literal) return $this->evalCastLiteral($string, 'DOUBLE'); } - return "'$literal'"; + return sprintf("'%s'", str_replace("'", "''", $literal)); } /** * Cast a literal. This is different between SQL1 and SQL2. - * - * @param string $literal - * @param string $type - * - * @return string */ - abstract public function evalCastLiteral($literal, $type); + abstract public function evalCastLiteral(string $literal, string $type): string; + + /** + * @param string $nodeTypeName The node type of the selector. If it does not contain starting and ending + * brackets ([]), they will be added automatically. + * @param string|null $selectorName The selector name. If it is different than the nodeTypeName, the alias is + * declared if supported by the SQL dialect. + */ + abstract public function evalSelector(string $nodeTypeName, ?string $selectorName = null): string; /** * Evaluate a path. This is different between SQL1 and SQL2. + */ + abstract public function evalPath(string $path): string; + + /** + * columns ::= (Column ',' {Column}) | '*'. * - * @param string $path + * With empty columns, SQL1 is different from SQL2 * - * @return string + * @param iterable $columns */ - abstract public function evalPath($path); + abstract public function evalColumns(iterable $columns): string; + + abstract public function evalColumn(string $selectorName, ?string $propertyName = null, ?string $colname = null): string; + + abstract public function evalPropertyExistence(?string $selectorName, string $propertyName): string; + + abstract public function evalPropertyValue(string $propertyName, ?string $selectorName = null); + + abstract public function evalChildNode(string $path, ?string $selectorName = null); + + abstract public function evalDescendantNode(string $path, ?string $selectorName = null): string; + abstract public function evalFullTextSearch(string $selectorName, string $searchExpression, ?string $propertyName = null): string; } diff --git a/src/PHPCR/Util/QOM/NotSupportedConstraintException.php b/src/PHPCR/Util/QOM/NotSupportedConstraintException.php index d6bb670c..4c245dec 100644 --- a/src/PHPCR/Util/QOM/NotSupportedConstraintException.php +++ b/src/PHPCR/Util/QOM/NotSupportedConstraintException.php @@ -1,5 +1,7 @@ convertSelector($source); } - throw new \InvalidArgumentException("Invalid Source"); + throw new \InvalidArgumentException('Invalid Source'); } /** * Constraint ::= And | Or | Not | Comparison | * PropertyExistence | FullTextSearch | - * SameNode | ChildNode | DescendantNode + * SameNode | ChildNode | DescendantNode. * * And ::= constraint1 'AND' constraint2 * Or ::= constraint1 'OR' constraint2 * Not ::= 'NOT' Constraint * - * @param QOM\ConstraintInterface $constraint - * @return string + * @throws \InvalidArgumentException + * @throws NotSupportedConstraintException */ - protected function convertConstraint(QOM\ConstraintInterface $constraint) + protected function convertConstraint(QOM\ConstraintInterface $constraint): string { if ($constraint instanceof QOM\AndInterface) { return $this->generator->evalAnd( $this->convertConstraint($constraint->getConstraint1()), - $this->convertConstraint($constraint->getConstraint2())); + $this->convertConstraint($constraint->getConstraint2()) + ); } + if ($constraint instanceof QOM\OrInterface) { return $this->generator->evalOr( $this->convertConstraint($constraint->getConstraint1()), - $this->convertConstraint($constraint->getConstraint2())); + $this->convertConstraint($constraint->getConstraint2()) + ); } + if ($constraint instanceof QOM\NotInterface) { return $this->generator->evalNot($this->convertConstraint($constraint->getConstraint())); } + if ($constraint instanceof QOM\ComparisonInterface) { return $this->convertComparison($constraint); } + if ($constraint instanceof QOM\PropertyExistenceInterface) { return $this->convertPropertyExistence($constraint); - } elseif ($constraint instanceof QOM\FullTextSearchInterface) { + } + if ($constraint instanceof QOM\FullTextSearchInterface) { return $this->convertFullTextSearch($constraint); } + if ($constraint instanceof QOM\SameNodeInterface) { - throw new NotSupportedConstraintException($constraint); + throw NotSupportedConstraintException::fromConstraint($constraint); } + if ($constraint instanceof QOM\ChildNodeInterface) { return $this->generator->evalChildNode( - $this->convertPath($constraint->getParentPath())); + $this->convertPath($constraint->getParentPath()) + ); } + if ($constraint instanceof QOM\DescendantNodeInterface) { return $this->generator->evalDescendantNode( - $this->convertPath($constraint->getAncestorPath())); + $this->convertPath($constraint->getAncestorPath()) + ); } // This should not happen, but who knows... - throw new \InvalidArgumentException("Invalid operand: " . get_class($constraint)); + $class = $constraint::class; + + throw new \InvalidArgumentException("Invalid operand: $class"); } /** - * DynamicOperand ::= PropertyValue | LowerCase | UpperCase + * DynamicOperand ::= PropertyValue | LowerCase | UpperCase. * * LowerCase ::= 'LOWER(' DynamicOperand ')' * UpperCase ::= 'UPPER(' DynamicOperand ')' * - * @param QOM\DynamicOperandInterface $operand - * @return string + * @throws NotSupportedOperandException + * @throws \InvalidArgumentException */ - protected function convertDynamicOperand(QOM\DynamicOperandInterface $operand) + protected function convertDynamicOperand(QOM\DynamicOperandInterface $operand): string { if ($operand instanceof QOM\PropertyValueInterface) { return $this->convertPropertyValue($operand); } + if ($operand instanceof QOM\LengthInterface) { - throw new NotSupportedOperandException($operand); + throw NotSupportedOperandException::fromOperand($operand); } + if ($operand instanceof QOM\NodeNameInterface) { - throw new NotSupportedOperandException($operand); + throw NotSupportedOperandException::fromOperand($operand); } + if ($operand instanceof QOM\NodeLocalNameInterface) { - throw new NotSupportedOperandException($operand); + throw NotSupportedOperandException::fromOperand($operand); } + if ($operand instanceof QOM\FullTextSearchScoreInterface) { - throw new NotSupportedOperandException($operand); + throw NotSupportedOperandException::fromOperand($operand); } + if ($operand instanceof QOM\LowerCaseInterface) { - $operand = $this->convertDynamicOperand($operand->getOperand()); + $operandText = $this->convertDynamicOperand($operand->getOperand()); - return $this->generator->evalLower($operand); + return $this->generator->evalLower($operandText); } + if ($operand instanceof QOM\UpperCaseInterface) { - $operand = $this->convertDynamicOperand($operand->getOperand()); + $operandText = $this->convertDynamicOperand($operand->getOperand()); - return $this->generator->evalUpper($operand); + return $this->generator->evalUpper($operandText); } // This should not happen, but who knows... - throw new \InvalidArgumentException("Invalid operand"); + throw new \InvalidArgumentException('Invalid operand'); } } diff --git a/src/PHPCR/Util/QOM/QomToSql2QueryConverter.php b/src/PHPCR/Util/QOM/QomToSql2QueryConverter.php index 1dcae723..5a364449 100644 --- a/src/PHPCR/Util/QOM/QomToSql2QueryConverter.php +++ b/src/PHPCR/Util/QOM/QomToSql2QueryConverter.php @@ -1,5 +1,7 @@ convertSelector($source); @@ -28,25 +28,26 @@ protected function convertSource(QOM\SourceInterface $source) return $this->convertJoin($source); } - throw new \InvalidArgumentException("Invalid Source"); + throw new \InvalidArgumentException('Invalid Source'); } /** * Join ::= left [JoinType] 'JOIN' right 'ON' JoinCondition * // If JoinType is omitted INNER is assumed. * left ::= Source - * right ::= Source + * right ::= Source. * * JoinType ::= Inner | LeftOuter | RightOuter * Inner ::= 'INNER' * LeftOuter ::= 'LEFT OUTER' * RightOuter ::= 'RIGHT OUTER' - * - * @param QOM\JoinInterface $join - * @return string */ - protected function convertJoin(QOM\JoinInterface $join) + protected function convertJoin(QOM\JoinInterface $join): string { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports join'); + } + $left = $this->convertSource($join->getLeft()); $right = $this->convertSource($join->getRight()); $condition = $this->convertJoinCondition($join->getJoinCondition()); @@ -58,27 +59,27 @@ protected function convertJoin(QOM\JoinInterface $join) * JoinCondition ::= EquiJoinCondition | * SameNodeJoinCondition | * ChildNodeJoinCondition | - * DescendantNodeJoinCondition + * DescendantNodeJoinCondition. * - * @param QOM\JoinConditionInterface $condition - * @return string + * @throws \InvalidArgumentException */ - protected function convertJoinCondition(QOM\JoinConditionInterface $condition) + protected function convertJoinCondition(QOM\JoinConditionInterface $condition): string { if ($condition instanceof QOM\EquiJoinConditionInterface) { - $sql2 = $this->convertEquiJoinCondition($condition); - } elseif ($condition instanceof QOM\SameNodeJoinConditionInterface) { - $sql2 = $this->convertSameNodeJoinCondition($condition); - } elseif ($condition instanceof QOM\ChildNodeJoinConditionInterface) { - $sql2 = $this->convertChildNodeJoinCondition($condition); - } elseif ($condition instanceof QOM\DescendantNodeJoinConditionInterface) { - $sql2 = $this->convertDescendantNodeJoinCondition($condition); - } else { - // This should not happen, but who knows... - throw new \InvalidArgumentException("Invalid operand"); + return $this->convertEquiJoinCondition($condition); + } + if ($condition instanceof QOM\SameNodeJoinConditionInterface) { + return $this->convertSameNodeJoinCondition($condition); + } + if ($condition instanceof QOM\ChildNodeJoinConditionInterface) { + return $this->convertChildNodeJoinCondition($condition); + } + if ($condition instanceof QOM\DescendantNodeJoinConditionInterface) { + return $this->convertDescendantNodeJoinCondition($condition); } - return $sql2; + // This should not happen, but who knows... + throw new \InvalidArgumentException('Invalid operand'); } /** @@ -87,18 +88,20 @@ protected function convertJoinCondition(QOM\JoinConditionInterface $condition) * selector1Name ::= selectorName * selector2Name ::= selectorName * property1Name ::= propertyName - * property2Name ::= propertyName - * - * @param QOM\EquiJoinConditionInterface $condition - * @return string + * property2Name ::= propertyName. */ - protected function convertEquiJoinCondition(QOM\EquiJoinConditionInterface $condition) + protected function convertEquiJoinCondition(QOM\EquiJoinConditionInterface $condition): string { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports equi join condition'); + } + return $this->generator->evalEquiJoinCondition( $condition->getSelector1Name(), $condition->getProperty1Name(), $condition->getSelector2Name(), - $condition->getProperty2Name()); + $condition->getProperty2Name() + ); } /** @@ -106,17 +109,19 @@ protected function convertEquiJoinCondition(QOM\EquiJoinConditionInterface $cond * 'ISSAMENODE(' selector1Name ',' * selector2Name * [',' selector2Path] ')' - * selector2Path ::= Path - * - * @param QOM\SameNodeJoinConditionInterface $condition - * @return string + * selector2Path ::= Path. */ - protected function convertSameNodeJoinCondition(QOM\SameNodeJoinConditionInterface $condition) + protected function convertSameNodeJoinCondition(QOM\SameNodeJoinConditionInterface $condition): string { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports same node join condition'); + } + return $this->generator->evalSameNodeJoinCondition( $condition->getSelector1Name(), $condition->getSelector2Name(), - null !== $condition->getSelector2Path() ? $this->convertPath($condition->getSelector2Path()) : null); + null !== $condition->getSelector2Path() ? $this->convertPath($condition->getSelector2Path()) : null + ); } /** @@ -124,16 +129,18 @@ protected function convertSameNodeJoinCondition(QOM\SameNodeJoinConditionInterfa * 'ISCHILDNODE(' childSelectorName ',' * parentSelectorName ')' * childSelectorName ::= selectorName - * parentSelectorName ::= selectorName - * - * @param QOM\ChildNodeJoinConditionInterface $condition - * @return string + * parentSelectorName ::= selectorName. */ - protected function convertChildNodeJoinCondition(QOM\ChildNodeJoinConditionInterface $condition) + protected function convertChildNodeJoinCondition(QOM\ChildNodeJoinConditionInterface $condition): string { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports child node join condition'); + } + return $this->generator->evalChildNodeJoinCondition( $condition->getChildSelectorName(), - $condition->getParentSelectorName()); + $condition->getParentSelectorName() + ); } /** @@ -141,22 +148,24 @@ protected function convertChildNodeJoinCondition(QOM\ChildNodeJoinConditionInter * 'ISDESCENDANTNODE(' descendantSelectorName ',' * ancestorSelectorName ')' * descendantSelectorName ::= selectorName - * ancestorSelectorName ::= selectorName - * - * @param QOM\DescendantNodeJoinConditionInterface $condition - * @return string + * ancestorSelectorName ::= selectorName. */ - protected function convertDescendantNodeJoinCondition(QOM\DescendantNodeJoinConditionInterface $condition) + protected function convertDescendantNodeJoinCondition(QOM\DescendantNodeJoinConditionInterface $condition): string { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports descendant node join condition'); + } + return $this->generator->evalDescendantNodeJoinCondition( $condition->getDescendantSelectorName(), - $condition->getAncestorSelectorName()); + $condition->getAncestorSelectorName() + ); } /** * Constraint ::= And | Or | Not | Comparison | * PropertyExistence | FullTextSearch | - * SameNode | ChildNode | DescendantNode + * SameNode | ChildNode | DescendantNode. * * And ::= constraint1 'AND' constraint2 * Or ::= constraint1 'OR' constraint2 @@ -174,56 +183,72 @@ protected function convertDescendantNodeJoinCondition(QOM\DescendantNodeJoinCond * // If only one selector exists in this query, explicit * specification of the selectorName is optional * - * @param QOM\ConstraintInterface $constraint - * @return string + * @throws \InvalidArgumentException */ - protected function convertConstraint(QOM\ConstraintInterface $constraint) + protected function convertConstraint(QOM\ConstraintInterface $constraint): string { if ($constraint instanceof QOM\AndInterface) { return $this->generator->evalAnd( $this->convertConstraint($constraint->getConstraint1()), - $this->convertConstraint($constraint->getConstraint2())); + $this->convertConstraint($constraint->getConstraint2()) + ); } + if ($constraint instanceof QOM\OrInterface) { return $this->generator->evalOr( $this->convertConstraint($constraint->getConstraint1()), - $this->convertConstraint($constraint->getConstraint2())); + $this->convertConstraint($constraint->getConstraint2()) + ); } + if ($constraint instanceof QOM\NotInterface) { return $this->generator->evalNot($this->convertConstraint($constraint->getConstraint())); } + if ($constraint instanceof QOM\ComparisonInterface) { return $this->convertComparison($constraint); } + if ($constraint instanceof QOM\PropertyExistenceInterface) { return $this->convertPropertyExistence($constraint); - } elseif ($constraint instanceof QOM\FullTextSearchInterface) { + } + if ($constraint instanceof QOM\FullTextSearchInterface) { return $this->convertFullTextSearch($constraint); } + if ($constraint instanceof QOM\SameNodeInterface) { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedConstraintException('Only SQL2 supports same node constraint'); + } + return $this->generator->evalSameNode( $this->convertPath($constraint->getPath()), - $constraint->getSelectorName()); + $constraint->getSelectorName() + ); } + if ($constraint instanceof QOM\ChildNodeInterface) { return $this->generator->evalChildNode( $this->convertPath($constraint->getParentPath()), - $constraint->getSelectorName()); + $constraint->getSelectorName() + ); } + if ($constraint instanceof QOM\DescendantNodeInterface) { return $this->generator->evalDescendantNode( $this->convertPath($constraint->getAncestorPath()), - $constraint->getSelectorName()); + $constraint->getSelectorName() + ); } // This should not happen, but who knows... - throw new \InvalidArgumentException("Invalid operand: " . get_class($constraint)); + throw new \InvalidArgumentException('Invalid operand: '.$constraint::class); } /** * DynamicOperand ::= PropertyValue | Length | NodeName | * NodeLocalName | FullTextSearchScore | - * LowerCase | UpperCase + * LowerCase | UpperCase. * * Length ::= 'LENGTH(' PropertyValue ')' * NodeName ::= 'NAME(' [selectorName] ')' // If only one selector exists @@ -232,41 +257,59 @@ protected function convertConstraint(QOM\ConstraintInterface $constraint) * LowerCase ::= 'LOWER(' DynamicOperand ')' * UpperCase ::= 'UPPER(' DynamicOperand ')' * - * @param QOM\DynamicOperandInterface $operand - * @return string + * @throws \InvalidArgumentException */ - protected function convertDynamicOperand(QOM\DynamicOperandInterface $operand) + protected function convertDynamicOperand(QOM\DynamicOperandInterface $operand): string { if ($operand instanceof QOM\PropertyValueInterface) { return $this->convertPropertyValue($operand); } + if ($operand instanceof QOM\LengthInterface) { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports length operand'); + } + return $this->generator->evalLength($this->convertPropertyValue($operand->getPropertyValue())); } if ($operand instanceof QOM\NodeNameInterface) { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports node operand'); + } + return $this->generator->evalNodeName($operand->getSelectorName()); } if ($operand instanceof QOM\NodeLocalNameInterface) { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports local node name operand'); + } + return $this->generator->evalNodeLocalName($operand->getSelectorName()); } + if ($operand instanceof QOM\FullTextSearchScoreInterface) { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports fulltext search score operand'); + } + return $this->generator->evalFullTextSearchScore($operand->getSelectorName()); } + if ($operand instanceof QOM\LowerCaseInterface) { - $operand = $this->convertDynamicOperand($operand->getOperand()); + $operandName = $this->convertDynamicOperand($operand->getOperand()); - return $this->generator->evalLower($operand); + return $this->generator->evalLower($operandName); } + if ($operand instanceof QOM\UpperCaseInterface) { - $operand = $this->convertDynamicOperand($operand->getOperand()); + $operandName = $this->convertDynamicOperand($operand->getOperand()); - return $this->generator->evalUpper($operand); + return $this->generator->evalUpper($operandName); } // This should not happen, but who knows... - throw new \InvalidArgumentException("Invalid operand"); + throw new \InvalidArgumentException('Invalid operand'); } - } diff --git a/src/PHPCR/Util/QOM/QueryBuilder.php b/src/PHPCR/Util/QOM/QueryBuilder.php index c0847ff3..046bc872 100644 --- a/src/PHPCR/Util/QOM/QueryBuilder.php +++ b/src/PHPCR/Util/QOM/QueryBuilder.php @@ -1,22 +1,26 @@ * @author Guilherme Blanco * @author Benjamin Eberlei @@ -24,88 +28,66 @@ class QueryBuilder { /** The builder states. */ - const STATE_DIRTY = 0; - const STATE_CLEAN = 1; + private const STATE_DIRTY = 0; + private const STATE_CLEAN = 1; /** - * @var integer The state of the query object. Can be dirty or clean. + * @var int The state of the query object. Can be dirty or clean. */ - private $state = self::STATE_CLEAN; + private int $state = self::STATE_CLEAN; /** - * @var QueryObjectModelFactoryInterface QOMFactory + * @var int the offset to retrieve only a slice of results */ - private $qomFactory; + private int $firstResult = 0; /** - * @var integer The maximum number of results to retrieve. + * @var int The maximum number of results to retrieve. 0 sets no maximum. */ - private $firstResult = null; + private int $maxResults = 0; /** - * @var integer The maximum number of results to retrieve. + * @var OrderingInterface[] */ - private $maxResults = null; + private array $orderings = []; - /** - * @var array with the orderings that determine the order of the result - */ - private $orderings = array(); + private ?ConstraintInterface $constraint = null; /** - * @var ConstraintInterface to apply to the query. + * @var ColumnInterface[] */ - private $constraint = null; + private array $columns = []; - /** - * @var array with the columns to be selected. - */ - private $columns = array(); + private ?SourceInterface $source = null; - /** - * @var SourceInterface source of the query. - */ - private $source = null; + private ?QueryObjectModelInterface $query = null; /** - * QOM tree - * - * @var QueryObjectModelInterface + * @var array the query parameters */ - private $query = null; + private array $params = []; - /** - * @var array The query parameters. - */ - private $params = array(); - - /** - * Initializes a new QueryBuilder - * - * @param QueryObjectModelFactoryInterface $qomFactory - */ - public function __construct(QueryObjectModelFactoryInterface $qomFactory) - { - $this->qomFactory = $qomFactory; + public function __construct( + private QueryObjectModelFactoryInterface $qomFactory + ) { } /** - * Get a query builder instance from an existing query + * Get a query builder instance from an existing query. * - * @param string $statement the statement in the specified language - * @param string $language the query language + * @param string|QueryObjectModelInterface $statement the statement in the specified language * - * @return QueryBuilder This QueryBuilder instance. + * @throws \InvalidArgumentException */ - public function setFromQuery($statement, $language) + public function setFromQuery(string|QueryObjectModelInterface $statement, string $queryLanguage): static { - if (QueryInterface::JCR_SQL2 === $language) { + if (QueryInterface::JCR_SQL2 === $queryLanguage) { $converter = new Sql2ToQomQueryConverter($this->qomFactory); $statement = $converter->parse($statement); } if (!$statement instanceof QueryObjectModelInterface) { - throw new \InvalidArgumentException("Language '$language' not supported"); + throw new \InvalidArgumentException("Language '$queryLanguage' not supported"); } $this->state = self::STATE_DIRTY; @@ -117,32 +99,23 @@ public function setFromQuery($statement, $language) return $this; } - /** - * Get the associated QOMFactory for this query builder - * - * @return QueryObjectModelFactoryInterface - */ - public function getQOMFactory() + public function getQOMFactory(): QueryObjectModelFactoryInterface { return $this->qomFactory; } /** - * Shortcut for getQOMFactory() + * Shortcut for getQOMFactory(). */ - public function qomf() + public function qomf(): QueryObjectModelFactoryInterface { return $this->getQOMFactory(); } /** * sets the position of the first result to retrieve (the "offset"). - * - * @param integer $firstResult The First result to return. - * - * @return QueryBuilder This QueryBuilder instance. */ - public function setFirstResult($firstResult) + public function setFirstResult(int $firstResult): static { $this->firstResult = $firstResult; @@ -153,22 +126,17 @@ public function setFirstResult($firstResult) * Gets the position of the first result the query object was set to retrieve (the "offset"). * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder. * - * @return integer The position of the first result. + * @return int the position of the first result */ - public function getFirstResult() + public function getFirstResult(): int { return $this->firstResult; } /** - * * Sets the maximum number of results to retrieve (the "limit"). - * - * @param integer $maxResults The maximum number of results to retrieve. - * - * @return QueryBuilder This QueryBuilder instance. */ - public function setMaxResults($maxResults) + public function setMaxResults(int $maxResults): static { $this->maxResults = $maxResults; @@ -178,20 +146,16 @@ public function setMaxResults($maxResults) /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if {@link setMaxResults} was not applied to this query builder. - * - * @return integer Maximum number of results. */ - public function getMaxResults() + public function getMaxResults(): int { return $this->maxResults; } /** - * Gets the array of orderings. - * - * @return \PHPCR\Query\QOM\OrderingInterface[] Orderings to apply. + * @return OrderingInterface[] */ - public function getOrderings() + public function getOrderings(): array { return $this->orderings; } @@ -199,21 +163,21 @@ public function getOrderings() /** * Adds an ordering to the query results. * - * @param DynamicOperandInterface $sort The ordering expression. - * @param string $order The ordering direction. + * @param DynamicOperandInterface $sort the ordering expression + * @param string $order the ordering direction * - * @return QueryBuilder This QueryBuilder instance. + * @throws \InvalidArgumentException */ - public function addOrderBy(DynamicOperandInterface $sort, $order = 'ASC') + public function addOrderBy(DynamicOperandInterface $sort, string $order = 'ASC'): static { $order = strtoupper($order); - if (!in_array($order, array('ASC', 'DESC'))) { + if (!in_array($order, ['ASC', 'DESC'])) { throw new \InvalidArgumentException('Order must be one of "ASC" or "DESC"'); } $this->state = self::STATE_DIRTY; - if ($order == 'DESC') { + if ('DESC' === $order) { $ordering = $this->qomFactory->descending($sort); } else { $ordering = $this->qomFactory->ascending($sort); @@ -227,14 +191,12 @@ public function addOrderBy(DynamicOperandInterface $sort, $order = 'ASC') * Specifies an ordering for the query results. * Replaces any previously specified orderings, if any. * - * @param DynamicOperandInterface $sort The ordering expression. - * @param string $order The ordering direction. - * - * @return QueryBuilder This QueryBuilder instance. + * @param DynamicOperandInterface $sort the ordering expression + * @param string $order the ordering direction */ - public function orderBy(DynamicOperandInterface $sort, $order = 'ASC') + public function orderBy(DynamicOperandInterface $sort, string $order = 'ASC'): static { - $this->orderings = array(); + $this->orderings = []; $this->addOrderBy($sort, $order); return $this; @@ -243,12 +205,8 @@ public function orderBy(DynamicOperandInterface $sort, $order = 'ASC') /** * Specifies one restriction (may be simple or composed). * Replaces any previously specified restrictions, if any. - * - * @param ConstraintInterface $constraint - * - * @return QueryBuilder This QueryBuilder instance. */ - public function where(ConstraintInterface $constraint) + public function where(ConstraintInterface $constraint): static { $this->state = self::STATE_DIRTY; $this->constraint = $constraint; @@ -257,18 +215,16 @@ public function where(ConstraintInterface $constraint) } /** - * Returns the constraint to apply - * - * @return ConstraintInterface the constraint to be applied + * Returns the constraint to apply. */ - public function getConstraint() + public function getConstraint(): ?ConstraintInterface { return $this->constraint; } /** * Creates a new constraint formed by applying a logical AND to the - * existing constraint and the new one + * existing constraint and the new one. * * Order of ands is important: * @@ -278,14 +234,11 @@ public function getConstraint() * * If there is no previous constraint then it will simply store the * provided one - * - * @param ConstraintInterface $constraint - * - * @return QueryBuilder This QueryBuilder instance. */ - public function andWhere(ConstraintInterface $constraint) + public function andWhere(ConstraintInterface $constraint): static { $this->state = self::STATE_DIRTY; + if ($this->constraint) { $this->constraint = $this->qomFactory->andConstraint($this->constraint, $constraint); } else { @@ -297,7 +250,7 @@ public function andWhere(ConstraintInterface $constraint) /** * Creates a new constraint formed by applying a logical OR to the - * existing constraint and the new one + * existing constraint and the new one. * * Order of ands is important: * @@ -307,14 +260,11 @@ public function andWhere(ConstraintInterface $constraint) * * If there is no previous constraint then it will simply store the * provided one - * - * @param ConstraintInterface $constraint - * - * @return QueryBuilder This QueryBuilder instance. */ - public function orWhere(ConstraintInterface $constraint) + public function orWhere(ConstraintInterface $constraint): static { $this->state = self::STATE_DIRTY; + if ($this->constraint) { $this->constraint = $this->qomFactory->orConstraint($this->constraint, $constraint); } else { @@ -325,23 +275,17 @@ public function orWhere(ConstraintInterface $constraint) } /** - * Returns the columns to be selected - * - * @return \PHPCR\Query\QOM\ColumnInterface[] The columns to be selected + * @return ColumnInterface[] */ - public function getColumns() + public function getColumns(): array { return $this->columns; } /** - * Sets the columns to be selected - * - * @param \PHPCR\Query\QOM\ColumnInterface[] $columns The columns to be selected - * - * @return QueryBuilder This QueryBuilder instance. + * @param ColumnInterface[] $columns */ - public function setColumns(array $columns) + public function setColumns(array $columns): static { $this->columns = $columns; @@ -351,33 +295,22 @@ public function setColumns(array $columns) /** * Identifies a property in the specified or default selector to include in the tabular view of query results. * Replaces any previously specified columns to be selected if any. - * - * @param string $selectorName - * @param string $propertyName - * @param string $columnName - * - * @return QueryBuilder This QueryBuilder instance. */ - public function select($selectorName, $propertyName, $columnName = null) + public function select(string $selectorName, string $propertyName, ?string $columnName = null): static { $this->state = self::STATE_DIRTY; - $this->columns = array($this->qomFactory->column($selectorName, $propertyName, $columnName)); + $this->columns = [$this->qomFactory->column($selectorName, $propertyName, $columnName)]; return $this; } /** * Adds a property in the specified or default selector to include in the tabular view of query results. - * - * @param string $selectorName - * @param string $propertyName - * @param string $columnName - * - * @return QueryBuilder This QueryBuilder instance. */ - public function addSelect($selectorName, $propertyName, $columnName = null) + public function addSelect(string $selectorName, string $propertyName, ?string $columnName = null): static { $this->state = self::STATE_DIRTY; + $this->columns[] = $this->qomFactory->column($selectorName, $propertyName, $columnName); return $this; @@ -386,12 +319,8 @@ public function addSelect($selectorName, $propertyName, $columnName = null) /** * Sets the default Selector or the node-tuple Source. Can be a selector * or a join. - * - * @param SourceInterface $source - * - * @return QueryBuilder This QueryBuilder instance. */ - public function from(SourceInterface $source) + public function from(SourceInterface $source): static { $this->state = self::STATE_DIRTY; $this->source = $source; @@ -402,9 +331,9 @@ public function from(SourceInterface $source) /** * Gets the default Selector. * - * @return SourceInterface The default selector. + * @return SourceInterface|null the default selector */ - public function getSource() + public function getSource(): ?SourceInterface { return $this->source; } @@ -412,14 +341,9 @@ public function getSource() /** * Performs an inner join between the stored source and the supplied source. * - * @param SourceInterface $rightSource - * @param JoinConditionInterface $joinCondition - * - * @return QueryBuilder This QueryBuilder instance. - * - * @throws RuntimeException if there is not an existing source. + * @throws \RuntimeException if there is not an existing source */ - public function join(SourceInterface $rightSource, JoinConditionInterface $joinCondition) + public function join(SourceInterface $rightSource, JoinConditionInterface $joinCondition): static { return $this->innerJoin($rightSource, $joinCondition); } @@ -427,14 +351,9 @@ public function join(SourceInterface $rightSource, JoinConditionInterface $joinC /** * Performs an inner join between the stored source and the supplied source. * - * @param SourceInterface $rightSource - * @param JoinConditionInterface $joinCondition - * - * @return QueryBuilder This QueryBuilder instance. - * - * @throws RuntimeException if there is not an existing source. + * @throws \RuntimeException if there is not an existing source */ - public function innerJoin(SourceInterface $rightSource, JoinConditionInterface $joinCondition) + public function innerJoin(SourceInterface $rightSource, JoinConditionInterface $joinCondition): static { return $this->joinWithType($rightSource, QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_INNER, $joinCondition); } @@ -442,14 +361,9 @@ public function innerJoin(SourceInterface $rightSource, JoinConditionInterface $ /** * Performs an left outer join between the stored source and the supplied source. * - * @param SourceInterface $rightSource - * @param JoinConditionInterface $joinCondition - * - * @return QueryBuilder This QueryBuilder instance. - * - * @throws RuntimeException if there is not an existing source. + * @throws \RuntimeException if there is not an existing source */ - public function leftJoin(SourceInterface $rightSource, JoinConditionInterface $joinCondition) + public function leftJoin(SourceInterface $rightSource, JoinConditionInterface $joinCondition): static { return $this->joinWithType($rightSource, QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_LEFT_OUTER, $joinCondition); } @@ -457,14 +371,9 @@ public function leftJoin(SourceInterface $rightSource, JoinConditionInterface $j /** * Performs a right outer join between the stored source and the supplied source. * - * @param SourceInterface $rightSource - * @param JoinConditionInterface $joinCondition - * - * @return QueryBuilder This QueryBuilder instance. - * - * @throws RuntimeException if there is not an existing source. + * @throws \RuntimeException if there is not an existing source */ - public function rightJoin(SourceInterface $rightSource, JoinConditionInterface $joinCondition) + public function rightJoin(SourceInterface $rightSource, JoinConditionInterface $joinCondition): static { return $this->joinWithType($rightSource, QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_RIGHT_OUTER, $joinCondition); } @@ -472,19 +381,16 @@ public function rightJoin(SourceInterface $rightSource, JoinConditionInterface $ /** * Performs an join between the stored source and the supplied source. * - * @param SourceInterface $rightSource - * @param string $joinType as specified in PHPCR\Query\QOM\QueryObjectModelConstantsInterface - * @param JoinConditionInterface $joinCondition - * - * @return QueryBuilder This QueryBuilder instance. + * @param string $joinType as specified in PHPCR\Query\QOM\QueryObjectModelConstantsInterface * - * @throws RuntimeException if there is not an existing source. + * @throws \RuntimeException if there is not an existing source */ - public function joinWithType(SourceInterface $rightSource, $joinType, JoinConditionInterface $joinCondition) + public function joinWithType(SourceInterface $rightSource, string $joinType, JoinConditionInterface $joinCondition): static { if (!$this->source) { throw new \RuntimeException('Cannot perform a join without a previous call to from'); } + $this->state = self::STATE_DIRTY; $this->source = $this->qomFactory->join($this->source, $rightSource, $joinType, $joinCondition); @@ -492,15 +398,14 @@ public function joinWithType(SourceInterface $rightSource, $joinType, JoinCondit } /** - * Gets the query built - * - * @return QueryObjectModelInterface + * Gets the query built. */ - public function getQuery() + public function getQuery(): QueryObjectModelInterface { - if ($this->query !== null && $this->state === self::STATE_CLEAN) { + if (null !== $this->query && self::STATE_CLEAN === $this->state) { return $this->query; } + $this->state = self::STATE_CLEAN; $this->query = $this->qomFactory->createQuery($this->source, $this->constraint, $this->orderings, $this->columns); @@ -517,12 +422,10 @@ public function getQuery() /** * Executes the query setting firstResult and maxResults. - * - * @return \PHPCR\Query\QueryResultInterface */ - public function execute() + public function execute(): QueryResultInterface { - if ($this->query === null || $this->state === self::STATE_DIRTY) { + if (null === $this->query || self::STATE_DIRTY === $this->state) { $this->query = $this->getQuery(); } @@ -530,46 +433,33 @@ public function execute() $this->query->bindValue($key, $value); } - $queryResult = $this->query->execute(); - - return $queryResult; + return $this->query->execute(); } /** * Sets a query parameter for the query being constructed. - * - * @param string $key The parameter name. - * @param mixed $value The parameter value. - * - * @return QueryBuilder This QueryBuilder instance. */ - public function setParameter($key, $value) + public function setParameter(string $parameterName, mixed $parameterValue): static { - $this->params[$key] = $value; + $this->params[$parameterName] = $parameterValue; return $this; } /** * Gets a (previously set) query parameter of the query being constructed. - * - * @param string $key The key (name) of the bound parameter. - * - * @return mixed The value of the bound parameter. */ - public function getParameter($key) + public function getParameter(string $parameterName): mixed { - return isset($this->params[$key]) ? $this->params[$key] : null; + return $this->params[$parameterName] ?? null; } /** * Sets a collection of query parameters for the query being constructed. * - * @param array $params The query parameters to set. - * - * @return QueryBuilder This QueryBuilder instance. + * @param array $params the query parameters to set */ - public function setParameters(array $params) + public function setParameters(array $params): static { $this->params = $params; @@ -577,11 +467,9 @@ public function setParameters(array $params) } /** - * Gets all defined query parameters for the query being constructed. - * - * @return array The currently defined query parameters. + * @return array Map of parameter name => parameter value */ - public function getParameters() + public function getParameters(): array { return $this->params; } diff --git a/src/PHPCR/Util/QOM/Sql1Generator.php b/src/PHPCR/Util/QOM/Sql1Generator.php index bbdf890e..452a164c 100644 --- a/src/PHPCR/Util/QOM/Sql1Generator.php +++ b/src/PHPCR/Util/QOM/Sql1Generator.php @@ -1,8 +1,8 @@ getPathForDescendantQuery($path); - $sql1 = "jcr:path LIKE '" . $path ."'"; - $sql1 .= " AND NOT jcr:path LIKE '" . $path . "/%'"; + $sql1 = "jcr:path LIKE '".$path."'"; + $sql1 .= " AND NOT jcr:path LIKE '".$path."/%'"; return $sql1; } /** - * Emulate descendant query with LIKE query - * - * @param string $path + * Emulate descendant query with LIKE query. * - * @return string + * @param string|null $selectorName Unused */ - public function evalDescendantNode($path) + public function evalDescendantNode(string $path, ?string $selectorName = null): string { $path = $this->getPathForDescendantQuery($path); - $sql1 = "jcr:path LIKE '" . $path . "'"; - return $sql1; + return "jcr:path LIKE '".$path."'"; } /** * PropertyExistence ::= - * propertyName 'IS NOT NULL' - - * @param string $selectorName declared to simplifiy interface - as there - * are no joins in SQL1 there is no need for a selector. - * @param string $propertyName + * propertyName 'IS NOT NULL'. * - * @return string + * @param string|null $selectorName declared to simplifiy interface - as there + * are no joins in SQL1 there is no need for a selector */ - public function evalPropertyExistence($selectorName, $propertyName) + public function evalPropertyExistence(?string $selectorName, string $propertyName): string { - return $propertyName . " IS NOT NULL"; + return $propertyName.' IS NOT NULL'; } /** * FullTextSearch ::= * 'CONTAINS(' (propertyName | '*') ') ',' * FullTextSearchExpression ')' - * FullTextSearchExpression ::= BindVariable | ''' FullTextSearchLiteral ''' + * FullTextSearchExpression ::= BindVariable | ''' FullTextSearchLiteral '''. * - * @param string $selectorName unusued - * @param string $searchExpression - * @param string $propertyName - * @return string + * @param string $selectorName unusued */ - public function evalFullTextSearch($selectorName, $searchExpression, $propertyName = null) + public function evalFullTextSearch(string $selectorName, string $searchExpression, ?string $propertyName = null): string { - $propertyName = $propertyName ? : '*'; + $propertyName = $propertyName ?: '*'; $sql1 = 'CONTAINS('; $sql1 .= $propertyName; - $sql1 .= ', ' . $searchExpression . ')'; + $sql1 .= ', '.$searchExpression.')'; return $sql1; } /** - * columns ::= (Column ',' {Column}) | '*' - * - * @param $columns - * - * @return string + * columns ::= (Column ',' {Column}) | '*'. */ - public function evalColumns($columns) + public function evalColumns(iterable $columns): string { - if (count($columns) === 0) { + if ((!is_array($columns) && !$columns instanceof \Countable) + || 0 === count($columns) + ) { return 's'; } $sql1 = ''; foreach ($columns as $column) { - if ($sql1 !== '') { + if ('' !== $sql1) { $sql1 .= ', '; } @@ -138,52 +119,44 @@ public function evalColumns($columns) } /** - * PropertyValue ::= propertyName + * PropertyValue ::= propertyName. * - * @param string $propertyName - * @param string $selectorName unused in SQL1 + * @param string|null $selectorName unused in SQL1 */ - public function evalPropertyValue($propertyName, $selectorName = null) + public function evalPropertyValue(string $propertyName, ?string $selectorName = null): string { return $propertyName; } - /** * Column ::= (propertyName) - * propertyName ::= Name + * propertyName ::= Name. * * No support for column name ('AS' columnName) in SQL1 * - * @param string $selectorName unused in SQL1 - * @param string $propertyName - * - * @return string + * @param string $selectorName unused in SQL1 + * @param string|null $colname unused in SQL1 */ - - public function evalColumn($selectorName = null, $propertyName = null) + public function evalColumn(string $selectorName, ?string $propertyName = null, ?string $colname = null): string { return $propertyName; } /** * Path ::= simplePath - * simplePath ::= A JCR Name that contains only SQL-legal characters - * - * @param string $path - * @return string + * simplePath ::= A JCR Name that contains only SQL-legal characters. */ - public function evalPath($path) + public function evalPath(string $path): string { return $path; } /** - * {@inheritDoc} + * {@inheritdoc} * * No explicit support, do some tricks where possible. */ - public function evalCastLiteral($literal, $type) + public function evalCastLiteral(string $literal, string $type): string { switch ($type) { case 'DATE': @@ -192,7 +165,7 @@ public function evalCastLiteral($literal, $type) return $literal; case 'DOUBLE': if ((int) $literal == $literal) { - return $literal .".0"; + return $literal.'.0'; } return $literal; diff --git a/src/PHPCR/Util/QOM/Sql2Generator.php b/src/PHPCR/Util/QOM/Sql2Generator.php index 6053e120..7c042f1d 100644 --- a/src/PHPCR/Util/QOM/Sql2Generator.php +++ b/src/PHPCR/Util/QOM/Sql2Generator.php @@ -1,8 +1,9 @@ addBracketsIfNeeded($nodeTypeName); if (null !== $selectorName && $nodeTypeName !== $selectorName) { // if the selector name is the same as the type name, this is implicit for sql2 - $sql2 .= ' AS ' . $selectorName; + $sql2 .= ' AS '.$selectorName; } return $sql2; @@ -41,16 +40,9 @@ public function evalSelector($nodeTypeName, $selectorName = null) * Join ::= left [JoinType] 'JOIN' right 'ON' JoinCondition * // If JoinType is omitted INNER is assumed. * left ::= Source - * right ::= Source - * - * @param string $left - * @param string $right - * @param string $joinCondition - * @param string $joinType - * - * @return string + * right ::= Source. */ - public function evalJoin($left, $right, $joinCondition, $joinType = '') + public function evalJoin(string $left, string $right, string $joinCondition, string $joinType = ''): string { return "$left {$joinType}JOIN $right ON $joinCondition"; } @@ -59,23 +51,16 @@ public function evalJoin($left, $right, $joinCondition, $joinType = '') * JoinType ::= Inner | LeftOuter | RightOuter * Inner ::= 'INNER' * LeftOuter ::= 'LEFT OUTER' - * RightOuter ::= 'RIGHT OUTER' - * - * @param string $joinType - * @return string + * RightOuter ::= 'RIGHT OUTER'. */ - public function evalJoinType($joinType) + public function evalJoinType(string $joinType): string { - switch ($joinType) { - case Constants::JCR_JOIN_TYPE_INNER: - return 'INNER '; - case Constants::JCR_JOIN_TYPE_LEFT_OUTER: - return 'LEFT OUTER '; - case Constants::JCR_JOIN_TYPE_RIGHT_OUTER: - return 'RIGHT OUTER '; - } - - return $joinType; + return match ($joinType) { + Constants::JCR_JOIN_TYPE_INNER => 'INNER ', + Constants::JCR_JOIN_TYPE_LEFT_OUTER => 'LEFT OUTER ', + Constants::JCR_JOIN_TYPE_RIGHT_OUTER => 'RIGHT OUTER ', + default => $joinType, + }; } /** @@ -84,17 +69,11 @@ public function evalJoinType($joinType) * selector1Name ::= selectorName * selector2Name ::= selectorName * property1Name ::= propertyName - * property2Name ::= propertyName - * - * @param string $sel1Name - * @param string $prop1Name - * @param string $sel2Name - * @param string $prop2Name - * @return string + * property2Name ::= propertyName. */ - public function evalEquiJoinCondition($sel1Name, $prop1Name, $sel2Name, $prop2Name) + public function evalEquiJoinCondition(string $sel1Name, string $prop1Name, string $sel2Name, string $prop2Name): string { - return $this->evalPropertyValue($prop1Name, $sel1Name) . '=' .$this->evalPropertyValue($prop2Name, $sel2Name); + return $this->evalPropertyValue($prop1Name, $sel1Name).'='.$this->evalPropertyValue($prop2Name, $sel2Name); } /** @@ -102,21 +81,15 @@ public function evalEquiJoinCondition($sel1Name, $prop1Name, $sel2Name, $prop2Na * 'ISSAMENODE(' selector1Name ',' * selector2Name * [',' selector2Path] ')' - * selector2Path ::= Path - * - * @param string $sel1Name - * @param string $sel2Name - * @param string $sel2Path - * @return string + * selector2Path ::= Path. */ - public function evalSameNodeJoinCondition($sel1Name, $sel2Name, $sel2Path = null) + public function evalSameNodeJoinCondition(string $sel1Name, string $sel2Name, ?string $sel2Path = null): string { $sql2 = 'ISSAMENODE(' - . $this->addBracketsIfNeeded($sel1Name) . ', ' - . $this->addBracketsIfNeeded($sel2Name) - ; + .$this->addBracketsIfNeeded($sel1Name).', ' + .$this->addBracketsIfNeeded($sel2Name); if (null !== $sel2Path) { - $sql2 .= ', ' . $sel2Path; + $sql2 .= ', '.$sel2Path; } $sql2 .= ')'; @@ -128,18 +101,13 @@ public function evalSameNodeJoinCondition($sel1Name, $sel2Name, $sel2Path = null * 'ISCHILDNODE(' childSelectorName ',' * parentSelectorName ')' * childSelectorName ::= selectorName - * parentSelectorName ::= selectorName - * - * @param string $childSelectorName - * @param string $parentSelectorName - * @return string + * parentSelectorName ::= selectorName. */ - public function evalChildNodeJoinCondition($childSelectorName, $parentSelectorName) + public function evalChildNodeJoinCondition(string $childSelectorName, string $parentSelectorName): string { return 'ISCHILDNODE(' - . $this->addBracketsIfNeeded($childSelectorName) . ', ' - . $this->addBracketsIfNeeded($parentSelectorName) . ')' - ; + .$this->addBracketsIfNeeded($childSelectorName).', ' + .$this->addBracketsIfNeeded($parentSelectorName).')'; } /** @@ -147,60 +115,46 @@ public function evalChildNodeJoinCondition($childSelectorName, $parentSelectorNa * 'ISDESCENDANTNODE(' descendantSelectorName ',' * ancestorSelectorName ')' * descendantSelectorName ::= selectorName - * ancestorSelectorName ::= selectorName - * - * @param string $descendantSelectorName - * @param string $ancestorselectorName - * @return string + * ancestorSelectorName ::= selectorName. */ - public function evalDescendantNodeJoinCondition($descendantSelectorName, $ancestorselectorName) + public function evalDescendantNodeJoinCondition(string $descendantSelectorName, string $ancestorselectorName): string { return 'ISDESCENDANTNODE(' - . $this->addBracketsIfNeeded($descendantSelectorName) . ', ' - . $this->addBracketsIfNeeded($ancestorselectorName) . ')' - ; + .$this->addBracketsIfNeeded($descendantSelectorName).', ' + .$this->addBracketsIfNeeded($ancestorselectorName).')'; } /** - * SameNode ::= 'ISSAMENODE(' [selectorName ','] Path ')' - * - * @param string $path - * @param string $selectorName + * SameNode ::= 'ISSAMENODE(' [selectorName ','] Path ')'. */ - public function evalSameNode($path, $selectorName = null) + public function evalSameNode(string $path, ?string $selectorName = null): string { $sql2 = 'ISSAMENODE('; - $sql2 .= null === $selectorName ? $path : $this->addBracketsIfNeeded($selectorName) . ', ' . $path; + $sql2 .= null === $selectorName ? $path : $this->addBracketsIfNeeded($selectorName).', '.$path; $sql2 .= ')'; return $sql2; } /** - * SameNode ::= 'ISCHILDNODE(' [selectorName ','] Path ')' - * - * @param string $path - * @param string $selectorName + * SameNode ::= 'ISCHILDNODE(' [selectorName ','] Path ')'. */ - public function evalChildNode($path, $selectorName = null) + public function evalChildNode(string $path, ?string $selectorName = null): string { $sql2 = 'ISCHILDNODE('; - $sql2 .= null === $selectorName ? $path : $this->addBracketsIfNeeded($selectorName) . ', ' . $path; + $sql2 .= null === $selectorName ? $path : $this->addBracketsIfNeeded($selectorName).', '.$path; $sql2 .= ')'; return $sql2; } /** - * SameNode ::= 'ISDESCENDANTNODE(' [selectorName ','] Path ')' - * - * @param string $path - * @param string $selectorName + * SameNode ::= 'ISDESCENDANTNODE(' [selectorName ','] Path ')'. */ - public function evalDescendantNode($path, $selectorName = null) + public function evalDescendantNode(string $path, ?string $selectorName = null): string { $sql2 = 'ISDESCENDANTNODE('; - $sql2 .= null === $selectorName ? $path : $this->addBracketsIfNeeded($selectorName) . ', ' . $path; + $sql2 .= null === $selectorName ? $path : $this->addBracketsIfNeeded($selectorName).', '.$path; $sql2 .= ')'; return $sql2; @@ -211,16 +165,11 @@ public function evalDescendantNode($path, $selectorName = null) * selectorName'.'propertyName 'IS NOT NULL' | * propertyName 'IS NOT NULL' If only one * selector exists in - * this query - * - * @param $selectorName - * @param $propertyName - * - * @return string + * this query. */ - public function evalPropertyExistence($selectorName, $propertyName) + public function evalPropertyExistence(?string $selectorName, string $propertyName): string { - return $this->evalPropertyValue($propertyName, $selectorName) . " IS NOT NULL"; + return $this->evalPropertyValue($propertyName, $selectorName).' IS NOT NULL'; } /** @@ -228,74 +177,58 @@ public function evalPropertyExistence($selectorName, $propertyName) * 'CONTAINS(' ([selectorName'.']propertyName | * selectorName'.*') ',' * FullTextSearchExpression ')' - * FullTextSearchExpression ::= BindVariable | ''' FullTextSearchLiteral ''' - * @param string $selectorName unusued - * @param string $searchExpression - * @param string $propertyName - * @return string + * FullTextSearchExpression ::= BindVariable | ''' FullTextSearchLiteral '''. */ - public function evalFullTextSearch($selectorName, $searchExpression, $propertyName = null) + public function evalFullTextSearch(string $selectorName, string $searchExpression, ?string $propertyName = null): string { $propertyName = $propertyName ?: '*'; $sql2 = 'CONTAINS('; $sql2 .= $this->evalPropertyValue($propertyName, $selectorName); - $sql2 .= ', ' . $searchExpression . ')'; + $sql2 .= ', '.$searchExpression.')'; return $sql2; } /** - * Length ::= 'LENGTH(' PropertyValue ')' - * - * @param string $propertyValue - * @return string + * Length ::= 'LENGTH(' PropertyValue ')'. */ - public function evalLength($propertyValue) + public function evalLength(string $propertyValue): string { return "LENGTH($propertyValue)"; } /** - * NodeName ::= 'NAME(' [selectorName] ')' - * - * @param string $selectorValue + * NodeName ::= 'NAME(' [selectorName] ')'. */ - public function evalNodeName($selectorValue = null) + public function evalNodeName(?string $selectorValue = null): string { return "NAME($selectorValue)"; } /** - * NodeLocalName ::= 'LOCALNAME(' [selectorName] ')' - * - * @param string $selectorValue + * NodeLocalName ::= 'LOCALNAME(' [selectorName] ')'. */ - public function evalNodeLocalName($selectorValue = null) + public function evalNodeLocalName(?string $selectorValue = null): string { return "LOCALNAME($selectorValue)"; } /** - * FullTextSearchScore ::= 'SCORE(' [selectorName] ')' - * - * @param string $selectorValue + * FullTextSearchScore ::= 'SCORE(' [selectorName] ')'. */ - public function evalFullTextSearchScore($selectorValue = null) + public function evalFullTextSearchScore(?string $selectorValue = null): string { return "SCORE($selectorValue)"; } /** - * PropertyValue ::= [selectorName'.'] propertyName // If only one selector exists - * - * @param string $propertyName - * @param string $selectorName + * PropertyValue ::= [selectorName'.'] propertyName // If only one selector exists. */ - public function evalPropertyValue($propertyName, $selectorName = null) + public function evalPropertyValue(string $propertyName, ?string $selectorName = null): string { - $sql2 = null !== $selectorName ? $this->addBracketsIfNeeded($selectorName) . '.' : ''; - if (false !== strpos($propertyName, ':')) { + $sql2 = null !== $selectorName ? $this->addBracketsIfNeeded($selectorName).'.' : ''; + if ('*' !== $propertyName && !str_starts_with($propertyName, '[')) { $propertyName = "[$propertyName]"; } $sql2 .= $propertyName; @@ -304,21 +237,19 @@ public function evalPropertyValue($propertyName, $selectorName = null) } /** - * columns ::= (Column ',' {Column}) | '*' - * - * @param $columns - * - * @return string + * columns ::= (Column ',' {Column}) | '*'. */ - public function evalColumns($columns) + public function evalColumns(iterable $columns): string { - if (count($columns) === 0) { + if ((!is_array($columns) && !$columns instanceof \Countable) + || 0 === count($columns) + ) { return '*'; } $sql2 = ''; foreach ($columns as $column) { - if ($sql2 !== '') { + if ('' !== $sql2) { $sql2 .= ', '; } @@ -334,24 +265,18 @@ public function evalColumns($columns) * (selectorName'.*') // If only one selector exists * selectorName ::= Name * propertyName ::= Name - * columnName ::= Name - * - * @param string $selectorName - * @param string $propertyName - * @param string $colname - * - * @return string + * columnName ::= Name. */ - public function evalColumn($selectorName, $propertyName = null, $colname = null) + public function evalColumn(string $selectorName, ?string $propertyName = null, ?string $colname = null): string { $sql2 = ''; if (null !== $selectorName && null === $propertyName && null === $colname) { - $sql2 .= $this->addBracketsIfNeeded($selectorName) . '.*'; + $sql2 .= $this->addBracketsIfNeeded($selectorName).'.*'; } else { $sql2 .= $this->evalPropertyValue($propertyName, $selectorName); if (null !== $colname && $colname !== $propertyName) { // if the column name is the same as the property name, this is implicit for sql2 - $sql2 .= ' AS ' . $colname; + $sql2 .= ' AS '.$colname; } } @@ -361,51 +286,45 @@ public function evalColumn($selectorName, $propertyName = null, $colname = null) /** * Path ::= '[' quotedPath ']' | '[' simplePath ']' | simplePath * quotedPath ::= A JCR Path that contains non-SQL-legal characters - * simplePath ::= A JCR Name that contains only SQL-legal characters - * - * @param string $path - * @return string + * simplePath ::= A JCR Name that contains only SQL-legal characters. */ - public function evalPath($path) + public function evalPath(string $path): string { - if ($path) { - $sql2 = $path; - // only ensure proper quoting if the user did not quote himself, we trust him to get it right if he did. - if (substr($path, 0,1) !== '[' && substr($path, -1) !== ']') { - if (false !== strpos($sql2, ' ') || false !== strpos($sql2, '.')) { - $sql2 = '"' . $sql2 . '"'; - } - $sql2 = '[' . $sql2 . ']'; + if (!$path) { + return $path; + } + $sql2 = $path; + // only ensure proper quoting if the user did not quote himself, we trust him to get it right if he did. + if (!str_starts_with($path, '[') && !str_ends_with($path, ']')) { + if (str_contains($sql2, ' ') || str_contains($sql2, '.')) { + $sql2 = '"'.$sql2.'"'; } - - return $sql2; + $sql2 = '['.$sql2.']'; } - return null; + return $sql2; } /** - * {@inheritDoc} + * {@inheritdoc} * * CastLiteral ::= 'CAST(' UncastLiteral ' AS ' PropertyType ')' */ - public function evalCastLiteral($literal, $type) + public function evalCastLiteral(string $literal, string $type): string { return "CAST('$literal' AS $type)"; } /** - * Add square brackets around a selector if needed - * - * @param string $selector + * Add square brackets around a selector if needed. * * @return string $selector guaranteed to have [] around it if needed */ - private function addBracketsIfNeeded($selector) + private function addBracketsIfNeeded(string $selector): string { - if (substr($selector, 0, 1) !== '[' - && substr($selector, -1) !== ']' - && false !== strpos($selector, ':') + if (!str_starts_with($selector, '[') + && !str_ends_with($selector, ']') + && str_contains($selector, ':') ) { return "[$selector]"; } diff --git a/src/PHPCR/Util/QOM/Sql2Scanner.php b/src/PHPCR/Util/QOM/Sql2Scanner.php index 9afafe89..0ec2a830 100644 --- a/src/PHPCR/Util/QOM/Sql2Scanner.php +++ b/src/PHPCR/Util/QOM/Sql2Scanner.php @@ -1,5 +1,7 @@ sql2 = $sql2; $this->tokens = $this->scan($this->sql2); @@ -56,10 +42,8 @@ public function __construct($sql2) * Return an empty string when there are no more tokens. * * @param int $offset number of tokens to look ahead - defaults to 0, the current token - * - * @return string */ - public function lookupNextToken($offset = 0) + public function lookupNextToken(int $offset = 0): string { if ($this->curpos + $offset < count($this->tokens)) { return trim($this->tokens[$this->curpos + $offset]); @@ -68,28 +52,15 @@ public function lookupNextToken($offset = 0) return ''; } - /** - * Get the delimiter that separated the two previous tokens - * - * @return string - */ - public function getPreviousDelimiter() - { - - return isset($this->delimiters[$this->curpos - 1]) ? $this->delimiters[$this->curpos - 1] : ' '; - } - /** * Get the next token and remove it from the queue. * Return an empty string when there are no more tokens. - * - * @return string */ - public function fetchNextToken() + public function fetchNextToken(): string { $token = $this->lookupNextToken(); - if ($token !== '') { - $this->curpos += 1; + if ('' !== $token) { + ++$this->curpos; } return trim($token); @@ -100,13 +71,14 @@ public function fetchNextToken() * not the case. The equality test is done case sensitively/insensitively * depending on the second parameter. * - * @param string $token The expected token - * @param boolean $case_insensitive + * @param string $token The expected token + * + * @throws InvalidQueryException */ - public function expectToken($token, $case_insensitive = true) + public function expectToken(string $token, bool $case_insensitive = true): void { $nextToken = $this->fetchNextToken(); - if (! $this->tokenIs($nextToken, $token, $case_insensitive)) { + if (!$this->tokenIs($nextToken, $token, $case_insensitive)) { throw new InvalidQueryException("Syntax error: Expected '$token', found '$nextToken' in {$this->sql2}"); } } @@ -114,12 +86,12 @@ public function expectToken($token, $case_insensitive = true) /** * Expect the next tokens to be the one given in the array of tokens and * throws an exception if it's not the case. - * @see expectToken * - * @param array $tokens - * @param boolean $case_insensitive + * @throws InvalidQueryException + * + * @see expectToken */ - public function expectTokens($tokens, $case_insensitive = true) + public function expectTokens(array $tokens, bool $case_insensitive = true): void { foreach ($tokens as $token) { $this->expectToken($token, $case_insensitive); @@ -127,14 +99,9 @@ public function expectTokens($tokens, $case_insensitive = true) } /** - * Test the equality of two tokens - * - * @param string $token - * @param string $value - * @param boolean $case_insensitive - * @return boolean + * Test the equality of two tokens. */ - public function tokenIs($token, $value, $case_insensitive = true) + public function tokenIs(string $token, string $value, bool $case_insensitive = true): bool { if ($case_insensitive) { $test = strtoupper($token) === strtoupper($value); @@ -146,57 +113,103 @@ public function tokenIs($token, $value, $case_insensitive = true) } /** - * Scan a SQL2 string a extract the tokens + * Scan a SQL2 string and extract the tokens. + * + * @param string $sql2 * - * @param string $sql2 * @return array */ protected function scan($sql2) { - $tokens = array(); - $token = strtok($sql2, " \n\t"); - while ($token !== false) { - $this->tokenize($tokens, $token); - $token = strtok(" \n\t"); - } + $tokens = []; + $currentToken = ''; + $tokenEndChars = ['.', ',', '(', ')', '=']; + + $stringStartCharacter = false; + $isEscaped = false; + $escapedQuotesCount = 0; + $splitString = \str_split($sql2); + $splitStringCount = count($splitString); + for ($index = 0; $index < $splitStringCount; ++$index) { + $character = $splitString[$index]; + if (!$stringStartCharacter && in_array($character, [' ', "\t", "\n", "\r"], true)) { + if ('' !== $currentToken) { + $tokens[] = $currentToken; + } + $currentToken = ''; + continue; + } + if (!$stringStartCharacter && in_array($character, $tokenEndChars, true)) { + if ('' !== $currentToken) { + $tokens[] = $currentToken; + } + $tokens[] = $character; + $currentToken = ''; + continue; + } - $regexp = ''; - foreach ($tokens as $token) { - $regexp[] = preg_quote($token, '/'); - } + // Handling the squared brackets in queries + if (!$isEscaped && '[' === $character) { + if ('' !== $currentToken) { + $tokens[] = $currentToken; + } + $stringSize = $this->parseBrackets($sql2, $index); + $bracketContent = substr($sql2, $index + 1, $stringSize - 2); + $tokens[] = '['.trim($bracketContent, '"').']'; - $regexp = '/^'.implode('([ \t\n]+)', $regexp).'$/'; - preg_match($regexp, $sql2, $this->delimiters); - $this->delimiters[0] = ''; + // We need to subtract one here because the for loop will automatically increment the index + $index += $stringSize - 1; + continue; + } - return $tokens; - } + $currentToken .= $character; - /** - * Tokenize a string returned by strtok to split the string at '.', ',', '(', '=' - * and ')' characters. - * - * @param array $tokens - * @param string $token - */ - protected function tokenize(&$tokens, $token) - { - $buffer = ''; - for ($i = 0; $i < strlen($token); $i++) { - $char = trim(substr($token, $i, 1)); - if (in_array($char, array('.', ',', '(', ')', '='))) { - if ($buffer !== '') { - $tokens[] = $buffer; - $buffer = ''; + if (!$isEscaped && in_array($character, ['"', "'"], true)) { + // Checking if the previous or next value is a ' to handle the weird SQL strings + // This will not check if the amount of quotes is even + $nextCharacter = $splitString[$index + 1] ?? ''; + if ("'" === $character && "'" === $nextCharacter) { + $isEscaped = true; + ++$escapedQuotesCount; + continue; + } + // If the escaped quotes are not paired up. eg. "I'''m cool" would be a parsing error + if (1 === $escapedQuotesCount % 2 && "'" !== $stringStartCharacter) { + throw new InvalidQueryException("Syntax error: Number of single quotes to be even: $currentToken"); + } + if ($character === $stringStartCharacter) { + // reached the end of the string + $stringStartCharacter = false; + $tokens[] = $currentToken; + $currentToken = ''; + } elseif (!$stringStartCharacter) { + // If there is no start character already we have found the beginning of a new string + $stringStartCharacter = $character; + + // When tokenizing `AS"abc"` add the current token (AS) as token already + if (strlen($currentToken) > 1) { + $tokens[] = substr($currentToken, 0, -1); + $currentToken = $character; + } } - $tokens[] = $char; - } else { - $buffer .= $char; } + $isEscaped = '\\' === $character; + } + if ('' !== $currentToken) { + $tokens[] = $currentToken; } - if ($buffer !== '') { - $tokens[] = $buffer; + if ($stringStartCharacter) { + throw new InvalidQueryException("Syntax error: unterminated quoted string $currentToken in '$sql2'"); } + + return $tokens; + } + + private function parseBrackets(string $query, int $index): int + { + $endPosition = strpos($query, ']', $index) + 1; + + return $endPosition - $index; } } diff --git a/src/PHPCR/Util/QOM/Sql2ToQomQueryConverter.php b/src/PHPCR/Util/QOM/Sql2ToQomQueryConverter.php index be59a0d6..e0bd27b3 100644 --- a/src/PHPCR/Util/QOM/Sql2ToQomQueryConverter.php +++ b/src/PHPCR/Util/QOM/Sql2ToQomQueryConverter.php @@ -1,26 +1,32 @@ factory = $factory; $this->valueConverter = $valueConverter ?: new ValueConverter(); @@ -77,28 +69,30 @@ public function __construct(QueryObjectModelFactoryInterface $factory, ValueConv /** * 6.7.1. Query - * Parse an SQL2 query and return the corresponding QOM QueryObjectModel + * Parse an SQL2 query and return the corresponding QOM QueryObjectModel. * * @param string $sql2 * - * @return QueryObjectModelInterface + * @throws InvalidQueryException */ - public function parse($sql2) + public function parse($sql2): QueryObjectModelInterface { $this->implicitSelectorName = null; $this->sql2 = $sql2; $this->scanner = new Sql2Scanner($sql2); $source = null; - $columnData = array(); + $columnData = []; $constraint = null; - $orderings = array(); + $orderings = []; - while ($this->scanner->lookupNextToken() !== '') { + while ('' !== $this->scanner->lookupNextToken()) { switch (strtoupper($this->scanner->lookupNextToken())) { case 'SELECT': + $this->scanner->expectToken('SELECT'); $columnData = $this->scanColumns(); break; case 'FROM': + $this->scanner->expectToken('FROM'); $source = $this->parseSource(); break; case 'WHERE': @@ -107,11 +101,11 @@ public function parse($sql2) break; case 'ORDER': // Ordering, check there is a BY - $this->scanner->expectTokens(array('ORDER', 'BY')); + $this->scanner->expectTokens(['ORDER', 'BY']); $orderings = $this->parseOrderings(); break; default: - throw new InvalidQueryException('Expected end of query, got ' . $this->scanner->lookupNextToken() . ' in ' . $this->sql2); + throw new InvalidQueryException('Error parsing query, unknown query part "'.$this->scanner->lookupNextToken().'" in: '.$this->sql2); } } @@ -121,38 +115,33 @@ public function parse($sql2) $columns = $this->buildColumns($columnData); - $query = $this->factory->createQuery($source, $constraint, $orderings, $columns); - - return $query; + return $this->factory->createQuery($source, $constraint, $orderings, $columns); } /** * 6.7.2. Source - * Parse an SQL2 source definition and return the corresponding QOM Source - * - * @return SourceInterface + * Parse an SQL2 source definition and return the corresponding QOM Source. */ - protected function parseSource() + protected function parseSource(): JoinInterface|SourceInterface|SelectorInterface { - $this->scanner->expectToken('FROM'); - $selector = $this->parseSelector(); $next = $this->scanner->lookupNextToken(); - if (in_array(strtoupper($next), array('JOIN', 'INNER', 'RIGHT', 'LEFT'))) { - return $this->parseJoin($selector); + $left = $selector; + + while (in_array(strtoupper($next), ['JOIN', 'INNER', 'RIGHT', 'LEFT'])) { + $left = $this->parseJoin($left); + $next = $this->scanner->lookupNextToken(); } - return $selector; + return $left; } /** * 6.7.3. Selector - * Parse an SQL2 selector and return a QOM\Selector - * - * @return \PHPCR\Query\QOM\SelectorInterface + * Parse an SQL2 selector and return a QOM\SelectorInterface. */ - protected function parseSelector() + protected function parseSelector(): SelectorInterface { $nodetype = $this->fetchTokenWithoutBrackets(); @@ -169,11 +158,9 @@ protected function parseSelector() } /** - * 6.7.4. Name - * - * @return string + * 6.7.4. Name. */ - protected function parseName() + protected function parseName(): string { return $this->scanner->fetchNextToken(); } @@ -181,12 +168,11 @@ protected function parseName() /** * 6.7.5. Join * 6.7.6. Join type - * Parse an SQL2 join source and return a QOM\Join + * Parse an SQL2 join source and return a QOM\Join. * - * @param string $leftSelector the left selector as it has been read by parseSource - * return \PHPCR\Query\QOM\JoinInterface + * @param SourceInterface $leftSelector the left selector as it has been read by parseSource */ - protected function parseJoin($leftSelector) + protected function parseJoin(SourceInterface $leftSelector): JoinInterface { $joinType = $this->parseJoinType(); $right = $this->parseSelector(); @@ -196,11 +182,11 @@ protected function parseJoin($leftSelector) } /** - * 6.7.6. Join type + * 6.7.6. Join type. * - * @return string + * @throws InvalidQueryException */ - protected function parseJoinType() + protected function parseJoinType(): string { $joinType = Constants::JCR_JOIN_TYPE_INNER; $token = $this->scanner->fetchNextToken(); @@ -213,11 +199,11 @@ protected function parseJoinType() $this->scanner->fetchNextToken(); break; case 'LEFT': - $this->scanner->expectTokens(array('OUTER', 'JOIN')); + $this->scanner->expectTokens(['OUTER', 'JOIN']); $joinType = Constants::JCR_JOIN_TYPE_LEFT_OUTER; break; case 'RIGHT': - $this->scanner->expectTokens(array('OUTER', 'JOIN')); + $this->scanner->expectTokens(['OUTER', 'JOIN']); $joinType = Constants::JCR_JOIN_TYPE_RIGHT_OUTER; break; default: @@ -229,11 +215,9 @@ protected function parseJoinType() /** * 6.7.7. JoinCondition - * Parse an SQL2 join condition and return a JoinConditionInterface - * - * @return JoinConditionInterface + * Parse an SQL2 join condition and return a JoinConditionInterface. */ - protected function parseJoinCondition() + protected function parseJoinCondition(): JoinConditionInterface { $this->scanner->expectToken('ON'); @@ -255,28 +239,24 @@ protected function parseJoinCondition() /** * 6.7.8. EquiJoinCondition - * Parse an SQL2 equijoin condition and return a EquiJoinConditionInterface - * - * @return EquiJoinConditionInterface + * Parse an SQL2 equijoin condition and return a EquiJoinConditionInterface. */ - protected function parseEquiJoin() + protected function parseEquiJoin(): EquiJoinConditionInterface { - list($selectorName1, $prop1) = $this->parseIdentifier(); + [$selectorName1, $prop1] = $this->parseIdentifier(); $this->scanner->expectToken('='); - list($selectorName2, $prop2) = $this->parseIdentifier(); + [$selectorName2, $prop2] = $this->parseIdentifier(); return $this->factory->equiJoinCondition($selectorName1, $prop1, $selectorName2, $prop2); } /** * 6.7.9 SameNodeJoinCondition - * Parse an SQL2 same node join condition and return a SameNodeJoinConditionInterface - * - * @return SameNodeJoinConditionInterface + * Parse an SQL2 same node join condition and return a SameNodeJoinConditionInterface. */ - protected function parseSameNodeJoinCondition() + protected function parseSameNodeJoinCondition(): SameNodeJoinConditionInterface { - $this->scanner->expectTokens(array('ISSAMENODE', '(')); + $this->scanner->expectTokens(['ISSAMENODE', '(']); $selectorName1 = $this->fetchTokenWithoutBrackets(); $this->scanner->expectToken(','); $selectorName2 = $this->fetchTokenWithoutBrackets(); @@ -296,13 +276,11 @@ protected function parseSameNodeJoinCondition() /** * 6.7.10 ChildNodeJoinCondition - * Parse an SQL2 child node join condition and return a ChildNodeJoinConditionInterface - * - * @return ChildNodeJoinConditionInterface + * Parse an SQL2 child node join condition and return a ChildNodeJoinConditionInterface. */ - protected function parseChildNodeJoinCondition() + protected function parseChildNodeJoinCondition(): ChildNodeJoinConditionInterface { - $this->scanner->expectTokens(array('ISCHILDNODE', '(')); + $this->scanner->expectTokens(['ISCHILDNODE', '(']); $child = $this->fetchTokenWithoutBrackets(); $this->scanner->expectToken(','); $parent = $this->fetchTokenWithoutBrackets(); @@ -313,13 +291,11 @@ protected function parseChildNodeJoinCondition() /** * 6.7.11 DescendantNodeJoinCondition - * Parse an SQL2 descendant node join condition and return a DescendantNodeJoinConditionInterface - * - * @return DescendantNodeJoinConditionInterface + * Parse an SQL2 descendant node join condition and return a DescendantNodeJoinConditionInterface. */ - protected function parseDescendantNodeJoinCondition() + protected function parseDescendantNodeJoinCondition(): DescendantNodeJoinConditionInterface { - $this->scanner->expectTokens(array('ISDESCENDANTNODE', '(')); + $this->scanner->expectTokens(['ISDESCENDANTNODE', '(']); $descendant = $this->fetchTokenWithoutBrackets(); $this->scanner->expectToken(','); $parent = $this->fetchTokenWithoutBrackets(); @@ -330,20 +306,23 @@ protected function parseDescendantNodeJoinCondition() /** * 6.7.13 And - * 6.7.14 Or + * 6.7.14 Or. * - * @return ConstraintInterface + * @param ConstraintInterface|null $lhs Left hand side + * @param int $minprec Precedence + * + * @throws \Exception */ - protected function parseConstraint($lhs = null, $minprec = 0) + protected function parseConstraint(?ConstraintInterface $lhs = null, int $minprec = 0): ?ConstraintInterface { - if ($lhs === null) { + if (null === $lhs) { $lhs = $this->parsePrimaryConstraint(); } - $opprec = array( + $opprec = [ 'OR' => 1, 'AND' => 2, - ); + ]; $op = strtoupper($this->scanner->lookupNextToken()); while (isset($opprec[$op]) && $opprec[$op] >= $minprec) { @@ -369,9 +348,7 @@ protected function parseConstraint($lhs = null, $minprec = 0) // this only happens if the operator is // in the $opprec-array but there is no // "elseif"-branch here for this operator. - throw new \Exception( - "Internal error: No action is defined for operator '$op'" - ); + throw new \Exception("Internal error: No action is defined for operator '$op'"); } $op = strtoupper($this->scanner->lookupNextToken()); @@ -381,11 +358,9 @@ protected function parseConstraint($lhs = null, $minprec = 0) } /** - * 6.7.12 Constraint - * - * @return ConstraintInterface + * 6.7.12 Constraint. */ - protected function parsePrimaryConstraint() + protected function parsePrimaryConstraint(): ConstraintInterface { $constraint = null; $token = $this->scanner->lookupNextToken(); @@ -422,14 +397,14 @@ protected function parsePrimaryConstraint() } } - if ($constraint === null) { + if (null === $constraint) { // It's not a property existence neither, then it's a comparison $constraint = $this->parseComparison(); } } // No constraint read, - if ($constraint === null) { + if (null === $constraint) { throw new InvalidQueryException("Syntax error: constraint expected in '{$this->sql2}'"); } @@ -437,11 +412,9 @@ protected function parsePrimaryConstraint() } /** - * 6.7.15 Not - * - * @return NotInterface + * 6.7.15 Not. */ - protected function parseNot() + protected function parseNot(): NotInterface { $this->scanner->expectToken('NOT'); @@ -449,18 +422,13 @@ protected function parseNot() } /** - * 6.7.16 Comparison + * 6.7.16 Comparison. * - * @return ComparisonInterface + * @throws InvalidQueryException */ - protected function parseComparison() + protected function parseComparison(): ComparisonInterface { $op1 = $this->parseDynamicOperand(); - - if (null === $op1) { - throw new InvalidQueryException("Syntax error: dynamic operator expected in '{$this->sql2}'"); - } - $operator = $this->parseOperator(); $op2 = $this->parseStaticOperand(); @@ -468,11 +436,11 @@ protected function parseComparison() } /** - * 6.7.17 Operator + * 6.7.17 Operator. * * @return string a constant from QueryObjectModelConstantsInterface */ - protected function parseOperator() + protected function parseOperator(): string { $token = $this->scanner->fetchNextToken(); switch (strtoupper($token)) { @@ -496,13 +464,11 @@ protected function parseOperator() } /** - * 6.7.18 PropertyExistence - * - * @return PropertyExistenceInterface + * 6.7.18 PropertyExistence. */ - protected function parsePropertyExistence() + protected function parsePropertyExistence(): ConstraintInterface { - list($selectorName, $prop) = $this->parseIdentifier(); + [$selectorName, $prop] = $this->parseIdentifier(); $this->scanner->expectToken('IS'); $token = $this->scanner->lookupNextToken(); @@ -512,34 +478,32 @@ protected function parsePropertyExistence() return $this->factory->notConstraint($this->factory->propertyExistence($selectorName, $prop)); } - $this->scanner->expectTokens(array('NOT', 'NULL')); + $this->scanner->expectTokens(['NOT', 'NULL']); return $this->factory->propertyExistence($selectorName, $prop); } /** - * 6.7.19 FullTextSearch - * - * @return FullTextSearchInterface + * 6.7.19 FullTextSearch. */ - protected function parseFullTextSearch() + protected function parseFullTextSearch(): FullTextSearchInterface { - $this->scanner->expectTokens(array('CONTAINS', '(')); + $this->scanner->expectTokens(['CONTAINS', '(']); - list($selectorName, $propertyName) = $this->parseIdentifier(); + [$selectorName, $propertyName] = $this->parseIdentifier(); $this->scanner->expectToken(','); - $expression = $this->parseLiteral()->getLiteralValue(); + $expression = $this->parseLiteralValue(); $this->scanner->expectToken(')'); return $this->factory->fullTextSearch($selectorName, $propertyName, $expression); } /** - * 6.7.20 SameNode + * 6.7.20 SameNode. */ - protected function parseSameNode() + protected function parseSameNode(): SameNodeInterface { - $this->scanner->expectTokens(array('ISSAMENODE', '(')); + $this->scanner->expectTokens(['ISSAMENODE', '(']); if ($this->scanner->tokenIs($this->scanner->lookupNextToken(1), ',')) { $selectorName = $this->scanner->fetchNextToken(); $this->scanner->expectToken(','); @@ -554,11 +518,11 @@ protected function parseSameNode() } /** - * 6.7.21 ChildNode + * 6.7.21 ChildNode. */ - protected function parseChildNode() + protected function parseChildNode(): ChildNodeInterface { - $this->scanner->expectTokens(array('ISCHILDNODE', '(')); + $this->scanner->expectTokens(['ISCHILDNODE', '(']); if ($this->scanner->tokenIs($this->scanner->lookupNextToken(1), ',')) { $selectorName = $this->scanner->fetchNextToken(); $this->scanner->expectToken(','); @@ -573,11 +537,11 @@ protected function parseChildNode() } /** - * 6.7.22 DescendantNode + * 6.7.22 DescendantNode. */ - protected function parseDescendantNode() + protected function parseDescendantNode(): DescendantNodeInterface { - $this->scanner->expectTokens(array('ISDESCENDANTNODE', '(')); + $this->scanner->expectTokens(['ISDESCENDANTNODE', '(']); if ($this->scanner->tokenIs($this->scanner->lookupNextToken(1), ',')) { $selectorName = $this->scanner->fetchNextToken(); $this->scanner->expectToken(','); @@ -600,8 +564,8 @@ protected function parseDescendantNode() */ protected function parsePath() { - $path = $this->parseLiteral()->getLiteralValue(); - if (substr($path, 0, 1) === '[' && substr($path, -1) === ']') { + $path = $this->parseLiteralValue(); + if (str_starts_with($path, '[') && str_ends_with($path, ']')) { $path = substr($path, 1, -1); } @@ -611,18 +575,16 @@ protected function parsePath() /** * Parse an SQL2 static operand * 6.7.35 BindVariable - * 6.7.36 Prefix - * - * @return StaticOperandInterface + * 6.7.36 Prefix. */ - protected function parseStaticOperand() + protected function parseStaticOperand(): StaticOperandInterface { $token = $this->scanner->lookupNextToken(); - if (substr($token, 0, 1) === '$') { + if (str_starts_with($token, '$')) { return $this->factory->bindVariable(substr($this->scanner->fetchNextToken(), 1)); } - return $this->parseLiteral(); + return $this->factory->literal($this->parseLiteralValue()); } /** @@ -633,11 +595,9 @@ protected function parseStaticOperand() * 6.7.31 FullTextSearchScore * 6.7.32 LowerCase * 6.7.33 UpperCase - * Parse an SQL2 dynamic operand - * - * @return DynamicOperandInterface + * Parse an SQL2 dynamic operand. */ - protected function parseDynamicOperand() + protected function parseDynamicOperand(): DynamicOperandInterface|PropertyValueInterface { $token = $this->scanner->lookupNextToken(); @@ -715,174 +675,97 @@ protected function parseDynamicOperand() /** * 6.7.27 PropertyValue - * Parse an SQL2 property value - * - * @return PropertyValueInterface + * Parse an SQL2 property value. */ - protected function parsePropertyValue() + protected function parsePropertyValue(): PropertyValueInterface { - list($selectorName, $prop) = $this->parseIdentifier(); + [$selectorName, $prop] = $this->parseIdentifier(); return $this->factory->propertyValue($selectorName, $prop); } - protected function parseCastLiteral($token) + protected function parseCastLiteral($token): mixed { - if ($this->scanner->tokenIs($token, 'CAST')) { - $this->scanner->expectToken('('); - $token = $this->scanner->fetchNextToken(); + if (!$this->scanner->tokenIs($token, 'CAST')) { + throw new \LogicException('parseCastLiteral when not a CAST'); + } - $quoteString = false; - if (substr($token, 0, 1) === '\'') { - $quoteString = "'"; - } elseif (substr($token, 0, 1) === '"') { - $quoteString = '"'; - } + $this->scanner->expectToken('('); + $token = $this->scanner->fetchNextToken(); - if ($quoteString) { - while (substr($token, -1) !== $quoteString) { - $nextToken = $this->scanner->fetchNextToken(); - if ('' === $nextToken) { - break; - } - $token .= $nextToken; - } + $quoteString = in_array($token[0], ['\'', '"'], true); - if (substr($token, -1) !== $quoteString) { - throw new InvalidQueryException("Syntax error: unterminated quoted string $token in '{$this->sql2}'"); - } - $token = substr($token, 1, -1); - $token = str_replace('\\'.$quoteString, $quoteString, $token); - } + if ($quoteString) { + $quotesUsed = $token[0]; + $token = substr($token, 1, -1); + // Un-escaping quotes + $token = str_replace('\\'.$quotesUsed, $quotesUsed, $token); + } - $this->scanner->expectToken('AS'); + $this->scanner->expectToken('AS'); - $type = $this->scanner->fetchNextToken(); - try { - $typeValue = PropertyType::valueFromName($type); - } catch (\InvalidArgumentException $e) { - throw new InvalidQueryException("Syntax error: attempting to cast to an invalid type '$type'"); - } + $type = $this->scanner->fetchNextToken(); - $this->scanner->expectToken(')'); + try { + $typeValue = PropertyType::valueFromName($type); + } catch (\InvalidArgumentException $e) { + throw new InvalidQueryException("Syntax error: attempting to cast to an invalid type '$type'"); + } - try { - $token = $this->valueConverter->convertType($token, $typeValue, PropertyType::STRING); - } catch (\Exception $e) { - throw new InvalidQueryException("Syntax error: attempting to cast string '$token' to type '$type'"); - } + $this->scanner->expectToken(')'); - return $this->factory->literal($token); + try { + $token = $this->valueConverter->convertType($token, $typeValue, PropertyType::STRING); + } catch (\Exception $e) { + throw new InvalidQueryException("Syntax error: attempting to cast string '$token' to type '$type'"); } + + return $token; } /** * 6.7.34 Literal - * Parse an SQL2 literal value - * - * @return LiteralInterface + * Parse an SQL2 literal value. */ - protected function parseLiteral() + protected function parseLiteralValue(): mixed { $token = $this->scanner->fetchNextToken(); if ($this->scanner->tokenIs($token, 'CAST')) { return $this->parseCastLiteral($token); } - $quoteString = false; - if (substr($token, 0, 1) === '\'') { - $quoteString = "'"; - } elseif (substr($token, 0, 1) === '"') { - $quoteString = '"'; - } + $quoteString = in_array($token[0], ['"', "'"], true); if ($quoteString) { - while (substr($token, -1) !== $quoteString) { - $nextToken = $this->scanner->fetchNextToken(); - if ('' === $nextToken) { - break; - } - $token .= $this->scanner->getPreviousDelimiter(); - $token .= $nextToken; - } - - if (substr($token, -1) !== $quoteString) { - throw new InvalidQueryException("Syntax error: unterminated quoted string $token in '{$this->sql2}'"); - } + $quotesUsed = $token[0]; $token = substr($token, 1, -1); - $token = str_replace('\\'.$quoteString, $quoteString, $token); - $token = str_replace("''", "'", $token); + // Unescape quotes + $token = str_replace(['\\'.$quotesUsed, "''"], [$quotesUsed, "'"], $token); if (preg_match('/^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d+)?$/', $token)) { if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $token)) { - $token.= ' 00:00:00'; + $token .= ' 00:00:00'; } $token = \DateTime::createFromFormat('Y-m-d H:i:s', $token); } } elseif (is_numeric($token)) { - $token = strpos($token, '.') === false ? (int) $token : (float) $token; - } elseif ($token == 'true') { + $token = !str_contains($token, '.') ? (int) $token : (float) $token; + } elseif ('true' === $token) { $token = true; - } elseif ($token == 'false') { + } elseif ('false' === $token) { $token = false; } - return $this->factory->literal($token); + return $token; } /** - * 6.7.34 Fulltext Literal - * Parse an SQL2 literal value - * - * @return LiteralInterface - */ - protected function parseFulltextLiteral() - { - $token = $this->scanner->fetchNextToken(); - if ($this->scanner->tokenIs($token, 'CAST')) { - return $this->parseCastLiteral($token); - } - - $quoteString = false; - if (substr($token, 0, 1) === '\'') { - $quoteString = "'"; - } elseif (substr($token, 0, 1) === '"') { - $quoteString = '"'; - } - - if ($quoteString) { - while (substr($token, -1) !== $quoteString) { - $nextToken = $this->scanner->fetchNextToken(); - if ('' === $nextToken) { - break; - } - $token .= $this->scanner->getPreviousDelimiter(); - $token .= $nextToken; - } - - if (substr($token, -1) !== $quoteString) { - throw new InvalidQueryException("Syntax error: unterminated quoted string $token in '{$this->sql2}'"); - } - $token = substr($token, 1, -1); - $token = str_replace('\\'.$quoteString, $quoteString, $token); - $illegalCharacters = array( - '\\!' => '!', '\\(' => '(', '\\:' => ':', '\\^' => '^', - '\\[' => '[', '\\]' => ']', '\\{' => '{', '\\}' => '}', - '\\\"' => '\"', '\\?' => '?', "''" => "'", - ); - - $token = strtr($token, $illegalCharacters); - - } - - return $this->factory->literal($token); - } - /** - * 6.7.37 Ordering + * 6.7.37 Ordering. */ - protected function parseOrderings() + protected function parseOrderings(): array { - $orderings = array(); + $orderings = []; $continue = true; + while ($continue) { $orderings[] = $this->parseOrdering(); if ($this->scanner->tokenIs($this->scanner->lookupNextToken(), ',')) { @@ -896,9 +779,9 @@ protected function parseOrderings() } /** - * 6.7.38 Order + * 6.7.38 Order. */ - protected function parseOrdering() + protected function parseOrdering(): OrderingInterface { $operand = $this->parseDynamicOperand(); $token = $this->scanner->lookupNextToken(); @@ -921,37 +804,34 @@ protected function parseOrdering() } /** - * 6.7.39 Column + * 6.7.39 Column. * * Scan the SQL2 columns definitions and return data arrays to convert to * columns once the FROM is parsed. * * @return array of array */ - protected function scanColumns() + protected function scanColumns(): array { - $this->scanner->expectToken('SELECT'); - // Wildcard - if ($this->scanner->lookupNextToken() === '*') { + if ('*' === $this->scanner->lookupNextToken()) { $this->scanner->fetchNextToken(); - return array(); + return []; } - $columns = array(); + $columns = []; $hasNext = true; while ($hasNext) { $columns[] = $this->scanColumn(); // Are there more columns? - if ($this->scanner->lookupNextToken() !== ',') { + if (',' !== $this->scanner->lookupNextToken()) { $hasNext = false; } else { $this->scanner->fetchNextToken(); } - } return $columns; @@ -960,13 +840,11 @@ protected function scanColumns() /** * Build the columns from the scanned column data. * - * @param array $data - * * @return ColumnInterface[] */ - protected function buildColumns($data) + protected function buildColumns(array $data): array { - $columns = array(); + $columns = []; foreach ($data as $col) { $columns[] = $this->buildColumn($col); } @@ -976,15 +854,13 @@ protected function buildColumns($data) /** * Get the next token and make sure to remove the brackets if the token is - * in the [ns:name] notation - * - * @return string + * in the [ns:name] notation. */ - private function fetchTokenWithoutBrackets() + private function fetchTokenWithoutBrackets(): string { $token = $this->scanner->fetchNextToken(); - if (substr($token, 0, 1) === '[' && substr($token, -1) === ']') { + if (str_starts_with($token, '[') && str_ends_with($token, ']')) { // Remove brackets around the selector name $token = substr($token, 1, -1); } @@ -995,17 +871,17 @@ private function fetchTokenWithoutBrackets() /** * Parse something that is expected to be a property identifier. * - * @param boolean $checkSelector whether we need to ensure a valid selector. + * @param bool $checkSelector whether we need to ensure a valid selector * - * @return array with selectorName and propertyName. If no selectorName is - * specified, defaults to $this->defaultSelectorName + * @return string[] with selectorName and propertyName. If no selectorName is + * specified, defaults to $this->defaultSelectorName */ - private function parseIdentifier($checkSelector = true) + private function parseIdentifier(bool $checkSelector = true): array { $token = $this->fetchTokenWithoutBrackets(); // selector.property - if ($this->scanner->lookupNextToken() === '.') { + if ('.' === $this->scanner->lookupNextToken()) { $selectorName = $token; $this->scanner->fetchNextToken(); $propertyName = $this->fetchTokenWithoutBrackets(); @@ -1018,21 +894,21 @@ private function parseIdentifier($checkSelector = true) $selectorName = $this->ensureSelectorName($selectorName); } - return array($selectorName, $propertyName); + return [$selectorName, $propertyName]; } /** * Add a selector name to the known selector names. * - * @param string $selectorName + * @throws InvalidQueryException */ - protected function updateImplicitSelectorName($selectorName) + protected function updateImplicitSelectorName(string $selectorName): void { if (null === $this->implicitSelectorName) { $this->implicitSelectorName = $selectorName; } else { if (!is_array($this->implicitSelectorName)) { - $this->implicitSelectorName = array($this->implicitSelectorName => $this->implicitSelectorName); + $this->implicitSelectorName = [$this->implicitSelectorName => $this->implicitSelectorName]; } if (isset($this->implicitSelectorName[$selectorName])) { throw new InvalidQueryException("Selector $selectorName is already in use"); @@ -1043,20 +919,16 @@ protected function updateImplicitSelectorName($selectorName) /** * Ensure that the parsedName is a valid selector, or return the implicit - * selector if its non-ambigous. - * - * @param string|null $parsedName - * - * @return string the selector to use + * selector if its non-ambiguous. * * @throws InvalidQueryException if there was no explicit selector and - * there is more than one selector available. + * there is more than one selector available */ - protected function ensureSelectorName($parsedName) + protected function ensureSelectorName(?string $parsedName): ?string { if (null !== $parsedName) { - if (is_array($this->implicitSelectorName) && !isset($this->implicitSelectorName[$parsedName]) - || !is_array($this->implicitSelectorName) && $this->implicitSelectorName !== $parsedName + if ((is_array($this->implicitSelectorName) && !isset($this->implicitSelectorName[$parsedName])) + || (!is_array($this->implicitSelectorName) && $this->implicitSelectorName !== $parsedName) ) { throw new InvalidQueryException("Unknown selector $parsedName in '{$this->sql2}'"); } @@ -1064,20 +936,18 @@ protected function ensureSelectorName($parsedName) return $parsedName; } if (is_array($this->implicitSelectorName)) { - throw new InvalidQueryException("Need an explicit selector name in join queries"); + throw new InvalidQueryException('Need an explicit selector name in join queries'); } return $this->implicitSelectorName; } /** - * Scan a single SQL2 column definition and return an array of information - * - * @return array + * Scan a single SQL2 column definition and return an array of information. */ - protected function scanColumn() + protected function scanColumn(): array { - list($selectorName, $propertyName) = $this->parseIdentifier(false); + [$selectorName, $propertyName] = $this->parseIdentifier(false); // AS name if ($this->scanner->tokenIs($this->scanner->lookupNextToken(), 'AS')) { @@ -1087,17 +957,17 @@ protected function scanColumn() $columnName = $propertyName; } - return array($selectorName, $propertyName, $columnName); + return [$selectorName, $propertyName, $columnName]; } /** - * Build a single SQL2 column definition + * Build a single SQL2 column definition. * - * @return ColumnInterface + * @param array $data with selector name, property name and column name */ - protected function buildColumn($data) + protected function buildColumn(array $data): ColumnInterface { - list($selectorName, $propertyName, $columnName) = $data; + [$selectorName, $propertyName, $columnName] = $data; $selectorName = $this->ensureSelectorName($selectorName); return $this->factory->column($selectorName, $propertyName, $columnName); diff --git a/src/PHPCR/Util/TraversingItemVisitor.php b/src/PHPCR/Util/TraversingItemVisitor.php index 2b20f7e3..4707e962 100644 --- a/src/PHPCR/Util/TraversingItemVisitor.php +++ b/src/PHPCR/Util/TraversingItemVisitor.php @@ -1,13 +1,13 @@ * @author Day Management AG, Switzerland - * * @license http://www.apache.org/licenses Apache License Version 2.0, January 2004 * @license http://opensource.org/licenses/MIT MIT License * @@ -36,72 +35,56 @@ abstract class TraversingItemVisitor implements ItemVisitorInterface /** * Indicates if traversal should be done in a breadth-first manner rather * than depth-first (which is the default). - * - * @var boolean */ - protected $breadthFirst = false; + protected bool $breadthFirst = false; /** * The 0-based depth up to which the hierarchy should be traversed (if it's * -1, the hierarchy will be traversed until there are no more children of * the current item). - * - * @var integer */ - protected $maxDepth = -1; + protected int $maxDepth = -1; /** * Queue used to implement breadth-first traversal. - * - * @var SplQueue */ - protected $currentQueue; + protected \SplQueue $currentQueue; /** * Queue used to implement breadth-first traversal. - * - * @var SplQueue */ - protected $nextQueue; + protected \SplQueue $nextQueue; /** * Used to track hierarchy depth of item currently being processed. - * - * @var integer */ - protected $currentDepth; + protected int $currentDepth; /** - * Constructs a new instance of this class. - * - * @param boolean $breadthFirst if $breadthFirst is true then traversal is - * done in a breadth-first manner; otherwise it is done in a - * depth-first manner (which is the default behavior). - * @param integer $maxDepth the 0-based depth relative to the root node up - * to which the hierarchy should be traversed (if it's -1, the - * hierarchy will be traversed until there are no more children of the - * current item). - * - * @api + * @param bool $breadthFirst if $breadthFirst is true then traversal is + * done in a breadth-first manner; otherwise it is done in a + * depth-first manner (which is the default behavior) + * @param int $maxDepth the 0-based depth relative to the root node up + * to which the hierarchy should be traversed (if it's -1, the + * hierarchy will be traversed until there are no more children of the + * current item) */ - public function __construct($breadthFirst = false, $maxDepth = -1) + public function __construct(bool $breadthFirst = false, int $maxDepth = -1) { $this->breadthFirst = $breadthFirst; $this->maxDepth = $maxDepth; - if ($this->breadthFirst === true) { - $this->currentQueue = new SplQueue(); - $this->nextQueue = new SplQueue(); + if (true === $this->breadthFirst) { + $this->currentQueue = new \SplQueue(); + $this->nextQueue = new \SplQueue(); } $this->currentDepth = 0; } /** - * Update the current depth level for indention - * - * @param int $level + * Update the current depth level for indention. */ - public function setLevel($level) + public function setLevel(int $level): void { $this->currentDepth = $level; } @@ -110,31 +93,31 @@ public function setLevel($level) * Implement this method to add behavior performed before a Property is * visited. * - * @param ItemInterface $item the Item that is accepting this - * visitor. - * @param integer $depth hierarchy level of this node (the root node starts - * at depth 0). + * @param ItemInterface $item the Item that is accepting this + * visitor + * @param int $depth hierarchy level of this node (the root node starts + * at depth 0) * - * @throws RepositoryException if an error occurs. + * @throws RepositoryException if an error occurs * * @api */ - abstract protected function entering(ItemInterface $item, $depth); + abstract protected function entering(ItemInterface $item, int $depth): void; /** * Implement this method to add behavior performed after a Property is * visited. * - * @param ItemInterface $item the Item that is accepting this - * visitor. - * @param integer $depth hierarchy level of this property (the root node - * starts at depth 0). + * @param ItemInterface $item the Item that is accepting this + * visitor + * @param int $depth hierarchy level of this property (the root node + * starts at depth 0) * - * @throws RepositoryException if an error occurs. + * @throws RepositoryException if an error occurs * * @api */ - abstract protected function leaving(ItemInterface $item, $depth); + abstract protected function leaving(ItemInterface $item, int $depth): void; /** * Called when the Visitor is passed to an Item. @@ -146,58 +129,66 @@ abstract protected function leaving(ItemInterface $item, $depth); * If this method throws, the visiting process is aborted. * * @param ItemInterface $item the Node or Property that is accepting - * this visitor. + * this visitor * - * @throws RepositoryException if an error occurs. + * @throws RepositoryException if an error occurs * * @api */ - public function visit(ItemInterface $item) + public function visit(ItemInterface $item): void { - if ($this->currentDepth == 0) { + if (0 === $this->currentDepth) { $this->currentDepth = $item->getDepth(); } if ($item instanceof PropertyInterface) { $this->entering($item, $this->currentDepth); $this->leaving($item, $this->currentDepth); } else { - /** @var $item NodeInterface */ + if (!$item instanceof NodeInterface) { + throw new RepositoryException(sprintf( + 'Internal error in TraversingItemVisitor: item %s at %s is not a node but %s', + $item->getName(), + $item->getPath(), + $item::class + )); + } + try { - if ($this->breadthFirst === false) { + if (false === $this->breadthFirst) { $this->entering($item, $this->currentDepth); - if ($this->maxDepth == -1 || $this->currentDepth < $this->maxDepth) { - $this->currentDepth++; + if (-1 === $this->maxDepth || $this->currentDepth < $this->maxDepth) { + ++$this->currentDepth; foreach ($item->getProperties() as $property) { - /** @var $property PropertyInterface */ + /* @var $property PropertyInterface */ $property->accept($this); } foreach ($item->getNodes() as $node) { - /** @var $node NodeInterface */ + /* @var $node NodeInterface */ $node->accept($this); } - $this->currentDepth--; + --$this->currentDepth; } $this->leaving($item, $this->currentDepth); } else { $this->entering($item, $this->currentDepth); $this->leaving($item, $this->currentDepth); - if ($this->maxDepth == -1 || $this->currentDepth < $this->maxDepth) { + if (-1 === $this->maxDepth || $this->currentDepth < $this->maxDepth) { foreach ($item->getProperties() as $property) { - /** @var $property PropertyInterface */ + /* @var $property PropertyInterface */ $property->accept($this); } foreach ($item->getNodes() as $node) { - /** @var $node NodeInterface */ + /* @var $node NodeInterface */ $node->accept($this); } } while (!$this->currentQueue->isEmpty() || !$this->nextQueue->isEmpty()) { if ($this->currentQueue->isEmpty()) { - $this->currentDepth++; + ++$this->currentDepth; $this->currentQueue = $this->nextQueue; - $this->nextQueue = new SplQueue(); + $this->nextQueue = new \SplQueue(); } $item = $this->currentQueue->dequeue(); $item->accept($this); @@ -206,6 +197,7 @@ public function visit(ItemInterface $item) } } catch (RepositoryException $exception) { $this->currentDepth = 0; + throw $exception; } } diff --git a/src/PHPCR/Util/TreeWalker.php b/src/PHPCR/Util/TreeWalker.php index 1db765fa..0588c94d 100644 --- a/src/PHPCR/Util/TreeWalker.php +++ b/src/PHPCR/Util/TreeWalker.php @@ -1,96 +1,71 @@ */ class TreeWalker { - /** - * Visitor for nodes - * - * @var ItemVisitorInterface - */ - protected $nodeVisitor; + protected ItemVisitorInterface $nodeVisitor; - /** - * Visitor for properties - * - * @var ItemVisitorInterface - */ - protected $propertyVisitor; + protected ?ItemVisitorInterface $propertyVisitor; /** - * Filters to apply to decide whether a node needs to be visited + * Filters to apply to decide whether a node needs to be visited. * - * @var array() + * @var TreeWalkerFilterInterface[] */ - protected $nodeFilters = array(); + protected array $nodeFilters = []; /** - * Filters to apply to decide whether a property needs to be visited + * Filters to apply to decide whether a property needs to be visited. * - * @var array() + * @var TreeWalkerFilterInterface[] */ - protected $propertyFilters = array(); + protected array $propertyFilters = []; /** - * Instantiate a tree walker - * - * @param ItemVisitorInterface $nodeVisitor The visitor for the nodes - * @param ItemVisitorInterface $propertyVisitor The visitor for the nodes properties + * @param ItemVisitorInterface $nodeVisitor The visitor for the nodes + * @param ItemVisitorInterface|null $propertyVisitor The visitor for the nodes properties */ - public function __construct(ItemVisitorInterface $nodeVisitor, ItemVisitorInterface $propertyVisitor = null) + public function __construct(ItemVisitorInterface $nodeVisitor, ?ItemVisitorInterface $propertyVisitor = null) { $this->nodeVisitor = $nodeVisitor; $this->propertyVisitor = $propertyVisitor; } - /** - * Add a filter to select the nodes that will be traversed - * - * @param TreeWalkerFilterInterface $filter - */ - public function addNodeFilter(TreeWalkerFilterInterface $filter) + public function addNodeFilter(TreeWalkerFilterInterface $filter): void { - if (!array_search($filter, $this->nodeFilters)) { + if (!in_array($filter, $this->nodeFilters, true)) { $this->nodeFilters[] = $filter; } } - /** - * Add a filter to select the properties that will be traversed - * - * @param TreeWalkerFilterInterface $filter - */ - public function addPropertyFilter(TreeWalkerFilterInterface $filter) + public function addPropertyFilter(TreeWalkerFilterInterface $filter): void { - if (!array_search($filter, $this->propertyFilters)) { + if (!in_array($filter, $this->propertyFilters, true)) { $this->propertyFilters[] = $filter; } } /** - * Return whether a node must be traversed or not - * - * @param NodeInterface $node - * - * @return boolean + * Return whether a node must be traversed or not. */ - protected function mustVisitNode(NodeInterface $node) + protected function mustVisitNode(NodeInterface $node): bool { foreach ($this->nodeFilters as $filter) { - if (! $filter->mustVisit($node)) { + if (!$filter->mustVisit($node)) { return false; } } @@ -99,16 +74,12 @@ protected function mustVisitNode(NodeInterface $node) } /** - * Return whether a node property must be traversed or not - * - * @param PropertyInterface $property - * - * @return boolean + * Return whether a node property must be traversed or not. */ - protected function mustVisitProperty(PropertyInterface $property) + protected function mustVisitProperty(PropertyInterface $property): bool { foreach ($this->propertyFilters as $filter) { - if (! $filter->mustVisit($property)) { + if (!$filter->mustVisit($property)) { return false; } } @@ -117,25 +88,28 @@ protected function mustVisitProperty(PropertyInterface $property) } /** - * Traverse a node - * - * @param NodeInterface $node - * @param int $recurse Max recursion level - * @param int $level Recursion level + * @param int $recurse Max recursion level + * @param int $level Current recursion level */ - public function traverse(NodeInterface $node, $recurse = -1, $level = 0) + public function traverse(NodeInterface $node, int $recurse = -1, int $level = 0): void { if ($this->mustVisitNode($node)) { - // Visit node - $this->nodeVisitor->setLevel($level); + if (method_exists($this->nodeVisitor, 'setLevel')) { + $this->nodeVisitor->setLevel($level); + } + if (method_exists($this->nodeVisitor, 'setShowFullPath')) { + $this->nodeVisitor->setShowFullPath(0 === $level); + } $node->accept($this->nodeVisitor); // Visit properties - if ($this->propertyVisitor !== null) { + if (null !== $this->propertyVisitor) { foreach ($node->getProperties() as $prop) { if ($this->mustVisitProperty($prop)) { - $this->propertyVisitor->setLevel($level); + if (method_exists($this->propertyVisitor, 'setLevel')) { + $this->propertyVisitor->setLevel($level); + } $prop->accept($this->propertyVisitor); } } diff --git a/src/PHPCR/Util/TreeWalkerFilterInterface.php b/src/PHPCR/Util/TreeWalkerFilterInterface.php index 299417d1..e967c932 100644 --- a/src/PHPCR/Util/TreeWalkerFilterInterface.php +++ b/src/PHPCR/Util/TreeWalkerFilterInterface.php @@ -1,5 +1,7 @@ */ interface TreeWalkerFilterInterface { /** - * Whether to visit the passed item - * - * @param \PHPCR\ItemInterface $item - * - * @return mixed + * Whether to visit the passed item. */ - public function mustVisit(ItemInterface $item); + public function mustVisit(ItemInterface $item): mixed; } diff --git a/src/PHPCR/Util/UUIDHelper.php b/src/PHPCR/Util/UUIDHelper.php index 3553cb4a..f9a76953 100644 --- a/src/PHPCR/Util/UUIDHelper.php +++ b/src/PHPCR/Util/UUIDHelper.php @@ -1,7 +1,11 @@ toString(); + } + + return sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', // 32 bits for "time_low" - mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), + random_int(0, 0xFFFF), + random_int(0, 0xFFFF), // 16 bits for "time_mid" - mt_rand( 0, 0xffff ), + random_int(0, 0xFFFF), // 16 bits for "time_hi_and_version", // four most significant bits holds version number 4 - mt_rand( 0, 0x0fff ) | 0x4000, + random_int(0, 0x0FFF) | 0x4000, // 16 bits, 8 bits for "clk_seq_hi_res", // 8 bits for "clk_seq_low", // two most significant bits holds zero and one for variant DCE1.1 - mt_rand( 0, 0x3fff ) | 0x8000, + random_int(0, 0x3FFF) | 0x8000, // 48 bits for "node" - mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ) + random_int(0, 0xFFFF), + random_int(0, 0xFFFF), + random_int(0, 0xFFFF) ); } } diff --git a/src/PHPCR/Util/ValueConverter.php b/src/PHPCR/Util/ValueConverter.php index e58858b4..8b1bc2a4 100644 --- a/src/PHPCR/Util/ValueConverter.php +++ b/src/PHPCR/Util/ValueConverter.php @@ -1,7 +1,11 @@ * * @api @@ -31,21 +34,33 @@ class ValueConverter * - if the given $value is a Node object, type will be REFERENCE, unless * $weak is set to true which results in WEAKREFERENCE * - if the given $value is a DateTime object, the type will be DATE. + * - if the $value is an empty array, the type is arbitrarily set to STRING + * - if the $value is a non-empty array, the type of its first element is + * chosen. * * Note that string is converted to date exactly if it matches the jcr * formatting spec for dates (sYYYY-MM-DDThh:mm:ss.sssTZD) according to * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.6.4.3%20From%20DATE%20To * - * @param mixed $value The variable we need to know the type of - * @param boolean $weak When a Node is given as $value this can be given - * as true to create a WEAKREFERENCE. + * @param mixed $value The variable we need to know the type of + * @param bool $weak when a Node is given as $value this can be given + * as true to create a WEAKREFERENCE * * @return int One of the type constants * * @throws ValueFormatException if the type can not be determined */ - public function determineType($value, $weak = false) + public function determineType(mixed $value, bool $weak = false): int { + if (is_array($value)) { + if (0 === count($value)) { + // there is no value to determine the type on. we arbitrarily + // chose string, which is what jackrabbit does as well. + return PropertyType::STRING; + } + $value = reset($value); + } + return PropertyType::determineType($value, $weak); } @@ -71,49 +86,49 @@ public function determineType($value, $weak = false) * * Table based on JCR spec * - - - - - - - - - - - - - - - - -

STRING (1)BINARY (2)LONG (3)DOUBLE (4)DATE (5)BOOLEAN (6)NAME(7)PATH (8)REFERENCE (9/10)URI (11)DECIMAL (12)
STRINGxUtf-8 encodedcast to intcast to floatSYYYY-MM-DDThh:Mm:ss.sssTZD'' is false, else trueif valid name, nameif valid path, as namecheck valid uuidRFC 3986string
BINARYUtf-8xConverted to string and then interpreted as above
LONGcast to stringString, then Utf-8xcast to floatUnix Time0 false else trueValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptioncast to string
DOUBLEcast to stringString, then Utf-8cast to intxUnix Time0.0 is false, else trueValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptioncast to string
DATESYYYY-MM-DDThh:
Mm:ss.sssTZD
String, then Utf-8Unix timestampUnix timestampxtrueValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionUnix timestamp
BOOLEANcast to stringString, then Utf-80/10.0/1.0ValueFormatExceptionx'0'/'1'ValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatException
NAMEQualified formString, then Utf-8ValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionxnoop (relative path)ValueFormatException„./“ and qualified name. % encode illegal charactersValueFormatException
PATHStandard formString, then Utf-8ValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionif relative path lenght 1 noop / otherwise ValueFormatExceptionxValueFormatException„./“ if not starting with /. % encode illegal charactersValueFormatException
REFERENCEnoopString, then Utf-8ValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionxValueFormatExceptionValueFormatException
URInoopString, then Utf-8ValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionsingle name: decode %, remove ./ else ValueFormatExceptionDecode %, remove leading ./ . if not star w. name, / or ./ then ValueFormatExceptionValueFormatExceptionxValueFormatException
DECIMALnoopUtf-8 encodedcast to intcast to floatUnix Time0 false else trueValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionx
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *

STRING (1)BINARY (2)LONG (3)DOUBLE (4)DATE (5)BOOLEAN (6)NAME(7)PATH (8)REFERENCE (9/10)URI (11)DECIMAL (12)
STRINGxUtf-8 encodedcast to intcast to floatSYYYY-MM-DDThh:Mm:ss.sssTZD'' is false, else trueif valid name, nameif valid path, as namecheck valid uuidRFC 3986string
BINARYUtf-8xConverted to string and then interpreted as above
LONGcast to stringString, then Utf-8xcast to floatUnix Time0 false else trueValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptioncast to string
DOUBLEcast to stringString, then Utf-8cast to intxUnix Time0.0 is false, else trueValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptioncast to string
DATESYYYY-MM-DDThh:
Mm:ss.sssTZD
String, then Utf-8Unix timestampUnix timestampxtrueValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionUnix timestamp
BOOLEANcast to stringString, then Utf-80/10.0/1.0ValueFormatExceptionx'0'/'1'ValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatException
NAMEQualified formString, then Utf-8ValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionxnoop (relative path)ValueFormatException„./“ and qualified name. % encode illegal charactersValueFormatException
PATHStandard formString, then Utf-8ValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionif relative path lenght 1 noop / otherwise ValueFormatExceptionxValueFormatException„./“ if not starting with /. % encode illegal charactersValueFormatException
REFERENCEnoopString, then Utf-8ValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionxValueFormatExceptionValueFormatException
URInoopString, then Utf-8ValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionsingle name: decode %, remove ./ else ValueFormatExceptionDecode %, remove leading ./ . if not star w. name, / or ./ then ValueFormatExceptionValueFormatExceptionxValueFormatException
DECIMALnoopUtf-8 encodedcast to intcast to floatUnix Time0 false else trueValueFormatExceptionValueFormatExceptionValueFormatExceptionValueFormatExceptionx
* * @param mixed $value The value or value array to check and convert * @param int $type Target type to convert into. One of the type constants in PropertyType - * @param int $srctype Source type to convert from, if not specified this is automatically determined, which will miss the string based types that are not strings (DECIMAL, NAME, PATH, URI) + * @param int $srcType Source type to convert from, if not specified this is automatically determined, which will miss the string based types that are not strings (DECIMAL, NAME, PATH, URI) * * @return mixed the value casted into the proper format (throws an exception if conversion is not possible) * - * @throws ValueFormatException is thrown if the specified value cannot be converted to the specified type - * @throws RepositoryException if the specified Node is not referenceable, the current Session is no longer active, or another error occurs. + * @throws RepositoryException if the specified Node is not referenceable, the current Session is no longer active, or another error occurs * @throws \InvalidArgumentException if the specified DateTime value cannot be expressed in the ISO 8601-based format defined in the JCR 2.0 specification and the implementation does not support dates incompatible with that format. + * @throws ValueFormatException is thrown if the specified value cannot be converted to the specified type * * @see http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.6.4%20Property%20Type%20Conversion */ - public function convertType($value, $type, $srctype = PropertyType::UNDEFINED) + public function convertType(mixed $value, int $type, int $srcType = PropertyType::UNDEFINED): mixed { if (is_array($value)) { - $ret = array(); + $ret = []; foreach ($value as $v) { - $ret[] = self::convertType($v, $type, $srctype); + $ret[] = self::convertType($v, $type, $srcType); } return $ret; } - if (PropertyType::UNDEFINED == $srctype) { - $srctype = $this->determineType($value); + if (PropertyType::UNDEFINED === $srcType) { + $srcType = $this->determineType($value); } if ($value instanceof PropertyInterface) { @@ -121,61 +136,58 @@ public function convertType($value, $type, $srctype = PropertyType::UNDEFINED) } // except on noop, stream needs to be read into string first - if (PropertyType::BINARY == $srctype && PropertyType::BINARY != $type && is_resource($value)) { + if (PropertyType::BINARY === $srcType && PropertyType::BINARY !== $type && is_resource($value)) { $t = stream_get_contents($value); rewind($value); $value = $t; - $srctype = PropertyType::STRING; - } elseif ((PropertyType::REFERENCE == $srctype || PropertyType::WEAKREFERENCE == $srctype ) + $srcType = PropertyType::STRING; + } elseif ((PropertyType::REFERENCE === $srcType || PropertyType::WEAKREFERENCE === $srcType) && $value instanceof NodeInterface ) { - /** @var $value NodeInterface */ - // In Jackrabbit a new node cannot be referenced until it has been persisted - // See: https://issues.apache.org/jira/browse/JCR-1614 - if ($value->isNew()) { - throw new ValueFormatException('Node ' . $value->getPath() . ' must be persisted before being referenceable'); - } - if (! $value->isNodeType('mix:referenceable')) { - throw new ValueFormatException('Node ' . $value->getPath() . ' is not referenceable'); + if (!$value->isNodeType('mix:referenceable')) { + throw new ValueFormatException('Node '.$value->getPath().' is not referenceable'); } $value = $value->getIdentifier(); } switch ($type) { case PropertyType::STRING: - switch ($srctype) { + switch ($srcType) { case PropertyType::DATE: - if (! $value instanceof \DateTime) { - throw new RepositoryException('Can not convert a date that is not a \DateTime instance to string'); + if (!$value instanceof \DateTime) { + throw new RepositoryException('Cannot convert a date that is not a \DateTime instance to string'); } - /** @var $value \DateTime */ + + /* @var $value DateTime */ // Milliseconds formatting is not possible in PHP so we // construct it by cutting microseconds to 3 positions. // This might not be as accurate as "real" rounded milliseconds. - return $value->format('Y-m-d\TH:i:s.') . - substr($value->format('u'), 0, 3) . + return $value->format('Y-m-d\TH:i:s.'). + substr($value->format('u'), 0, 3). $value->format('P'); case PropertyType::NAME: case PropertyType::PATH: // TODO: The name/path is converted to qualified form according to the current local namespace mapping (see §3.2.5.2 Qualified Form). - return $value; + return $value; default: if (is_object($value)) { - throw new ValueFormatException('Can not convert object of class '.get_class($value).' to STRING'); + throw new ValueFormatException('Cannot convert object of class "'.$value::class.'" to STRING'); } if (is_resource($value)) { throw new ValueFormatException('Inconsistency: Non-binary property should not have resource stream value'); } + // TODO: how can we provide ValueFormatException on failure? invalid casting leads to 'catchable fatal error' instead of exception return (string) $value; } + // no break case PropertyType::BINARY: if (is_resource($value)) { return $value; } - if (! is_string($value)) { - $value = $this->convertType($value, PropertyType::STRING, $srctype); + if (!is_string($value)) { + $value = $this->convertType($value, PropertyType::STRING, $srcType); } $f = fopen('php://memory', 'rwb+'); fwrite($f, $value); @@ -184,94 +196,106 @@ public function convertType($value, $type, $srctype = PropertyType::UNDEFINED) return $f; case PropertyType::LONG: - switch ($srctype) { + switch ($srcType) { case PropertyType::STRING: case PropertyType::LONG: case PropertyType::DOUBLE: case PropertyType::BOOLEAN: case PropertyType::DECIMAL: - return (integer) $value; + return (int) $value; case PropertyType::DATE: - if (! $value instanceof \DateTime) { + if (!$value instanceof \DateTime) { throw new RepositoryException('something weird'); } - /** @var $value \DateTime */ + /* @var $value DateTime */ return $value->getTimestamp(); } if (is_object($value)) { - throw new ValueFormatException('Can not convert object of class '.get_class($value).' to a LONG'); + throw new ValueFormatException('Cannot convert object of class "'.$value::class.'" to a LONG'); } - throw new ValueFormatException('Can not convert '.var_export($value, true).' to a LONG'); + throw new ValueFormatException('Cannot convert "'.var_export($value, true).'" to a LONG'); case PropertyType::DOUBLE: - switch ($srctype) { + switch ($srcType) { case PropertyType::STRING: case PropertyType::LONG: case PropertyType::DOUBLE: case PropertyType::BOOLEAN: case PropertyType::DECIMAL: - return (double) $value; + return (float) $value; case PropertyType::DATE: - if (! $value instanceof \DateTime) { + if (!$value instanceof \DateTime) { throw new RepositoryException('something weird'); } - /** @var $value \DateTime */ + /* @var $value DateTime */ - return (double) $value->getTimestamp(); + return (float) $value->getTimestamp(); } if (is_object($value)) { - throw new ValueFormatException('Can not convert object of class '.get_class($value).' to a DOUBLE'); + throw new ValueFormatException('Cannot convert object of class "'.$value::class.'" to a DOUBLE'); } - throw new ValueFormatException('Can not convert '.var_export($value, true).' to a DOUBLE'); + throw new ValueFormatException('Cannot convert "'.var_export($value, true).'" to a DOUBLE'); case PropertyType::DATE: - switch ($srctype) { + switch ($srcType) { case PropertyType::STRING: case PropertyType::DATE: if ($value instanceof \DateTime) { return $value; } + try { return new \DateTime($value); } catch (\Exception $e) { - throw new ValueFormatException("String '$value' is not a valid date", null, $e); + throw new ValueFormatException("String '$value' is not a valid date", 0, $e); } case PropertyType::LONG: + return (new \DateTime()) + ->setTimestamp($value) + ; case PropertyType::DOUBLE: + return (new \DateTime()) + ->setTimestamp((int) round($value)) + ; case PropertyType::DECIMAL: - $datetime = new \DateTime(); - $datetime = $datetime->setTimestamp($value); + if (function_exists('bccomp') + && 1 === \bccomp($value, (string) PHP_INT_MAX) + ) { + throw new ValueFormatException('Decimal number is too large for integer'); + } - return $datetime; + return (new \DateTime()) + ->setTimestamp((int) round((float) $value)) + ; } if (is_object($value)) { - throw new ValueFormatException('Can not convert object of class '.get_class($value).' to a DATE'); + throw new ValueFormatException('Cannot convert object of class "'.$value::class.'" to a DATE'); } - throw new ValueFormatException('Can not convert '.var_export($value, true).' to DATE'); + throw new ValueFormatException('Cannot convert "'.var_export($value, true).'" to DATE'); case PropertyType::BOOLEAN: - switch ($srctype) { + switch ($srcType) { case PropertyType::STRING: case PropertyType::LONG: case PropertyType::DOUBLE: case PropertyType::BOOLEAN: - return (boolean) $value; + return (bool) $value; case PropertyType::DATE: - /** @var $value \DateTime */ + /* @var $value DateTime */ - return (boolean) $value->getTimestamp(); + return (bool) $value->getTimestamp(); case PropertyType::DECIMAL: - return (boolean) ((double) $value); // '0' is false too + return (bool) ((float) $value); // '0' is false too } if (is_object($value)) { - throw new ValueFormatException('Can not convert object of class '.get_class($value).' to a BOOLEAN'); + throw new ValueFormatException('Cannot convert object of class "'.$value::class.'" to a BOOLEAN'); } - throw new ValueFormatException('Can not convert '.var_export($value, true).' to a BOOLEAN'); + throw new ValueFormatException('Cannot convert "'.var_export($value, true).'" to a BOOLEAN'); case PropertyType::NAME: - switch ($srctype) { + switch ($srcType) { case PropertyType::STRING: case PropertyType::PATH: case PropertyType::NAME: @@ -282,12 +306,12 @@ public function convertType($value, $type, $srctype = PropertyType::UNDEFINED) return $value; } if (is_object($value)) { - throw new ValueFormatException('Can not convert object of class '.get_class($value).' to a NAME'); + throw new ValueFormatException('Cannot convert object of class "'.$value::class.'" to a NAME'); } - throw new ValueFormatException('Can not convert '.var_export($value, true).' to NAME'); + throw new ValueFormatException('Cannot convert "'.var_export($value, true).'" to NAME'); case PropertyType::PATH: - switch ($srctype) { + switch ($srcType) { case PropertyType::STRING: // TODO: check if valid return $value; @@ -299,39 +323,39 @@ public function convertType($value, $type, $srctype = PropertyType::UNDEFINED) return $value; } if (is_object($value)) { - throw new ValueFormatException('Can not convert object of class '.get_class($value).' to a PATH'); + throw new ValueFormatException('Cannot convert object of class "'.$value::class.'" to a PATH'); } - throw new ValueFormatException('Can not convert '.var_export($value, true).' to PATH'); + throw new ValueFormatException('Cannot convert "'.var_export($value, true).'" to PATH'); case PropertyType::REFERENCE: case PropertyType::WEAKREFERENCE: - switch ($srctype) { + switch ($srcType) { case PropertyType::STRING: case PropertyType::REFERENCE: case PropertyType::WEAKREFERENCE: if (empty($value)) { - //TODO check if string is valid uuid - throw new ValueFormatException('Value '.var_export($value, true).' is not a valid unique id'); + // TODO check if string is valid uuid + throw new ValueFormatException('Value "'.var_export($value, true).'" is not a valid unique id'); } return $value; } if (is_object($value)) { - throw new ValueFormatException('Can not convert object of class '.get_class($value).' to a unique id'); + throw new ValueFormatException('Cannot convert object of class "'.$value::class.'" to a unique id'); } - throw new ValueFormatException('Can not convert '.var_export($value, true).' to unique id'); + throw new ValueFormatException('Cannot convert "'.var_export($value, true).'" to unique id'); case PropertyType::URI: - switch ($srctype) { + switch ($srcType) { case PropertyType::STRING: // TODO: check if valid return $value; case PropertyType::NAME: return '../'.rawurlencode($value); case PropertyType::PATH: - if (strlen($value) > 0 - && '/' != $value[0] - && '.' != $value[0] + if ('' !== $value + && '/' !== $value[0] + && '.' !== $value[0] ) { $value = './'.$value; } @@ -341,12 +365,12 @@ public function convertType($value, $type, $srctype = PropertyType::UNDEFINED) return $value; } if (is_object($value)) { - throw new ValueFormatException('Can not convert object of class '.get_class($value).' to a URI'); + throw new ValueFormatException('Cannot convert object of class "'.$value::class.'" to a URI'); } - throw new ValueFormatException('Can not convert '.var_export($value, true).' to URI'); + throw new ValueFormatException('Cannot convert "'.var_export($value, true).'" to URI'); case PropertyType::DECIMAL: - switch ($srctype) { + switch ($srcType) { case PropertyType::STRING: // TODO: validate return $value; @@ -356,17 +380,17 @@ public function convertType($value, $type, $srctype = PropertyType::UNDEFINED) case PropertyType::DECIMAL: return (string) $value; case PropertyType::DATE: - /** @var $value \DateTime */ + /* @var $value DateTime */ return (string) $value->getTimestamp(); } if (is_object($value)) { - throw new ValueFormatException('Can not convert object of class '.get_class($value).' to a DECIMAL'); + throw new ValueFormatException('Cannot convert object of class "'.$value::class.'" to a DECIMAL'); } - throw new ValueFormatException('Can not convert '.var_export($value, true).' to a DECIMAL'); + throw new ValueFormatException('Cannot convert "'.var_export($value, true).'" to a DECIMAL'); default: - throw new ValueFormatException("Unexpected target type $type in conversion"); + throw new ValueFormatException("Unexpected target type '$type' in conversion"); } } } diff --git a/tests/PHPCR/Tests/Stubs/MockNode.php b/tests/PHPCR/Tests/Stubs/MockNode.php index ea937214..5d27eac6 100644 --- a/tests/PHPCR/Tests/Stubs/MockNode.php +++ b/tests/PHPCR/Tests/Stubs/MockNode.php @@ -1,9 +1,14 @@ + */ abstract class MockNode implements \Iterator, NodeInterface { } diff --git a/tests/PHPCR/Tests/Stubs/MockNodeTypeManager.php b/tests/PHPCR/Tests/Stubs/MockNodeTypeManager.php index 97f696f0..f3fe1df2 100644 --- a/tests/PHPCR/Tests/Stubs/MockNodeTypeManager.php +++ b/tests/PHPCR/Tests/Stubs/MockNodeTypeManager.php @@ -1,9 +1,15 @@ + */ abstract class MockNodeTypeManager implements \Iterator, NodeTypeManagerInterface { } diff --git a/tests/PHPCR/Tests/Stubs/MockRow.php b/tests/PHPCR/Tests/Stubs/MockRow.php index 298e2bef..95112378 100644 --- a/tests/PHPCR/Tests/Stubs/MockRow.php +++ b/tests/PHPCR/Tests/Stubs/MockRow.php @@ -1,9 +1,14 @@ + */ abstract class MockRow implements \Iterator, RowInterface { } diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/files/TestFile.php b/tests/PHPCR/Tests/Util/CND/Fixtures/files/TestFile.php index 4f3f0988..b8db0e88 100644 --- a/tests/PHPCR/Tests/Util/CND/Fixtures/files/TestFile.php +++ b/tests/PHPCR/Tests/Util/CND/Fixtures/files/TestFile.php @@ -5,14 +5,14 @@ class TestClass { /** - * Block comment + * Block comment. */ public function testMethod($testParam) { // Line comment $string = 'This is a "Test // string"'; - return "Test string"; + return 'Test string'; } // String in "comment" diff --git a/tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php b/tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php index 64e30845..2e4dc33e 100644 --- a/tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php +++ b/tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php @@ -1,20 +1,29 @@ assertInstanceOf('\PHPCR\Util\CND\Reader\BufferReader', $reader); - $this->assertAttributeEquals(str_replace("\r\n", "\n", $buffer) . $reader->getEofMarker(), 'buffer', $reader); - $this->assertAttributeEquals(0, 'startPos', $reader); - $this->assertAttributeEquals(0, 'forwardPos', $reader); + $reflection = new \ReflectionClass($reader); + $bufferProperty = $reflection->getProperty('buffer'); + $bufferProperty->setAccessible(true); + $this->assertSame(str_replace("\r\n", "\n", $buffer).$reader->getEofMarker(), $bufferProperty->getValue($reader)); + $startPos = $reflection->getProperty('startPos'); + $startPos->setAccessible(true); + $this->assertSame(0, $startPos->getValue($reader)); + $forwardPos = $reflection->getProperty('forwardPos'); + $forwardPos->setAccessible(true); + $this->assertSame(0, $forwardPos->getValue($reader)); $this->assertEquals(1, $reader->getCurrentLine()); $this->assertEquals(1, $reader->getCurrentColumn()); @@ -85,14 +94,20 @@ public function test__construct() $this->assertEquals($reader->getEofMarker(), $reader->forward()); } - public function test__constructEmptyString() + public function testConstructEmptyString(): void { $reader = new BufferReader(''); - $this->assertInstanceOf('\PHPCR\Util\CND\Reader\BufferReader', $reader); - $this->assertAttributeEquals($reader->getEofMarker(), 'buffer', $reader); - $this->assertAttributeEquals(0, 'startPos', $reader); - $this->assertAttributeEquals(0, 'forwardPos', $reader); + $reflection = new \ReflectionClass($reader); + $buffer = $reflection->getProperty('buffer'); + $buffer->setAccessible(true); + $this->assertSame($reader->getEofMarker(), $buffer->getValue($reader)); + $startPos = $reflection->getProperty('startPos'); + $startPos->setAccessible(true); + $this->assertSame(0, $startPos->getValue($reader)); + $forwardPos = $reflection->getProperty('forwardPos'); + $forwardPos->setAccessible(true); + $this->assertSame(0, $forwardPos->getValue($reader)); $this->assertEquals(1, $reader->getCurrentLine()); $this->assertEquals(1, $reader->getCurrentColumn()); @@ -104,5 +119,4 @@ public function test__constructEmptyString() $this->assertEquals($reader->getEofMarker(), $reader->forward()); $this->assertEquals($reader->getEofMarker(), $reader->consume()); } - } diff --git a/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php b/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php index b1397684..1e49cbb4 100644 --- a/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php +++ b/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php @@ -1,51 +1,72 @@ filepath = __DIR__ . '/../Fixtures/files/TestFile.txt'; + $this->filepath = __DIR__.'/../Fixtures/files/TestFile.txt'; $this->reader = new FileReader($this->filepath); - $this->lines = array( + $this->lines = [ 'This is a test file...', '', '...containing dummy content.', - '' - ); + '', + ]; $this->chars = array_merge( preg_split('//', $this->lines[0], -1, PREG_SPLIT_NO_EMPTY), - array("\n", "\n"), + ["\n", "\n"], preg_split('//', $this->lines[2], -1, PREG_SPLIT_NO_EMPTY), - array("\n", "\n") + ["\n", "\n"] ); } - /** - * @expectedException \InvalidArgumentException - */ - public function test__construct_fileNotFound() + public function testConstructFileNotFound(): void { - $reader = new FileReader('unexisting_file'); + $this->expectException(\InvalidArgumentException::class); + + new FileReader('unexisting_file'); } - public function testGetPath() + public function testGetPath(): void { $this->assertEquals($this->filepath, $this->reader->getPath()); } - public function testGetNextChar() + public function testGetNextChar(): void { $curLine = 1; $curCol = 1; - for ($i = 0; $i < count($this->chars); $i++) { - + for ($i = 0; $i < count($this->chars); ++$i) { $peek = $this->reader->currentChar(); if ($peek === $this->reader->getEofMarker()) { @@ -60,11 +81,11 @@ public function testGetNextChar() $this->assertFalse($this->reader->isEof()); // Assert isEol is true at end of the lines - if ($peek === "\n") { - $curLine++; + if ("\n" === $peek) { + ++$curLine; $curCol = 1; } else { - $curCol++; + ++$curCol; } // Assert the next character is the expected one @@ -84,5 +105,4 @@ public function testGetNextChar() $this->assertTrue($this->reader->isEof()); $this->assertEquals(false, $this->reader->forwardChar()); } - } diff --git a/tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php b/tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php index eb908fbe..7021b05f 100644 --- a/tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php +++ b/tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php @@ -1,122 +1,130 @@ + */ + private array $expectedTokens = [ // - array(Token::TK_SYMBOL, '<'), - array(Token::TK_SYMBOL, '?'), - array(Token::TK_IDENTIFIER, 'php'), - array(Token::TK_NEWLINE, ''), - array(Token::TK_NEWLINE, ''), + [Token::TK_SYMBOL, '<'], + [Token::TK_SYMBOL, '?'], + [Token::TK_IDENTIFIER, 'php'], + [Token::TK_NEWLINE, ''], + [Token::TK_NEWLINE, ''], // namespace Test\Foobar; - array(Token::TK_IDENTIFIER, 'namespace'), - array(Token::TK_WHITESPACE, ''), - array(Token::TK_IDENTIFIER, 'Test'), - array(Token::TK_SYMBOL, '\\'), - array(Token::TK_IDENTIFIER, 'Foobar'), - array(Token::TK_SYMBOL, ';'), - array(Token::TK_NEWLINE, ''), - array(Token::TK_NEWLINE, ''), + [Token::TK_IDENTIFIER, 'namespace'], + [Token::TK_WHITESPACE, ''], + [Token::TK_IDENTIFIER, 'Test'], + [Token::TK_SYMBOL, '\\'], + [Token::TK_IDENTIFIER, 'Foobar'], + [Token::TK_SYMBOL, ';'], + [Token::TK_NEWLINE, ''], + [Token::TK_NEWLINE, ''], // class TestClass { - array(Token::TK_IDENTIFIER, 'class'), - array(Token::TK_WHITESPACE, ''), - array(Token::TK_IDENTIFIER, 'TestClass'), - array(Token::TK_NEWLINE, ''), - array(Token::TK_SYMBOL, '{'), - array(Token::TK_NEWLINE, ''), + [Token::TK_IDENTIFIER, 'class'], + [Token::TK_WHITESPACE, ''], + [Token::TK_IDENTIFIER, 'TestClass'], + [Token::TK_NEWLINE, ''], + [Token::TK_SYMBOL, '{'], + [Token::TK_NEWLINE, ''], // /** ... */ - array(Token::TK_WHITESPACE, ''), - array(Token::TK_COMMENT, "/**\n * Block comment\n */"), - array(Token::TK_NEWLINE, ''), + [Token::TK_WHITESPACE, ''], + [Token::TK_COMMENT, "/**\n * Block comment.\n */"], + [Token::TK_NEWLINE, ''], // public function testMethod($testParam) { - array(Token::TK_WHITESPACE, ''), - array(Token::TK_IDENTIFIER, 'public'), - array(Token::TK_WHITESPACE, ''), - array(Token::TK_IDENTIFIER, 'function'), - array(Token::TK_WHITESPACE, ''), - array(Token::TK_IDENTIFIER, 'testMethod'), - array(Token::TK_SYMBOL, '('), - array(Token::TK_SYMBOL, '$'), - array(Token::TK_IDENTIFIER, 'testParam'), - array(Token::TK_SYMBOL, ')'), - array(Token::TK_NEWLINE, ''), - array(Token::TK_WHITESPACE, ''), - array(Token::TK_SYMBOL, '{'), - array(Token::TK_NEWLINE, ''), + [Token::TK_WHITESPACE, ''], + [Token::TK_IDENTIFIER, 'public'], + [Token::TK_WHITESPACE, ''], + [Token::TK_IDENTIFIER, 'function'], + [Token::TK_WHITESPACE, ''], + [Token::TK_IDENTIFIER, 'testMethod'], + [Token::TK_SYMBOL, '('], + [Token::TK_SYMBOL, '$'], + [Token::TK_IDENTIFIER, 'testParam'], + [Token::TK_SYMBOL, ')'], + [Token::TK_NEWLINE, ''], + [Token::TK_WHITESPACE, ''], + [Token::TK_SYMBOL, '{'], + [Token::TK_NEWLINE, ''], // // Line comment - array(Token::TK_WHITESPACE, ''), - array(Token::TK_COMMENT, '// Line comment'), - array(Token::TK_NEWLINE, ''), + [Token::TK_WHITESPACE, ''], + [Token::TK_COMMENT, '// Line comment'], + [Token::TK_NEWLINE, ''], // $string = 'This is a "Test // string"'; - array(Token::TK_WHITESPACE, ''), - array(Token::TK_SYMBOL, '$'), - array(Token::TK_IDENTIFIER, 'string'), - array(Token::TK_WHITESPACE, ''), - array(Token::TK_SYMBOL, '='), - array(Token::TK_WHITESPACE, ''), - array(Token::TK_STRING, '\'This is a "Test // string"\''), - array(Token::TK_SYMBOL, ';'), - array(Token::TK_NEWLINE, ''), + [Token::TK_WHITESPACE, ''], + [Token::TK_SYMBOL, '$'], + [Token::TK_IDENTIFIER, 'string'], + [Token::TK_WHITESPACE, ''], + [Token::TK_SYMBOL, '='], + [Token::TK_WHITESPACE, ''], + [Token::TK_STRING, '\'This is a "Test // string"\''], + [Token::TK_SYMBOL, ';'], + [Token::TK_NEWLINE, ''], // empty line before return - array(Token::TK_NEWLINE, ''), + [Token::TK_NEWLINE, ''], // return "Test string"; - array(Token::TK_WHITESPACE, ''), - array(Token::TK_IDENTIFIER, 'return'), - array(Token::TK_WHITESPACE, ''), - array(Token::TK_STRING, '"Test string"'), - array(Token::TK_SYMBOL, ';'), - array(Token::TK_NEWLINE, ''), + [Token::TK_WHITESPACE, ''], + [Token::TK_IDENTIFIER, 'return'], + [Token::TK_WHITESPACE, ''], + [Token::TK_STRING, '\'Test string\''], + [Token::TK_SYMBOL, ';'], + [Token::TK_NEWLINE, ''], // } - array(Token::TK_WHITESPACE, ''), - array(Token::TK_SYMBOL, '}'), - array(Token::TK_NEWLINE, ''), - array(Token::TK_NEWLINE, ''), + [Token::TK_WHITESPACE, ''], + [Token::TK_SYMBOL, '}'], + [Token::TK_NEWLINE, ''], + [Token::TK_NEWLINE, ''], // // String in "comment" - array(Token::TK_WHITESPACE, ''), - array(Token::TK_COMMENT, '// String in "comment"'), - array(Token::TK_NEWLINE, ''), + [Token::TK_WHITESPACE, ''], + [Token::TK_COMMENT, '// String in "comment"'], + [Token::TK_NEWLINE, ''], // } - array(Token::TK_SYMBOL, '}'), - array(Token::TK_NEWLINE, ''), - ); + [Token::TK_SYMBOL, '}'], + [Token::TK_NEWLINE, ''], + ]; - protected $expectedTokensNoEmptyToken; + /** + * @var array + */ + protected array $expectedTokensNoEmptyToken; - public function setUp() + public function setUp(): void { - $this->expectedTokensNoEmptyToken = array(); + $this->expectedTokensNoEmptyToken = []; foreach ($this->expectedTokens as $token) { - if ($token[0] !== Token::TK_NEWLINE && $token[0] !== Token::TK_WHITESPACE) { + if (Token::TK_NEWLINE !== $token[0] && Token::TK_WHITESPACE !== $token[0]) { $this->expectedTokensNoEmptyToken[] = $token; } } } - public function testScan() + public function testScan(): void { - $reader = new FileReader(__DIR__ . '/../Fixtures/files/TestFile.php'); + $reader = new FileReader(__DIR__.'/../Fixtures/files/TestFile.php'); // Test the raw file with newlines and whitespaces $scanner = new GenericScanner(new DefaultScannerContext()); @@ -124,9 +132,9 @@ public function testScan() $this->assertTokens($this->expectedTokens, $queue); } - public function testFilteredScan() + public function testFilteredScan(): void { - $reader = new FileReader(__DIR__ . '/../Fixtures/files/TestFile.php'); + $reader = new FileReader(__DIR__.'/../Fixtures/files/TestFile.php'); // Test the raw file with newlines and whitespaces $context = new DefaultScannerContext(); @@ -138,7 +146,10 @@ public function testFilteredScan() $this->assertTokens($this->expectedTokensNoEmptyToken, $queue); } - protected function assertTokens($tokens, TokenQueue $queue) + /** + * @param array $tokens + */ + protected function assertTokens(array $tokens, TokenQueue $queue): void { $queue->reset(); @@ -147,10 +158,11 @@ protected function assertTokens($tokens, TokenQueue $queue) $token = $queue->peek(); while ($it->valid()) { + $this->assertInstanceOf(Token::class, $token); $expectedToken = $it->current(); - $this->assertFalse($queue->isEof(), 'There is no more tokens, expected = ' . $expectedToken[1]); + $this->assertFalse($queue->isEof(), 'There is no more tokens, expected = '.$expectedToken[1]); $this->assertToken($expectedToken[0], $expectedToken[1], $token); @@ -161,12 +173,18 @@ protected function assertTokens($tokens, TokenQueue $queue) $this->assertTrue($queue->isEof(), 'There are more unexpected tokens.'); } - protected function assertToken($type, $data, Token $token) + protected function assertToken(int $type, string $data, Token $token): void { - $this->assertEquals($type, $token->getType(), - sprintf('Expected token [%s, %s], found [%s, %s]', Token::getTypeName($type), $data, Token::getTypeName($token->getType()), $token->getData())); - - $this->assertEquals($data, trim($token->getData()), - sprintf('Expected token [%s, %s], found [%s, %s]', Token::getTypeName($type), $data, Token::getTypeName($token->getType()), $token->getData())); + $this->assertEquals( + $type, + $token->getType(), + sprintf('Expected token [%s, %s], found [%s, %s]', Token::getTypeName($type), $data, Token::getTypeName($token->getType()), $token->getData()) + ); + + $this->assertEquals( + $data, + trim($token->getData()), + sprintf('Expected token [%s, %s], found [%s, %s]', Token::getTypeName($type), $data, Token::getTypeName($token->getType()), $token->getData()) + ); } } diff --git a/tests/PHPCR/Tests/Util/CND/Scanner/TokenQueueTest.php b/tests/PHPCR/Tests/Util/CND/Scanner/TokenQueueTest.php index 087a8d0b..8b653c50 100644 --- a/tests/PHPCR/Tests/Util/CND/Scanner/TokenQueueTest.php +++ b/tests/PHPCR/Tests/Util/CND/Scanner/TokenQueueTest.php @@ -1,13 +1,41 @@ token0 = new Token(0, 'token 0'); $this->token1 = new Token(1, 'token 1'); @@ -21,25 +49,28 @@ public function setUp() $this->queue->add($this->token3); } - public function testAdd() + public function testAdd(): void { $queue = new TokenQueue(); - $this->assertAttributeEquals(array(), 'tokens', $queue); + $reflection = new \ReflectionClass($queue); + $tokens = $reflection->getProperty('tokens'); + $tokens->setAccessible(true); + $this->assertSame([], $tokens->getValue($queue)); $queue->add($this->token0); - $this->assertAttributeEquals(array($this->token0), 'tokens', $queue); + $this->assertSame([$this->token0], $tokens->getValue($queue)); $queue->add($this->token1); - $this->assertAttributeEquals(array($this->token0, $this->token1), 'tokens', $queue); + $this->assertSame([$this->token0, $this->token1], $tokens->getValue($queue)); } - public function testResetAndPeek() + public function testResetAndPeek(): void { $this->assertEquals($this->token0, $this->queue->reset()); $this->assertEquals($this->token0, $this->queue->peek()); } - public function testIsEofAndNext() + public function testIsEofAndNext(): void { // Token0 $this->assertFalse($this->queue->isEof()); @@ -61,7 +92,7 @@ public function testIsEofAndNext() $this->assertTrue($this->queue->isEof()); } - public function testIsEofEmptyQueue() + public function testIsEofEmptyQueue(): void { $queue = new TokenQueue(); $this->assertTrue($queue->isEof()); @@ -69,7 +100,7 @@ public function testIsEofEmptyQueue() $this->assertFalse($queue->isEof()); } - public function testGet() + public function testGet(): void { $this->queue->reset(); $this->assertEquals($this->token0, $this->queue->get()); @@ -79,10 +110,10 @@ public function testGet() $this->assertEquals(false, $this->queue->get()); } - public function testGetIterator() + public function testGetIterator(): void { $this->assertEquals( - array($this->token0, $this->token1, $this->token2, $this->token3), + [$this->token0, $this->token1, $this->token2, $this->token3], iterator_to_array($this->queue->getIterator()) ); } diff --git a/tests/PHPCR/Tests/Util/CND/Scanner/TokenTest.php b/tests/PHPCR/Tests/Util/CND/Scanner/TokenTest.php index e110f5b9..79cf6603 100644 --- a/tests/PHPCR/Tests/Util/CND/Scanner/TokenTest.php +++ b/tests/PHPCR/Tests/Util/CND/Scanner/TokenTest.php @@ -1,35 +1,42 @@ token = new Token(123, 'foobar'); } - public function test__construct() + public function testConstruct(): void { - $this->assertAttributeEquals(123, 'type', $this->token); - $this->assertAttributeEquals('foobar', 'data', $this->token); + $this->assertSame(123, $this->token->getType()); + $this->assertSame('foobar', $this->token->getData()); } - public function testGetData() + public function testGetData(): void { $this->assertEquals('foobar', $this->token->getData()); } - public function testGetType() + public function testGetType(): void { $this->assertEquals(123, $this->token->getType()); } - public function test__toString() + public function testToString(): void { $this->assertEquals('TOKEN(123, \'foobar\', 0, 0)', $this->token->__toString()); } - } diff --git a/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php index bcd37c6a..a0300cec 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php @@ -1,58 +1,63 @@ session = $this->getMock('PHPCR\SessionInterface'); - $this->workspace = $this->getMock('PHPCR\WorkspaceInterface'); - $this->repository = $this->getMock('PHPCR\RepositoryInterface'); - $this->queryManager = $this->getMock('PHPCR\Query\QueryManagerInterface'); + $this->session = $this->createMock(SessionInterface::class); + $this->workspace = $this->createMock(WorkspaceInterface::class); + $this->repository = $this->createMock(RepositoryInterface::class); + $this->queryManager = $this->createMock(QueryManagerInterface::class); - $this->row1 = $this->getMock('PHPCR\Tests\Stubs\MockRow'); - $this->node1 = $this->getMock('PHPCR\Tests\Stubs\MockNode'); + $this->row1 = $this->createMock(MockRow::class); + $this->node1 = $this->createMock(MockNode::class); - $this->dumperHelper = $this->getMockBuilder( - 'PHPCR\Util\Console\Helper\PhpcrConsoleDumperHelper' - )->disableOriginalConstructor()->getMock(); + $this->dumperHelper = $this->getMockBuilder(PhpcrConsoleDumperHelper::class) + ->disableOriginalConstructor() + ->getMock(); - $this->helperSet = new HelperSet(array( + $this->helperSet = new HelperSet([ 'phpcr' => new PhpcrHelper($this->session), 'phpcr_console_dumper' => $this->dumperHelper, - )); + ]); - $this->session->expects($this->any()) + $this->session ->method('getWorkspace') - ->will($this->returnValue($this->workspace)); + ->willReturn($this->workspace); - $this->workspace->expects($this->any()) + $this->workspace ->method('getName') - ->will($this->returnValue('test')); + ->willReturn('test'); - $this->workspace->expects($this->any()) + $this->workspace ->method('getQueryManager') - ->will($this->returnValue($this->queryManager)); + ->willReturn($this->queryManager); - $this->queryManager->expects($this->any()) + $this->queryManager ->method('getSupportedQueryLanguages') - ->will($this->returnValue(array('JCR-SQL2'))); + ->willReturn(['JCR-SQL2']); $this->application = new Application(); $this->application->setHelperSet($this->helperSet); @@ -108,20 +113,14 @@ public function setUp() /** * Build and execute the command tester. * - * @param string $name command name - * @param array $args command arguments - * @param int $status expected return status - * - * @return CommandTester + * @param mixed[] $arguments */ - public function executeCommand($name, $args, $status = 0) + public function executeCommand(string $commandName, array $arguments, int $expectedReturnStatus = 0): CommandTester { - $command = $this->application->find($name); + $command = $this->application->find($commandName); $commandTester = new CommandTester($command); - $args = array_merge(array( - 'command' => $command->getName(), - ), $args); - $this->assertEquals($status, $commandTester->execute($args)); + $arguments = array_merge(['command' => $command->getName()], $arguments); + $this->assertEquals($expectedReturnStatus, $commandTester->execute($arguments)); return $commandTester; } diff --git a/tests/PHPCR/Tests/Util/Console/Command/NodeDumpCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodeDumpCommandTest.php index 229a19ce..5e5da9a9 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/NodeDumpCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodeDumpCommandTest.php @@ -1,96 +1,92 @@ treeWalker = $this->getMockBuilder( - 'PHPCR\Util\TreeWalker' - )->disableOriginalConstructor()->getMock(); + $this->treeWalker = $this->getMockBuilder(TreeWalker::class) + ->disableOriginalConstructor() + ->getMock(); $ndCommand = new NodeDumpCommand(); $this->application->add($ndCommand); } - public function testCommand() + public function testCommand(): void { $this->dumperHelper ->expects($this->once()) ->method('getTreeWalker') - ->will($this->returnValue($this->treeWalker)) - ; + ->willReturn($this->treeWalker); + $this->session ->expects($this->once()) ->method('getNode') ->with('/') - ->will($this->returnValue($this->node1)) - ; + ->willReturn($this->node1); + $this->treeWalker ->expects($this->once()) ->method('traverse') - ->with($this->node1) - ; + ->with($this->node1); - $this->executeCommand('phpcr:node:dump', array()); + $this->executeCommand('phpcr:node:dump', []); } - public function testCommandIdentifier() + public function testCommandIdentifier(): void { $uuid = UUIDHelper::generateUUID(); $this->dumperHelper ->expects($this->once()) ->method('getTreeWalker') - ->will($this->returnValue($this->treeWalker)) - ; + ->willReturn($this->treeWalker); + $this->session ->expects($this->once()) ->method('getNodeByIdentifier') ->with($uuid) - ->will($this->returnValue($this->node1)) - ; + ->willReturn($this->node1); + $this->treeWalker ->expects($this->once()) ->method('traverse') - ->with($this->node1) - ; + ->with($this->node1); - $this->executeCommand('phpcr:node:dump', array('identifier' => $uuid)); + $this->executeCommand('phpcr:node:dump', ['identifier' => $uuid]); } - public function testInvalidRefFormat() + public function testInvalidRefFormat(): void { - try { - $this->executeCommand('phpcr:node:dump', array('--ref-format' => 'xy')); - $this->fail('invalid ref-format did not produce exception'); - } catch (\Exception $e) { - // success - } + $this->expectException(\Exception::class); + + $this->executeCommand('phpcr:node:dump', ['--ref-format' => 'xy']); + $this->fail('invalid ref-format did not produce exception'); } - public function testNotFound() + public function testNotFound(): void { $this->session ->expects($this->once()) ->method('getNode') ->with('/') - ->will($this->throwException(new ItemNotFoundException())) - ; + ->will($this->throwException(new ItemNotFoundException())); - $ct = $this->executeCommand('phpcr:node:dump', array(), 1); - $this->assertContains('does not exist', $ct->getDisplay()); + $ct = $this->executeCommand('phpcr:node:dump', [], 1); + $this->assertStringContainsString('does not exist', $ct->getDisplay()); } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/NodeMoveCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodeMoveCommandTest.php index 487b2f73..4dee112a 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/NodeMoveCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodeMoveCommandTest.php @@ -1,27 +1,39 @@ > + */ + public function provideCommand(): array { - return array( - array(array('source' => '/foo', 'destination' => '/bar')) - ); + return [ + [ + [ + 'source' => '/foo', + 'destination' => '/bar', + ], + ], + ]; } /** * @dataProvider provideCommand + * + * @param array $args */ - public function testCommand($args) + public function testCommand(array $args): void { $this->session->expects($this->once()) ->method('move') ->with($args['source'], $args['destination']); + $this->session->expects($this->once()) ->method('save'); diff --git a/tests/PHPCR/Tests/Util/Console/Command/NodeRemoveCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodeRemoveCommandTest.php index 4dcd02c0..6381ba59 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/NodeRemoveCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodeRemoveCommandTest.php @@ -1,38 +1,39 @@ application->add(new NodeRemoveCommand()); } - public function testRemove() + public function testRemove(): void { $this->session->expects($this->once()) ->method('removeItem') ->with('/cms'); - $ct = $this->executeCommand('phpcr:node:remove', array( + $this->executeCommand('phpcr:node:remove', [ '--force' => true, 'path' => '/cms', - )); + ]); } - /** - * @expectedException \LogicException - */ - public function testRemoveRoot() + public function testRemoveRoot(): void { - $ct = $this->executeCommand('phpcr:node:remove', array( + $this->expectException(\LogicException::class); + + $this->executeCommand('phpcr:node:remove', [ '--force' => true, 'path' => '/', - )); + ]); } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/NodeTouchCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodeTouchCommandTest.php index 5a4c63d7..afc746d3 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/NodeTouchCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodeTouchCommandTest.php @@ -1,115 +1,116 @@ application->add($command); - // override default concrete instance with mock - $this->phpcrHelper = $this->getMockBuilder('PHPCR\Util\Console\Helper\PhpcrHelper') + // Override default concrete instance with mock + $this->phpcrHelper = $this->getMockBuilder(PhpcrHelper::class) ->disableOriginalConstructor() - ->getMock() - ; - $this->phpcrHelper->expects($this->any()) + ->getMock(); + + $this->phpcrHelper ->method('getSession') - ->will($this->returnValue($this->session)) - ; - $this->phpcrHelper->expects($this->any()) + ->willReturn($this->session); + + $this->phpcrHelper ->method('getName') - ->will($this->returnValue('phpcr')) - ; + ->willReturn('phpcr'); + $this->helperSet->set($this->phpcrHelper); } - public function testTouch() + public function testTouch(): void { $node = $this->node1; - $child = $this->getMock('PHPCR\Tests\Stubs\MockNode'); + $child = $this->createMock(MockNode::class); $this->session->expects($this->exactly(2)) ->method('getNode') - ->will($this->returnCallback(function ($path) use ($node) { + ->willReturnCallback(function ($path) use ($node) { switch ($path) { case '/': return $node; case '/cms': throw new PathNotFoundException(); } - throw new \Exception('Unexpected ' . $path); - })); + + throw new \Exception('Unexpected '.$path); + }); $this->node1->expects($this->once()) ->method('addNode') ->with('cms') - ->will($this->returnValue($child)) - ; + ->willReturn($child); $this->session->expects($this->once()) ->method('save'); - $this->executeCommand('phpcr:node:touch', array( - 'path' => '/cms', - )); + $this->executeCommand('phpcr:node:touch', ['path' => '/cms']); } - public function testUpdate() + public function testUpdate(): void { - $nodeType = $this->getMock('PHPCR\NodeType\NodeTypeInterface'); + $nodeType = $this->createMock(NodeTypeInterface::class); $nodeType->expects($this->once()) ->method('getName') - ->will($this->returnValue('nt:unstructured')) - ; + ->willReturn('nt:unstructured'); $this->session->expects($this->exactly(1)) ->method('getNode') ->with('/cms') - ->will($this->returnValue($this->node1)) - ; + ->willReturn($this->node1); + $this->node1->expects($this->once()) ->method('getPrimaryNodeType') - ->will($this->returnValue($nodeType)) - ; + ->willReturn($nodeType); $me = $this; $this->phpcrHelper->expects($this->once()) ->method('processNode') - ->will($this->returnCallback(function ($output, $node, $options) use ($me) { + ->willReturnCallback(function ($output, $node, $options) use ($me): void { $me->assertEquals($me->node1, $node); - $me->assertEquals(array( - 'setProp' => array('foo=bar'), - 'removeProp' => array('bar'), - 'addMixins' => array('foo:bar'), - 'removeMixins' => array('bar:foo'), + $me->assertEquals([ + 'setProp' => ['foo=bar'], + 'removeProp' => ['bar'], + 'addMixins' => ['foo:bar'], + 'removeMixins' => ['bar:foo'], 'dump' => true, - ), $options); - })); + ], $options); + }); - $this->executeCommand('phpcr:node:touch', array( + $this->executeCommand('phpcr:node:touch', [ 'path' => '/cms', - '--set-prop' => array('foo=bar'), - '--remove-prop' => array('bar'), - '--add-mixin' => array('foo:bar'), - '--remove-mixin' => array('bar:foo'), + '--set-prop' => ['foo=bar'], + '--remove-prop' => ['bar'], + '--add-mixin' => ['foo:bar'], + '--remove-mixin' => ['bar:foo'], '--dump' => true, - )); + ]); } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/NodeTypeListCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodeTypeListCommandTest.php index e7b28f9f..fb25baf0 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/NodeTypeListCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodeTypeListCommandTest.php @@ -1,33 +1,44 @@ application->add(new NodeTypeListCommand()); - $this->nodeTypeManager = $this->getMockBuilder( - 'PHPCR\Tests\Stubs\MockNodeTypeManager' - )->disableOriginalConstructor()->getMock(); + $this->nodeTypeManager = $this->getMockBuilder(MockNodeTypeManager::class) + ->disableOriginalConstructor() + ->getMock(); } - public function testNodeTypeList() + public function testNodeTypeList(): void { $this->session->expects($this->once()) ->method('getWorkspace') - ->will($this->returnValue($this->workspace)); + ->willReturn($this->workspace); + $this->workspace->expects($this->once()) ->method('getNodeTypeManager') - ->will($this->returnValue($this->nodeTypeManager)); + ->willReturn($this->nodeTypeManager); + $this->nodeTypeManager->expects($this->once()) ->method('getAllNodeTypes') - ->will($this->returnValue(array())); - $ct = $this->executeCommand('phpcr:node-type:list', array( - )); + ->willReturn([]); + + $this->executeCommand('phpcr:node-type:list', []); } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/NodeTypeRegisterCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodeTypeRegisterCommandTest.php index 6c7adfb4..712b4a13 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/NodeTypeRegisterCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodeTypeRegisterCommandTest.php @@ -1,34 +1,45 @@ application->add(new NodeTypeRegisterCommand()); - $this->nodeTypeManager = $this->getMockBuilder( - 'PHPCR\Tests\Stubs\MockNodeTypeManager' - )->disableOriginalConstructor()->getMock(); + $this->nodeTypeManager = $this->getMockBuilder(MockNodeTypeManager::class) + ->disableOriginalConstructor() + ->getMock(); } - public function testNodeTypeRegister() + public function testNodeTypeRegister(): void { $this->session->expects($this->once()) ->method('getWorkspace') - ->will($this->returnValue($this->workspace)); + ->willReturn($this->workspace); + $this->workspace->expects($this->once()) ->method('getNodeTypeManager') - ->will($this->returnValue($this->nodeTypeManager)); + ->willReturn($this->nodeTypeManager); + $this->nodeTypeManager->expects($this->once()) ->method('registerNodeTypesCnd'); - $ct = $this->executeCommand('phpcr:node-type:register', array( - 'cnd-file' => array(__DIR__.'/fixtures/cnd_dummy.cnd'), - )); + $this->executeCommand('phpcr:node-type:register', [ + 'cnd-file' => [__DIR__.'/fixtures/cnd_dummy.cnd'], + ]); } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php index db523a21..6806109e 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php @@ -1,119 +1,129 @@ application->add(new NodesUpdateCommand()); - $this->query = $this->getMock('PHPCR\Query\QueryInterface'); + $this->query = $this->createMock(QueryInterface::class); } - public function provideNodeUpdate() + /** + * @return array>> + */ + public function provideNodeUpdate(): array { - return array( - - // no query specified - array(array( - 'exception' => 'InvalidArgumentException', - )), - - // specify query - array(array( + return [ + // No query specified + [[ + 'exception' => \InvalidArgumentException::class, + ]], + // Specify query + [[ 'query' => 'SELECT * FROM nt:unstructured WHERE foo="bar"', - )), - - // set, remote properties and mixins - array(array( - 'setProp' => array(array('foo', 'bar')), - 'removeProp' => array('bar'), - 'addMixin' => array('mixin1'), - 'removeMixin' => array('mixin1'), + ]], + // Set, remote properties and mixins + [[ + 'setProp' => [['foo', 'bar']], + 'removeProp' => ['bar'], + 'addMixin' => ['mixin1'], + 'removeMixin' => ['mixin1'], 'query' => 'SELECT * FROM nt:unstructured', - )), - ); + ]], + ]; } - protected function setupQueryManager($options) + /** + * @param array $options + */ + protected function setupQueryManager(array $options): void { - $options = array_merge(array( - 'query' => '', - ), $options); + $options = array_merge(['query' => ''], $options); - $this->session->expects($this->any()) + $this->session ->method('getWorkspace') - ->will($this->returnValue($this->workspace)); - $this->workspace->expects($this->any()) + ->willReturn($this->workspace); + + $this->workspace ->method('getQueryManager') - ->will($this->returnValue($this->queryManager)); + ->willReturn($this->queryManager); - $this->queryManager->expects($this->any()) + $this->queryManager ->method('createQuery') ->with($options['query'], 'JCR-SQL2') - ->will($this->returnValue($this->query)); - $this->query->expects($this->any()) + ->willReturn($this->query); + + $this->query ->method('execute') - ->will($this->returnValue(array( - $this->row1, - ))); - $this->row1->expects($this->any()) + ->willReturn([$this->row1]); + + $this->row1 ->method('getNode') - ->will($this->returnValue($this->node1)); + ->willReturn($this->node1); } /** * @dataProvider provideNodeUpdate + * + * @param array $options */ - public function testNodeUpdate($options) + public function testNodeUpdate(array $options): void { - $options = array_merge(array( + $options = array_merge([ 'query' => null, - 'setProp' => array(), - 'removeProp' => array(), - 'addMixin' => array(), - 'removeMixin' => array(), + 'setProp' => [], + 'removeProp' => [], + 'addMixin' => [], + 'removeMixin' => [], 'exception' => null, - ), $options); + ], $options); if ($options['exception']) { - $this->setExpectedException($options['exception']); + $this->expectException($options['exception']); } $this->setupQueryManager($options); - $args = array( - '--query-language' => null, + $args = [ '--query' => $options['query'], '--no-interaction' => true, - '--set-prop' => array(), - '--remove-prop' => array(), - '--add-mixin' => array(), - '--remove-mixin' => array(), - ); + '--set-prop' => [], + '--remove-prop' => [], + '--add-mixin' => [], + '--remove-mixin' => [], + ]; + $setPropertyArguments = []; foreach ($options['setProp'] as $setProp) { - list($prop, $value) = $setProp; - $this->node1->expects($this->at(0)) - ->method('setProperty') - ->with($prop, $value); - + [$prop, $value] = $setProp; + $setPropertyArguments[] = [$prop, $value, null]; $args['--set-prop'][] = $prop.'='.$value; } foreach ($options['removeProp'] as $prop) { - $this->node1->expects($this->at(1)) - ->method('setProperty') - ->with($prop, null); - + $setPropertyArguments[] = [$prop, null, null]; $args['--remove-prop'][] = $prop; } + $this->node1 + ->method('setProperty') + ->withConsecutive(...$setPropertyArguments); + foreach ($options['addMixin'] as $mixin) { $this->node1->expects($this->once()) ->method('addMixin') @@ -130,23 +140,23 @@ public function testNodeUpdate($options) $args['--remove-mixin'][] = $mixin; } - $ct = $this->executeCommand('phpcr:nodes:update', $args); + $this->executeCommand('phpcr:nodes:update', $args); } - public function testApplyClosure() + public function testApplyClosure(): void { - $args = array( - '--query' => "SELECT foo FROM bar", + $args = [ + '--query' => 'SELECT foo FROM bar', '--no-interaction' => true, - '--apply-closure' => array( + '--apply-closure' => [ '$session->getNodeByIdentifier("/foo"); $node->setProperty("foo", "bar");', - function ($session, $node) { + function ($session, $node): void { $node->setProperty('foo', 'bar'); - } - ), - ); + }, + ], + ]; - $this->setupQueryManager(array('query' => 'SELECT foo FROM bar')); + $this->setupQueryManager(['query' => 'SELECT foo FROM bar']); $this->node1->expects($this->exactly(2)) ->method('setProperty') @@ -156,6 +166,6 @@ function ($session, $node) { ->method('getNodeByIdentifier') ->with('/foo'); - $ct = $this->executeCommand('phpcr:nodes:update', $args); + $this->executeCommand('phpcr:nodes:update', $args); } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceCreateCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceCreateCommandTest.php index 1181b43a..f3352a3b 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceCreateCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceCreateCommandTest.php @@ -1,77 +1,88 @@ application->add(new WorkspaceCreateCommand()); } - public function testCreate() + public function testCreate(): void { $this->session->expects($this->once()) ->method('getWorkspace') - ->will($this->returnValue($this->workspace)) - ; + ->willReturn($this->workspace); + $this->session->expects($this->once()) ->method('getRepository') - ->will($this->returnValue($this->repository)) - ; + ->willReturn($this->repository); + $this->repository->expects($this->once()) ->method('getDescriptor') ->with(RepositoryInterface::OPTION_WORKSPACE_MANAGEMENT_SUPPORTED) - ->will($this->returnValue(true)) - ; + ->willReturn(true); + $this->workspace->expects($this->once()) ->method('createWorkspace') - ->with('test_workspace') - ; + ->with('test_workspace'); + $this->workspace->expects($this->once()) ->method('getAccessibleWorkspaceNames') - ->will($this->returnValue(array('default'))) - ; + ->willReturn(['default']); - $this->executeCommand('phpcr:workspace:create', array( - 'name' => 'test_workspace' - )); + $this->executeCommand('phpcr:workspace:create', [ + 'name' => 'test_workspace', + ]); } /** * Handle trying to create existing workspace. */ - public function testCreateExisting() + public function testCreateExisting(): void { - $this->session->expects($this->once()) + $this->session->expects($this->exactly(2)) ->method('getWorkspace') - ->will($this->returnValue($this->workspace)) - ; - $this->session->expects($this->once()) + ->willReturn($this->workspace); + + $this->session->expects($this->exactly(2)) ->method('getRepository') - ->will($this->returnValue($this->repository)); - $this->repository->expects($this->once()) + ->willReturn($this->repository); + + $this->repository->expects($this->exactly(2)) ->method('getDescriptor') ->with(RepositoryInterface::OPTION_WORKSPACE_MANAGEMENT_SUPPORTED) - ->will($this->returnValue(true)) - ; - $this->workspace->expects($this->once()) + ->willReturn(true); + + $this->workspace->expects($this->exactly(2)) ->method('getAccessibleWorkspaceNames') - ->will($this->returnValue(array('default', 'test'))) - ; + ->willReturn(['default', 'test']); $tester = $this->executeCommand( 'phpcr:workspace:create', - array('name' => 'test'), + ['name' => 'test'], 2 ); - $this->assertContains('already has a workspace called "test"', $tester->getDisplay()); + $this->assertStringContainsString('already has a workspace called "test"', $tester->getDisplay()); + + $tester = $this->executeCommand( + 'phpcr:workspace:create', + [ + 'name' => 'test', + '--ignore-existing' => true, + ], + 0 + ); + + $this->assertStringContainsString('already has a workspace called "test"', $tester->getDisplay()); } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceDeleteCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceDeleteCommandTest.php index e4b446aa..30df1404 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceDeleteCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceDeleteCommandTest.php @@ -1,67 +1,67 @@ application->add(new WorkspaceDeleteCommand()); } - public function testDelete() + public function testDelete(): void { $this->session->expects($this->once()) ->method('getWorkspace') - ->will($this->returnValue($this->workspace)) - ; + ->willReturn($this->workspace); + $this->workspace->expects($this->once()) ->method('getAccessibleWorkspaceNames') - ->will($this->returnValue(array('default', 'test_workspace', 'other'))) - ; + ->willReturn(['default', 'test_workspace', 'other']); + $this->session->expects($this->once()) ->method('getRepository') - ->will($this->returnValue($this->repository)) - ; + ->willReturn($this->repository); + $this->repository->expects($this->once()) ->method('getDescriptor') ->with(RepositoryInterface::OPTION_WORKSPACE_MANAGEMENT_SUPPORTED) - ->will($this->returnValue(true)) - ; + ->willReturn(true); + $this->workspace->expects($this->once()) ->method('deleteWorkspace') - ->with('test_workspace') - ; + ->with('test_workspace'); - $ct = $this->executeCommand('phpcr:workspace:delete', array( + $ct = $this->executeCommand('phpcr:workspace:delete', [ 'name' => 'test_workspace', '--force' => 'true', - )); + ]); - $this->assertContains("Deleted workspace 'test_workspace'.", $ct->getDisplay()); + $this->assertStringContainsString("Deleted workspace 'test_workspace'.", $ct->getDisplay()); } - public function testDeleteNonexistent() + public function testDeleteNonexistent(): void { $this->session->expects($this->once()) ->method('getWorkspace') - ->will($this->returnValue($this->workspace)) - ; + ->willReturn($this->workspace); + $this->workspace->expects($this->once()) ->method('getAccessibleWorkspaceNames') - ->will($this->returnValue(array('default', 'other'))) - ; + ->willReturn(['default', 'other']); - $ct = $this->executeCommand('phpcr:workspace:delete', array( + $ct = $this->executeCommand('phpcr:workspace:delete', [ 'name' => 'test_workspace', '--force' => 'true', - )); + ]); - $this->assertContains("Workspace 'test_workspace' does not exist.", $ct->getDisplay()); + $this->assertStringContainsString("Workspace 'test_workspace' does not exist.", $ct->getDisplay()); } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceExportCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceExportCommandTest.php index 55c525c9..1868ed0c 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceExportCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceExportCommandTest.php @@ -1,33 +1,55 @@ application->add(new WorkspaceExportCommand()); } - public function testNodeTypeList() + public function tearDown(): void + { + unlink('test'); + } + + public function testNodeTypeList(): void { $this->session->expects($this->once()) ->method('getRepository') - ->will($this->returnValue($this->repository)); + ->willReturn($this->repository); + $this->repository->expects($this->once()) ->method('getDescriptor') ->with(RepositoryInterface::OPTION_XML_EXPORT_SUPPORTED) - ->will($this->returnValue(true)); + ->willReturn(true); + $this->session->expects($this->once()) ->method('exportSystemView'); - $ct = $this->executeCommand('phpcr:workspace:export', array( - 'filename' => 'test' - )); + if (method_exists($this, 'assertFileDoesNotExist')) { + $this->assertFileDoesNotExist('test', 'test export file must not exist, it will be overwritten'); + } else { + // support phpunit 8 and older, can be removed when we only support php 9 or newer + $this->assertFileNotExists('test', 'test export file must not exist, it will be overwritten'); + } + + $ct = $this->executeCommand('phpcr:workspace:export', [ + 'filename' => 'test', + ]); + + if (method_exists($ct, 'getStatusCode')) { + // Only available since symfony 2.4 + $this->assertEquals(0, $ct->getStatusCode()); + } + $this->assertFileExists('test'); } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceImportCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceImportCommandTest.php index d4641a55..13e16cf4 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceImportCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceImportCommandTest.php @@ -1,35 +1,64 @@ application->add(new WorkspaceImportCommand()); } - public function testNodeTypeList() + public function testImport(): void { $this->session->expects($this->once()) ->method('getRepository') - ->will($this->returnValue($this->repository)); + ->willReturn($this->repository); + $this->repository->expects($this->once()) ->method('getDescriptor') ->with(RepositoryInterface::OPTION_XML_IMPORT_SUPPORTED) - ->will($this->returnValue(true)); + ->willReturn(true); + + $this->session->expects($this->once()) + ->method('importXml') + ->with('/', 'test_import.xml', ImportUUIDBehaviorInterface::IMPORT_UUID_CREATE_NEW); + + $ct = $this->executeCommand('phpcr:workspace:import', [ + 'filename' => 'test_import.xml', + ]); + + $this->assertStringContainsString('Successfully imported', $ct->getDisplay()); + } + + public function testImportUuidBehaviorThrow(): void + { + $this->session->expects($this->once()) + ->method('getRepository') + ->willReturn($this->repository); + + $this->repository->expects($this->once()) + ->method('getDescriptor') + ->with(RepositoryInterface::OPTION_XML_IMPORT_SUPPORTED) + ->willReturn(true); + $this->session->expects($this->once()) - ->method('importXml'); + ->method('importXml') + ->with('/', 'test_import.xml', ImportUUIDBehaviorInterface::IMPORT_UUID_COLLISION_THROW); - $ct = $this->executeCommand('phpcr:workspace:import', array( - 'filename' => 'test_import.xml' - )); + $ct = $this->executeCommand('phpcr:workspace:import', [ + 'filename' => 'test_import.xml', + '--uuid-behavior' => 'throw', + ]); - $this->assertContains('Successfully imported', $ct->getDisplay()); + $this->assertStringContainsString('Successfully imported', $ct->getDisplay()); } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceListCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceListCommandTest.php index 53f2264d..1044200d 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceListCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceListCommandTest.php @@ -1,38 +1,39 @@ application->add(new WorkspaceListCommand()); } - public function testNodeTypeList() + public function testNodeTypeList(): void { $this->session->expects($this->once()) ->method('getWorkspace') - ->will($this->returnValue($this->workspace)); + ->willReturn($this->workspace); + $this->workspace->expects($this->once()) ->method('getAccessibleWorkspaceNames') - ->will($this->returnValue(array( - 'foo', 'bar' - ))); + ->willReturn(['foo', 'bar']); - $ct = $this->executeCommand('phpcr:workspace:list', array( - )); + $ct = $this->executeCommand('phpcr:workspace:list', [ + ]); - $expected = <<assertEquals($expected, $ct->getDisplay()); } diff --git a/tests/PHPCR/Tests/Util/Console/Command/WorkspacePurgeCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/WorkspacePurgeCommandTest.php index b83fbb40..f37ecfe1 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/WorkspacePurgeCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/WorkspacePurgeCommandTest.php @@ -1,32 +1,36 @@ application->add(new WorkspacePurgeCommand()); } - public function testNodeTypePurge() + public function testNodeTypePurge(): void { $this->session->expects($this->once()) ->method('getRootNode') - ->will($this->returnValue($this->node1)); + ->willReturn($this->node1); + $this->node1->expects($this->once()) ->method('getProperties') - ->will($this->returnValue(array())); + ->willReturn([]); + $this->node1->expects($this->once()) ->method('getNodes') - ->will($this->returnValue(array())); + ->willReturn([]); - $ct = $this->executeCommand('phpcr:workspace:purge', array( + $this->executeCommand('phpcr:workspace:purge', [ '--force' => true, - )); + ]); } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceQueryCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceQueryCommandTest.php index 274d9536..d7510126 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceQueryCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceQueryCommandTest.php @@ -1,43 +1,57 @@ application->add(new WorkspaceQueryCommand()); - $this->query = $this->getMock('PHPCR\Query\QueryInterface'); + $this->query = $this->createMock(QueryInterface::class); } - public function testQuery() + public function testQuery(): void { - $this->queryManager->expects($this->any()) + $this->queryManager ->method('getSupportedQueryLanguages') - ->will($this->returnValue(array('JCR-SQL2'))); - $this->session->expects($this->any()) + ->willReturn(['JCR-SQL2']); + + $this->session ->method('getWorkspace') - ->will($this->returnValue($this->workspace)); - $this->workspace->expects($this->any()) + ->willReturn($this->workspace); + + $this->workspace ->method('getQueryManager') - ->will($this->returnValue($this->queryManager)); + ->willReturn($this->queryManager); + $this->queryManager->expects($this->once()) ->method('createQuery') ->with('SELECT foo FROM foo', 'JCR-SQL2') - ->will($this->returnValue($this->query)); + ->willReturn($this->query); + $this->query->expects($this->once()) ->method('getLanguage') - ->will($this->returnValue('FOOBAR')); + ->willReturn('FOOBAR'); + $this->query->expects($this->once()) ->method('execute') - ->will($this->returnValue(array())); + ->willReturn([]); - $ct = $this->executeCommand('phpcr:workspace:query', array( + $this->executeCommand('phpcr:workspace:query', [ 'query' => 'SELECT foo FROM foo', - )); + ]); } } diff --git a/tests/PHPCR/Tests/Util/Console/Helper/PhpcrConsoleDumperHelperTest.php b/tests/PHPCR/Tests/Util/Console/Helper/PhpcrConsoleDumperHelperTest.php index 54d40f53..748fb872 100644 --- a/tests/PHPCR/Tests/Util/Console/Helper/PhpcrConsoleDumperHelperTest.php +++ b/tests/PHPCR/Tests/Util/Console/Helper/PhpcrConsoleDumperHelperTest.php @@ -1,59 +1,66 @@ outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $this->helper = new PhpcrConsoleDumperHelper; + $this->outputMock = $this->createMock(OutputInterface::class); + $this->helper = new PhpcrConsoleDumperHelper(); } - public function provideHelper() + /** + * @return array>> + */ + public function provideHelper(): array { - return array( - array(array()), - array(array( - 'show_props' => true, - )), - ); + return [ + [[]], + [['show_props' => true]], + ]; } /** * @dataProvider provideHelper + * + * @param array $options */ - public function testGetTreeWalker($options) + public function testGetTreeWalker(array $options): void { - $options = array_merge(array( + $options = array_merge([ 'dump_uuids' => false, 'ref_format' => 'uuid', 'show_props' => false, 'show_sys_nodes' => false, - ), $options); + ], $options); $tw = $this->helper->getTreeWalker($this->outputMock, $options); - $this->assertInstanceOf( - 'PHPCR\Util\TreeWalker', - $tw - ); - $refl = new \ReflectionClass($tw); - $propVisitorProp = $refl->getProperty('propertyVisitor'); + $reflection = new \ReflectionClass($tw); + $propVisitorProp = $reflection->getProperty('propertyVisitor'); $propVisitorProp->setAccessible(true); $propVisitor = $propVisitorProp->getValue($tw); - if ($options['show_props'] === true) { - $this->assertInstanceOf( - 'PHPCR\Util\Console\Helper\TreeDumper\ConsoleDumperPropertyVisitor', - $propVisitor - ); + if (true === $options['show_props']) { + $this->assertInstanceOf(ConsoleDumperPropertyVisitor::class, $propVisitor); } else { $this->assertNull($propVisitor); } diff --git a/tests/PHPCR/Tests/Util/NodeHelperTest.php b/tests/PHPCR/Tests/Util/NodeHelperTest.php index 33179c75..1878745e 100644 --- a/tests/PHPCR/Tests/Util/NodeHelperTest.php +++ b/tests/PHPCR/Tests/Util/NodeHelperTest.php @@ -1,44 +1,62 @@ 'http://phpcr/', 'b' => 'http://jcr/'); - private $usedNames = array('a:x', 'b:y', 'c'); + /** + * @var array + */ + private array $namespaces = ['a' => 'http://phpcr/', 'b' => 'http://jcr/']; + + /** + * @var string[] + */ + private array $usedNames = ['a:x', 'b:y', 'c']; - public static function hints() + /** + * @return array + */ + public static function hints(): array { - return array( - array('', true), - array(':', true), - array('{}', true), - array('b:', 'b:'), - array('{http://jcr}', 'b:'), - array('b:z', 'b:z'), - array('{http://phpcr}bar', 'a:bar'), - ); + return [ + ['', true], + [':', true], + ['{}', true], + ['b:', 'b:'], + ['{http://jcr}', 'b:'], + ['b:z', 'b:z'], + ['{http://phpcr}bar', 'a:bar'], + ]; } - public static function invalidHints() + /** + * @return array> + */ + public static function invalidHints(): array { - return array( - array('::'), - array('a'), // no colon - array('a:foo:'), - array('{foo'), - array('x:'), // not an existing namespace prefix - array('{http://xy}'), // not an existing namespace uri - array('x:a'), // not an existing namespace prefix with a local name prefix - array('{http://xy}a'), // not an existing namespace uri with a local name prefix - ); + return [ + ['::'], + ['a'], // no colon + ['a:foo:'], + ['{foo'], + ['x:'], // not an existing namespace prefix + ['{http://xy}'], // not an existing namespace uri + ['x:a'], // not an existing namespace prefix with a local name prefix + ['{http://xy}a'], // not an existing namespace uri with a local name prefix + ]; } - public function testGenerateAutoNodeNameNoHint() + public function testGenerateAutoNodeNameNoHint(): void { $result = NodeHelper::generateAutoNodeName($this->usedNames, $this->namespaces, 'a'); $this->assertEquals('a:', substr($result, 0, 2)); @@ -47,134 +65,139 @@ public function testGenerateAutoNodeNameNoHint() /** * @dataProvider hints */ - public function testGenerateAutoNodeName($hint, $expect) + public function testGenerateAutoNodeName(string $hint, bool|string $expect): void { $result = NodeHelper::generateAutoNodeName($this->usedNames, $this->namespaces, 'a', $hint); if (true === $expect) { - $this->assertFalse(strpos($result, ':')); + $this->assertStringNotContainsString(':', $result); } else { + $this->assertIsString($expect); $this->assertEquals($expect, substr($result, 0, strlen($expect))); } } /** * @dataProvider invalidHints - * @expectedException \PHPCR\RepositoryException */ - public function testGenerateAutoNodeNameInvalid($hint) + public function testGenerateAutoNodeNameInvalid(string $hint): void { + $this->expectException(RepositoryException::class); NodeHelper::generateAutoNodeName($this->usedNames, $this->namespaces, 'a', $hint); } - public function testIsSystemItem() + public function testIsSystemItem(): void { - $sys = $this->getMock('PHPCR\Tests\Stubs\MockNode'); + $sys = $this->createMock(MockNode::class); + $sys->expects($this->once()) ->method('getDepth') - ->will($this->returnValue(0)) - ; + ->willReturn(0); + $sys->expects($this->once()) ->method('getName') - ->will($this->returnValue('jcr:root')) - ; + ->willReturn('jcr:root'); + $this->assertTrue(NodeHelper::isSystemItem($sys)); - $sys = $this->getMock('PHPCR\Tests\Stubs\MockNode'); + $sys = $this->createMock(MockNode::class); $sys->expects($this->once()) ->method('getDepth') - ->will($this->returnValue(1)) - ; + ->willReturn(1); + $sys->expects($this->once()) ->method('getName') - ->will($this->returnValue('jcr:system')) - ; + ->willReturn('jcr:system'); + $this->assertTrue(NodeHelper::isSystemItem($sys)); - $top = $this->getMock('PHPCR\Tests\Stubs\MockNode'); + $top = $this->createMock(MockNode::class); $top->expects($this->once()) ->method('getDepth') - ->will($this->returnValue(1)) - ; + ->willReturn(1); + $top->expects($this->once()) ->method('getName') - ->will($this->returnValue('jcrname')) // this is NOT in the jcr namespace - ; + ->willReturn('jcrname'); // this is NOT in the jcr namespace + $this->assertFalse(NodeHelper::isSystemItem($top)); - $deep = $this->getMock('PHPCR\Tests\Stubs\MockNode'); + $deep = $this->createMock(MockNode::class); $deep->expects($this->once()) ->method('getDepth') - ->will($this->returnValue(2)) - ; + ->willReturn(2); + $this->assertFalse(NodeHelper::isSystemItem($deep)); } - public function testCalculateOrderBeforeSwapLast() + public function testCalculateOrderBeforeSwapLast(): void { - $old = array('one', 'two', 'three', 'four'); - $new = array('one', 'two', 'four', 'three'); + $old = ['one', 'two', 'three', 'four']; + $new = ['one', 'two', 'four', 'three']; $reorders = NodeHelper::calculateOrderBefore($old, $new); - $expected = array( + $expected = [ 'three' => null, - 'two' => 'four', // TODO: this is an unnecessary but harmless NOOP. we should try to eliminate - ); + 'two' => 'four', // TODO: this is an unnecessary but harmless NOOP. we should try to eliminate + ]; + $this->assertEquals($expected, $reorders); } - public function testCalculateOrderBeforeSwap() + public function testCalculateOrderBeforeSwap(): void { - $old = array('one', 'two', 'three', 'four'); - $new = array('one', 'four', 'three', 'two'); + $old = ['one', 'two', 'three', 'four']; + $new = ['one', 'four', 'three', 'two']; $reorders = NodeHelper::calculateOrderBefore($old, $new); - $expected = array( + $expected = [ 'three' => 'two', - 'two' => null, - ); + 'two' => null, + ]; + $this->assertEquals($expected, $reorders); } - public function testCalculateOrderBeforeReverse() + public function testCalculateOrderBeforeReverse(): void { - $old = array('one', 'two', 'three', 'four'); - $new = array('four', 'three', 'two', 'one'); + $old = ['one', 'two', 'three', 'four']; + $new = ['four', 'three', 'two', 'one']; $reorders = NodeHelper::calculateOrderBefore($old, $new); - $expected = array( + $expected = [ 'three' => 'two', - 'two' => 'one', - 'one' => null, - ); + 'two' => 'one', + 'one' => null, + ]; $this->assertEquals($expected, $reorders); } - public function testCalculateOrderBeforeDeleted() + public function testCalculateOrderBeforeDeleted(): void { - $old = array('one', 'two', 'three', 'four'); - $new = array('one', 'three', 'two'); + $old = ['one', 'two', 'three', 'four']; + $new = ['one', 'three', 'two']; $reorders = NodeHelper::calculateOrderBefore($old, $new); - $expected = array( + $expected = [ 'two' => null, - 'one' => 'three', // TODO: this is an unnecessary but harmless NOOP. we should try to eliminate - ); + 'one' => 'three', // TODO: this is an unnecessary but harmless NOOP. we should try to eliminate + ]; + $this->assertEquals($expected, $reorders); } /** * @group benchmark */ - public function testBenchmarkOrderBeforeArray() + public function testBenchmarkOrderBeforeArray(): void { - $nodes = array(); + $nodes = []; - for ($i = 0; $i < 100000; $i++) { - $nodes[] = 'test' . $i; + for ($i = 0; $i < 100000; ++$i) { + $nodes[] = 'test'.$i; } $start = microtime(true); diff --git a/tests/PHPCR/Tests/Util/PathHelperTest.php b/tests/PHPCR/Tests/Util/PathHelperTest.php index 3be5f7eb..5bafb693 100644 --- a/tests/PHPCR/Tests/Util/PathHelperTest.php +++ b/tests/PHPCR/Tests/Util/PathHelperTest.php @@ -1,133 +1,142 @@ assertTrue(PathHelper::assertValidAbsolutePath('/parent/child')); - } - - public function testAssertValidPathRoot() - { - $this->assertTrue(PathHelper::assertValidAbsolutePath('/')); - } - - public function testAssertValidPathNamespaced() - { - $this->assertTrue(PathHelper::assertValidAbsolutePath('/jcr:foo_/b-a/0^.txt')); - } - - public function testAssertValidPathIndexed() - { - $this->assertTrue(PathHelper::assertValidAbsolutePath('/parent[7]/child')); - } - - public function testAssertValidPathIndexedAtEnd() + /** + * @dataProvider dataproviderValidAbsolutePaths + */ + public function testAssertValidAbsolutePath(string $path, bool $destination = false): void { - $this->assertTrue(PathHelper::assertValidAbsolutePath('/parent[7]/child[3]')); + $this->assertTrue(PathHelper::assertValidAbsolutePath($path, $destination)); } /** - * @expectedException \PHPCR\RepositoryException + * @return array */ - public function testAssertValidTargetPathNoIndex() + public static function dataproviderValidAbsolutePaths(): array { - PathHelper::assertValidAbsolutePath('/parent/child[7]', true); + return [ + ['/parent/child'], + ['/'], + ['/jcr:foo_/b-a/0^.txt'], + ['/parent[7]/child'], + ['/parent[7]/child', true], // index is allowed in destination parent path, only not in last element + ['/parent[7]/child[3]'], + ]; } /** - * @expectedException \PHPCR\RepositoryException + * @dataProvider dataproviderInvalidAbsolutePaths */ - public function testAssertValidPathNotAbsolute() + public function testAssertInvalidAbsolutePath(string $path, bool $destination = false): void { - PathHelper::assertValidAbsolutePath('parent'); + $this->expectException(RepositoryException::class); + + PathHelper::assertValidAbsolutePath($path, $destination); } /** - * @expectedException \PHPCR\RepositoryException + * @dataProvider dataproviderValidAbsolutePathsWithNamespaces */ - public function testAssertValidPathDouble() + public function testAssertAbsolutePathNamespace(string $path): void { - PathHelper::assertValidAbsolutePath('/parent//child'); + $this->assertTrue(PathHelper::assertValidAbsolutePath($path, false, true, ['jcr', 'nt'])); } /** - * @expectedException \PHPCR\RepositoryException + * @return array */ - public function testAssertValidPathParent() + public function dataproviderValidAbsolutePathsWithNamespaces(): array { - PathHelper::assertValidAbsolutePath('/parent/../child'); + return [ + ['/parent/child'], + ['/jcr:localname'], + ['/jcr:localname/test'], + ['/jcr:localname/test/nt:node'], + ['/jcr:localname/test/nt:node/bla'], + ['/'], + ['/jcr:foo_/b-a/0^.txt'], + ['/parent[7]/child'], + ['/jcr:localname[3]/test'], + ]; } - /** - * @expectedException \PHPCR\RepositoryException - */ - public function testAssertValidPathSelf() + public function testAssertInvalidNamespaceAbsolutePath(): void { - PathHelper::assertValidAbsolutePath('/parent/./child'); + $this->expectException(NamespaceException::class); + $this->expectExceptionMessage('invalidprefix and other-ns'); + + PathHelper::assertValidAbsolutePath('/invalidprefix:localname/other-ns:test/invalidprefix:node/bla', false, true, ['jcr', 'nt']); } /** - * @expectedException \PHPCR\RepositoryException + * @dataProvider dataproviderInvalidAbsolutePaths */ - public function testAssertValidPathTrailing() + public function testAssertInvalidAbsolutePathNoThrow(string $path, bool $destination = false): void { - PathHelper::assertValidAbsolutePath('/parent/child/'); + $this->assertFalse(PathHelper::assertValidAbsolutePath($path, $destination, false)); } - public function testAssertValidPathNoThrow() + /** + * @return array + */ + public function dataproviderInvalidAbsolutePaths(): array { - $this->assertFalse(PathHelper::assertValidAbsolutePath('parent', false, false)); + return [ + ['/parent/child[7]', true], // destination last element with index + ['parent'], // not absolute + ['/parent//child'], + ['//'], + ['/parent/../child'], + ['/parent/./child'], + ['/parent/child/'], + ]; } // assertValidLocalName tests - public function testAssertValidLocalName() + public function testAssertValidLocalName(): void { $this->assertTrue(PathHelper::assertValidLocalName('nodename')); } - public function testAssertValidLocalNameRootnode() + public function testAssertValidLocalNameRootnode(): void { $this->assertTrue(PathHelper::assertValidLocalName('')); } /** - * @expectedException \PHPCR\RepositoryException - */ - public function testAssertValidLocalNameNamespaced() - { - $this->assertTrue(PathHelper::assertValidLocalName('jcr:nodename')); - } - - /** - * @expectedException \PHPCR\RepositoryException + * @dataProvider dataproviderInvalidLocalNames */ - public function testAssertValidLocalNamePath() + public function testAssertInvalidLocalName(string $name): void { - $this->assertTrue(PathHelper::assertValidLocalName('/path')); - } + $this->expectException(RepositoryException::class); - /** - * @expectedException \PHPCR\RepositoryException - */ - public function testAssertValidLocalNameSelf() - { - PathHelper::assertValidLocalName('.'); + PathHelper::assertValidLocalName($name); } /** - * @expectedException \PHPCR\RepositoryException + * @return array */ - public function testAssertValidLocalNameParent() + public static function dataproviderInvalidLocalNames(): array { - PathHelper::assertValidLocalName('..'); + return [ + ['jcr:nodename'], + ['/path'], + ['.'], + ['..'], + ]; } // normalizePath tests @@ -135,151 +144,255 @@ public function testAssertValidLocalNameParent() /** * @dataProvider dataproviderNormalizePath */ - public function testNormalizePath($inputPath, $outputPath) + public function testNormalizePath(string $inputPath, string $outputPath): void { $this->assertSame($outputPath, PathHelper::normalizePath($inputPath)); } - public static function dataproviderNormalizePath() - { - return array( - array('/', '/'), - array('/../foo', '/foo'), - array('/../', '/'), - array('/foo/../bar', '/bar'), - array('/foo/./bar', '/foo/bar'), - ); - } - - public static function dataproviderNormalizePathInvalid() + /** + * @return array + */ + public static function dataproviderNormalizePath(): array { - return array( - array('foo/bar'), - array('bar'), - array('/foo/bar/'), - array(''), - array(new \stdClass()), - ); + return [ + ['/', '/'], + ['/../foo', '/foo'], + ['/../', '/'], + ['/foo/../bar', '/bar'], + ['/foo/./bar', '/foo/bar'], + ]; } /** * @dataProvider dataproviderNormalizePathInvalid - * @expectedException \PHPCR\RepositoryException */ - public function testNormalizePathInvalidThrow($input) + public function testNormalizePathInvalidThrow(string $input): void { + $this->expectException(RepositoryException::class); + PathHelper::normalizePath($input); } /** * @dataProvider dataproviderNormalizePathInvalid */ - public function testNormalizePathInvalidNoThrow($input) + public function testNormalizePathInvalidNoThrow(string $input): void { $this->assertFalse(PathHelper::normalizePath($input, true, false)); } + /** + * @return array + */ + public static function dataproviderNormalizePathInvalid(): array + { + return [ + ['foo/bar'], + ['bar'], + ['/foo/bar/'], + [''], + ['//'], + ]; + } + // absolutizePath tests /** * @dataProvider dataproviderAbsolutizePath */ - public function testAbsolutizePath($inputPath, $context, $outputPath) + public function testAbsolutizePath(string $inputPath, string $context, string $outputPath): void { $this->assertSame($outputPath, PathHelper::absolutizePath($inputPath, $context)); } - public static function dataproviderAbsolutizePath() + /** + * @return array + */ + public static function dataproviderAbsolutizePath(): array { - return array( - array('/../foo', '/', '/foo'), - array('../', '/', '/'), - array('../foo/bar', '/baz', '/foo/bar'), - array('foo/./bar', '/baz', '/baz/foo/bar'), - ); + return [ + ['/../foo', '/', '/foo'], + ['../', '/', '/'], + ['../foo/bar', '/baz', '/foo/bar'], + ['foo/./bar', '/baz', '/baz/foo/bar'], + ]; } /** - * @expectedException \PHPCR\RepositoryException * @dataProvider dataproviderAbsolutizePathInvalid */ - public function testAbsolutizePathInvalidThrow($inputPath, $context, $target) + public function testAbsolutizePathInvalidThrow(string $inputPath, string $context, bool $target): void { + $this->expectException(RepositoryException::class); PathHelper::absolutizePath($inputPath, $context, $target); } /** * @dataProvider dataproviderAbsolutizePathInvalid */ - public function testAbsolutizePathInvalidNoThrow($inputPath, $context, $target) + public function testAbsolutizePathInvalidNoThrow(string $inputPath, string $context, bool $target): void { $this->assertFalse(PathHelper::absolutizePath($inputPath, $context, $target, false)); } - public static function dataproviderAbsolutizePathInvalid() + /** + * @return array + */ + public static function dataproviderAbsolutizePathInvalid(): array { - return array( - array('', '/context', false), - array(null, '/context', false), - array('foo', null, false), - array(new \stdClass(), '/context', false), - array('foo[2]', '/bar', true), - ); + return [ + ['', '/context', false], + ['//', '/context', false], + ['foo', '//', false], + ['foo[2]', '/bar', true], + ]; } - // getParentPath tests + // relativizePath tests - public function testGetParentPath() + /** + * @dataProvider dataproviderRelativizePath + */ + public function testRelativizePath(string $inputPath, string $context, string $outputPath): void { - $this->assertEquals('/parent', PathHelper::getParentPath('/parent/child')); + $this->assertSame($outputPath, PathHelper::relativizePath($inputPath, $context)); } - public function testGetParentPathNamespaced() + /** + * @return array + */ + public static function dataproviderRelativizePath(): array { - $this->assertEquals('/jcr:parent', PathHelper::getParentPath('/jcr:parent/ns:child')); + return [ + ['/parent/path/child', '/parent', 'path/child'], + ['/child', '/', 'child'], + ]; } - public function testGetParentPathNodeAtRoot() + /** + * @dataProvider dataproviderRelativizePathInvalid + */ + public function testRelativizePathInvalidThrow(string $inputPath, string $context): void { - $this->assertEquals('/', PathHelper::getParentPath('/parent')); + $this->expectException(RepositoryException::class); + + PathHelper::relativizePath($inputPath, $context); } - public function testGetParentPathRoot() + /** + * @dataProvider dataproviderRelativizePathInvalid + */ + public function testRelativizePathInvalidNoThrow(string $inputPath, string $context): void { - $this->assertEquals('/', PathHelper::getParentPath('/')); + $this->assertFalse(PathHelper::relativizePath($inputPath, $context, false)); } - public function provideGetNodeName() + /** + * @return array + */ + public static function dataproviderRelativizePathInvalid(): array { - return array( - array('/parent/child', 'child'), - array('/parent/ns:child', 'ns:child'), - array('/', ''), - ); + return [ + ['/path', '/context'], + ['/parent', '/parent/child'], + ]; + } + + // getParentPath tests + + /** + * @dataProvider dataproviderParentPath + */ + public function testGetParentPath(string $path, string $parent): void + { + $this->assertEquals($parent, PathHelper::getParentPath($path)); } /** - * @dataProvider provideGetNodeName + * @return array */ - public function testGetNodeName($path, $expected = null) + public function dataproviderParentPath(): array + { + return [ + ['/parent/child', '/parent'], + ['/jcr:parent/ns:child', '/jcr:parent'], + ['/child', '/'], + ['/', '/'], + ]; + } + + // getNodeName tests + + /** + * @dataProvider dataproviderGetNodeName + */ + public function testGetNodeName(string $path, string $expected): void { $this->assertEquals($expected, PathHelper::getNodeName($path)); } /** - * @expectedException PHPCR\RepositoryException - * @expectedExceptionMessage must be an absolute path + * @return array + */ + public function dataproviderGetNodeName(): array + { + return [ + ['/parent/child', 'child'], + ['/parent/ns:child', 'ns:child'], + ['/', ''], + ]; + } + + /** + * @dataProvider dataproviderGetLocalNodeName + */ + public function testGetLocalNodeName(string $path, string $expected): void + { + $this->assertEquals($expected, PathHelper::getLocalNodeName($path)); + } + + /** + * @return array */ - public function testGetNodeNameMustBeAbsolute() + public function dataproviderGetLocalNodeName(): array { + return [ + ['/parent/child', 'child'], + ['/foo:child', 'child'], + ['/parent/ns:child', 'child'], + ['/ns:parent/child:foo', 'foo'], + ['/', ''], + ]; + } + + public function testGetNodeNameMustBeAbsolute(): void + { + $this->expectException(RepositoryException::class); + $this->expectExceptionMessage('must be an absolute path'); + PathHelper::getNodeName('foobar'); } - public function testGetPathDepth() + // getPathDepth tests + + /** + * @dataProvider dataproviderPathDepth + */ + public function testGetPathDepth(string $path, int $depth): void { - $this->assertEquals(0, PathHelper::getPathDepth('/')); - $this->assertEquals(1, PathHelper::getPathDepth('/foo')); - $this->assertEquals(2, PathHelper::getPathDepth('/foo/bar')); - $this->assertEquals(2, PathHelper::getPathDepth('/foo/bar/')); + $this->assertEquals($depth, PathHelper::getPathDepth($path)); + } + + /** + * @return array + */ + public function dataproviderPathDepth(): array + { + return [ + ['/', 0], + ['/foo', 1], + ['/foo/bar', 2], + ['/foo/bar/', 2], + ]; } } diff --git a/tests/PHPCR/Tests/Util/QOM/BaseSqlGeneratorTest.php b/tests/PHPCR/Tests/Util/QOM/BaseSqlGeneratorTest.php new file mode 100644 index 00000000..c89b55e3 --- /dev/null +++ b/tests/PHPCR/Tests/Util/QOM/BaseSqlGeneratorTest.php @@ -0,0 +1,22 @@ +generator->evalNot('foo = bar'); + $this->assertSame('(NOT foo = bar)', $string); + } +} diff --git a/tests/PHPCR/Tests/Util/QOM/QueryBuilderTest.php b/tests/PHPCR/Tests/Util/QOM/QueryBuilderTest.php index 24982733..b89f7e2a 100644 --- a/tests/PHPCR/Tests/Util/QOM/QueryBuilderTest.php +++ b/tests/PHPCR/Tests/Util/QOM/QueryBuilderTest.php @@ -1,35 +1,68 @@ qf = $this->getMock('PHPCR\Query\QOM\QueryObjectModelFactoryInterface', array(), array()); + $this->qf = $this->getMockBuilder(QueryObjectModelFactoryInterface::class) + ->setMethods([]) + ->setConstructorArgs([]) + ->getMock(); } - public function testSetFirstResult() + public function testSetFirstResult(): void { $qb = new QueryBuilder($this->qf); $qb->setFirstResult(15); $this->assertEquals(15, $qb->getFirstResult()); } - public function testSetMaxResults() + public function testSetMaxResults(): void { $qb = new QueryBuilder($this->qf); $qb->setMaxResults(15); $this->assertEquals(15, $qb->getMaxResults()); } - public function testAddOrderBy() + /** + * @return DynamicOperandInterface + */ + private function createDynamicOperandMock() + { + /** @var DynamicOperandInterface $dynamicOperand */ + $dynamicOperand = $this->getMockBuilder(DynamicOperandInterface::class) + ->setMethods([]) + ->setConstructorArgs([]) + ->getMock(); + + return $dynamicOperand; + } + + public function testAddOrderBy(): void { - $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array()); + $dynamicOperand = $this->createDynamicOperandMock(); + $qb = new QueryBuilder($this->qf); $qb->addOrderBy($dynamicOperand, 'ASC'); $qb->addOrderBy($dynamicOperand, 'DESC'); @@ -37,9 +70,10 @@ public function testAddOrderBy() $orderings = $qb->getOrderings(); } - public function testAddOrderByLowercase() + public function testAddOrderByLowercase(): void { - $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array()); + $dynamicOperand = $this->createDynamicOperandMock(); + $qb = new QueryBuilder($this->qf); $qb->addOrderBy($dynamicOperand, 'asc'); $qb->addOrderBy($dynamicOperand, 'desc'); @@ -47,92 +81,112 @@ public function testAddOrderByLowercase() $orderings = $qb->getOrderings(); } - /** - * @expectedException \InvalidArgumentException - */ - public function testAddOrderByInvalid() + public function testAddOrderByInvalid(): void { - $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array()); + $this->expectException(\InvalidArgumentException::class); + + $dynamicOperand = $this->createDynamicOperandMock(); + $qb = new QueryBuilder($this->qf); $qb->addOrderBy($dynamicOperand, 'FOO'); } - public function testOrderBy() + public function testOrderBy(): void { - $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array()); + $dynamicOperand = $this->createDynamicOperandMock(); + $qb = new QueryBuilder($this->qf); $qb->orderBy($dynamicOperand, 'ASC'); $qb->orderBy($dynamicOperand, 'ASC'); $this->assertCount(1, $qb->getOrderings()); } - public function testOrderAscending() + public function testOrderAscending(): void { - $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array()); + $dynamicOperand = $this->createDynamicOperandMock(); + $this->qf->expects($this->once()) ->method('ascending') ->with($this->equalTo($dynamicOperand)); + $qb = new QueryBuilder($this->qf); $qb->addOrderBy($dynamicOperand, 'ASC'); } - public function testOrderDescending() + public function testOrderDescending(): void { - $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array()); + $dynamicOperand = $this->createDynamicOperandMock(); + $this->qf->expects($this->once()) ->method('descending') ->with($this->equalTo($dynamicOperand)); + $qb = new QueryBuilder($this->qf); $qb->addOrderBy($dynamicOperand, 'DESC'); } - public function testOrderAscendingIsDefault() + public function testOrderAscendingIsDefault(): void { - $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array()); + $dynamicOperand = $this->createDynamicOperandMock(); + $this->qf->expects($this->once()) ->method('ascending') ->with($this->equalTo($dynamicOperand)); + $qb = new QueryBuilder($this->qf); $qb->addOrderBy($dynamicOperand); } - public function testWhere() + /** + * @return ConstraintInterface + */ + private function createConstraintMock() + { + /** @var ConstraintInterface $constraint */ + $constraint = $this->getMockBuilder(ConstraintInterface::class) + ->setMethods([]) + ->setConstructorArgs([]) + ->getMock(); + + return $constraint; + } + + public function testWhere(): void { - $constraint = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array()); + $constraint = $this->createConstraintMock(); + $qb = new QueryBuilder($this->qf); $qb->where($constraint); $this->assertEquals($constraint, $qb->getConstraint()); } - public function testAndWhere() + public function testAndWhere(): void { - $dynamicOperand = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array()); - $this->qf = $this->getMock('PHPCR\Query\QOM\QueryObjectModelFactoryInterface', array(), array()); $this->qf->expects($this->once()) ->method('andConstraint'); - $constraint1 = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array()); - $constraint2 = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array()); + $constraint1 = $this->createConstraintMock(); + $constraint2 = $this->createConstraintMock(); + $qb = new QueryBuilder($this->qf); $qb->where($constraint1); $qb->andWhere($constraint2); } - public function testOrWhere() + public function testOrWhere(): void { - $dynamicOperand = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array()); - $this->qf = $this->getMock('PHPCR\Query\QOM\QueryObjectModelFactoryInterface', array(), array()); $this->qf->expects($this->once()) ->method('orConstraint'); - $constraint1 = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array()); - $constraint2 = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array()); + $constraint1 = $this->createConstraintMock(); + $constraint2 = $this->createConstraintMock(); + $qb = new QueryBuilder($this->qf); $qb->where($constraint1); $qb->orWhere($constraint2); } - public function testSelect() + public function testSelect(): void { $qb = new QueryBuilder($this->qf); $this->assertCount(0, $qb->getColumns()); @@ -142,7 +196,7 @@ public function testSelect() $this->assertCount(1, $qb->getColumns()); } - public function testAddSelect() + public function testAddSelect(): void { $qb = new QueryBuilder($this->qf); $this->assertCount(0, $qb->getColumns()); @@ -152,130 +206,222 @@ public function testAddSelect() $this->assertCount(2, $qb->getColumns()); } - public function testFrom() + /** + * @return SourceInterface + */ + private function createSourceMock() { - $source = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array()); + /** @var SourceInterface $source */ + $source = $this->getMockBuilder(SourceInterface::class) + ->setMethods([]) + ->setConstructorArgs([]) + ->getMock(); + + return $source; + } + + public function testFrom(): void + { + $source = $this->createSourceMock(); + $qb = new QueryBuilder($this->qf); $qb->from($source); $this->assertEquals($source, $qb->getSource()); } - public function testInvalidJoin() + /** + * @return SameNodeJoinConditionInterface + */ + private function createSameNodeJoinConditionMock() + { + /** @var SameNodeJoinConditionInterface $joinCondition */ + $joinCondition = $this->getMockBuilder(SameNodeJoinConditionInterface::class) + ->setMethods([]) + ->setConstructorArgs([]) + ->getMock(); + + return $joinCondition; + } + + public function testInvalidJoin(): void { - $this->setExpectedException('\RuntimeException'); - $source = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array()); - $joinCondition = $this->getMock('PHPCR\Query\QOM\SameNodeJoinConditionInterface', array(), array()); + $this->expectException(\RuntimeException::class); + + $source = $this->createSourceMock(); + $joinCondition = $this->createSameNodeJoinConditionMock(); + $qb = new QueryBuilder($this->qf); $qb->join($source, $joinCondition); } - public function testJoin() + public function testJoin(): void { - $source1 = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array()); - $source2= $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array()); - $joinCondition = $this->getMock('PHPCR\Query\QOM\SameNodeJoinConditionInterface', array(), array()); + $source1 = $this->createSourceMock(); + $source2 = $this->createSourceMock(); + $joinCondition = $this->createSameNodeJoinConditionMock(); + + $this->qf->expects($this->once()) + ->method('join') + ->with( + $source1, + $source2, + $this->equalTo(QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_INNER), + $joinCondition + ); + $qb = new QueryBuilder($this->qf); $qb->from($source1); $qb->join($source2, $joinCondition); } - public function testRightJoin() + public function testRightJoin(): void { - $source1 = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array()); - $source2= $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array()); - $joinCondition = $this->getMock('PHPCR\Query\QOM\SameNodeJoinConditionInterface', array(), array()); + $source1 = $this->createSourceMock(); + $source2 = $this->createSourceMock(); + $joinCondition = $this->createSameNodeJoinConditionMock(); + $this->qf->expects($this->once()) ->method('join') - ->with($source1, $source2, $this->equalTo(\PHPCR\Query\QOM\QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_RIGHT_OUTER), $joinCondition); + ->with( + $source1, + $source2, + $this->equalTo(QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_RIGHT_OUTER), + $joinCondition + ); + $qb = new QueryBuilder($this->qf); $qb->from($source1); $qb->rightJoin($source2, $joinCondition); } - public function testLeftJoin() + public function testLeftJoin(): void { - $source1 = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array()); - $source2= $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array()); - $joinCondition = $this->getMock('PHPCR\Query\QOM\SameNodeJoinConditionInterface', array(), array()); + $source1 = $this->createSourceMock(); + $source2 = $this->createSourceMock(); + $joinCondition = $this->createSameNodeJoinConditionMock(); + $this->qf->expects($this->once()) - ->method('join') - ->with($source1, $source2, $this->equalTo(\PHPCR\Query\QOM\QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_LEFT_OUTER), $joinCondition); + ->method('join') + ->with( + $source1, + $source2, + $this->equalTo(QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_LEFT_OUTER), + $joinCondition + ); + $qb = new QueryBuilder($this->qf); $qb->from($source1); $qb->leftJoin($source2, $joinCondition); } - public function testGetQuery() + public function testGetQuery(): void { - $source = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array()); - $constraint = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array()); + $source = $this->createSourceMock(); + $constraint = $this->createConstraintMock(); + $qb = new QueryBuilder($this->qf); $qb->from($source); $qb->where($constraint); + $this->qf->expects($this->once()) ->method('createQuery') - ->will($this->returnValue("true")); + ->willReturn($this->createMock(QueryObjectModelInterface::class)); + $qb->getQuery(); } - public function testGetQueryWithOffsetAndLimit() + + /** + * @return QueryObjectModelInterface&MockObject + */ + private function createQueryMock() { - $source = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array()); - $constraint = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array()); - $query = $this->getMock('PHPCR\Query\QOM\QueryObjectModelInterface', array(), array()); + $query = $this->getMockBuilder(QueryObjectModelInterface::class) + ->setMethods([]) + ->setConstructorArgs([]) + ->getMock(); + + return $query; + } + + public function testGetQueryWithOffsetAndLimit(): void + { + $source = $this->createSourceMock(); + $constraint = $this->createConstraintMock(); + + $query = $this->createQueryMock(); + $qb = new QueryBuilder($this->qf); $qb->from($source); $qb->where($constraint); $qb->setFirstResult(13); $qb->setMaxResults(42); + $query->expects($this->once()) ->method('setOffset'); $query->expects($this->once()) ->method('setLimit'); + $this->qf->expects($this->once()) ->method('createQuery') - ->will($this->returnValue($query)); + ->willReturn($query); + $qb->getQuery(); } - public function testSetParameter() + public function testSetParameter(): void { + $key = 'key'; + $value = 'value'; + $qb = new QueryBuilder($this->qf); - $key = "key"; - $value = "value"; $qb->setParameter($key, $value); + $this->assertEquals($value, $qb->getParameter($key)); } - public function testSetParameters() + public function testSetParameters(): void { + $key1 = 'key1'; + $value1 = 'value1'; + $key2 = 'key2'; + $value2 = 'value2'; + $qb = new QueryBuilder($this->qf); - $key1 = "key1"; - $value1 = "value1"; - $key2 = "key2"; - $value2 = "value2"; - $qb->setParameters(array($key1, $value1), array($key2, $value2)); + $qb->setParameters([ + $key1 => $value1, + $key2 => $value2, + ]); $this->assertCount(2, $qb->getParameters()); } - public function testExecute() + public function testExecute(): void { - $source = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array()); - $constraint = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array()); - $query = $this->getMock('PHPCR\Query\QueryInterface', array(), array()); + $source = $this->createSourceMock(); + $constraint = $this->createConstraintMock(); + $query = $this->createQueryMock(); + + $result = $this->createMock(QueryResultInterface::class); $query->expects($this->once()) - ->method('execute'); + ->method('execute') + ->willReturn($result) + ; $query->expects($this->once()) ->method('bindValue'); + $this->qf->expects($this->once()) ->method('createQuery') - ->with($source, $constraint, array(), array()) - ->will($this->returnValue($query)); + ->with($source, $constraint, [], []) + ->willReturn($query); + $qb = new QueryBuilder($this->qf); - $qb->from($source) - ->where($constraint) - ->setFirstResult(10) - ->setMaxResults(10) - ->setParameter('Key', 'value') - ->execute(); + $this->assertSame( + $result, + $qb->from($source) + ->where($constraint) + ->setFirstResult(10) + ->setMaxResults(10) + ->setParameter('Key', 'value') + ->execute() + ); } } diff --git a/tests/PHPCR/Tests/Util/QOM/Sql1GeneratorTest.php b/tests/PHPCR/Tests/Util/QOM/Sql1GeneratorTest.php index a05aa4c7..8798c678 100644 --- a/tests/PHPCR/Tests/Util/QOM/Sql1GeneratorTest.php +++ b/tests/PHPCR/Tests/Util/QOM/Sql1GeneratorTest.php @@ -1,92 +1,98 @@ generator = new Sql1Generator(new ValueConverter()); } - public function testLiteral() + public function testLiteral(): void { $literal = $this->generator->evalLiteral('Foobar'); $this->assertEquals("'Foobar'", $literal); } - public function testDateTimeLiteral() + public function testDateTimeLiteral(): void { $literal = $this->generator->evalLiteral(new \DateTime('2011-12-23T00:00:00.000+00:00')); $this->assertEquals("TIMESTAMP '2011-12-23T00:00:00.000+00:00'", $literal); } - public function testBoolLiteral() + public function testBoolLiteral(): void { $literal = $this->generator->evalLiteral(true); $this->assertEquals("'true'", $literal); } - public function testLongLiteral() + public function testLongLiteral(): void { $literal = $this->generator->evalLiteral(11); - $this->assertSame("11", $literal); + $this->assertSame('11', $literal); } - public function testDoubleLiteral() + public function testDoubleLiteral(): void { $literal = $this->generator->evalLiteral(11.0); - $this->assertSame("11.0", $literal); + $this->assertSame('11.0', $literal); } - public function testChildNode() + public function testChildNode(): void { - $literal = $this->generator->evalChildNode("/"); + $literal = $this->generator->evalChildNode('/'); $this->assertSame("jcr:path LIKE '/%' AND NOT jcr:path LIKE '/%/%'", $literal); - $literal = $this->generator->evalChildNode("/foo/bar/baz"); + $literal = $this->generator->evalChildNode('/foo/bar/baz'); $this->assertSame("jcr:path LIKE '/foo[%]/bar[%]/baz[%]/%' AND NOT jcr:path LIKE '/foo[%]/bar[%]/baz[%]/%/%'", $literal); } - public function testDescendantNode() + public function testDescendantNode(): void { - $literal = $this->generator->evalDescendantNode("/"); + $literal = $this->generator->evalDescendantNode('/'); $this->assertSame("jcr:path LIKE '/%'", $literal); - $literal = $this->generator->evalDescendantNode("/foo/bar/baz"); + $literal = $this->generator->evalDescendantNode('/foo/bar/baz'); $this->assertSame("jcr:path LIKE '/foo[%]/bar[%]/baz[%]/%'", $literal); } - public function testPopertyExistence() + public function testPopertyExistence(): void { - $literal = $this->generator->evalPropertyExistence(null, "foo"); - $this->assertSame("foo IS NOT NULL", $literal); + $literal = $this->generator->evalPropertyExistence(null, 'foo'); + $this->assertSame('foo IS NOT NULL', $literal); } - public function testFullTextSearch() + public function testFullTextSearch(): void { - $literal = $this->generator->evalFullTextSearch(null, "'foo'"); + $literal = $this->generator->evalFullTextSearch('', "'foo'"); $this->assertSame("CONTAINS(*, 'foo')", $literal); - $literal = $this->generator->evalFullTextSearch(null, "'foo'", "bar"); + $literal = $this->generator->evalFullTextSearch('', "'foo'", 'bar'); $this->assertSame("CONTAINS(bar, 'foo')", $literal); } - public function testColumns() + public function testColumns(): void { - $literal = $this->generator->evalColumns(null); - $this->assertSame("s", $literal); - $literal = $this->generator->evalColumns(array("bar","foo")); - $this->assertSame("bar, foo", $literal); + $literal = $this->generator->evalColumns([]); + $this->assertSame('s', $literal); + $literal = $this->generator->evalColumns(['bar', 'foo']); + $this->assertSame('bar, foo', $literal); } - public function testPropertyValue() + public function testPropertyValue(): void { - $literal = $this->generator->evalPropertyValue("foo"); - $this->assertSame("foo", $literal); + $literal = $this->generator->evalPropertyValue('foo'); + $this->assertSame('foo', $literal); } } diff --git a/tests/PHPCR/Tests/Util/QOM/Sql2GeneratorTest.php b/tests/PHPCR/Tests/Util/QOM/Sql2GeneratorTest.php index e5cbc750..f7d8c761 100644 --- a/tests/PHPCR/Tests/Util/QOM/Sql2GeneratorTest.php +++ b/tests/PHPCR/Tests/Util/QOM/Sql2GeneratorTest.php @@ -1,86 +1,91 @@ generator = new Sql2Generator(new ValueConverter()); } - public function testLiteral() + public function testLiteral(): void { $literal = $this->generator->evalLiteral('Foobar'); $this->assertEquals("'Foobar'", $literal); } - public function testDateTimeLiteral() + public function testDateTimeLiteral(): void { $literal = $this->generator->evalLiteral(new \DateTime('2011-12-23T00:00:00.000+00:00')); $this->assertEquals("CAST('2011-12-23T00:00:00.000+00:00' AS DATE)", $literal); } - public function testBoolLiteral() + public function testBoolLiteral(): void { $literal = $this->generator->evalLiteral(true); $this->assertEquals("CAST('true' AS BOOLEAN)", $literal); } - public function testLongLiteral() + public function testLongLiteral(): void { $literal = $this->generator->evalLiteral(11); $this->assertEquals("CAST('11' AS LONG)", $literal); } - public function testDoubleLiteral() + public function testDoubleLiteral(): void { $literal = $this->generator->evalLiteral(11.0); $this->assertEquals("CAST('11' AS DOUBLE)", $literal); } - public function testChildNode() + public function testChildNode(): void { - $literal = $this->generator->evalChildNode("/foo/bar/baz"); - $this->assertSame("ISCHILDNODE(/foo/bar/baz)", $literal); + $literal = $this->generator->evalChildNode('/foo/bar/baz'); + $this->assertSame('ISCHILDNODE(/foo/bar/baz)', $literal); } - public function testDescendantNode() + public function testDescendantNode(): void { - $literal = $this->generator->evalDescendantNode("/foo/bar/baz"); - $this->assertSame("ISDESCENDANTNODE(/foo/bar/baz)", $literal); + $literal = $this->generator->evalDescendantNode('/foo/bar/baz'); + $this->assertSame('ISDESCENDANTNODE(/foo/bar/baz)', $literal); } - public function testPopertyExistence() + public function testPopertyExistence(): void { - $literal = $this->generator->evalPropertyExistence(null, "foo"); - $this->assertSame("foo IS NOT NULL", $literal); + $literal = $this->generator->evalPropertyExistence(null, 'foo'); + $this->assertSame('[foo] IS NOT NULL', $literal); } - public function testFullTextSearch() + public function testFullTextSearch(): void { - $literal = $this->generator->evalFullTextSearch("data", "'foo'"); + $literal = $this->generator->evalFullTextSearch('data', "'foo'"); $this->assertSame("CONTAINS(data.*, 'foo')", $literal); - $literal = $this->generator->evalFullTextSearch("data", "'foo'", "bar"); - $this->assertSame("CONTAINS(data.bar, 'foo')", $literal); + $literal = $this->generator->evalFullTextSearch('data', "'foo'", 'bar'); + $this->assertSame("CONTAINS(data.[bar], 'foo')", $literal); } - public function testColumns() + public function testColumns(): void { - $literal = $this->generator->evalColumns(null); - $this->assertSame("*", $literal); - $literal = $this->generator->evalColumns(array("bar","foo")); - $this->assertSame("bar, foo", $literal); + $literal = $this->generator->evalColumns([]); + $this->assertSame('*', $literal); + $literal = $this->generator->evalColumns(['bar', 'foo']); + $this->assertSame('bar, foo', $literal); } - public function testPropertyValue() + public function testPropertyValue(): void { - $literal = $this->generator->evalPropertyValue("foo"); - $this->assertSame("foo", $literal); + $literal = $this->generator->evalPropertyValue('foo'); + $this->assertSame('[foo]', $literal); } } diff --git a/tests/PHPCR/Tests/Util/QOM/Sql2ScannerTest.php b/tests/PHPCR/Tests/Util/QOM/Sql2ScannerTest.php new file mode 100644 index 00000000..9918d94b --- /dev/null +++ b/tests/PHPCR/Tests/Util/QOM/Sql2ScannerTest.php @@ -0,0 +1,204 @@ +expectTokensFromScanner($scanner, $expected); + } + + /** + * @dataProvider dataTestStringTokenization + */ + public function testStringTokenization(string $query): void + { + $scanner = new Sql2Scanner($query); + $expected = [ + 'SELECT', + 'page', + '.', + '*', + 'FROM', + '[nt:unstructured]', + 'AS', + 'page', + 'WHERE', + 'name', + '=', + '"Hello world"', + ]; + + $this->expectTokensFromScanner($scanner, $expected); + } + + /** + * @return array + */ + public function dataTestStringTokenization(): array + { + $multilineQuery = <<<'SQL' + SELECT page.* + FROM [nt:unstructured] AS page + WHERE name ="Hello world" + SQL; + + return [ + 'single line query' => ['SELECT page.* FROM [nt:unstructured] AS page WHERE name ="Hello world"'], + 'multi line query' => [$multilineQuery], + ]; + } + + public function testEscapingStrings(): void + { + $sql = <<expectTokensFromScanner($scanner, $expected); + } + + public function testSQLEscapedStrings(): void + { + $sql = "WHERE page.name = 'Hello, it''s me.'"; + + $scanner = new Sql2Scanner($sql); + $expected = [ + 'WHERE', + 'page', + '.', + 'name', + '=', + "'Hello, it''s me.'", + ]; + + $this->expectTokensFromScanner($scanner, $expected); + } + + public function testSQLEscapedStrings2(): void + { + $sql = "WHERE page.name = 'Hello, it''' AND"; + + $scanner = new Sql2Scanner($sql); + $expected = [ + 'WHERE', + 'page', + '.', + 'name', + '=', + "'Hello, it'''", + 'AND', + ]; + + $this->expectTokensFromScanner($scanner, $expected); + } + + public function testSquareBrackets(): void + { + $sql = 'WHERE ISSAMENODE(file, ["/home node"])'; + + $scanner = new Sql2Scanner($sql); + $expected = [ + 'WHERE', + 'ISSAMENODE', + '(', + 'file', + ',', + '[/home node]', + ')', + ]; + + $this->expectTokensFromScanner($scanner, $expected); + } + + public function testSquareBracketsWithoutQuotes(): void + { + $sql = 'WHERE ISSAMENODE(file, [/home node])'; + + $scanner = new Sql2Scanner($sql); + $expected = [ + 'WHERE', + 'ISSAMENODE', + '(', + 'file', + ',', + '[/home node]', + ')', + ]; + + $this->expectTokensFromScanner($scanner, $expected); + } + + public function testTokenizingWithMissingSpaces(): void + { + $sql = 'SELECT * AS"all"'; + + $scanner = new Sql2Scanner($sql); + $expected = [ + 'SELECT', + '*', + 'AS', + '"all"', + ]; + + $this->expectTokensFromScanner($scanner, $expected); + } + + public function testThrowingErrorOnUnclosedString(): void + { + $this->expectException(InvalidQueryException::class); + new Sql2Scanner('SELECT page.* FROM [nt:unstructured] AS page WHERE name ="Hello '); + } + + /** + * Function to assert that the tokens the scanner finds match the expected output + * and the entire expected output is consumed. + * + * @param array $expected + */ + private function expectTokensFromScanner(Sql2Scanner $scanner, array $expected): void + { + $actualTokens = []; + while ($token = $scanner->fetchNextToken()) { + $actualTokens[] = $token; + } + + $this->assertEquals($expected, $actualTokens); + } +} diff --git a/tests/PHPCR/Tests/Util/QOM/Sql2ToQomQueryConverterTest.php b/tests/PHPCR/Tests/Util/QOM/Sql2ToQomQueryConverterTest.php new file mode 100644 index 00000000..d52beeed --- /dev/null +++ b/tests/PHPCR/Tests/Util/QOM/Sql2ToQomQueryConverterTest.php @@ -0,0 +1,44 @@ +qomFactory = $this->createMock(QueryObjectModelFactoryInterface::class); + $this->valueConverter = $this->createMock(ValueConverter::class); + $this->converter = new Sql2ToQomQueryConverter($this->qomFactory, $this->valueConverter); + } + + public function testInvalid(): void + { + $this->expectException(InvalidQueryException::class); + $this->expectExceptionMessage('Error parsing query'); + + $this->converter->parse('SELECTING WITH AN INVALID QUERY'); + } +} diff --git a/tests/PHPCR/Tests/Util/TraversingItemVisitorTest.php b/tests/PHPCR/Tests/Util/TraversingItemVisitorTest.php index 3867723c..abc3be63 100644 --- a/tests/PHPCR/Tests/Util/TraversingItemVisitorTest.php +++ b/tests/PHPCR/Tests/Util/TraversingItemVisitorTest.php @@ -1,10 +1,14 @@ markTestSkipped('TODO: implement tests for breath and depth first and with and without limit'); } diff --git a/tests/PHPCR/Tests/Util/UUIDHelperTest.php b/tests/PHPCR/Tests/Util/UUIDHelperTest.php index ab7de3f2..f6efc33d 100644 --- a/tests/PHPCR/Tests/Util/UUIDHelperTest.php +++ b/tests/PHPCR/Tests/Util/UUIDHelperTest.php @@ -1,18 +1,21 @@ assertEquals(1, preg_match('/^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$/', $id)); } - public function testIsUUID() + public function testIsUUID(): void { $this->assertTrue(UUIDHelper::isUUID('550e8400-e29b-41d4-a716-446655440000')); $this->assertTrue(UUIDHelper::isUUID('00000000-0000-0000-C000-000000000046')); diff --git a/tests/PHPCR/Tests/Util/ValueConverterTest.php b/tests/PHPCR/Tests/Util/ValueConverterTest.php index b6ca3c15..28f0b87c 100644 --- a/tests/PHPCR/Tests/Util/ValueConverterTest.php +++ b/tests/PHPCR/Tests/Util/ValueConverterTest.php @@ -1,316 +1,324 @@ valueConverter = new ValueConverter(); } - public function dataConversionMatrix() + /** + * @return array + */ + public function dataConversionMatrix(): array { $stream = fopen('php://memory', '+rw'); + $this->assertIsResource($stream); fwrite($stream, 'test string'); rewind($stream); - $datestream = fopen('php://memory', '+rw'); - fwrite($datestream, '17.12.2010 GMT'); - rewind($datestream); + $dateStream = fopen('php://memory', '+rw'); + $this->assertIsResource($dateStream); + fwrite($dateStream, '17.12.2010 GMT'); + rewind($dateStream); - $numberstream = fopen('php://memory', '+rw'); - fwrite($numberstream, '123.456'); - rewind($numberstream); + $numberStream = fopen('php://memory', '+rw'); + $this->assertIsResource($numberStream); + fwrite($numberStream, '123.456'); + rewind($numberStream); - $namestream = fopen('php://memory', '+rw'); - fwrite($namestream, 'test'); - rewind($namestream); + $nameStream = fopen('php://memory', '+rw'); + $this->assertIsResource($nameStream); + fwrite($nameStream, 'test'); + rewind($nameStream); - $uuidstream = fopen('php://memory', '+rw'); - fwrite($uuidstream, '38b7cf18-c417-477a-af0b-c1e92a290c9a'); - rewind($uuidstream); + $uuidStream = fopen('php://memory', '+rw'); + $this->assertIsResource($uuidStream); + fwrite($uuidStream, '38b7cf18-c417-477a-af0b-c1e92a290c9a'); + rewind($uuidStream); $datetimeLong = new \DateTime(); $datetimeLong->setTimestamp(123); - $nodemock = $this->getMock('PHPCR\Tests\Stubs\MockNode'); - $nodemock - ->expects($this->any()) + $nodeMock = $this->createMock(MockNode::class); + $nodeMock ->method('getIdentifier') - ->will($this->returnValue('38b7cf18-c417-477a-af0b-c1e92a290c9a')) - ; - $nodemock - ->expects($this->any()) - ->method('isNodetype') + ->willReturn('38b7cf18-c417-477a-af0b-c1e92a290c9a'); + + $nodeMock + ->method('isNodeType') ->with('mix:referenceable') - ->will($this->returnValue(true)) - ; - - return array( - // string to... - array('test string', PropertyType::STRING, 'test string', PropertyType::STRING), - array('test string', PropertyType::STRING, 0, PropertyType::LONG), - array('378.37', PropertyType::STRING, 378, PropertyType::LONG), - array('test string', PropertyType::STRING, 0.0, PropertyType::DOUBLE), - array('249.39', PropertyType::STRING, 249.39, PropertyType::DOUBLE), - array('test string', PropertyType::STRING, null, PropertyType::DATE), - array('17.12.2010 GMT', PropertyType::STRING, new \DateTime('17.12.2010 GMT'), PropertyType::DATE), - array('test string', PropertyType::STRING, true, PropertyType::BOOLEAN), - array('false', PropertyType::STRING, true, PropertyType::BOOLEAN), - array('', PropertyType::STRING, false, PropertyType::BOOLEAN), - // TODO: check NAME may not have spaces array('test string', PropertyType::STRING, null, PropertyType::NAME), - array('test', PropertyType::STRING, 'test', PropertyType::NAME), - // TODO: check PATH may not have spaces array('test string', PropertyType::STRING, null, PropertyType::PATH), - array('../the/node', PropertyType::STRING, '../the/node', PropertyType::PATH), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::STRING, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE), - // TODO: should we move UUIDHelper to phpcr so we can check in PropertyType? array('test string', PropertyType::STRING, null, PropertyType::REFERENCE), - array('', PropertyType::STRING, null, PropertyType::REFERENCE), - array('http://phpcr.github.com/', PropertyType::STRING, 'http://phpcr.github.com/', PropertyType::URI), - array('test string', PropertyType::STRING, 'test string', PropertyType::DECIMAL), // up to the decimal functions to validate - - // stream to... - array($stream, PropertyType::BINARY, 'test string', PropertyType::STRING), - array($stream, PropertyType::BINARY, 0, PropertyType::LONG), - array($numberstream, PropertyType::BINARY, 123, PropertyType::LONG), - array($stream, PropertyType::BINARY, 0.0, PropertyType::DOUBLE), - array($numberstream, PropertyType::BINARY, 123.456, PropertyType::DOUBLE), - array($stream, PropertyType::BINARY, null, PropertyType::DATE), - array($datestream, PropertyType::BINARY, new \DateTime('17.12.2010 GMT'), PropertyType::DATE), - array($stream, PropertyType::BINARY, true, PropertyType::BOOLEAN), - // TODO: check NAME may not have spaces array($stream, PropertyType::BINARY, null, PropertyType::NAME), - array($namestream, PropertyType::BINARY, 'test', PropertyType::NAME), - // TODO: check PATH may not have spaces array($stream, PropertyType::BINARY, null, PropertyType::PATH), - // TODO: should we move UUIDHelper to phpcr so we can check in PropertyType? array($stream, PropertyType::STRING, null, PropertyType::REFERENCE), - array($uuidstream, PropertyType::BINARY, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE), - array($stream, PropertyType::BINARY, 'test string', PropertyType::DECIMAL), // up to the decimal functions to validate - - // invalid stream resource - array($stream, PropertyType::DECIMAL, null, PropertyType::STRING), - - // long to... - array(123, PropertyType::LONG, '123', PropertyType::STRING), - array(123, PropertyType::LONG, 123, PropertyType::LONG), - array(123, PropertyType::LONG, 123.0, PropertyType::DOUBLE), - array(123, PropertyType::LONG, $datetimeLong, PropertyType::DATE), - array(123, PropertyType::LONG, true, PropertyType::BOOLEAN), - array(0, PropertyType::LONG, false, PropertyType::BOOLEAN), - array(123, PropertyType::LONG, null, PropertyType::NAME), - array(123, PropertyType::LONG, null, PropertyType::PATH), - array(123, PropertyType::LONG, null, PropertyType::REFERENCE), - array(123, PropertyType::LONG, null, PropertyType::URI), - array(123, PropertyType::LONG, '123', PropertyType::DECIMAL), - - // double to... - array(123.1, PropertyType::DOUBLE, '123.1', PropertyType::STRING), - array(123.1, PropertyType::DOUBLE, 123, PropertyType::LONG), - array(123.1, PropertyType::DOUBLE, 123.1, PropertyType::DOUBLE), - array(123.1, PropertyType::DOUBLE, $datetimeLong, PropertyType::DATE), - array(123.1, PropertyType::DOUBLE, true, PropertyType::BOOLEAN), - array(0.0, PropertyType::DOUBLE, false, PropertyType::BOOLEAN), - array(123.1, PropertyType::DOUBLE, null, PropertyType::NAME), - array(123.1, PropertyType::DOUBLE, null, PropertyType::PATH), - array(123.1, PropertyType::DOUBLE, null, PropertyType::REFERENCE), - array(123.1, PropertyType::DOUBLE, null, PropertyType::URI), - array(123.1, PropertyType::DOUBLE, '123.1', PropertyType::DECIMAL), - - // date to... - array($datetimeLong, PropertyType::DATE, $datetimeLong->format('Y-m-d\TH:i:s.') . substr($datetimeLong->format('u'), 0, 3) . $datetimeLong->format('P'), PropertyType::STRING), - array($datetimeLong, PropertyType::DATE, 123, PropertyType::LONG), - array($datetimeLong, PropertyType::DATE, 123.0, PropertyType::DOUBLE), - array($datetimeLong, PropertyType::DATE, $datetimeLong, PropertyType::DATE), - array($datetimeLong, PropertyType::DATE, true, PropertyType::BOOLEAN), - array($datetimeLong, PropertyType::DATE, null, PropertyType::NAME), - array($datetimeLong, PropertyType::DATE, null, PropertyType::PATH), - array($datetimeLong, PropertyType::DATE, null, PropertyType::REFERENCE), - array($datetimeLong, PropertyType::DATE, null, PropertyType::URI), - array($datetimeLong, PropertyType::DATE, '123', PropertyType::DECIMAL), - - // boolean to... - array(true, PropertyType::BOOLEAN, '1', PropertyType::STRING), - array(false, PropertyType::BOOLEAN, '', PropertyType::STRING), - array(true, PropertyType::BOOLEAN, null, PropertyType::DATE), - array(true, PropertyType::BOOLEAN, 1, PropertyType::LONG), - array(true, PropertyType::BOOLEAN, 1.0, PropertyType::DOUBLE), - array(true, PropertyType::BOOLEAN, true, PropertyType::BOOLEAN), - array(true, PropertyType::BOOLEAN, null, PropertyType::NAME), - array(true, PropertyType::BOOLEAN, null, PropertyType::PATH), - array(true, PropertyType::BOOLEAN, null, PropertyType::REFERENCE), - array(true, PropertyType::BOOLEAN, null, PropertyType::URI), - array(true, PropertyType::BOOLEAN, '1', PropertyType::DECIMAL), - array(false, PropertyType::BOOLEAN, '', PropertyType::DECIMAL), - - // name to... - array('name', PropertyType::NAME, 'name', PropertyType::STRING), - array('name', PropertyType::NAME, null, PropertyType::DATE), - array('name', PropertyType::NAME, null, PropertyType::LONG), - array('name', PropertyType::NAME, null, PropertyType::DOUBLE), - array('name', PropertyType::NAME, null, PropertyType::BOOLEAN), - array('name', PropertyType::NAME, 'name', PropertyType::NAME), - array('name', PropertyType::NAME, 'name', PropertyType::PATH), - array('name', PropertyType::NAME, null, PropertyType::REFERENCE), - array('name', PropertyType::NAME, '../name', PropertyType::URI), - array('näme', PropertyType::NAME, '../n%C3%A4me', PropertyType::URI), - array('name', PropertyType::NAME, null, PropertyType::DECIMAL), - - // path to... - array('../test/path', PropertyType::PATH, '../test/path', PropertyType::STRING), - array('../test/path', PropertyType::PATH, null, PropertyType::DATE), - array('../test/path', PropertyType::PATH, null, PropertyType::LONG), - array('../test/path', PropertyType::PATH, null, PropertyType::DOUBLE), - array('../test/path', PropertyType::PATH, null, PropertyType::BOOLEAN), - // TODO: fix array('../test/path', PropertyType::PATH, null, PropertyType::NAME), - // TODO: fix array('./path', PropertyType::PATH, 'path', PropertyType::NAME), - array('../test/path', PropertyType::PATH, '../test/path', PropertyType::PATH), - array('../test/path', PropertyType::PATH, null, PropertyType::REFERENCE), - array('../test/path', PropertyType::PATH, '../test/path', PropertyType::URI), - array('../test/päth', PropertyType::PATH, '../test/p%C3%A4th', PropertyType::URI), - array('test', PropertyType::PATH, './test', PropertyType::URI), - array('../test/path', PropertyType::PATH, null, PropertyType::DECIMAL), - - // reference to... - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::STRING), - array($nodemock, PropertyType::REFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::STRING), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE), - array($nodemock, PropertyType::REFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::DATE), - array($nodemock, PropertyType::REFERENCE, null, PropertyType::DATE), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::LONG), - array($nodemock, PropertyType::REFERENCE, null, PropertyType::LONG), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::DOUBLE), - array($nodemock, PropertyType::REFERENCE, null, PropertyType::DOUBLE), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::BOOLEAN), - array($nodemock, PropertyType::REFERENCE, null, PropertyType::BOOLEAN), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::NAME), - array($nodemock, PropertyType::REFERENCE, null, PropertyType::NAME), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::PATH), - array($nodemock, PropertyType::REFERENCE, null, PropertyType::PATH), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::URI), - array($nodemock, PropertyType::REFERENCE, null, PropertyType::URI), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::DECIMAL), - array($nodemock, PropertyType::REFERENCE, null, PropertyType::DECIMAL), - - array($this, PropertyType::REFERENCE, null, PropertyType::STRING), - array($this, PropertyType::REFERENCE, null, PropertyType::BINARY), - array($this, PropertyType::REFERENCE, null, PropertyType::DATE), - array($this, PropertyType::REFERENCE, null, PropertyType::LONG), - array($this, PropertyType::REFERENCE, null, PropertyType::DOUBLE), - array($this, PropertyType::REFERENCE, null, PropertyType::BOOLEAN), - array($this, PropertyType::REFERENCE, null, PropertyType::NAME), - array($this, PropertyType::REFERENCE, null, PropertyType::PATH), - array($this, PropertyType::REFERENCE, null, PropertyType::URI), - array($this, PropertyType::REFERENCE, null, PropertyType::DECIMAL), - - // weak to... - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::STRING), - array($nodemock, PropertyType::WEAKREFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::STRING), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE), - array($nodemock, PropertyType::WEAKREFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::DATE), - array($nodemock, PropertyType::WEAKREFERENCE, null, PropertyType::DATE), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::LONG), - array($nodemock, PropertyType::WEAKREFERENCE, null, PropertyType::LONG), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::DOUBLE), - array($nodemock, PropertyType::WEAKREFERENCE, null, PropertyType::DOUBLE), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::BOOLEAN), - array($nodemock, PropertyType::WEAKREFERENCE, null, PropertyType::BOOLEAN), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::NAME), - array($nodemock, PropertyType::WEAKREFERENCE, null, PropertyType::NAME), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::PATH), - array($nodemock, PropertyType::WEAKREFERENCE, null, PropertyType::PATH), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::URI), - array($nodemock, PropertyType::WEAKREFERENCE, null, PropertyType::URI), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE), - array('38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::DECIMAL), - array($nodemock, PropertyType::WEAKREFERENCE, null, PropertyType::DECIMAL), + ->willReturn(true); + + return [ + // String to... + ['test string', PropertyType::STRING, 'test string', PropertyType::STRING], + ['test string', PropertyType::STRING, 0, PropertyType::LONG], + ['378.37', PropertyType::STRING, 378, PropertyType::LONG], + ['test string', PropertyType::STRING, 0.0, PropertyType::DOUBLE], + ['249.39', PropertyType::STRING, 249.39, PropertyType::DOUBLE], + ['test string', PropertyType::STRING, null, PropertyType::DATE], + ['17.12.2010 GMT', PropertyType::STRING, new \DateTime('17.12.2010 GMT'), PropertyType::DATE], + ['test string', PropertyType::STRING, true, PropertyType::BOOLEAN], + ['false', PropertyType::STRING, true, PropertyType::BOOLEAN], + ['', PropertyType::STRING, false, PropertyType::BOOLEAN], + ['test', PropertyType::STRING, 'test', PropertyType::NAME], + ['test space', PropertyType::STRING, 'test space', PropertyType::NAME], + ['../the/node', PropertyType::STRING, '../the/node', PropertyType::PATH], + ['../the space/node', PropertyType::STRING, '../the space/node', PropertyType::PATH], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::STRING, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE], + // TODO: should we move UUIDHelper to phpcr so we can check in PropertyType? ['test string', PropertyType::STRING, null, PropertyType::REFERENCE], + ['', PropertyType::STRING, null, PropertyType::REFERENCE], + ['http://phpcr.github.com/', PropertyType::STRING, 'http://phpcr.github.com/', PropertyType::URI], + ['test string', PropertyType::STRING, 'test string', PropertyType::DECIMAL], // up to the decimal functions to validate + + // Stream to... + [$stream, PropertyType::BINARY, 'test string', PropertyType::STRING], + [$stream, PropertyType::BINARY, 0, PropertyType::LONG], + [$numberStream, PropertyType::BINARY, 123, PropertyType::LONG], + [$stream, PropertyType::BINARY, 0.0, PropertyType::DOUBLE], + [$numberStream, PropertyType::BINARY, 123.456, PropertyType::DOUBLE], + [$stream, PropertyType::BINARY, null, PropertyType::DATE], + [$dateStream, PropertyType::BINARY, new \DateTime('17.12.2010 GMT'), PropertyType::DATE], + [$stream, PropertyType::BINARY, true, PropertyType::BOOLEAN], + [$nameStream, PropertyType::BINARY, 'test', PropertyType::NAME], + // TODO: should we move UUIDHelper to phpcr so we can check in PropertyType? [$stream, PropertyType::STRING, null, PropertyType::REFERENCE], + [$uuidStream, PropertyType::BINARY, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE], + [$stream, PropertyType::BINARY, 'test string', PropertyType::DECIMAL], // up to the decimal functions to validate + + // Invalid stream resource + [$stream, PropertyType::DECIMAL, null, PropertyType::STRING], + + // Long to... + [123, PropertyType::LONG, '123', PropertyType::STRING], + [123, PropertyType::LONG, 123, PropertyType::LONG], + [123, PropertyType::LONG, 123.0, PropertyType::DOUBLE], + [123, PropertyType::LONG, $datetimeLong, PropertyType::DATE], + [123, PropertyType::LONG, true, PropertyType::BOOLEAN], + [0, PropertyType::LONG, false, PropertyType::BOOLEAN], + [123, PropertyType::LONG, null, PropertyType::NAME], + [123, PropertyType::LONG, null, PropertyType::PATH], + [123, PropertyType::LONG, null, PropertyType::REFERENCE], + [123, PropertyType::LONG, null, PropertyType::URI], + [123, PropertyType::LONG, '123', PropertyType::DECIMAL], + + // Double to... + [123.1, PropertyType::DOUBLE, '123.1', PropertyType::STRING], + [123.1, PropertyType::DOUBLE, 123, PropertyType::LONG], + [123.1, PropertyType::DOUBLE, 123.1, PropertyType::DOUBLE], + [123.1, PropertyType::DOUBLE, $datetimeLong, PropertyType::DATE], + [123.1, PropertyType::DOUBLE, true, PropertyType::BOOLEAN], + [0.0, PropertyType::DOUBLE, false, PropertyType::BOOLEAN], + [123.1, PropertyType::DOUBLE, null, PropertyType::NAME], + [123.1, PropertyType::DOUBLE, null, PropertyType::PATH], + [123.1, PropertyType::DOUBLE, null, PropertyType::REFERENCE], + [123.1, PropertyType::DOUBLE, null, PropertyType::URI], + [123.1, PropertyType::DOUBLE, '123.1', PropertyType::DECIMAL], + + // Date to... + [$datetimeLong, PropertyType::DATE, $datetimeLong->format('Y-m-d\TH:i:s.').substr($datetimeLong->format('u'), 0, 3).$datetimeLong->format('P'), PropertyType::STRING], + [$datetimeLong, PropertyType::DATE, 123, PropertyType::LONG], + [$datetimeLong, PropertyType::DATE, 123.0, PropertyType::DOUBLE], + [$datetimeLong, PropertyType::DATE, $datetimeLong, PropertyType::DATE], + [$datetimeLong, PropertyType::DATE, true, PropertyType::BOOLEAN], + [$datetimeLong, PropertyType::DATE, null, PropertyType::NAME], + [$datetimeLong, PropertyType::DATE, null, PropertyType::PATH], + [$datetimeLong, PropertyType::DATE, null, PropertyType::REFERENCE], + [$datetimeLong, PropertyType::DATE, null, PropertyType::URI], + [$datetimeLong, PropertyType::DATE, '123', PropertyType::DECIMAL], + + // Boolean to... + [true, PropertyType::BOOLEAN, '1', PropertyType::STRING], + [false, PropertyType::BOOLEAN, '', PropertyType::STRING], + [true, PropertyType::BOOLEAN, null, PropertyType::DATE], + [true, PropertyType::BOOLEAN, 1, PropertyType::LONG], + [true, PropertyType::BOOLEAN, 1.0, PropertyType::DOUBLE], + [true, PropertyType::BOOLEAN, true, PropertyType::BOOLEAN], + [true, PropertyType::BOOLEAN, null, PropertyType::NAME], + [true, PropertyType::BOOLEAN, null, PropertyType::PATH], + [true, PropertyType::BOOLEAN, null, PropertyType::REFERENCE], + [true, PropertyType::BOOLEAN, null, PropertyType::URI], + [true, PropertyType::BOOLEAN, '1', PropertyType::DECIMAL], + [false, PropertyType::BOOLEAN, '', PropertyType::DECIMAL], + + // Name to... + ['name', PropertyType::NAME, 'name', PropertyType::STRING], + ['name', PropertyType::NAME, null, PropertyType::DATE], + ['name', PropertyType::NAME, null, PropertyType::LONG], + ['name', PropertyType::NAME, null, PropertyType::DOUBLE], + ['name', PropertyType::NAME, null, PropertyType::BOOLEAN], + ['name', PropertyType::NAME, 'name', PropertyType::NAME], + ['name', PropertyType::NAME, 'name', PropertyType::PATH], + ['name', PropertyType::NAME, null, PropertyType::REFERENCE], + ['name', PropertyType::NAME, '../name', PropertyType::URI], + ['näme', PropertyType::NAME, '../n%C3%A4me', PropertyType::URI], + ['name', PropertyType::NAME, null, PropertyType::DECIMAL], + + // Path to... + ['../test/path', PropertyType::PATH, '../test/path', PropertyType::STRING], + ['../test/path', PropertyType::PATH, null, PropertyType::DATE], + ['../test/path', PropertyType::PATH, null, PropertyType::LONG], + ['../test/path', PropertyType::PATH, null, PropertyType::DOUBLE], + ['../test/path', PropertyType::PATH, null, PropertyType::BOOLEAN], + // TODO: fix ['../test/path', PropertyType::PATH, null, PropertyType::NAME], + // TODO: fix ['./path', PropertyType::PATH, 'path', PropertyType::NAME], + ['../test/path', PropertyType::PATH, '../test/path', PropertyType::PATH], + ['../test/path', PropertyType::PATH, null, PropertyType::REFERENCE], + ['../test/path', PropertyType::PATH, '../test/path', PropertyType::URI], + ['../test/päth', PropertyType::PATH, '../test/p%C3%A4th', PropertyType::URI], + ['test', PropertyType::PATH, './test', PropertyType::URI], + ['../test/path', PropertyType::PATH, null, PropertyType::DECIMAL], + + // Reference to... + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::STRING], + [$nodeMock, PropertyType::REFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::STRING], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE], + [$nodeMock, PropertyType::REFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::DATE], + [$nodeMock, PropertyType::REFERENCE, null, PropertyType::DATE], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::LONG], + [$nodeMock, PropertyType::REFERENCE, null, PropertyType::LONG], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::DOUBLE], + [$nodeMock, PropertyType::REFERENCE, null, PropertyType::DOUBLE], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::BOOLEAN], + [$nodeMock, PropertyType::REFERENCE, null, PropertyType::BOOLEAN], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::NAME], + [$nodeMock, PropertyType::REFERENCE, null, PropertyType::NAME], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::PATH], + [$nodeMock, PropertyType::REFERENCE, null, PropertyType::PATH], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::URI], + [$nodeMock, PropertyType::REFERENCE, null, PropertyType::URI], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE, null, PropertyType::DECIMAL], + [$nodeMock, PropertyType::REFERENCE, null, PropertyType::DECIMAL], + + [$this, PropertyType::REFERENCE, null, PropertyType::STRING], + [$this, PropertyType::REFERENCE, null, PropertyType::BINARY], + [$this, PropertyType::REFERENCE, null, PropertyType::DATE], + [$this, PropertyType::REFERENCE, null, PropertyType::LONG], + [$this, PropertyType::REFERENCE, null, PropertyType::DOUBLE], + [$this, PropertyType::REFERENCE, null, PropertyType::BOOLEAN], + [$this, PropertyType::REFERENCE, null, PropertyType::NAME], + [$this, PropertyType::REFERENCE, null, PropertyType::PATH], + [$this, PropertyType::REFERENCE, null, PropertyType::URI], + [$this, PropertyType::REFERENCE, null, PropertyType::DECIMAL], + + // Weak to... + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::STRING], + [$nodeMock, PropertyType::WEAKREFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::STRING], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE], + [$nodeMock, PropertyType::WEAKREFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::DATE], + [$nodeMock, PropertyType::WEAKREFERENCE, null, PropertyType::DATE], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::LONG], + [$nodeMock, PropertyType::WEAKREFERENCE, null, PropertyType::LONG], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::DOUBLE], + [$nodeMock, PropertyType::WEAKREFERENCE, null, PropertyType::DOUBLE], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::BOOLEAN], + [$nodeMock, PropertyType::WEAKREFERENCE, null, PropertyType::BOOLEAN], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::NAME], + [$nodeMock, PropertyType::WEAKREFERENCE, null, PropertyType::NAME], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::PATH], + [$nodeMock, PropertyType::WEAKREFERENCE, null, PropertyType::PATH], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::URI], + [$nodeMock, PropertyType::WEAKREFERENCE, null, PropertyType::URI], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, '38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::REFERENCE], + ['38b7cf18-c417-477a-af0b-c1e92a290c9a', PropertyType::WEAKREFERENCE, null, PropertyType::DECIMAL], + [$nodeMock, PropertyType::WEAKREFERENCE, null, PropertyType::DECIMAL], // uri to... - array('http://phpcr.githbub.com/doc/html', PropertyType::URI, 'http://phpcr.githbub.com/doc/html', PropertyType::STRING), - array('http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::DATE), - array('http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::LONG), - array('http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::DOUBLE), - array('http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::BOOLEAN), - // TODO: fix array('http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::NAME), - // TODO: fix array('http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::PATH), - array('http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::REFERENCE), - array('http://phpcr.githbub.com/doc/html', PropertyType::URI, 'http://phpcr.githbub.com/doc/html', PropertyType::URI), - array('http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::DECIMAL), + ['http://phpcr.githbub.com/doc/html', PropertyType::URI, 'http://phpcr.githbub.com/doc/html', PropertyType::STRING], + ['http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::DATE], + ['http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::LONG], + ['http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::DOUBLE], + ['http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::BOOLEAN], + // TODO: fix ['http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::NAME], + // TODO: fix ['http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::PATH], + ['http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::REFERENCE], + ['http://phpcr.githbub.com/doc/html', PropertyType::URI, 'http://phpcr.githbub.com/doc/html', PropertyType::URI], + ['http://phpcr.githbub.com/doc/html', PropertyType::URI, null, PropertyType::DECIMAL], // decimal to... - array('123.4', PropertyType::DECIMAL, '123.4', PropertyType::STRING), - array('123.4', PropertyType::DECIMAL, $datetimeLong, PropertyType::DATE), - array('123.4', PropertyType::DECIMAL, 123, PropertyType::LONG), - array('123.4', PropertyType::DECIMAL, 123.4, PropertyType::DOUBLE), - array('123.4', PropertyType::DECIMAL, true, PropertyType::BOOLEAN), - array('0', PropertyType::DECIMAL, false, PropertyType::BOOLEAN), - array('123.4', PropertyType::DECIMAL, null, PropertyType::NAME), - array('123.4', PropertyType::DECIMAL, null, PropertyType::PATH), - array('123.4', PropertyType::DECIMAL, null, PropertyType::URI), - array('123.4', PropertyType::DECIMAL, null, PropertyType::REFERENCE), - array('123.4', PropertyType::DECIMAL, '123.4', PropertyType::DECIMAL), - ); + ['123.4', PropertyType::DECIMAL, '123.4', PropertyType::STRING], + ['123.4', PropertyType::DECIMAL, $datetimeLong, PropertyType::DATE], + ['123.4', PropertyType::DECIMAL, 123, PropertyType::LONG], + ['123.4', PropertyType::DECIMAL, 123.4, PropertyType::DOUBLE], + ['123.4', PropertyType::DECIMAL, true, PropertyType::BOOLEAN], + ['0', PropertyType::DECIMAL, false, PropertyType::BOOLEAN], + ['123.4', PropertyType::DECIMAL, null, PropertyType::NAME], + ['123.4', PropertyType::DECIMAL, null, PropertyType::PATH], + ['123.4', PropertyType::DECIMAL, null, PropertyType::URI], + ['123.4', PropertyType::DECIMAL, null, PropertyType::REFERENCE], + ['123.4', PropertyType::DECIMAL, '123.4', PropertyType::DECIMAL], + ]; } /** - * Skip binary target as its a special case - * - * @param int $srctype PropertyType constant to convert from - * @param $validtargets array map of target type => value | null for exception + * Skip binary target as its a special case. * * @dataProvider dataConversionMatrix */ - public function testConvertType($value, $srctype, $expected, $targettype) + public function testConvertType(mixed $value, int $srcType, mixed $expected, int $targetType): void { if (null === $expected) { try { - $this->valueConverter->convertType($value, $targettype, $srctype); - $this->fail('Excpected that this conversion would throw an exception'); - } catch (\PHPCR\ValueFormatException $e) { + $this->valueConverter->convertType($value, $targetType, $srcType); + $this->fail('Expected that this conversion would throw an exception'); + } catch (ValueFormatException $e) { // expected $this->assertTrue(true); // make it assert something } } else { if ($expected instanceof \DateTime) { - $result = $this->valueConverter->convertType($value, $targettype, $srctype); - $this->assertInstanceOf('DateTime', $result); + $result = $this->valueConverter->convertType($value, $targetType, $srcType); + $this->assertInstanceOf(\DateTime::class, $result); $this->assertEquals($expected->getTimestamp(), $result->getTimestamp()); } else { - $this->assertSame($expected, $this->valueConverter->convertType($value, $targettype, $srctype)); + $this->assertSame($expected, $this->valueConverter->convertType($value, $targetType, $srcType)); } } } - public function testConvertTypeToBinary() + public function testConvertTypeToBinary(): void { $stream = $this->valueConverter->convertType('test string', PropertyType::BINARY); - $this->assertInternalType('resource', $stream); + $this->assertIsResource($stream); $string = stream_get_contents($stream); $this->assertEquals('test string', $string); $stream = $this->valueConverter->convertType('test string', PropertyType::BINARY, PropertyType::BINARY); - $this->assertInternalType('resource', $stream); + $this->assertIsResource($stream); $string = stream_get_contents($stream); $this->assertEquals('test string', $string); $date = new \DateTime('20.12.2012'); $stream = $this->valueConverter->convertType($date, PropertyType::BINARY); - $this->assertInternalType('resource', $stream); + $this->assertIsResource($stream); $string = stream_get_contents($stream); + $this->assertIsString($string); $readDate = new \DateTime($string); $this->assertEquals($date->getTimestamp(), $readDate->getTimestamp()); - $stream = fopen('php://memory', '+rw'); + $stream = fopen('php://memory', '+rwb'); + $this->assertIsResource($stream); fwrite($stream, 'test string'); rewind($stream); @@ -320,25 +328,27 @@ public function testConvertTypeToBinary() // if conversion to string works, should be fine for all others } - public function testConvertTypeArray() + public function testConvertTypeArray(): void { - $result = $this->valueConverter->convertType(array('2012-01-10', '2012-02-12'), + $result = $this->valueConverter->convertType( + ['2012-01-10', '2012-02-12'], PropertyType::DATE, - PropertyType::STRING); - $this->assertInternalType('array', $result); + PropertyType::STRING + ); + $this->assertIsArray($result); $this->assertCount(2, $result); - $this->assertInstanceOf('DateTime', $result[0]); - $this->assertInstanceOf('DateTime', $result[1]); + $this->assertInstanceOf(\DateTime::class, $result[0]); + $this->assertInstanceOf(\DateTime::class, $result[1]); $this->assertEquals('2012-01-10', $result[0]->format('Y-m-d')); $this->assertEquals('2012-02-12', $result[1]->format('Y-m-d')); - $result = $this->valueConverter->convertType(array(), PropertyType::STRING, PropertyType::NAME); - $this->assertEquals(array(), $result); + $result = $this->valueConverter->convertType([], PropertyType::STRING, PropertyType::NAME); + $this->assertEquals([], $result); } - public function testConvertTypeAutodetect() + public function testConvertTypeAutodetect(): void { $date = new \DateTime('2012-10-10'); $result = $this->valueConverter->convertType($date, PropertyType::STRING); @@ -346,97 +356,95 @@ public function testConvertTypeAutodetect() $this->assertEquals($date->getTimestamp(), $result->getTimestamp()); $result = $this->valueConverter->convertType('2012-03-13T21:00:55.000+01:00', PropertyType::DATE); - $this->assertInstanceOf('DateTime', $result); + $this->assertInstanceOf(\DateTime::class, $result); $this->assertEquals(1331668855, $result->getTimestamp()); } - /** - * @expectedException \PHPCR\ValueFormatException - */ - public function testConvertTypeArrayInvalid() + public function testConvertTypeArrayInvalid(): void { - $this->valueConverter->convertType(array('a', 'b', 'c'), PropertyType::NAME, PropertyType::REFERENCE); + $this->expectException(ValueFormatException::class); + + $this->valueConverter->convertType(['a', 'b', 'c'], PropertyType::NAME, PropertyType::REFERENCE); } - /** - * @expectedException \PHPCR\ValueFormatException - */ - public function testConvertInvalidString() + public function testConvertInvalidString(): void { + $this->expectException(ValueFormatException::class); + $this->valueConverter->convertType($this, PropertyType::STRING); } - /** - * @expectedException \PHPCR\ValueFormatException - */ - public function testConvertInvalidBinary() + + public function testConvertInvalidBinary(): void { + $this->expectException(ValueFormatException::class); + $this->valueConverter->convertType($this, PropertyType::BINARY); } - /** - * @expectedException \PHPCR\ValueFormatException - */ - public function testConvertInvalidDate() + + public function testConvertInvalidDate(): void { + $this->expectException(ValueFormatException::class); + $this->valueConverter->convertType($this, PropertyType::DATE); } - /** - * @expectedException \PHPCR\ValueFormatException - */ - public function testConvertNewNode() + public function testConvertNewNode(): void { - $nodemock = $this->getMock('PHPCR\Tests\Stubs\MockNode'); - $nodemock - ->expects($this->once()) + $this->expectException(ValueFormatException::class); + + $nodeMock = $this->createMock(MockNode::class); + $nodeMock + ->expects($this->never()) ->method('isNew') - ->will($this->returnValue(true)) - ; - $this->valueConverter->convertType($nodemock, PropertyType::STRING); + ->willReturn(true); + $this->valueConverter->convertType($nodeMock, PropertyType::STRING); } - /** - * @expectedException \PHPCR\ValueFormatException - */ - public function testConvertNonrefNode() + + public function testConvertNonRefNode(): void { - $nodemock = $this->getMock('PHPCR\Tests\Stubs\MockNode'); - $nodemock - ->expects($this->once()) + $this->expectException(ValueFormatException::class); + + $nodeMock = $this->createMock(MockNode::class); + $nodeMock + ->expects($this->never()) ->method('isNew') - ->will($this->returnValue(false)) - ; - $nodemock + ->willReturn(false); + $nodeMock ->expects($this->once()) - ->method('isNodetype') + ->method('isNodeType') ->with('mix:referenceable') - ->will($this->returnValue(false)) - ; - $this->valueConverter->convertType($nodemock, PropertyType::STRING); + ->willReturn(false); + $this->valueConverter->convertType($nodeMock, PropertyType::STRING); } - public function dataDateTargetType() + /** + * @return array> + */ + public function dataDateTargetType(): array { - return array( - array(PropertyType::STRING), - array(PropertyType::LONG), - array(PropertyType::DOUBLE), - ); + return [ + [PropertyType::STRING], + [PropertyType::LONG], + [PropertyType::DOUBLE], + ]; } /** - * Check if the util will survive a broken implementation - * @expectedException \PHPCR\RepositoryException + * Check if the util will survive a broken implementation. + * * @dataProvider dataDateTargetType */ - public function testConvertInvalidDateValue($targettype) + public function testConvertInvalidDateValue(int $targettype): void { + $this->expectException(RepositoryException::class); + $this->valueConverter->convertType('', $targettype, PropertyType::DATE); } - /** - * @expectedException \PHPCR\ValueFormatException - */ - public function testConvertTypeInvalidTarget() + public function testConvertTypeInvalidTarget(): void { + $this->expectException(ValueFormatException::class); + $this->valueConverter->convertType('test', PropertyType::UNDEFINED); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index a76b18ad..434d92b1 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,20 +1,14 @@ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache Software License 2.0 - * @link http://phpcr.github.com/ + * + * @see http://phpcr.github.io/ */ -if (!class_exists('PHPUnit_Framework_TestCase') || - version_compare(PHPUnit_Runner_Version::id(), '3.5') < 0 -) { - die('PHPUnit framework is required, at least 3.5 version'); -} - -if (!class_exists('PHPUnit_Framework_MockObject_MockBuilder')) { - die('PHPUnit MockObject plugin is required, at least 1.0.8 version'); -} // $file2 for run tests if phpcr-utils lib inside of vendor directory. $file = __DIR__.'/../vendor/autoload.php'; diff --git a/tests/phpunit.xml.dist b/tests/phpunit.xml.dist deleted file mode 100644 index 826e25bd..00000000 --- a/tests/phpunit.xml.dist +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - ./ - - -