From 54fb032ff64b11e478096831bb5575d618f25ec5 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 17 Feb 2023 02:02:42 +0100 Subject: [PATCH] validate with phpstan --- .gitattributes | 9 ++ .github/workflows/static.yml | 42 +++++++++ .github/workflows/test-application.yaml | 4 +- composer.json | 5 +- phpstan.neon.dist | 9 ++ phpstan.tests.neon.dist | 4 + src/PHPCR/Util/CND/Parser/CndParser.php | 5 - src/PHPCR/Util/CND/Reader/FileReader.php | 2 +- src/PHPCR/Util/CND/Writer/CndWriter.php | 16 ++-- .../Util/Console/Command/BaseCommand.php | 32 +++++-- .../Console/Command/NodeRemoveCommand.php | 2 +- .../Console/Command/NodesUpdateCommand.php | 4 +- src/PHPCR/Util/Console/Helper/PhpcrHelper.php | 2 +- .../TreeDumper/ConsoleDumperNodeVisitor.php | 2 +- .../ConsoleDumperPropertyVisitor.php | 5 + src/PHPCR/Util/NodeHelper.php | 4 +- .../Util/QOM/BaseQomToSqlQueryConverter.php | 2 - src/PHPCR/Util/QOM/BaseSqlGenerator.php | 91 +++++++++++++++++-- .../Util/QOM/QomToSql2QueryConverter.php | 74 +++++++++++---- src/PHPCR/Util/QOM/Sql1Generator.php | 8 +- src/PHPCR/Util/QOM/Sql2Generator.php | 23 ++--- .../Util/QOM/Sql2ToQomQueryConverter.php | 5 + src/PHPCR/Util/TraversingItemVisitor.php | 10 +- src/PHPCR/Util/TreeWalker.php | 14 ++- .../Tests/Util/CND/Reader/FileReaderTest.php | 5 + .../Util/CND/Scanner/GenericScannerTest.php | 6 +- .../Tests/Util/QOM/BaseSqlGeneratorTest.php | 6 ++ 27 files changed, 307 insertions(+), 84 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/static.yml create mode 100644 phpstan.neon.dist create mode 100644 phpstan.tests.neon.dist 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..b1403f2f --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,42 @@ +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@v2 + + - 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@v2 + + - 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 diff --git a/.github/workflows/test-application.yaml b/.github/workflows/test-application.yaml index 5c82c002..760386b7 100644 --- a/.github/workflows/test-application.yaml +++ b/.github/workflows/test-application.yaml @@ -18,15 +18,15 @@ jobs: fail-fast: false matrix: include: - - php-version: '7.1' - dependencies: 'lowest' - php-version: '7.2' + dependencies: 'lowest' - php-version: '7.3' - php-version: '7.4' - php-version: '8.0' - php-version: '8.0' dev-dependencies: true - php-version: '8.1' + - php-version: '8.2' steps: - name: Checkout project diff --git a/composer.json b/composer.json index fda2633a..3364e456 100644 --- a/composer.json +++ b/composer.json @@ -27,13 +27,14 @@ } ], "require": { - "php": "^7.1 || ^8.0", + "php": "^7.2 || ^8.0", "phpcr/phpcr": "~2.1.0", "symfony/console": "^2.3 || ^3.0 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { "ramsey/uuid": "^3.5", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0", + "phpstan/phpstan": "^1.9" }, "suggest": { "ramsey/uuid": "A library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID)." diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..bc5b3efe --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,9 @@ +parameters: + level: 2 + paths: + - src + ignoreErrors: + - + message: "#Symfony\\\\Component\\\\Console\\\\Helper\\\\DialogHelper#" + count: 3 + path: src/PHPCR/Util/Console/Command/BaseCommand.php diff --git a/phpstan.tests.neon.dist b/phpstan.tests.neon.dist new file mode 100644 index 00000000..0efe94d4 --- /dev/null +++ b/phpstan.tests.neon.dist @@ -0,0 +1,4 @@ +parameters: + level: 1 + paths: + - tests diff --git a/src/PHPCR/Util/CND/Parser/CndParser.php b/src/PHPCR/Util/CND/Parser/CndParser.php index 0a18429f..dab5dc9e 100644 --- a/src/PHPCR/Util/CND/Parser/CndParser.php +++ b/src/PHPCR/Util/CND/Parser/CndParser.php @@ -733,11 +733,6 @@ protected function parseQueryOpsAttribute() $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; } diff --git a/src/PHPCR/Util/CND/Reader/FileReader.php b/src/PHPCR/Util/CND/Reader/FileReader.php index 2115b5ff..353594d3 100644 --- a/src/PHPCR/Util/CND/Reader/FileReader.php +++ b/src/PHPCR/Util/CND/Reader/FileReader.php @@ -13,7 +13,7 @@ class FileReader extends BufferReader /** * @var string */ - protected $filePath; + protected $path; /** * @param string $path diff --git a/src/PHPCR/Util/CND/Writer/CndWriter.php b/src/PHPCR/Util/CND/Writer/CndWriter.php index 4df28c75..2ab75382 100644 --- a/src/PHPCR/Util/CND/Writer/CndWriter.php +++ b/src/PHPCR/Util/CND/Writer/CndWriter.php @@ -5,7 +5,6 @@ use PHPCR\NamespaceRegistryInterface; use PHPCR\NodeType\NodeDefinitionInterface; use PHPCR\NodeType\NodeTypeDefinitionInterface; -use PHPCR\NodeType\NodeTypeManagerInterface; use PHPCR\NodeType\NodeTypeTemplateInterface; use PHPCR\NodeType\PropertyDefinitionInterface; use PHPCR\PropertyType; @@ -34,9 +33,6 @@ class CndWriter /** @var array hashmap of prefix => namespace uri */ private $namespaces = []; - /** - * @param NodeTypeManagerInterface $ntm - */ public function __construct(NamespaceRegistryInterface $ns) { $this->ns = $ns; @@ -135,6 +131,11 @@ protected function writeNodeType(NodeTypeDefinitionInterface $nodeType) return $s; } + /** + * @param PropertyDefinitionInterface[] $properties + * + * @return string + */ private function writeProperties($properties) { if (null === $properties) { @@ -145,7 +146,6 @@ private function writeProperties($properties) $s = ''; - /** @var $property PropertyDefinitionInterface */ foreach ($properties as $property) { $this->checkNamespace($property->getName()); $s .= '- '.$property->getName(); @@ -196,6 +196,11 @@ private function writeProperties($properties) return $s; } + /** + * @param NodeDefinitionInterface[] $children + * + * @return string + */ private function writeChildren($children) { if (null === $children) { @@ -206,7 +211,6 @@ private function writeChildren($children) $s = ''; - /** @var $child NodeDefinitionInterface */ foreach ($children as $child) { $this->checkNamespace($child->getName()); $s .= '+ '.$child->getName(); diff --git a/src/PHPCR/Util/Console/Command/BaseCommand.php b/src/PHPCR/Util/Console/Command/BaseCommand.php index eb6d82b4..5fdd56e7 100644 --- a/src/PHPCR/Util/Console/Command/BaseCommand.php +++ b/src/PHPCR/Util/Console/Command/BaseCommand.php @@ -6,6 +6,8 @@ use PHPCR\Util\Console\Helper\PhpcrConsoleDumperHelper; use PHPCR\Util\Console\Helper\PhpcrHelper; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\DialogHelper; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; @@ -51,20 +53,20 @@ protected function getPhpcrConsoleDumperHelper() * * @param InputInterface $input * @param OutputInterface $output - * @param string $question + * @param string $questionText * @param string $default * * @return string */ - protected function ask(InputInterface $input, OutputInterface $output, $question, $default = null) + protected function ask(InputInterface $input, OutputInterface $output, $questionText, $default = null) { if ($this->getHelperSet()->has('question')) { - $question = new Question($question, $default); + $question = new Question($questionText, $default); - return $this->getHelper('question')->ask($input, $output, $question); + return $this->getQuestionHelper()->ask($input, $output, $question); } - return $this->getHelper('dialog')->ask($output, $question, $default); + return $this->getDialogHelper()->ask($output, $questionText, $default); } /** @@ -72,19 +74,29 @@ protected function ask(InputInterface $input, OutputInterface $output, $question * * @param InputInterface $input * @param OutputInterface $output - * @param string $question + * @param string $questionText * @param bool $default * * @return string */ - protected function askConfirmation(InputInterface $input, OutputInterface $output, $question, $default = true) + protected function askConfirmation(InputInterface $input, OutputInterface $output, $questionText, $default = true) { if ($this->getHelperSet()->has('question')) { - $question = new ConfirmationQuestion($question, $default); + $question = new ConfirmationQuestion($questionText, $default); - return $this->getHelper('question')->ask($input, $output, $question); + return $this->getQuestionHelper()->ask($input, $output, $question); } - return $this->getHelper('dialog')->askConfirmation($output, $question, $default); + return $this->getDialogHelper()->askConfirmation($output, $questionText, $default); + } + + private function getQuestionHelper(): QuestionHelper + { + return $this->getHelper('question'); + } + + private function getDialogHelper(): DialogHelper + { + return $this->getHelper('dialog'); } } diff --git a/src/PHPCR/Util/Console/Command/NodeRemoveCommand.php b/src/PHPCR/Util/Console/Command/NodeRemoveCommand.php index f9a31275..280fb5a7 100644 --- a/src/PHPCR/Util/Console/Command/NodeRemoveCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeRemoveCommand.php @@ -107,7 +107,7 @@ protected function execute(InputInterface $input, OutputInterface $output) if ($onlyChildren) { $baseNode = $session->getNode($path, 0); - /** @var $childNode NodeInterface */ + /** @var NodeInterface $childNode */ foreach ($baseNode->getNodes() as $childNode) { $childNodePath = $childNode->getPath(); $childNode->remove(); diff --git a/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php index 6b13eb22..50aa22d0 100644 --- a/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php +++ b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php @@ -125,7 +125,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $persistIn = $persistCounter; - /** @var $row RowInterface */ + /** @var RowInterface $row */ foreach ($result as $i => $row) { $output->writeln(sprintf( 'Updating node: [%d] %s.', @@ -169,7 +169,7 @@ private function shouldExecute(InputInterface $input, OutputInterface $output, Q ))); if ($response === 'L') { - /** @var $row RowInterface */ + /** @var RowInterface $row */ foreach ($result as $i => $row) { $output->writeln(sprintf(' - [%d] %s', $i, $row->getPath())); } diff --git a/src/PHPCR/Util/Console/Helper/PhpcrHelper.php b/src/PHPCR/Util/Console/Helper/PhpcrHelper.php index 8c0d7ce2..edb6edc4 100644 --- a/src/PHPCR/Util/Console/Helper/PhpcrHelper.php +++ b/src/PHPCR/Util/Console/Helper/PhpcrHelper.php @@ -129,7 +129,7 @@ public function processNode(OutputInterface $output, NodeInterface $node, array if ($operations['dump']) { $output->writeln('Node dump: '); - /** @var $property PropertyInterface */ + /** @var PropertyInterface $property */ foreach ($node->getProperties() as $property) { $value = $property->getValue(); if (!is_string($value)) { diff --git a/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperNodeVisitor.php b/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperNodeVisitor.php index f6f710dc..c64438b3 100644 --- a/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperNodeVisitor.php +++ b/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperNodeVisitor.php @@ -58,7 +58,7 @@ public function setShowFullPath($showFullPath) public function visit(ItemInterface $item) { if (!$item instanceof NodeInterface) { - throw new Exception("Internal error: did not expect to visit a non-node object: $item"); + throw new Exception('Internal error: did not expect to visit a non-node object: '.get_class($item)); } if ($item->getDepth() === 0) { diff --git a/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperPropertyVisitor.php b/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperPropertyVisitor.php index 3cc44664..63ccbc7b 100644 --- a/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperPropertyVisitor.php +++ b/src/PHPCR/Util/Console/Helper/TreeDumper/ConsoleDumperPropertyVisitor.php @@ -27,6 +27,11 @@ class ConsoleDumperPropertyVisitor extends ConsoleDumperItemVisitor */ protected $expandReferences; + /** + * @var string + */ + private $refFormat; + /** * Instantiate property visitor. * diff --git a/src/PHPCR/Util/NodeHelper.php b/src/PHPCR/Util/NodeHelper.php index 7997c25c..fada226e 100644 --- a/src/PHPCR/Util/NodeHelper.php +++ b/src/PHPCR/Util/NodeHelper.php @@ -86,14 +86,14 @@ public static function purgeWorkspace(SessionInterface $session) { $root = $session->getRootNode(); - /** @var $property PropertyInterface */ + /** @var PropertyInterface $property */ foreach ($root->getProperties() as $property) { if (!self::isSystemItem($property)) { $property->remove(); } } - /** @var $node NodeInterface */ + /** @var NodeInterface $node */ foreach ($root->getNodes() as $node) { if (!self::isSystemItem($node)) { $node->remove(); diff --git a/src/PHPCR/Util/QOM/BaseQomToSqlQueryConverter.php b/src/PHPCR/Util/QOM/BaseQomToSqlQueryConverter.php index c2a1ac21..9cab6b0c 100644 --- a/src/PHPCR/Util/QOM/BaseQomToSqlQueryConverter.php +++ b/src/PHPCR/Util/QOM/BaseQomToSqlQueryConverter.php @@ -248,7 +248,6 @@ protected function convertPropertyValue(QOM\PropertyValueInterface $value) protected function convertOrderings(array $orderings) { $list = []; - /** @var $ordering QOM\OrderingInterface */ foreach ($orderings as $ordering) { $order = $this->generator->evalOrder($ordering->getOrder()); $operand = $this->convertDynamicOperand($ordering->getOperand()); @@ -314,7 +313,6 @@ protected function convertColumns(array $columns) { $list = []; - /** @var $column QOM\ColumnInterface */ foreach ($columns as $column) { $selector = $column->getSelectorName(); $property = $column->getPropertyName(); diff --git a/src/PHPCR/Util/QOM/BaseSqlGenerator.php b/src/PHPCR/Util/QOM/BaseSqlGenerator.php index 027318e7..e023fbdc 100644 --- a/src/PHPCR/Util/QOM/BaseSqlGenerator.php +++ b/src/PHPCR/Util/QOM/BaseSqlGenerator.php @@ -40,17 +40,17 @@ public function __construct(ValueConverter $valueConverter) */ public function evalQuery($source, $columns, $constraint = '', $orderings = '') { - $sql1 = "SELECT $columns FROM $source"; + $sql = "SELECT $columns FROM $source"; if ($constraint) { - $sql1 .= " WHERE $constraint"; + $sql .= " WHERE $constraint"; } if ($orderings) { - $sql1 .= " ORDER BY $orderings"; + $sql .= " ORDER BY $orderings"; } - return $sql1; + return $sql; } /** @@ -169,17 +169,17 @@ public function evalUpper($operand) */ public function evalOrderings($orderings) { - $sql2 = ''; + $sql = ''; foreach ($orderings as $ordering) { - if ($sql2 !== '') { - $sql2 .= ', '; + if ($sql !== '') { + $sql .= ', '; } - $sql2 .= $ordering; + $sql .= $ordering; } - return $sql2; + return $sql; } /** @@ -292,12 +292,83 @@ public function evalLiteral($literal) */ abstract public function evalCastLiteral($literal, $type); + /** + * @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. + * + * @return string + */ + abstract public function evalSelector($nodeTypeName, $selectorName = null); + /** * Evaluate a path. This is different between SQL1 and SQL2. * * @param string $path * - * @return string + * @return string|null */ abstract public function evalPath($path); + + /** + * columns ::= (Column ',' {Column}) | '*'. + * + * With empty columns, SQL1 is different from SQL2 + * + * @param $columns + * + * @return string + */ + abstract public function evalColumns($columns); + + /** + * @param string $selectorName + * @param string $propertyName + * @param string $colname + * + * @return string + */ + abstract public function evalColumn($selectorName, $propertyName = null, $colname = null); + + /** + * @param $selectorName + * @param $propertyName + * + * @return string + */ + abstract public function evalPropertyExistence($selectorName, $propertyName); + + /** + * @param string $propertyName + * @param string $selectorName + * + * @return string + */ + abstract public function evalPropertyValue($propertyName, $selectorName = null); + + /** + * @param string $path + * @param string $selectorName + * + * @return string + */ + abstract public function evalChildNode($path, $selectorName = null); + + /** + * @param string $path + * @param string $selectorName + * + * @return string + */ + abstract public function evalDescendantNode($path, $selectorName = null); + + /** + * @param string $selectorName + * @param string $searchExpression + * @param string $propertyName + * + * @return string + */ + abstract public function evalFullTextSearch($selectorName, $searchExpression, $propertyName = null); } diff --git a/src/PHPCR/Util/QOM/QomToSql2QueryConverter.php b/src/PHPCR/Util/QOM/QomToSql2QueryConverter.php index 7c92ed62..fdef9c07 100644 --- a/src/PHPCR/Util/QOM/QomToSql2QueryConverter.php +++ b/src/PHPCR/Util/QOM/QomToSql2QueryConverter.php @@ -51,6 +51,10 @@ protected function convertSource(QOM\SourceInterface $source) */ protected function convertJoin(QOM\JoinInterface $join) { + 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()); @@ -73,19 +77,20 @@ protected function convertJoin(QOM\JoinInterface $join) protected function convertJoinCondition(QOM\JoinConditionInterface $condition) { 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'); } /** @@ -102,6 +107,10 @@ protected function convertJoinCondition(QOM\JoinConditionInterface $condition) */ protected function convertEquiJoinCondition(QOM\EquiJoinConditionInterface $condition) { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports equi join condition'); + } + return $this->generator->evalEquiJoinCondition( $condition->getSelector1Name(), $condition->getProperty1Name(), @@ -123,6 +132,10 @@ protected function convertEquiJoinCondition(QOM\EquiJoinConditionInterface $cond */ protected function convertSameNodeJoinCondition(QOM\SameNodeJoinConditionInterface $condition) { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports same node join condition'); + } + return $this->generator->evalSameNodeJoinCondition( $condition->getSelector1Name(), $condition->getSelector2Name(), @@ -143,6 +156,10 @@ protected function convertSameNodeJoinCondition(QOM\SameNodeJoinConditionInterfa */ protected function convertChildNodeJoinCondition(QOM\ChildNodeJoinConditionInterface $condition) { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports child node join condition'); + } + return $this->generator->evalChildNodeJoinCondition( $condition->getChildSelectorName(), $condition->getParentSelectorName() @@ -162,6 +179,10 @@ protected function convertChildNodeJoinCondition(QOM\ChildNodeJoinConditionInter */ protected function convertDescendantNodeJoinCondition(QOM\DescendantNodeJoinConditionInterface $condition) { + if (!$this->generator instanceof Sql2Generator) { + throw new NotSupportedOperandException('Only SQL2 supports descendant node join condition'); + } + return $this->generator->evalDescendantNodeJoinCondition( $condition->getDescendantSelectorName(), $condition->getAncestorSelectorName() @@ -221,11 +242,16 @@ protected function convertConstraint(QOM\ConstraintInterface $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() @@ -275,31 +301,47 @@ protected function convertDynamicOperand(QOM\DynamicOperandInterface $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... diff --git a/src/PHPCR/Util/QOM/Sql1Generator.php b/src/PHPCR/Util/QOM/Sql1Generator.php index c06a0612..6c9927a2 100644 --- a/src/PHPCR/Util/QOM/Sql1Generator.php +++ b/src/PHPCR/Util/QOM/Sql1Generator.php @@ -64,11 +64,12 @@ public function evalChildNode($path, $selectorName = null) /** * Emulate descendant query with LIKE query. * - * @param string $path + * @param string $path + * @param string|null $selectorName Unused * * @return string */ - public function evalDescendantNode($path) + public function evalDescendantNode($path, $selectorName = null) { $path = $this->getPathForDescendantQuery($path); @@ -161,10 +162,11 @@ public function evalPropertyValue($propertyName, $selectorName = null) * * @param string $selectorName unused in SQL1 * @param string $propertyName + * @param string $colname unused in SQL1 * * @return string */ - public function evalColumn($selectorName = null, $propertyName = null) + public function evalColumn($selectorName = null, $propertyName = null, $colname = null) { return $propertyName; } diff --git a/src/PHPCR/Util/QOM/Sql2Generator.php b/src/PHPCR/Util/QOM/Sql2Generator.php index a9389117..45fd7751 100644 --- a/src/PHPCR/Util/QOM/Sql2Generator.php +++ b/src/PHPCR/Util/QOM/Sql2Generator.php @@ -237,7 +237,7 @@ public function evalPropertyExistence($selectorName, $propertyName) * FullTextSearchExpression ')' * FullTextSearchExpression ::= BindVariable | ''' FullTextSearchLiteral '''. * - * @param string $selectorName unusued + * @param string $selectorName * @param string $searchExpression * @param string $propertyName * @@ -389,18 +389,19 @@ public function evalColumn($selectorName, $propertyName = null, $colname = null) */ public function evalPath($path) { - 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 (strpos($path, '[') !== 0 && substr($path, -1) !== ']') { + if (false !== strpos($sql2, ' ') || false !== strpos($sql2, '.')) { + $sql2 = '"'.$sql2.'"'; } - - return $sql2; + $sql2 = '['.$sql2.']'; } + + return $sql2; } /** diff --git a/src/PHPCR/Util/QOM/Sql2ToQomQueryConverter.php b/src/PHPCR/Util/QOM/Sql2ToQomQueryConverter.php index 40a336bd..76aca66b 100644 --- a/src/PHPCR/Util/QOM/Sql2ToQomQueryConverter.php +++ b/src/PHPCR/Util/QOM/Sql2ToQomQueryConverter.php @@ -69,6 +69,11 @@ class Sql2ToQomQueryConverter */ protected $implicitSelectorName = null; + /** + * @var ValueConverter + */ + private $valueConverter; + /** * Instantiate a converter. * diff --git a/src/PHPCR/Util/TraversingItemVisitor.php b/src/PHPCR/Util/TraversingItemVisitor.php index 447b35dc..713df4c8 100644 --- a/src/PHPCR/Util/TraversingItemVisitor.php +++ b/src/PHPCR/Util/TraversingItemVisitor.php @@ -159,7 +159,15 @@ public function visit(ItemInterface $item) $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(), + get_class($item) + )); + } + try { if ($this->breadthFirst === false) { $this->entering($item, $this->currentDepth); diff --git a/src/PHPCR/Util/TreeWalker.php b/src/PHPCR/Util/TreeWalker.php index 670c4133..94b460f1 100644 --- a/src/PHPCR/Util/TreeWalker.php +++ b/src/PHPCR/Util/TreeWalker.php @@ -122,20 +122,26 @@ protected function mustVisitProperty(PropertyInterface $property) * @param int $recurse Max recursion level * @param int $level Recursion level */ - public function traverse(NodeInterface $node, $recurse = -1, $level = 0) + public function traverse(NodeInterface $node, $recurse = -1, $level = 0): void { if ($this->mustVisitNode($node)) { // Visit node - $this->nodeVisitor->setLevel($level); - $this->nodeVisitor->setShowFullPath(0 === $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) { 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/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php b/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php index 23329e48..5d3d1a2d 100644 --- a/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php +++ b/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php @@ -23,6 +23,11 @@ class FileReaderTest extends TestCase */ private $lines; + /** + * @var string[] + */ + private $chars; + public function setUp(): void { $this->filepath = __DIR__.'/../Fixtures/files/TestFile.txt'; diff --git a/tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php b/tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php index dab5d01e..59f9377d 100644 --- a/tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php +++ b/tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php @@ -10,8 +10,6 @@ use PHPCR\Util\CND\Scanner\TokenFilter; use PHPCR\Util\CND\Scanner\TokenQueue; use PHPUnit\Framework\TestCase; -use Test; -use TestClass; class GenericScannerTest extends TestCase { @@ -27,7 +25,7 @@ class GenericScannerTest extends TestCase // namespace Test\Foobar; [Token::TK_IDENTIFIER, 'namespace'], [Token::TK_WHITESPACE, ''], - [Token::TK_IDENTIFIER, Test::class], + [Token::TK_IDENTIFIER, 'Test'], [Token::TK_SYMBOL, '\\'], [Token::TK_IDENTIFIER, 'Foobar'], [Token::TK_SYMBOL, ';'], @@ -37,7 +35,7 @@ class GenericScannerTest extends TestCase // class TestClass { [Token::TK_IDENTIFIER, 'class'], [Token::TK_WHITESPACE, ''], - [Token::TK_IDENTIFIER, TestClass::class], + [Token::TK_IDENTIFIER, 'TestClass'], [Token::TK_NEWLINE, ''], [Token::TK_SYMBOL, '{'], [Token::TK_NEWLINE, ''], diff --git a/tests/PHPCR/Tests/Util/QOM/BaseSqlGeneratorTest.php b/tests/PHPCR/Tests/Util/QOM/BaseSqlGeneratorTest.php index e5ed115b..0f11e57d 100644 --- a/tests/PHPCR/Tests/Util/QOM/BaseSqlGeneratorTest.php +++ b/tests/PHPCR/Tests/Util/QOM/BaseSqlGeneratorTest.php @@ -2,10 +2,16 @@ namespace PHPCR\Tests\Util\QOM; +use PHPCR\Util\QOM\BaseSqlGenerator; use PHPUnit\Framework\TestCase; abstract class BaseSqlGeneratorTest extends TestCase { + /** + * @var BaseSqlGenerator + */ + protected $generator; + public function testNot() { $string = $this->generator->evalNot('foo = bar');