├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── LIFECYCLE.md ├── README.md ├── composer.json └── src ├── Clauses ├── CallClause.php ├── CallProcedureClause.php ├── Clause.php ├── CreateClause.php ├── DeleteClause.php ├── LimitClause.php ├── MatchClause.php ├── MergeClause.php ├── OptionalMatchClause.php ├── OrderByClause.php ├── RawClause.php ├── RemoveClause.php ├── ReturnClause.php ├── SetClause.php ├── SkipClause.php ├── UnionClause.php ├── WhereClause.php └── WithClause.php ├── Expressions ├── Exists.php ├── Label.php ├── Literals │ ├── Boolean.php │ ├── Float_.php │ ├── Integer.php │ ├── List_.php │ ├── Literal.php │ ├── Map.php │ └── String_.php ├── Operators │ ├── Addition.php │ ├── BinaryOperator.php │ ├── BooleanBinaryOperator.php │ ├── BooleanUnaryOperator.php │ ├── ComparisonBinaryOperator.php │ ├── ComparisonUnaryOperator.php │ ├── Conjunction.php │ ├── Contains.php │ ├── Disjunction.php │ ├── Division.php │ ├── EndsWith.php │ ├── Equality.php │ ├── ExclusiveDisjunction.php │ ├── Exponentiation.php │ ├── GreaterThan.php │ ├── GreaterThanOrEqual.php │ ├── In.php │ ├── Inequality.php │ ├── IsNotNull.php │ ├── IsNull.php │ ├── LessThan.php │ ├── LessThanOrEqual.php │ ├── MathematicalBinaryOperator.php │ ├── MathematicalUnaryOperator.php │ ├── ModuloDivision.php │ ├── Multiplication.php │ ├── Negation.php │ ├── Operator.php │ ├── Regex.php │ ├── StartsWith.php │ ├── StringSpecificComparisonBinaryOperator.php │ ├── Subtraction.php │ ├── UnaryMinus.php │ └── UnaryOperator.php ├── Parameter.php ├── Procedures │ ├── All.php │ ├── Any.php │ ├── Date.php │ ├── DateTime.php │ ├── Exists.php │ ├── IsEmpty.php │ ├── LocalDateTime.php │ ├── LocalTime.php │ ├── None.php │ ├── Point.php │ ├── Procedure.php │ ├── Raw.php │ ├── Single.php │ └── Time.php ├── Property.php ├── RawExpression.php └── Variable.php ├── Patterns ├── CompletePattern.php ├── Node.php ├── Path.php ├── Pattern.php ├── PropertyPattern.php ├── RelatablePattern.php └── Relationship.php ├── Query.php ├── QueryConvertible.php ├── Syntax ├── Alias.php └── PropertyReplacement.php ├── Traits ├── CastTrait.php ├── ErrorTrait.php ├── EscapeTrait.php ├── NameGenerationTrait.php ├── PatternTraits │ ├── PatternTrait.php │ └── PropertyPatternTrait.php └── TypeTraits │ ├── AnyTypeTrait.php │ ├── CompositeTypeTraits │ ├── CompositeTypeTrait.php │ ├── ListTypeTrait.php │ └── MapTypeTrait.php │ ├── MethodTraits │ └── PropertyMethodTrait.php │ ├── PropertyTypeTraits │ ├── BooleanTypeTrait.php │ ├── DateTimeTypeTrait.php │ ├── DateTypeTrait.php │ ├── FloatTypeTrait.php │ ├── IntegerTypeTrait.php │ ├── LocalDateTimeTypeTrait.php │ ├── LocalTimeTypeTrait.php │ ├── NumeralTypeTrait.php │ ├── PointTypeTrait.php │ ├── PropertyTypeTrait.php │ ├── StringTypeTrait.php │ └── TimeTypeTrait.php │ └── StructuralTypeTraits │ ├── NodeTypeTrait.php │ ├── PathTypeTrait.php │ ├── RelationshipTypeTrait.php │ └── StructuralTypeTrait.php ├── Types ├── AnyType.php ├── CompositeTypes │ ├── CompositeType.php │ ├── ListType.php │ └── MapType.php ├── Methods │ └── PropertyMethod.php ├── PropertyTypes │ ├── BooleanType.php │ ├── DateTimeType.php │ ├── DateType.php │ ├── FloatType.php │ ├── IntegerType.php │ ├── LocalDateTimeType.php │ ├── LocalTimeType.php │ ├── NumeralType.php │ ├── PointType.php │ ├── PropertyType.php │ ├── StringType.php │ └── TimeType.php ├── README.md └── StructuralTypes │ ├── NodeType.php │ ├── PathType.php │ ├── RelationshipType.php │ └── StructuralType.php └── functions.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to the php-cypher-dsl project will be documented in this 4 | file. A changelog has been kept from version 5.0.0 onwards. 5 | 6 | The format is based on [Keep a Changelog], and this project adheres to 7 | [Semantic Versioning]. 8 | 9 | ## 6.0.0 - 2023-09-19 10 | 11 | ### Changed 12 | 13 | - Change the license from the GPL-v2.0-or-later license to the more permissive 14 | MIT license. 15 | 16 | ## 5.0.0 - 2023-01-09 17 | 18 | ### Added 19 | 20 | - Add a blank changelog. 21 | 22 | [keep a changelog]: https://keepachangelog.com/en/1.0.0/ 23 | [semantic versioning]: https://semver.org/spec/v2.0.0.html 24 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This Code of Conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting the project maintainer at marijnvanwezel@gmail.com. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 45 | version 1.3.0, available at 46 | [https://contributor-covenant.org/version/1/3/0/][version] 47 | 48 | [homepage]: https://contributor-covenant.org 49 | [version]: https://contributor-covenant.org/version/1/3/0/ 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Wikibase Solutions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LIFECYCLE.md: -------------------------------------------------------------------------------- 1 | # Version lifecycle 2 | 3 | Since php-cypher-dsl version 5.0.0, support is governed by this version 4 | lifecycle. 5 | 6 | **Bugfix support** means that issues will be fixed for a version of 7 | php-cypher-dsl. Bugfix support for every major release `X` ends **six months** 8 | after the release of the next major version (`X+1`). Bugfix support for minor 9 | release `X.Y` will end with the release of the next minor version (`X.(Y+1)`). 10 | 11 | It is strongly recommended to use a version of php-cypher-dsl that has active 12 | bugfix support. Versions that are no longer receiving bugfix support will not 13 | receive any security or bug fixes. They may contain known critical 14 | vulnerabilities and other major bugs, including the threat of possible data 15 | loss and/or corruption. 16 | 17 | ## Supported versions 18 | 19 | | Major version | PHP version | Initial release | End of bugfix support | 20 | |------------------|-------------|-----------------|-----------------------| 21 | | php-cypher-dsl 5 | >=7.4 | Jan 9th, 2023 | Mar 19th, 2024 | 22 | | php-cypher-dsl 6 | >=7.4 | Sep 19th, 2023 | To be determined | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-cypher-dsl 2 | 3 | The `php-cypher-dsl` library provides a way to construct advanced Cypher 4 | queries in an object-oriented and type-safe manner. 5 | 6 | ## Documentation 7 | 8 | [The documentation can be found on the wiki 9 | here.](https://github.com/WikibaseSolutions/php-cypher-dsl/wiki) 10 | 11 | ## Installation 12 | 13 | ### Requirements 14 | 15 | `php-cypher-dsl` requires PHP 7.4 or greater; using the latest version of PHP 16 | is highly recommended. 17 | 18 | ### Installation through Composer 19 | 20 | You can install `php-cypher-dsl` through composer by running the following 21 | command: 22 | 23 | ``` 24 | composer require "wikibase-solutions/php-cypher-dsl" 25 | ``` 26 | 27 | ## Contributing 28 | 29 | Please refer to [CONTRIBUTING.md](https://github.com/neo4j-php/php-cypher-dsl/blob/main/.github/CONTRIBUTING.md) for information on how to contribute to this project. 30 | 31 | ## Example 32 | 33 | To construct a query to find all of Tom Hanks' co-actors, you can use the 34 | following code: 35 | 36 | ```php 37 | use function WikibaseSolutions\CypherDSL\node; 38 | use function WikibaseSolutions\CypherDSL\query; 39 | 40 | $tom = node("Person")->withProperties(["name" => "Tom Hanks"]); 41 | $coActors = node(); 42 | 43 | $statement = query() 44 | ->match($tom->relationshipTo(Query::node(), "ACTED_IN")->relationshipFrom($coActors, "ACTED_IN")) 45 | ->returning($coActors->property("name")) 46 | ->build(); 47 | ``` 48 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wikibase-solutions/php-cypher-dsl", 3 | "description": "A query builder for the Cypher query language written in PHP", 4 | "type": "library", 5 | "keywords": [ 6 | "neo4j", 7 | "cypher", 8 | "dsl", 9 | "graph", 10 | "querybuilder", 11 | "query", 12 | "builder" 13 | ], 14 | "homepage": "https://wikibase-solutions.com", 15 | "license": "MIT", 16 | "authors": [ 17 | { 18 | "name": "Marijn van Wezel", 19 | "email": "marijn@wikibase.nl", 20 | "homepage": "https://wikibase-solutions.com/author/marijn", 21 | "role": "Maintainer" 22 | }, 23 | { 24 | "name": "Wout Gevaert", 25 | "email": "wout@wikibase.nl", 26 | "homepage": "https://wikibase-solutions.com/author/wout", 27 | "role": "Developer" 28 | }, 29 | { 30 | "name": "Ghlen Nagels", 31 | "email": "ghlen@nagels.tech", 32 | "homepage": "https://nagels.tech", 33 | "role": "Developer" 34 | }, 35 | { 36 | "name": "Lars Akkermans", 37 | "email": "lars@wikibase.nl", 38 | "homepage": "https://wikibase-solutions.com/author/lars", 39 | "role": "Developer" 40 | } 41 | ], 42 | "support": { 43 | "email": "info@wikibase-solutions.com", 44 | "issues": "https://github.com/WikibaseSolutions/php-cypher-dsl/issues", 45 | "wiki": "https://github.com/WikibaseSolutions/php-cypher-dsl/wiki", 46 | "source": "https://github.com/WikibaseSolutions/php-cypher-dsl" 47 | }, 48 | "require": { 49 | "php": ">=7.4", 50 | "ext-ctype": "*", 51 | "ext-openssl": "*", 52 | "symfony/polyfill-php80": "^1.25", 53 | "symfony/polyfill-php81": "^1.25" 54 | }, 55 | "require-dev": { 56 | "phpunit/phpunit": "~9.0", 57 | "infection/infection": "^0.25.5", 58 | "friendsofphp/php-cs-fixer": "^3.0", 59 | "rregeer/phpunit-coverage-check": "^0.3.1", 60 | "phpstan/phpstan": "^1.8" 61 | }, 62 | "autoload": { 63 | "files": [ 64 | "src/functions.php" 65 | ], 66 | "psr-4": { 67 | "WikibaseSolutions\\CypherDSL\\": "src/" 68 | } 69 | }, 70 | "autoload-dev": { 71 | "psr-4": { 72 | "WikibaseSolutions\\CypherDSL\\Tests\\": "tests/" 73 | } 74 | }, 75 | "config": { 76 | "allow-plugins": { 77 | "infection/extension-installer": true 78 | } 79 | }, 80 | "scripts": { 81 | "php-cs-fixer": "php-cs-fixer fix --config .php-cs-fixer.dist.php --verbose" 82 | }, 83 | "minimum-stability": "stable", 84 | "prefer-stable": true 85 | } 86 | -------------------------------------------------------------------------------- /src/Clauses/CallClause.php: -------------------------------------------------------------------------------- 1 | subQuery = $subQuery; 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * Add one or more variables to include in the WITH clause. 58 | * 59 | * @param Pattern|string|Variable ...$variables 60 | * 61 | * @return $this 62 | * 63 | * @see https://neo4j.com/docs/cypher-manual/current/clauses/call-subquery/#subquery-correlated-importing 64 | */ 65 | public function addWithVariable(...$variables): self 66 | { 67 | $res = []; 68 | 69 | foreach ($variables as $variable) { 70 | $res[] = self::toVariable($variable); 71 | } 72 | 73 | $this->withVariables = array_merge($this->withVariables, $res); 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * Returns the query that is being called. This query does not include the WITH clause that is inserted 80 | * if there are any correlated variables. 81 | */ 82 | public function getSubQuery(): ?Query 83 | { 84 | return $this->subQuery; 85 | } 86 | 87 | /** 88 | * Returns the variables that will be included in the WITH clause. 89 | * 90 | * @return Variable[] 91 | */ 92 | public function getWithVariables(): array 93 | { 94 | return $this->withVariables; 95 | } 96 | 97 | /** 98 | * @inheritDoc 99 | */ 100 | protected function getSubject(): string 101 | { 102 | if (!isset($this->subQuery)) { 103 | return ''; 104 | } 105 | 106 | $subQuery = $this->subQuery->build(); 107 | 108 | if ($subQuery === '') { 109 | return ''; 110 | } 111 | 112 | if ($this->withVariables !== []) { 113 | $subQuery = Query::new()->with($this->withVariables)->toQuery() . ' ' . $subQuery; 114 | } 115 | 116 | return sprintf('{ %s }', $subQuery); 117 | } 118 | 119 | /** 120 | * @inheritDoc 121 | */ 122 | protected function getClause(): string 123 | { 124 | return 'CALL'; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Clauses/CallProcedureClause.php: -------------------------------------------------------------------------------- 1 | procedure = $procedure; 50 | 51 | return $this; 52 | } 53 | 54 | /** 55 | * Adds a variable to yield. 56 | * 57 | * @param Alias|string|Variable $yields The variable to yield 58 | * 59 | * @return $this 60 | */ 61 | public function addYield(...$yields): self 62 | { 63 | $res = []; 64 | 65 | foreach ($yields as $yield) { 66 | $res[] = $yield instanceof Alias ? $yield : self::toName($yield); 67 | } 68 | 69 | $this->yields = array_merge($this->yields, $res); 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * Returns the procedure to call. 76 | * 77 | * @return Procedure 78 | */ 79 | public function getProcedure(): ?Procedure 80 | { 81 | return $this->procedure; 82 | } 83 | 84 | /** 85 | * Returns the variables to yield. 86 | * 87 | * @return Alias[]|Variable[]|(Alias|Variable)[] 88 | */ 89 | public function getYields(): array 90 | { 91 | return $this->yields; 92 | } 93 | 94 | /** 95 | * @inheritDoc 96 | */ 97 | protected function getClause(): string 98 | { 99 | return "CALL"; 100 | } 101 | 102 | /** 103 | * @inheritDoc 104 | */ 105 | protected function getSubject(): string 106 | { 107 | if (!isset($this->procedure)) { 108 | return ""; 109 | } 110 | 111 | $subject = $this->procedure->toQuery(); 112 | 113 | if (!empty($this->yields)) { 114 | $yields = array_map(static fn ($variableOrAlias): string => $variableOrAlias->toQuery(), $this->yields); 115 | $subject .= sprintf(" YIELD %s", implode(", ", $yields)); 116 | } 117 | 118 | return $subject; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Clauses/Clause.php: -------------------------------------------------------------------------------- 1 | getClause() === "") { 35 | // If we have an empty clause (for example, for RAW queries), return nothing at all 36 | return ""; 37 | } 38 | 39 | if ($this->getSubject() === "") { 40 | // If we have an empty subject, either return the empty clause, or nothing at all 41 | return $this->canBeEmpty() ? $this->getClause() : ""; 42 | } 43 | 44 | return sprintf("%s %s", $this->getClause(), $this->getSubject()); 45 | } 46 | 47 | /** 48 | * Returns the subject of this object. The subject is anything after 49 | * the clause. For example, in the partial query "MATCH (a)", the subject 50 | * would be "(a)". 51 | */ 52 | abstract protected function getSubject(): string; 53 | 54 | /** 55 | * Returns the clause this object describes. For instance "MATCH". 56 | */ 57 | abstract protected function getClause(): string; 58 | } 59 | -------------------------------------------------------------------------------- /src/Clauses/CreateClause.php: -------------------------------------------------------------------------------- 1 | patterns = array_merge($this->patterns, $pattern); 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * Returns the patterns of the CREATE clause. 50 | * 51 | * @return CompletePattern[] 52 | */ 53 | public function getPatterns(): array 54 | { 55 | return $this->patterns; 56 | } 57 | 58 | /** 59 | * @inheritDoc 60 | */ 61 | protected function getClause(): string 62 | { 63 | return "CREATE"; 64 | } 65 | 66 | /** 67 | * @inheritDoc 68 | */ 69 | protected function getSubject(): string 70 | { 71 | return implode( 72 | ", ", 73 | array_map(static fn (CompletePattern $pattern): string => $pattern->toQuery(), $this->patterns) 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Clauses/DeleteClause.php: -------------------------------------------------------------------------------- 1 | detach = $detach; 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * Add one or more structures to delete. 57 | * 58 | * @param Pattern|StructuralType $structures The structures to delete 59 | * 60 | * @return $this 61 | */ 62 | public function addStructure(...$structures): self 63 | { 64 | $res = []; 65 | 66 | foreach ($structures as $structure) { 67 | $res[] = self::toStructuralType($structure); 68 | } 69 | 70 | $this->structures = array_merge($this->structures, $res); 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Returns whether the deletion detaches the relationships. 77 | */ 78 | public function detachesDeletion(): bool 79 | { 80 | return $this->detach; 81 | } 82 | 83 | /** 84 | * Returns the structures to delete. 85 | * 86 | * @return StructuralType[] 87 | */ 88 | public function getStructural(): array 89 | { 90 | return $this->structures; 91 | } 92 | 93 | /** 94 | * @inheritDoc 95 | */ 96 | protected function getClause(): string 97 | { 98 | if ($this->detach) { 99 | return "DETACH DELETE"; 100 | } 101 | 102 | return "DELETE"; 103 | } 104 | 105 | /** 106 | * @inheritDoc 107 | */ 108 | protected function getSubject(): string 109 | { 110 | return implode( 111 | ", ", 112 | array_map(static fn (StructuralType $structure) => $structure->toQuery(), $this->structures) 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Clauses/LimitClause.php: -------------------------------------------------------------------------------- 1 | limit = self::toIntegerType($limit); 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * Returns the limit of the clause. 50 | */ 51 | public function getLimit(): ?IntegerType 52 | { 53 | return $this->limit; 54 | } 55 | 56 | /** 57 | * @inheritDoc 58 | */ 59 | protected function getClause(): string 60 | { 61 | return "LIMIT"; 62 | } 63 | 64 | /** 65 | * @inheritDoc 66 | */ 67 | protected function getSubject(): string 68 | { 69 | if (isset($this->limit)) { 70 | return $this->limit->toQuery(); 71 | } 72 | 73 | return ""; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Clauses/MatchClause.php: -------------------------------------------------------------------------------- 1 | patterns = array_merge($this->patterns, $pattern); 40 | 41 | return $this; 42 | } 43 | 44 | /** 45 | * Returns the patterns to match. 46 | * 47 | * @return CompletePattern[] 48 | */ 49 | public function getPatterns(): array 50 | { 51 | return $this->patterns; 52 | } 53 | 54 | /** 55 | * @inheritDoc 56 | */ 57 | protected function getClause(): string 58 | { 59 | return "MATCH"; 60 | } 61 | 62 | /** 63 | * @inheritDoc 64 | */ 65 | protected function getSubject(): string 66 | { 67 | return implode( 68 | ", ", 69 | array_map(static fn (CompletePattern $pattern): string => $pattern->toQuery(), $this->patterns) 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Clauses/MergeClause.php: -------------------------------------------------------------------------------- 1 | pattern = $pattern; 50 | 51 | return $this; 52 | } 53 | 54 | /** 55 | * The clause to execute on all nodes that need to be created. 56 | * 57 | * @see https://neo4j.com/docs/cypher-manual/current/clauses/merge/#merge-merge-with-on-create 58 | * 59 | * @return $this 60 | */ 61 | public function setOnCreate(?SetClause $createClause): self 62 | { 63 | $this->createClause = $createClause; 64 | 65 | return $this; 66 | } 67 | 68 | /** 69 | * The clause to execute on all found nodes. 70 | * 71 | * @see https://neo4j.com/docs/cypher-manual/current/clauses/merge/#merge-merge-with-on-match 72 | * 73 | * @return $this 74 | */ 75 | public function setOnMatch(?SetClause $matchClause): self 76 | { 77 | $this->matchClause = $matchClause; 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * Returns the pattern to MERGE. 84 | */ 85 | public function getPattern(): ?CompletePattern 86 | { 87 | return $this->pattern; 88 | } 89 | 90 | /** 91 | * Returns the clause to execute when the pattern is matched. 92 | */ 93 | public function getOnCreateClause(): ?SetClause 94 | { 95 | return $this->createClause; 96 | } 97 | 98 | /** 99 | * Returns the clause to execute when the pattern is matched. 100 | */ 101 | public function getOnMatchClause(): ?SetClause 102 | { 103 | return $this->matchClause; 104 | } 105 | 106 | /** 107 | * @inheritDoc 108 | */ 109 | protected function getClause(): string 110 | { 111 | return "MERGE"; 112 | } 113 | 114 | /** 115 | * @inheritDoc 116 | */ 117 | protected function getSubject(): string 118 | { 119 | if (!isset($this->pattern)) { 120 | return ""; 121 | } 122 | 123 | $queryParts = [$this->pattern->toQuery()]; 124 | 125 | if (isset($this->createClause)) { 126 | $queryParts[] = sprintf("ON CREATE %s", $this->createClause->toQuery()); 127 | } 128 | 129 | if (isset($this->matchClause)) { 130 | $queryParts[] = sprintf("ON MATCH %s", $this->matchClause->toQuery()); 131 | } 132 | 133 | return implode(" ", $queryParts); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Clauses/OptionalMatchClause.php: -------------------------------------------------------------------------------- 1 | patterns = array_merge($this->patterns, $pattern); 40 | 41 | return $this; 42 | } 43 | 44 | /** 45 | * Returns the patterns to optionally match. 46 | * 47 | * @return CompletePattern[] 48 | */ 49 | public function getPatterns(): array 50 | { 51 | return $this->patterns; 52 | } 53 | 54 | /** 55 | * @inheritDoc 56 | */ 57 | protected function getClause(): string 58 | { 59 | return "OPTIONAL MATCH"; 60 | } 61 | 62 | /** 63 | * @inheritDoc 64 | */ 65 | protected function getSubject(): string 66 | { 67 | return implode( 68 | ", ", 69 | array_map(static fn (CompletePattern $pattern): string => $pattern->toQuery(), $this->patterns) 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Clauses/OrderByClause.php: -------------------------------------------------------------------------------- 1 | properties = array_merge($this->properties, $property); 49 | 50 | return $this; 51 | } 52 | 53 | /** 54 | * Set to sort in a DESCENDING order. 55 | * 56 | * @return OrderByClause 57 | */ 58 | public function setDescending(bool $descending = true): self 59 | { 60 | $this->descending = $descending; 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * Returns the properties to order. 67 | * 68 | * @return Property[] 69 | */ 70 | public function getProperties(): array 71 | { 72 | return $this->properties; 73 | } 74 | 75 | /** 76 | * Returns whether the ordering is in descending order. 77 | */ 78 | public function isDescending(): bool 79 | { 80 | return $this->descending; 81 | } 82 | 83 | /** 84 | * @inheritDoc 85 | */ 86 | protected function getClause(): string 87 | { 88 | return "ORDER BY"; 89 | } 90 | 91 | /** 92 | * @inheritDoc 93 | */ 94 | protected function getSubject(): string 95 | { 96 | $properties = array_map(static fn (Property $property): string => $property->toQuery(), $this->properties); 97 | $subject = implode(", ", $properties); 98 | 99 | if ($this->descending) { 100 | $subject .= ' DESCENDING'; 101 | } 102 | 103 | return $subject; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Clauses/RawClause.php: -------------------------------------------------------------------------------- 1 | clause = $clause; 39 | $this->subject = $subject; 40 | } 41 | 42 | /** 43 | * @inheritDoc 44 | */ 45 | protected function getClause(): string 46 | { 47 | return $this->clause; 48 | } 49 | 50 | /** 51 | * @inheritDoc 52 | */ 53 | protected function getSubject(): string 54 | { 55 | return $this->subject; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Clauses/RemoveClause.php: -------------------------------------------------------------------------------- 1 | expressions = array_merge($this->expressions, $expressions); 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * Returns the expressions in the REMOVE clause. 50 | * 51 | * @return Label[]|Property[]|(Label|Property)[] 52 | */ 53 | public function getExpressions(): array 54 | { 55 | return $this->expressions; 56 | } 57 | 58 | /** 59 | * @inheritDoc 60 | */ 61 | protected function getClause(): string 62 | { 63 | return "REMOVE"; 64 | } 65 | 66 | /** 67 | * @inheritDoc 68 | */ 69 | protected function getSubject(): string 70 | { 71 | return implode( 72 | ", ", 73 | array_map(static fn (QueryConvertible $expression) => $expression->toQuery(), $this->expressions) 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Clauses/ReturnClause.php: -------------------------------------------------------------------------------- 1 | columns = array_merge($this->columns, $res); 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Sets this query to only return unique rows. 65 | * 66 | * @return $this 67 | * 68 | * @see https://neo4j.com/docs/cypher-manual/current/clauses/return/#return-unique-results 69 | */ 70 | public function setDistinct(bool $distinct = true): self 71 | { 72 | $this->distinct = $distinct; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Returns the columns to return. Aliased columns have string keys instead of integers. 79 | * 80 | * @return Alias[]|AnyType[]|(Alias|AnyType)[] 81 | */ 82 | public function getColumns(): array 83 | { 84 | return $this->columns; 85 | } 86 | 87 | /** 88 | * Returns whether the returned results are distinct. 89 | */ 90 | public function isDistinct(): bool 91 | { 92 | return $this->distinct; 93 | } 94 | 95 | /** 96 | * @inheritDoc 97 | */ 98 | protected function getClause(): string 99 | { 100 | return $this->distinct ? 101 | "RETURN DISTINCT" : 102 | "RETURN"; 103 | } 104 | 105 | /** 106 | * @inheritDoc 107 | */ 108 | protected function getSubject(): string 109 | { 110 | return implode( 111 | ", ", 112 | array_map(static fn (QueryConvertible $column) => $column->toQuery(), $this->columns) 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Clauses/SetClause.php: -------------------------------------------------------------------------------- 1 | assertClassArray('expression', [PropertyReplacement::class, Label::class], $expressions); 43 | $this->expressions = array_merge($this->expressions, $expressions); 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * Returns the expressions to SET. 50 | * 51 | * @return Label[]|PropertyReplacement[]|(Label|PropertyReplacement)[] 52 | */ 53 | public function getExpressions(): array 54 | { 55 | return $this->expressions; 56 | } 57 | 58 | /** 59 | * @inheritDoc 60 | */ 61 | protected function getClause(): string 62 | { 63 | return "SET"; 64 | } 65 | 66 | /** 67 | * @inheritDoc 68 | */ 69 | protected function getSubject(): string 70 | { 71 | return implode( 72 | ", ", 73 | array_map(static fn (QueryConvertible $expression): string => $expression->toQuery(), $this->expressions) 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Clauses/SkipClause.php: -------------------------------------------------------------------------------- 1 | skip = self::toIntegerType($skip); 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * Returns the amount to skip. 47 | */ 48 | public function getSkip(): ?IntegerType 49 | { 50 | return $this->skip; 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | protected function getClause(): string 57 | { 58 | return "SKIP"; 59 | } 60 | 61 | /** 62 | * @inheritDoc 63 | */ 64 | protected function getSubject(): string 65 | { 66 | if (isset($this->skip)) { 67 | return $this->skip->toQuery(); 68 | } 69 | 70 | return ""; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Clauses/UnionClause.php: -------------------------------------------------------------------------------- 1 | all = $all; 40 | 41 | return $this; 42 | } 43 | 44 | /** 45 | * Returns whether the union includes all results or removes the duplicates instead. 46 | */ 47 | public function includesAll(): bool 48 | { 49 | return $this->all; 50 | } 51 | 52 | /** 53 | * @inheritDoc 54 | */ 55 | public function canBeEmpty(): bool 56 | { 57 | return true; 58 | } 59 | 60 | /** 61 | * @inheritDoc 62 | */ 63 | protected function getClause(): string 64 | { 65 | return 'UNION'; 66 | } 67 | 68 | /** 69 | * @inheritDoc 70 | */ 71 | protected function getSubject(): string 72 | { 73 | return $this->all ? 'ALL' : ''; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Clauses/WhereClause.php: -------------------------------------------------------------------------------- 1 | expression === null) { 58 | $this->expression = $expression; 59 | } elseif ($operator === self::AND) { 60 | $this->expression = $this->expression->and($expression); 61 | } elseif ($operator === self::OR) { 62 | $this->expression = $this->expression->or($expression); 63 | } elseif ($operator === self::XOR) { 64 | $this->expression = $this->expression->xor($expression); 65 | } 66 | 67 | return $this; 68 | } 69 | 70 | /** 71 | * Returns the expression to match. 72 | */ 73 | public function getExpression(): ?BooleanType 74 | { 75 | return $this->expression; 76 | } 77 | 78 | /** 79 | * @inheritDoc 80 | */ 81 | protected function getClause(): string 82 | { 83 | return "WHERE"; 84 | } 85 | 86 | /** 87 | * @inheritDoc 88 | */ 89 | protected function getSubject(): string 90 | { 91 | if (!isset($this->expression)) { 92 | return ""; 93 | } 94 | 95 | return $this->expression->toQuery(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Clauses/WithClause.php: -------------------------------------------------------------------------------- 1 | entries = array_merge($this->entries, $res); 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * Returns the expression to include in the clause. 60 | * 61 | * @return Alias[]|Variable[]|(Alias|Variable)[] 62 | */ 63 | public function getEntries(): array 64 | { 65 | return $this->entries; 66 | } 67 | 68 | /** 69 | * @inheritDoc 70 | */ 71 | protected function getClause(): string 72 | { 73 | return "WITH"; 74 | } 75 | 76 | /** 77 | * @inheritDoc 78 | */ 79 | protected function getSubject(): string 80 | { 81 | return implode( 82 | ", ", 83 | array_map(static fn (QueryConvertible $expression) => $expression->toQuery(), $this->entries) 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Expressions/Exists.php: -------------------------------------------------------------------------------- 1 | match = $match; 53 | $this->where = $where; 54 | $this->insertParentheses = $insertParentheses; 55 | } 56 | 57 | /** 58 | * Returns the MATCH part of the EXISTS expression. 59 | */ 60 | public function getMatch(): MatchClause 61 | { 62 | return $this->match; 63 | } 64 | 65 | /** 66 | * Returns the WHERE part of the expression. 67 | */ 68 | public function getWhere(): ?WhereClause 69 | { 70 | return $this->where; 71 | } 72 | 73 | /** 74 | * Returns whether it inserts parentheses around the expression. 75 | */ 76 | public function insertsParentheses(): bool 77 | { 78 | return $this->insertParentheses; 79 | } 80 | 81 | /** 82 | * @inheritDoc 83 | */ 84 | public function toQuery(): string 85 | { 86 | if (isset($this->where)) { 87 | return sprintf( 88 | $this->insertParentheses ? "(EXISTS { %s %s })" : "EXISTS { %s %s }", 89 | $this->match->toQuery(), 90 | $this->where->toQuery() 91 | ); 92 | } 93 | 94 | return sprintf($this->insertParentheses ? "(EXISTS { %s })" : "EXISTS { %s }", $this->match->toQuery()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Expressions/Label.php: -------------------------------------------------------------------------------- 1 | variable = $variable; 47 | $this->labels = array_map([self::class, 'escape'], $labels); 48 | } 49 | 50 | /** 51 | * Adds one or more labels to this class. 52 | * 53 | * @param string ...$labels One or more labels to add 54 | * 55 | * @return $this 56 | */ 57 | public function addLabels(string ...$labels): self 58 | { 59 | $this->labels = array_merge($this->labels, array_map([self::class, 'escape'], $labels)); 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Returns the escaped labels in this class. 66 | * 67 | * @return string[] 68 | */ 69 | public function getLabels(): array 70 | { 71 | return $this->labels; 72 | } 73 | 74 | /** 75 | * Returns the variable to which the labels are attached. 76 | */ 77 | public function getVariable(): Variable 78 | { 79 | return $this->variable; 80 | } 81 | 82 | /** 83 | * @inheritDoc 84 | */ 85 | public function toQuery(): string 86 | { 87 | $query = $this->variable->toQuery(); 88 | 89 | foreach ($this->labels as $label) { 90 | $query = sprintf("%s:%s", $query, $label); 91 | } 92 | 93 | return $query; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Expressions/Literals/Boolean.php: -------------------------------------------------------------------------------- 1 | value = $value; 35 | } 36 | 37 | /** 38 | * Returns the boolean value. 39 | */ 40 | public function getValue(): bool 41 | { 42 | return $this->value; 43 | } 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | public function toQuery(): string 49 | { 50 | return $this->value ? "true" : "false"; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Expressions/Literals/Float_.php: -------------------------------------------------------------------------------- 1 | value = $value; 35 | } 36 | 37 | /** 38 | * Returns the integer value. 39 | */ 40 | public function getValue(): float 41 | { 42 | return $this->value; 43 | } 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | public function toQuery(): string 49 | { 50 | $value = (string) $this->value; 51 | 52 | if (ctype_digit($value) || ($value[0] === '-' && ctype_digit(substr($value, 1)))) { 53 | $value .= '.0'; 54 | } 55 | 56 | return $value; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Expressions/Literals/Integer.php: -------------------------------------------------------------------------------- 1 | value = $parsedValue; 50 | } 51 | 52 | /** 53 | * Returns a string representation of the integer value. 54 | */ 55 | public function getValue(): string 56 | { 57 | return $this->value; 58 | } 59 | 60 | /** 61 | * @inheritDoc 62 | */ 63 | public function toQuery(): string 64 | { 65 | return $this->value; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Expressions/Literals/List_.php: -------------------------------------------------------------------------------- 1 | expressions = array_map([self::class, 'toAnyType'], $expressions); 44 | } 45 | 46 | /** 47 | * Add one or more expressions to the list. 48 | * 49 | * @param AnyType|bool|float|int|mixed[]|Pattern|string ...$expressions 50 | * 51 | * @return $this 52 | */ 53 | public function addExpression(...$expressions): self 54 | { 55 | $this->expressions = array_merge($this->expressions, array_map([self::class, 'toAnyType'], $expressions)); 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * The homogeneous list of expressions. 62 | * 63 | * @return AnyType[] 64 | */ 65 | public function getExpressions(): array 66 | { 67 | return $this->expressions; 68 | } 69 | 70 | /** 71 | * Returns whether this list is empty. 72 | */ 73 | public function isEmpty(): bool 74 | { 75 | return empty($this->expressions); 76 | } 77 | 78 | /** 79 | * @inheritDoc 80 | */ 81 | public function toQuery(): string 82 | { 83 | $expressions = array_map( 84 | static fn (AnyType $expression): string => $expression->toQuery(), 85 | $this->expressions 86 | ); 87 | 88 | return sprintf("[%s]", implode(", ", $expressions)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Expressions/Literals/Map.php: -------------------------------------------------------------------------------- 1 | elements = array_map([self::class, 'toAnyType'], $elements); 46 | } 47 | 48 | /** 49 | * Adds an element for the given name with the given value. Overrides the element if the $key already exists. 50 | * 51 | * @param string $key The name/label for the element 52 | * @param mixed $value The value of the element 53 | * 54 | * @return $this 55 | */ 56 | public function add(string $key, $value): self 57 | { 58 | $this->elements[$key] = self::toAnyType($value); 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Merges the given map with this map. 65 | * 66 | * @param Map $map The map to merge 67 | * 68 | * @return $this 69 | */ 70 | public function mergeWith(self $map): self 71 | { 72 | $this->elements = array_merge($this->elements, $map->getElements()); 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Returns the elements of this map as an associative array with key-value pairs. 79 | * 80 | * @return AnyType[] 81 | */ 82 | public function getElements(): array 83 | { 84 | return $this->elements; 85 | } 86 | 87 | /** 88 | * Checks if this map is empty. 89 | */ 90 | public function isEmpty(): bool 91 | { 92 | return empty($this->elements); 93 | } 94 | 95 | /** 96 | * @inheritDoc 97 | */ 98 | public function toQuery(): string 99 | { 100 | $pairs = []; 101 | 102 | foreach ($this->elements as $key => $value) { 103 | $pairs[] = sprintf("%s: %s", self::escape((string) $key), $value->toQuery()); 104 | } 105 | 106 | return sprintf("{%s}", implode(", ", $pairs)); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Expressions/Literals/String_.php: -------------------------------------------------------------------------------- 1 | value = $value; 42 | } 43 | 44 | /** 45 | * Whether to use double quotes or not. 46 | */ 47 | public function useDoubleQuotes(bool $useDoubleQuotes = true): void 48 | { 49 | $this->useDoubleQuotes = $useDoubleQuotes; 50 | } 51 | 52 | /** 53 | * Returns the string value. 54 | */ 55 | public function getValue(): string 56 | { 57 | return $this->value; 58 | } 59 | 60 | /** 61 | * Returns whether the string uses double quotes. Single quotes are used if false. 62 | */ 63 | public function usesDoubleQuotes(): bool 64 | { 65 | return $this->useDoubleQuotes; 66 | } 67 | 68 | /** 69 | * @inheritDoc 70 | */ 71 | public function toQuery(): string 72 | { 73 | // Encodes both "'" and '"', so returning in either context is safe 74 | $value = $this->encodeSpecials($this->value); 75 | 76 | if ($this->useDoubleQuotes) { 77 | return sprintf('"%s"', $value); 78 | } 79 | 80 | return sprintf("'%s'", $value); 81 | } 82 | 83 | /** 84 | * Encodes special string characters in Cypher. 85 | * 86 | * @param string $value The string to encode 87 | * 88 | * @return string The encoded string 89 | */ 90 | private static function encodeSpecials(string $value): string 91 | { 92 | // See https://s3.amazonaws.com/artifacts.opencypher.org/openCypher9.pdf (Note on string literals) 93 | return str_replace( 94 | ["\\", "\t", "\u{0008}", "\n", "\r", "\f", "'", "\""], 95 | ["\\\\", "\\t", "\\b", "\\n", "\\r", "\\f", "\\'", "\\\""], 96 | $value 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Expressions/Operators/Addition.php: -------------------------------------------------------------------------------- 1 | left = $left; 40 | $this->right = $right; 41 | } 42 | 43 | /** 44 | * Gets the left-hand of the expression. 45 | */ 46 | public function getLeft(): AnyType 47 | { 48 | return $this->left; 49 | } 50 | 51 | /** 52 | * Gets the right-hand of the expression. 53 | */ 54 | public function getRight(): AnyType 55 | { 56 | return $this->right; 57 | } 58 | 59 | /** 60 | * @inheritDoc 61 | */ 62 | protected function toInner(): string 63 | { 64 | return sprintf("%s %s %s", $this->left->toQuery(), $this->getOperator(), $this->right->toQuery()); 65 | } 66 | 67 | /** 68 | * Returns the operator. For instance, this function would return "-" for the minus operator. 69 | */ 70 | abstract protected function getOperator(): string; 71 | } 72 | -------------------------------------------------------------------------------- /src/Expressions/Operators/BooleanBinaryOperator.php: -------------------------------------------------------------------------------- 1 | " 21 | * - less than: "<" 22 | * - greater than: ">" 23 | * - less than or equal to: "<=" 24 | * - greater than or equal to: ">=" 25 | * 26 | * In additional, there are some string-specific comparison operators: 27 | * 28 | * - case-sensitive prefix search on strings: "STARTS WITH" 29 | * - case-sensitive suffix search on strings: "ENDS WITH" 30 | * - case-sensitive inclusion search in strings: "CONTAINS" 31 | * 32 | * @see https://neo4j.com/docs/cypher-manual/current/syntax/operators/#query-operators-comparison Corresponding documentation on Neo4j.com 33 | */ 34 | abstract class ComparisonBinaryOperator extends BinaryOperator implements BooleanType 35 | { 36 | use BooleanTypeTrait; 37 | 38 | /** 39 | * @inheritDoc 40 | * 41 | * @param AnyType $left The left-hand of the comparison operator 42 | * @param AnyType $right The right-hand of the comparison operator 43 | */ 44 | public function __construct(AnyType $left, AnyType $right, bool $insertParentheses = true) 45 | { 46 | parent::__construct($left, $right, $insertParentheses); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Expressions/Operators/ComparisonUnaryOperator.php: -------------------------------------------------------------------------------- 1 | ) operator. 14 | * 15 | * @see https://neo4j.com/docs/cypher-manual/current/syntax/operators/#query-operators-comparison Corresponding documentation on Neo4j.com 16 | */ 17 | final class GreaterThan extends ComparisonBinaryOperator 18 | { 19 | /** 20 | * @inheritDoc 21 | */ 22 | protected function getOperator(): string 23 | { 24 | return ">"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Expressions/Operators/GreaterThanOrEqual.php: -------------------------------------------------------------------------------- 1 | =) operator. 14 | * 15 | * @see https://neo4j.com/docs/cypher-manual/current/syntax/operators/#query-operators-comparison Corresponding documentation on Neo4j.com 16 | */ 17 | final class GreaterThanOrEqual extends ComparisonBinaryOperator 18 | { 19 | /** 20 | * @inheritDoc 21 | */ 22 | protected function getOperator(): string 23 | { 24 | return ">="; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Expressions/Operators/In.php: -------------------------------------------------------------------------------- 1 | ) operator. 14 | * 15 | * @see https://neo4j.com/docs/cypher-manual/current/syntax/operators/#query-operators-comparison Corresponding documentation on Neo4j.com 16 | */ 17 | final class Inequality extends ComparisonBinaryOperator 18 | { 19 | /** 20 | * @inheritDoc 21 | */ 22 | protected function getOperator(): string 23 | { 24 | return "<>"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Expressions/Operators/IsNotNull.php: -------------------------------------------------------------------------------- 1 | insertParentheses = $insertParentheses; 34 | } 35 | 36 | /** 37 | * Returns whether the operator inserts parenthesis. 38 | */ 39 | public function insertsParentheses(): bool 40 | { 41 | return $this->insertParentheses; 42 | } 43 | 44 | /** 45 | * @inheritDoc 46 | */ 47 | public function toQuery(): string 48 | { 49 | $format = $this->insertParentheses ? "(%s)" : "%s"; 50 | $inner = $this->toInner(); 51 | 52 | return sprintf($format, $inner); 53 | } 54 | 55 | /** 56 | * Returns the inner part of the application of the operator, that is, without any parentheses. 57 | */ 58 | abstract protected function toInner(): string; 59 | } 60 | -------------------------------------------------------------------------------- /src/Expressions/Operators/Regex.php: -------------------------------------------------------------------------------- 1 | expression = $expression; 34 | } 35 | 36 | /** 37 | * Returns whether this is a postfix operator or not. 38 | */ 39 | public function isPostfix(): bool 40 | { 41 | return false; 42 | } 43 | 44 | /** 45 | * Returns the expression to negate. 46 | */ 47 | public function getExpression(): AnyType 48 | { 49 | return $this->expression; 50 | } 51 | 52 | /** 53 | * @inheritDoc 54 | */ 55 | protected function toInner(): string 56 | { 57 | $expression = $this->expression->toQuery(); 58 | $operator = $this->getOperator(); 59 | 60 | return $this->isPostfix() ? 61 | sprintf("%s %s", $expression, $operator) : 62 | sprintf("%s %s", $operator, $expression); 63 | } 64 | 65 | /** 66 | * Returns the operator. For instance, this function would return "-" for the minus operator. 67 | */ 68 | abstract protected function getOperator(): string; 69 | } 70 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/All.php: -------------------------------------------------------------------------------- 1 | variable = $variable; 55 | $this->list = $list; 56 | $this->predicate = $predicate; 57 | } 58 | 59 | /** 60 | * @inheritDoc 61 | */ 62 | protected function getSignature(): string 63 | { 64 | return "all(%s IN %s WHERE %s)"; 65 | } 66 | 67 | /** 68 | * @inheritDoc 69 | */ 70 | protected function getParameters(): array 71 | { 72 | return [$this->variable, $this->list, $this->predicate]; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/Any.php: -------------------------------------------------------------------------------- 1 | variable = $variable; 55 | $this->list = $list; 56 | $this->predicate = $predicate; 57 | } 58 | 59 | /** 60 | * @inheritDoc 61 | */ 62 | protected function getSignature(): string 63 | { 64 | return "any(%s IN %s WHERE %s)"; 65 | } 66 | 67 | /** 68 | * @inheritDoc 69 | */ 70 | protected function getParameters(): array 71 | { 72 | return [$this->variable, $this->list, $this->predicate]; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/Date.php: -------------------------------------------------------------------------------- 1 | value = $value; 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | protected function getSignature(): string 47 | { 48 | return $this->value ? "date(%s)" : "date()"; 49 | } 50 | 51 | /** 52 | * @inheritDoc 53 | */ 54 | protected function getParameters(): array 55 | { 56 | return $this->value ? [$this->value] : []; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/DateTime.php: -------------------------------------------------------------------------------- 1 | value = $value; 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | protected function getSignature(): string 47 | { 48 | return $this->value ? "datetime(%s)" : "datetime()"; 49 | } 50 | 51 | /** 52 | * @inheritDoc 53 | */ 54 | protected function getParameters(): array 55 | { 56 | return $this->value ? [$this->value] : []; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/Exists.php: -------------------------------------------------------------------------------- 1 | expression = $expression; 43 | } 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | protected function getSignature(): string 49 | { 50 | return "exists(%s)"; 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | protected function getParameters(): array 57 | { 58 | return [$this->expression]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/IsEmpty.php: -------------------------------------------------------------------------------- 1 | list = $list; 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | protected function getSignature(): string 57 | { 58 | return "isEmpty(%s)"; 59 | } 60 | 61 | /** 62 | * @inheritDoc 63 | */ 64 | protected function getParameters(): array 65 | { 66 | return [$this->list]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/LocalDateTime.php: -------------------------------------------------------------------------------- 1 | value = $value; 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | protected function getSignature(): string 47 | { 48 | return $this->value ? "localdatetime(%s)" : "localdatetime()"; 49 | } 50 | 51 | /** 52 | * @inheritDoc 53 | */ 54 | protected function getParameters(): array 55 | { 56 | return $this->value ? [$this->value] : []; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/LocalTime.php: -------------------------------------------------------------------------------- 1 | value = $value; 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | protected function getSignature(): string 47 | { 48 | return $this->value ? "localtime(%s)" : "localtime()"; 49 | } 50 | 51 | /** 52 | * @inheritDoc 53 | */ 54 | protected function getParameters(): array 55 | { 56 | return $this->value ? [$this->value] : []; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/None.php: -------------------------------------------------------------------------------- 1 | variable = $variable; 55 | $this->list = $list; 56 | $this->predicate = $predicate; 57 | } 58 | 59 | /** 60 | * @inheritDoc 61 | */ 62 | protected function getSignature(): string 63 | { 64 | return "none(%s IN %s WHERE %s)"; 65 | } 66 | 67 | /** 68 | * @inheritDoc 69 | */ 70 | protected function getParameters(): array 71 | { 72 | return [$this->variable, $this->list, $this->predicate]; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/Point.php: -------------------------------------------------------------------------------- 1 | map = $map; 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | protected function getSignature(): string 47 | { 48 | return "point(%s)"; 49 | } 50 | 51 | /** 52 | * @inheritDoc 53 | */ 54 | protected function getParameters(): array 55 | { 56 | return [$this->map]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/Single.php: -------------------------------------------------------------------------------- 1 | variable = $variable; 55 | $this->list = $list; 56 | $this->predicate = $predicate; 57 | } 58 | 59 | /** 60 | * @inheritDoc 61 | */ 62 | protected function getSignature(): string 63 | { 64 | return "single(%s IN %s WHERE %s)"; 65 | } 66 | 67 | /** 68 | * @inheritDoc 69 | */ 70 | protected function getParameters(): array 71 | { 72 | return [$this->variable, $this->list, $this->predicate]; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Expressions/Procedures/Time.php: -------------------------------------------------------------------------------- 1 | value = $value; 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | protected function getSignature(): string 47 | { 48 | return $this->value ? "time(%s)" : "time()"; 49 | } 50 | 51 | /** 52 | * @inheritDoc 53 | */ 54 | protected function getParameters(): array 55 | { 56 | return $this->value ? [$this->value] : []; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Patterns/CompletePattern.php: -------------------------------------------------------------------------------- 1 | ) 34 | * relationship. 35 | * 36 | * @param RelatablePattern $pattern The pattern to attach to the end of this pattern 37 | * @param null|string $type The type of the relationship 38 | * @param null|MapType|mixed[] $properties The properties to attach to the relationship 39 | * @param null|string|Variable $name The name fo the relationship 40 | */ 41 | public function relationshipTo(self $pattern, ?string $type = null, $properties = null, $name = null): Path; 42 | 43 | /** 44 | * Forms a new path by adding the given relatable pattern to the end of this pattern using a left (<--) 45 | * relationship. 46 | * 47 | * @param RelatablePattern $pattern The pattern to attach to the end of this pattern 48 | * @param null|string $type The type of the relationship 49 | * @param null|MapType|mixed[] $properties The properties to attach to the relationship 50 | * @param null|string|Variable $name The name fo the relationship 51 | */ 52 | public function relationshipFrom(self $pattern, ?string $type = null, $properties = null, $name = null): Path; 53 | 54 | /** 55 | * Forms a new path by adding the given relatable pattern to the end of this pattern using a unidirectional 56 | * (--/<-->) relationship. 57 | * 58 | * @param RelatablePattern $pattern The pattern to attach to the end of this pattern 59 | * @param null|string $type The type of the relationship 60 | * @param null|MapType|mixed[] $properties The properties to attach to the relationship 61 | * @param null|string|Variable $name The name fo the relationship 62 | */ 63 | public function relationshipUni(self $pattern, ?string $type = null, $properties = null, $name = null): Path; 64 | } 65 | -------------------------------------------------------------------------------- /src/QueryConvertible.php: -------------------------------------------------------------------------------- 1 | original = $original; 43 | $this->variable = $variable; 44 | } 45 | 46 | /** 47 | * Gets the original item of the alias. 48 | */ 49 | public function getOriginal(): AnyType 50 | { 51 | return $this->original; 52 | } 53 | 54 | /** 55 | * Gets the variable from the alias. 56 | */ 57 | public function getVariable(): Variable 58 | { 59 | return $this->variable; 60 | } 61 | 62 | /** 63 | * @inheritDoc 64 | */ 65 | public function toQuery(): string 66 | { 67 | return \sprintf("%s AS %s", $this->original->toQuery(), $this->variable->toQuery()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Syntax/PropertyReplacement.php: -------------------------------------------------------------------------------- 1 | property = $property; 56 | $this->value = $value; 57 | } 58 | 59 | /** 60 | * Whether to use the property mutation instead of the property replacement 61 | * operator. 62 | * 63 | * @return $this 64 | */ 65 | public function setMutate(bool $mutate = true): self 66 | { 67 | $this->mutate = $mutate; 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * Returns whether the assignment uses property mutation instead of replacement. 74 | */ 75 | public function mutates(): bool 76 | { 77 | return $this->mutate; 78 | } 79 | 80 | /** 81 | * Returns the name of the property to which we assign a (new) value. 82 | * 83 | * @return Property|Variable 84 | */ 85 | public function getProperty() 86 | { 87 | return $this->property; 88 | } 89 | 90 | /** 91 | * Returns value to assign to the property. 92 | */ 93 | public function getValue(): AnyType 94 | { 95 | return $this->value; 96 | } 97 | 98 | /** 99 | * @inheritDoc 100 | */ 101 | public function toQuery(): string 102 | { 103 | return sprintf( 104 | "%s %s %s", 105 | $this->property->toQuery(), 106 | $this->mutate ? "+=" : "=", 107 | $this->value->toQuery() 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Traits/EscapeTrait.php: -------------------------------------------------------------------------------- 1 | 65534) { 42 | // Remark: Some versions of Neo4j support names up to 65535 characters, but we just take the lower bound 43 | throw new InvalidArgumentException("A name cannot be longer than 65534 (2^16 - 2) characters"); 44 | } 45 | 46 | if (\preg_match('/^\p{L}[\p{L}\d_]*$/u', $name)) { 47 | // The name is already valid and does not need to be escaped 48 | return $name; 49 | } 50 | 51 | // Escape backticks that are included in $name by doubling them. 52 | $name = \str_replace('`', '``', $name); 53 | 54 | return \sprintf("`%s`", $name); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Traits/NameGenerationTrait.php: -------------------------------------------------------------------------------- 1 | variable !== null; 33 | } 34 | 35 | /** 36 | * Explicitly assign a named variable to this object. 37 | * 38 | * @param null|string|Variable $variable 39 | * 40 | * @return $this 41 | */ 42 | public function withVariable($variable): self 43 | { 44 | $this->variable = $variable === null ? null : self::toName($variable); 45 | 46 | return $this; 47 | } 48 | 49 | /** 50 | * Returns the variable of this object. This function generates a variable if none has been set. 51 | */ 52 | public function getVariable(): Variable 53 | { 54 | if (!isset($this->variable)) { 55 | $this->variable = new Variable(); 56 | } 57 | 58 | return $this->variable; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Traits/PatternTraits/PropertyPatternTrait.php: -------------------------------------------------------------------------------- 1 | getVariable(), $property); 39 | } 40 | 41 | /** 42 | * @inheritDoc 43 | */ 44 | public function withProperties($properties): self 45 | { 46 | $this->properties = self::toMapType($properties); 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * @inheritDoc 53 | */ 54 | public function addProperty(string $key, $property): self 55 | { 56 | $this->makeMap()->add($key, $property); 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * @inheritDoc 63 | */ 64 | public function addProperties($properties): self 65 | { 66 | self::assertClass('properties', [Map::class, 'array'], $properties); 67 | 68 | $map = $this->makeMap(); 69 | 70 | if (is_array($properties)) { 71 | $res = []; 72 | 73 | foreach ($properties as $key => $property) { 74 | $res[$key] = self::toAnyType($property); 75 | } 76 | 77 | // Cast the array to a Map 78 | $properties = new Map($res); 79 | } 80 | 81 | $map->mergeWith($properties); 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * @inheritDoc 88 | */ 89 | public function getProperties(): ?MapType 90 | { 91 | return $this->properties; 92 | } 93 | 94 | /** 95 | * Initialises the properties in this pattern. 96 | */ 97 | private function makeMap(): Map 98 | { 99 | if (!isset($this->properties)) { 100 | $this->properties = new Map(); 101 | } elseif (!$this->properties instanceof Map) { 102 | // Adding to a map is not natively supported by the MapType, but it is supported by Map. Syntactically, it 103 | // is not possible to add new items to, for instance, a Variable, even though it implements MapType. It is 104 | // however still useful to be able to add items to objects where a Map is used (that is, an object of 105 | // MapType with the {} syntax). 106 | throw new TypeError('$this->properties must be of type Map to support "addProperty"'); 107 | } 108 | 109 | return $this->properties; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Traits/TypeTraits/AnyTypeTrait.php: -------------------------------------------------------------------------------- 1 |