├── .editorconfig ├── .gitignore ├── .php_cs ├── .travis.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── build ├── install-jdk8.sh └── install-neo.sh ├── composer.json ├── phpunit.xml.dist ├── src ├── Client.php ├── ClientBuilder.php ├── ClientInterface.php ├── Config.php ├── Connection │ ├── Connection.php │ └── ConnectionManager.php ├── Event │ ├── FailureEvent.php │ ├── PostRunEvent.php │ └── PreRunEvent.php ├── Exception │ ├── Neo4jException.php │ ├── Neo4jExceptionInterface.php │ └── NeoClientExceptionInterface.php ├── Formatter │ ├── RecordView.php │ ├── Response.php │ ├── ResponseFormatter.php │ ├── Result.php │ └── Type │ │ ├── MapAccess.php │ │ ├── Node.php │ │ ├── Path.php │ │ └── Relationship.php ├── HttpDriver │ ├── Configuration.php │ ├── Driver.php │ ├── GraphDatabase.php │ ├── Pipeline.php │ ├── Result │ │ ├── ResultSummary.php │ │ └── StatementStatistics.php │ ├── Session.php │ └── Transaction.php ├── Neo4jClientEvents.php ├── Result │ └── ResultCollection.php ├── Schema │ └── Label.php ├── Stack.php ├── StackInterface.php └── Transaction │ └── Transaction.php └── tests ├── Example ├── ExampleTestCase.php └── ReadmeExampleTest.php ├── Integration ├── BuildWithEventListenersIntegrationTest.php ├── ClientGetExceptionIntegrationTest.php ├── ClientSetupIntegrationTest.php ├── CombinedStatisticsIntegrationTest.php ├── CypherIntegrationTest.php ├── EventListener.php ├── GetLabelsProcedureTest.php ├── IntegrationTestCase.php ├── ResultIntegrationTest.php ├── StatementParametersTest.php ├── StatisticsIntegrationTest.php └── TransactionIntegrationTest.php ├── Issues ├── DrupalIssueTest.php ├── Issue105Test.php ├── Issue143Test.php ├── Issue40Test.php └── ReportedIssuesTest.php └── Unit ├── Connection └── ConnectionUnitTest.php └── Stub └── DummyDriver.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .php_cs.cache 2 | bin/* 3 | cache/ 4 | composer.lock 5 | neoclient.yml 6 | test.php 7 | tests/_reports 8 | tests/database_settings.yml 9 | vendor/ 10 | phpunit.xml 11 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | $header = <<<'EOF' 13 | This file is part of the GraphAware Neo4j Client package. 14 | 15 | (c) GraphAware Limited 16 | 17 | For the full copyright and license information, please view the LICENSE 18 | file that was distributed with this source code. 19 | EOF; 20 | 21 | $finder = PhpCsFixer\Finder::create() 22 | ->in(__DIR__.'/src/') 23 | ->in(__DIR__.'/tests/') 24 | ; 25 | 26 | return PhpCsFixer\Config::create() 27 | ->setRules([ 28 | '@Symfony' => true, 29 | 30 | 'array_syntax' => ['syntax' => 'short'], 31 | 'header_comment' => ['header' => $header], 32 | 'linebreak_after_opening_tag' => true, 33 | 'ordered_imports' => true, 34 | 'phpdoc_order' => true, 35 | 36 | // 'modernize_types_casting' => true, 37 | // 'no_useless_return' => true, 38 | // 'phpdoc_add_missing_param_annotation' => true, 39 | // 'protected_to_private' => true, 40 | // 'strict_param' => true, 41 | ]) 42 | ->setFinder($finder) 43 | ; 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.6 4 | - 7.0 5 | - 7.1 6 | - 7.2 7 | - 7.3 8 | 9 | before_install: 10 | - sudo apt-get update > /dev/null 11 | # install Oracle JDK8 12 | - sh -c ./build/install-jdk8.sh 13 | # install and launch neo4j 14 | - sh -c ./build/install-neo.sh 15 | - composer self-update 16 | - travis_retry composer install --prefer-source --no-interaction 17 | 18 | script: 19 | - vendor/bin/phpunit --exclude-group fail 20 | 21 | notifications: 22 | email: "christophe@graphaware.com" 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for v4 2 | 3 | 4.6.3 - 16 October 2016 4 | 5 | - Added a convenient method for having a default return when there is no record and firstRecord is called 6 | 7 | 4.6.2 - 10 October 2016 8 | 9 | - Fixes Issue 54 (inconsistent behavior of getRecord on empty cursor between http and bolt 10 | 11 | 4.6.0 - 01 October 2016 12 | 13 | - Client class parameterizable 14 | 15 | 4.4.5 - 05 July 2016 16 | 17 | - Fixed an issue with `relationshipValue()` 18 | 19 | 4.4.4 20 | 21 | - Added preflight to stack 22 | 23 | 4.4.3 - 09 June 2016 24 | 25 | - Fixed same issue as 4.4.2 in a transaction 26 | 27 | 4.4.2 - 09 June 2016 28 | 29 | - Fixed an issue where empty nested arrays were not converted to json objects 30 | 31 | 4.4.1 - 06 June 2016 32 | 33 | - Upgraded to latest commons 34 | 35 | 4.4.0 - 28 May 2016 36 | 37 | - Added getLabels method to the client 38 | 39 | 4.3.1 - 13 May 2016 40 | 41 | - Added the possibility to pass a default value to `Record::get()` to be returned if the record doesn't contains the given key 42 | 43 | 4.2.0 - 06 May 2016 44 | 45 | - Added events dispatching before and after running statements and stacks 46 | 47 | 4.1.1 - 06 May 2016 48 | 49 | - Added `registerExistingConnection` in ConnectionManager 50 | 51 | 4.1.0 - 02 May 2016 52 | 53 | - Added `updateStatistics()` method on the ResultCollection for combined statistics of stacks, transactions, etc.. 54 | 55 | 4.0.2 - 28 Apr 2016 56 | 57 | - Fixed a bug where relationships deleted count was not hydrated in the http result update statistics 58 | 59 | 4.0.1 - 27 Apr 2016 60 | 61 | - Fixed a bug where `nodeValue` was using a hardcoded identifier [8bf11473c9870c2423de2763622d2674b97216db](8bf11473c9870c2423de2763622d2674b97216db) 62 | 63 | 4.0.0 - 25 Apr 2016 64 | 65 | Initial 4.0 release for support with Neo4j 3.0 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.0-cli 2 | RUN apt-get update && apt-get install -y \ 3 | libfreetype6-dev \ 4 | libjpeg62-turbo-dev \ 5 | libmcrypt-dev \ 6 | libpng12-dev \ 7 | && docker-php-ext-install -j$(nproc) iconv mcrypt \ 8 | && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ 9 | && docker-php-ext-install -j$(nproc) gd \ 10 | && docker-php-ext-install -j$(nproc) bcmath \ 11 | && docker-php-ext-install -j$(nproc) mbstring -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2016 GraphAware Limited 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | THIS REPOSITORY IS ARCHIVED. 2 | 3 | Please head to http://github.com/neo4j-php or http://neo4j.com/developer/php for up-to-date PHP support for Neo4j. 4 | 5 | # GraphAware Neo4j PHP Client 6 | 7 | ## An Enterprise Grade Client for Neo4j 8 | 9 | [![Build Status](https://travis-ci.org/graphaware/neo4j-php-client.svg?branch=master)](https://travis-ci.org/graphaware/neo4j-php-client) 10 | [![Latest Stable Version](https://poser.pugx.org/graphaware/neo4j-php-client/v/stable.svg)](https://packagist.org/packages/graphaware/neo4j-php-client) 11 | [![Total Downloads](https://poser.pugx.org/graphaware/neo4j-php-client/downloads.svg)](https://packagist.org/packages/graphaware/neo4j-php-client) 12 | [![License](https://poser.pugx.org/graphaware/neo4j-php-client/license.svg)](https://packagist.org/packages/graphaware/neo4j-php-client) 13 | 14 | ## Introduction 15 | 16 | Neo4j-PHP-Client is the most advanced and flexible [Neo4j](http://neo4j.com) Client for PHP. 17 | 18 | ### What is Neo4j? 19 | 20 | Neo4j is a transactional, open-source graph database. A graph database manages data in a connected data structure, capable of representing any kind of data in a very accessible way. Information is stored in nodes and relationships connecting them, both of which can have arbitrary properties. To learn more visit [What is a Graph Database](http://neo4j.com/developer/graph-database/)? 21 | 22 | ### Key features 23 | 24 | * Supports multiple connections 25 | * Support for Bolt binary protocol 26 | * Built-in and automatic support for *Neo4j Enterprise HA Master-Slave Mode* with auto slaves fallback 27 | 28 | #### Neo4j Version Support 29 | 30 | | **Version** | **Tested** | 31 | |-------------|-------------| 32 | | <= 2.2.6 | No | 33 | | >= 2.2.6 | Yes | 34 | | 2.2 | Yes | 35 | | 2.3 | Yes | 36 | | 3.0 + | Yes | 37 | 38 | #### Neo4j Feature Support 39 | 40 | | **Feature** | **Supported?** | 41 | |----------------------|----------------| 42 | | Auth | Yes | 43 | | Remote Cypher | Yes | 44 | | Transactions | Yes | 45 | | High Availability | Yes | 46 | | Embedded JVM support | No | 47 | | Binary Protocol | Yes | 48 | 49 | ### Requirements 50 | 51 | * PHP >= 5.6 52 | * ext-bcmath 53 | * ext-mbstring 54 | * A Neo4j database (minimum version 2.2.6) 55 | 56 | ### Getting Help 57 | 58 | You can: 59 | 60 | * [Ask a question on StackOverflow](http://stackoverflow.com/questions/ask?tags=graphaware,php,neo4j) 61 | * For bugs, please feel free to create a [new issue on GitHub](https://github.com/graphaware/neo4j-php-client/issues/new) 62 | 63 | ### Implementations 64 | 65 | * [Symfony Framework Bundle](https://github.com/neo4j-contrib/neo4j-symfony) 66 | 67 | ## Installation and basic usage 68 | 69 | ### Installation 70 | 71 | Add the library to your composer dependencies : 72 | 73 | ```bash 74 | composer require "graphaware/neo4j-php-client:^4.0" 75 | ``` 76 | 77 | Require the composer autoloader, configure your connection by providing a connection alias and your connection settings : 78 | 79 | ```php 80 | addConnection('default', 'http://neo4j:password@localhost:7474') // Example for HTTP connection configuration (port is optional) 88 | ->addConnection('bolt', 'bolt://neo4j:password@localhost:7687') // Example for BOLT connection configuration (port is optional) 89 | ->build(); 90 | ``` 91 | 92 | You're now ready to connect to your database. 93 | 94 | NB: The build method will process configuration settings and return you a `Client` instance. 95 | 96 | ### Basic Usage 97 | 98 | #### Sending a Cypher Query 99 | 100 | ```php 101 | $client->run('CREATE (n:Person)'); 102 | ``` 103 | 104 | #### Sending a Cypher Query with parameters 105 | 106 | ```php 107 | $client->run('CREATE (n:Person) SET n += {infos}', ['infos' => ['name' => 'Ales', 'age' => 34]]); 108 | ``` 109 | 110 | #### Reading a Result 111 | 112 | ```php 113 | $result = $client->run('MATCH (n:Person) RETURN n'); 114 | // a result always contains a collection (array) of Record objects 115 | 116 | // get all records 117 | $records = $result->getRecords(); 118 | 119 | // get the first or (if expected only one) the only record 120 | 121 | $record = $result->getRecord(); 122 | ``` 123 | 124 | A `Record` object contains the values of one record from your Cypher query : 125 | 126 | ```php 127 | $query = 'MATCH (n:Person)-[:FOLLOWS]->(friend) RETURN n.name, collect(friend) as friends'; 128 | $result = $client->run($query); 129 | 130 | foreach ($result->getRecords() as $record) { 131 | echo sprintf('Person name is : %s and has %d number of friends', $record->value('name'), count($record->value('friends'))); 132 | } 133 | ``` 134 | 135 | ### Cypher statements and Stacks 136 | 137 | Ideally, you would stack your statements and issue them all at once in order to improve performance. 138 | 139 | You can create Cypher statement stacks that act as a Bag and run this stack with the client, example : 140 | 141 | ```php 142 | $stack = $client->stack(); 143 | 144 | $stack->push('CREATE (n:Person {uuid: {uuid} })', ['uuid' => '123-fff']); 145 | $stack->push('MATCH (n:Person {uuid: {uuid1} }), (n2:Person {uuid: {uuid2} }) MERGE (n)-[:FOLLOWS]->(n2)', ['uuid1' => '123-fff', 'uuid2' => '456-ddd']); 146 | 147 | $results = $client->runStack($stack); 148 | ``` 149 | 150 | ### Tagging your Cypher statements 151 | 152 | Sometimes, you may want to retrieve a specific result from a Stack, an easy way to do this is to tag your Cypher statements. 153 | 154 | The tag is passed via the 3rd argument of the `run` or `push` methods : 155 | 156 | ```php 157 | $stack = $client->stack(); 158 | 159 | $stack->push('CREATE (n:Person {uuid: {uuid} })', ['uuid' => '123-fff'], 'user_create'); 160 | $stack->push('MATCH (n:Person {uuid: {uuid1} }), (n2:Person {uuid: {uuid2} }) MERGE (n)-[r:FOLLOWS]->(n2) RETURN id(r) as relId', ['uuid1' => '123-fff', 'uuid2' => '456-ddd'], 'user_follows'); 161 | 162 | $results = $client->runStack($stack); 163 | 164 | $followResult = $results->get('user_follows'); 165 | $followRelationshipId = $followResult->getRecord()->value('relId'); 166 | ``` 167 | 168 | ### Working with Result sets 169 | 170 | 171 | #### Basics 172 | 173 | The `run` method returns you a single `Result` object. Other methods where you can expect multiple results returns a `ResultCollection` object which is Traversable. 174 | 175 | The `Result` object contains the `records` and the `summary` of the statement, the following methods are available in the API : 176 | 177 | ```php 178 | 179 | $result->firstRecord(); // Returns the first record of the Statement Result 180 | 181 | $result->records(); // Returns all records 182 | 183 | $result->summarize(); // Returns the ResultSummary 184 | ``` 185 | 186 | #### Summary 187 | 188 | The `ResultSummary` contains the `Statement`, the Statistics and the QueryPlan if available : 189 | 190 | ```php 191 | $summary = $result->summarize(); 192 | 193 | $query = $summary->statement()->text(); 194 | 195 | $stats = $summary->updateStatistics(); 196 | 197 | $nodesUpdated = $stats->nodesUpdated(); 198 | $propertiesSet = $stats->propertiesSet(); 199 | 200 | // Does the statement affected the graph ? 201 | $affected = $stats->containsUpdates(); 202 | ``` 203 | 204 | #### Record Values 205 | 206 | Each record contains one row of values returned by the Cypher query : 207 | 208 | ``` 209 | 210 | $query = 'MATCH (n:Person) n, n.name as name, n.age as age'; 211 | $result = $client->run($query); 212 | 213 | foreach ($result->records() as $record) { 214 | print_r($record->get('n')); // nodes returned are automatically hydrated to Node objects 215 | 216 | echo $record->value('name') . PHP_EOL; 217 | echo $record->value('age') . PHP_EOL; 218 | } 219 | ``` 220 | 221 | The client takes care of the hydration of Graph objects to PHP Objects, so it is for Node, Relationship and Path : 222 | 223 | ##### Node 224 | 225 | * `labels()` : returns an array of labels (string) 226 | * `identity()` : returns the internal ID of the node 227 | * `values()` : returns the properties of the node (array) 228 | * `value($key)` : returns the value for the given property key 229 | * `hasValue($key)` : returns whether or not the nodes has a property with the given key 230 | * `keys()` : returns you an array representing the keys of the node properties 231 | * `hasLabel($label)` : returns whether or not the node has the given label (boolean) 232 | 233 | 234 | ##### Relationship 235 | 236 | * `type()` : returns the relationship type 237 | * `identity()` : returns the internal ID of the relationship 238 | * `values()` : returns the properties of the relationship (array) 239 | * `value($key)` : returns the value for the given property key 240 | * `hasValue($key)` : returns whether or not the relationship has a property with the given key 241 | * `keys()` : returns you an array representing the keys of the relationship properties 242 | * `startNodeIdentity` : returns the start node id 243 | * `endNodeIdentity` : returns the end node id 244 | 245 | ##### Path 246 | 247 | * `start()` : returns the start node of the path 248 | * `end()` : returns the end node of the path 249 | * `length()` : returns the length of the path 250 | * `nodes()` : returns all the nodes in the path 251 | * `relationships` : returns all the relationships in the path 252 | 253 | #### Handling Results (from v3 to v4) 254 | 255 | 256 | There are 3 main concepts around this topic : 257 | 258 | 1. a Result 259 | 2. a Record 260 | 3. a RecordValue 261 | 262 | Let's take a look at a query we do in the browser containing multiple possibilities of types : 263 | 264 | ``` 265 | MATCH (n:Address) 266 | RETURN n.address as addr, n, collect(id(n)) as ids 267 | LIMIT 5 268 | ``` 269 | 270 | ![screen shot 2016-05-11 at 20 54 34bis](https://cloud.githubusercontent.com/assets/1222009/15192806/1a39cb30-17bb-11e6-8687-ed861411af2d.png) 271 | 272 | 273 | ##### Result 274 | 275 | A `Result` is a collection of `Record` objects, every **row** you see in the browser is a `Record` and contains `Record Value`s. 276 | 277 | * In blue the Result 278 | * In orange a Record 279 | * In green a RecordValue 280 | 281 | ##### Record 282 | 283 | In contrary to the previous versions of the client, there is no more automatic merging of all the records into one big record, so you will need to iterate all the records from the `Result` : 284 | 285 | ```php 286 | $query = 'MATCH (n:Address) 287 | RETURN n.address as addr, n, collect(id(n)) as ids 288 | LIMIT 5'; 289 | $result = $client->run($query); 290 | 291 | foreach ($result->records() as $record) { 292 | // here we do what we want with one record (one row in the browser result) 293 | print_r($record); 294 | } 295 | ``` 296 | 297 | ##### Record Value 298 | 299 | Every record contains a collection of `Record Value`s, which are identified by a `key`, the key is the identifier you give in the `RETURN` clause of the Cypher query. In our example, a `Record` will contain the following keys : 300 | 301 | * addr 302 | * n 303 | * ids 304 | 305 | In order to access the value, you make use of the `get()` method on the `Record` object : 306 | 307 | ```php 308 | $address = $record->get('addr'); 309 | ``` 310 | 311 | The type of the value is depending of what you return from Neo4j, in our case the following values will be returned : 312 | 313 | * a `string` for the `addr` value 314 | * a `Node` for the `n` value 315 | * an `array` of `integers` for the `ids` value 316 | 317 | Meaning that : 318 | 319 | ```php 320 | $record->get('addr'); // returns a string 321 | $record->get('n'); // returns a Node object 322 | $record->get('ids'); // returns an array 323 | ``` 324 | 325 | `Node`, `Relationship` and `Path` objects have then further methods, so if you know that the node returned by the identifier `n` has a `countries` property on it, you can access it like this : 326 | 327 | ```php 328 | $addressNode = $record->get('n'); 329 | $countries = $addressNode->value('countries'); 330 | ``` 331 | 332 | The `Record` object contains three methods for IDE friendlyness, namely : 333 | 334 | ```php 335 | $record->nodeValue('n'); 336 | $record->relationshipValue('r'); 337 | $record->pathValue('p'); 338 | ``` 339 | 340 | This does not offer something extra, just that the docblocks hint the IDE for autocompletion. 341 | 342 | 343 | ##### Extra: ResultCollection 344 | 345 | When you use `Stack` objects for sending multiple statements at once, it will return you a `ResultCollection` object containing a collection of `Result`s. So you need to iterate the results before accessing the records. 346 | 347 | 348 | ### Working with Transactions 349 | 350 | The Client provides a Transaction object that ease how you would work with transactions. 351 | 352 | #### Creating a Transaction 353 | 354 | ```php 355 | $tx = $client->transaction(); 356 | ``` 357 | 358 | At this stage, nothing has been sent to the server yet (the statement BEGIN has not been sent), this permits to stack queries or Stack objects before commiting them. 359 | 360 | #### Stack a query 361 | 362 | ``` 363 | $tx->push('CREATE (n:Person) RETURN id(n)'); 364 | ``` 365 | 366 | Again, until now nothing has been sent. 367 | 368 | #### Run a query in a Transaction 369 | 370 | Sometimes you want to get an immediate result of a statement inside the transaction, this can be done with the `run` method : 371 | 372 | ```php 373 | $result = $tx->run('CREATE (n:Person) SET n.name = {name} RETURN id(n)', ['name' => 'Michal']); 374 | 375 | echo $result->getRecord()->value("id(n)"); 376 | ``` 377 | 378 | If the transaction has not yet begun, the BEGIN of the transaction will be done automatically. 379 | 380 | #### You can also push or run Stacks 381 | 382 | ```php 383 | $stack = $client->stack(); 384 | $stack->push('CREATE (n:Person {uuid: {uuid} })', ['uuid' => '123-fff']); 385 | $stack->push('MATCH (n:Person {uuid: {uuid1} }), (n2:Person {uuid: {uuid2} }) MERGE (n)-[:FOLLOWS]->(n2)', ['uuid1' => '123-fff', 'uuid2' => '456-ddd']); 386 | 387 | $tx->pushStack($stack); 388 | // or 389 | $results = $tx->runStack($stack); 390 | ``` 391 | 392 | ### Commit and Rollback 393 | 394 | if you have queued statements in your transaction (those added with the `push` methods) and you have finish your job, you can commit the transaction and receive 395 | the results : 396 | 397 | ```php 398 | $stack = $client->stack(); 399 | $stack->push('CREATE (n:Person {uuid: {uuid} })', ['uuid' => '123-fff']); 400 | $stack->push('MATCH (n:Person {uuid: {uuid1} }), (n2:Person {uuid: {uuid2} }) MERGE (n)-[:FOLLOWS]->(n2)', ['uuid1' => '123-fff', 'uuid2' => '456-ddd']); 401 | 402 | $tx->pushStack($stack); 403 | $tx->pushQuery('MATCH (n) RETURN count(n)'); 404 | 405 | $results = $tx->commit(); 406 | ``` 407 | 408 | After a commit, you will not be able to `push` or `run` statements in this transaction. 409 | 410 | ### Working with multiple connections 411 | 412 | Generally speaking, you would better use HAProxy for running Neo4j in a cluster environment. However sometimes it makes sense to 413 | have full control to which instance you send your statements. 414 | 415 | Let's assume a environment with 3 neo4j nodes : 416 | 417 | ```php 418 | $client = ClientBuilder::create() 419 | ->addConnection('node1', 'bolt://10.0.0.1') 420 | ->addConnection('node2', 'bolt://10.0.0.2') 421 | ->addConnection('node3', 'bolt://10.0.0.3') 422 | ->setMaster('node1') 423 | ->build(); 424 | ``` 425 | 426 | By default, the `$client->run()` command will send your Cypher statements to the first registered connection in the list. 427 | 428 | You can specify to which connection to send the statement by specifying its alias as 4th argument to the run parameter : 429 | 430 | ```php 431 | $result = $client->run('CREATE (n) RETURN n', null, null, 'node1'); 432 | ``` 433 | 434 | The client is also aware of the manually configured master connection, so sending your writes can be easier with : 435 | 436 | ```php 437 | $client->runWrite('CREATE (n:User {login: 123})'); 438 | ``` 439 | 440 | ### Helper Methods 441 | 442 | ``` 443 | $client->getLabels(); 444 | ``` 445 | 446 | Returns an array of `Label` objects. 447 | 448 | ### Event Dispatching 449 | 450 | 3 types of events are dispatched during the `run` methods : 451 | 452 | * `PreRunEvent` : before the statement or stack is run. 453 | * `PostRunEvent` : after the statement or stack is run. 454 | * `FailureEvent` : in case of failure, you can disable the client to throw an exception with this event. 455 | 456 | ##### Registering listeners 457 | 458 | Example : 459 | 460 | ```php 461 | $client = ClientBuilder::create() 462 | ->addConnection('default', 'bolt://localhost') 463 | ->registerEventListener(Neo4jClientEvents::NEO4J_PRE_RUN, array($listener, 'onPreRun') 464 | ->build(); 465 | ``` 466 | 467 | The event dispatcher is available via the client with the `$client->getEventDispatcher` methods. 468 | 469 | ### Settings 470 | 471 | #### Timeout (deprecated) 472 | 473 | You can configure a global timeout for the connections : 474 | 475 | ```php 476 | $client = ClientBuilder::create() 477 | ->addConnection('default', 'http://localhost:7474') 478 | ->setDefaultTimeout(3) 479 | ->build(); 480 | ``` 481 | 482 | The timeout by default is 5 seconds. 483 | 484 | This feature is deprecated and will be removed in version 5. See Http client settings below. 485 | 486 | ### TLS 487 | 488 | You can enable TLS encryption for the Bolt Protocol by passing a `Configuration` instance when building the connection, here 489 | is a simple example : 490 | 491 | ``` 492 | $config = \GraphAware\Bolt\Configuration::newInstance() 493 | ->withCredentials('bolttest', 'L7n7SfTSj') 494 | ->withTLSMode(\GraphAware\Bolt\Configuration::TLSMODE_REQUIRED); 495 | 496 | $client = ClientBuilder::create() 497 | ->addConnection('default', 'bolt://hodccomjfkgdenl.dbs.gdb.com:24786', config) 498 | ->build(); 499 | ``` 500 | 501 | #### HTTP client settings 502 | 503 | We use HTTPlug to give you full control of the HTTP client. Version 4 of the Neo4jClient comes with Guzzle6 by default 504 | to preserve backward compatibility. Version 5 will give you the option to choose whatever client you want. Read more 505 | about HTTPlug [in their documentation](http://docs.php-http.org/en/latest/httplug/users.html). 506 | 507 | To configure your client you may add it to `Configuration`. Below is an example using `php-http/curl-client`. 508 | 509 | ```php 510 | use Http\Client\Curl\Client; 511 | 512 | $options = [ 513 | CURLOPT_CONNECTTIMEOUT => 3, // The number of seconds to wait while trying to connect. 514 | CURLOPT_SSL_VERIFYPEER => false // Stop cURL from verifying the peer's certificate 515 | ]; 516 | $httpClient = new Client(null, null, $options); 517 | 518 | $config = \GraphAware\Neo4j\Client\HttpDriver\Configuration::create($httpClient); 519 | $client = ClientBuilder::create() 520 | ->addConnection('default', 'http://neo4j:password@localhost:7474', $config) 521 | ->build(); 522 | ``` 523 | 524 | ### License 525 | 526 | The library is released under the MIT License, refer to the LICENSE file bundled with this package. 527 | -------------------------------------------------------------------------------- /build/install-jdk8.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get dependencies (for adding repos) 4 | sudo apt-get install -y python-software-properties 5 | sudo add-apt-repository -y ppa:webupd8team/java 6 | sudo apt-get update 7 | 8 | # install oracle jdk 8 9 | sudo apt-get install -y oracle-java8-installer 10 | sudo update-alternatives --auto java 11 | sudo update-alternatives --auto javac -------------------------------------------------------------------------------- /build/install-neo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export JAVA_HOME=/usr/lib/jvm/java-8-oracle 4 | export JRE_HOME=/usr/lib/jvm/java-8-oracle 5 | 6 | wget http://dist.neo4j.org/neo4j-enterprise-3.3.0-unix.tar.gz > null 7 | mkdir neo 8 | tar xzf neo4j-enterprise-3.3.0-unix.tar.gz -C neo --strip-components=1 > null 9 | sed -i.bak '0,/\(dbms\.security\.auth_enabled=\).*/s/^#//g' ./neo/conf/neo4j.conf 10 | neo/bin/neo4j start > null & -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphaware/neo4j-php-client", 3 | "type": "library", 4 | "description": "Neo4j-PHP-Client is the most advanced PHP Client for Neo4j", 5 | "keywords": [ 6 | "graph", 7 | "database", 8 | "neo4j", 9 | "cluster", 10 | "client", 11 | "bolt", 12 | "http", 13 | "high-availability" 14 | ], 15 | "homepage": "http://graphaware.com", 16 | "license": "MIT", 17 | "authors": [ 18 | { 19 | "name": "Christophe Willemsen", 20 | "email": "christophe@graphaware.com" 21 | } 22 | ], 23 | "require": { 24 | "php": "^5.6 || ^7.0", 25 | "ext-bcmath": "*", 26 | "ext-mbstring": "*", 27 | "graphaware/neo4j-common": "^3.4", 28 | "graphaware/neo4j-bolt": "^1.5", 29 | "symfony/event-dispatcher": "^2.7 || ^3.0 || ^4.0", 30 | "myclabs/php-enum": "^1.4", 31 | "php-http/httplug": "^1.0", 32 | "php-http/message-factory": "^1.0", 33 | "php-http/client-common": "^1.0", 34 | "php-http/discovery": "^1.0", 35 | 36 | "php-http/message": "^1.0", 37 | "php-http/guzzle6-adapter": "^1.0" 38 | }, 39 | "require-dev": { 40 | "friendsofphp/php-cs-fixer": "^2.0", 41 | "phpunit/phpunit": "^4.0", 42 | "symfony/stopwatch": "^3.0" 43 | }, 44 | "autoload": { 45 | "psr-4": { 46 | "GraphAware\\Neo4j\\Client\\": "src/" 47 | } 48 | }, 49 | "autoload-dev": { 50 | "psr-4": { 51 | "GraphAware\\Neo4j\\Client\\Tests\\": "tests/" 52 | } 53 | }, 54 | "minimum-stability": "dev" 55 | } 56 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests 6 | 7 | 8 | 9 | 10 | tests 11 | vendor 12 | bin 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client; 13 | 14 | use GraphAware\Common\Cypher\Statement; 15 | use GraphAware\Common\Result\Record; 16 | use GraphAware\Neo4j\Client\Connection\ConnectionManager; 17 | use GraphAware\Neo4j\Client\Event\FailureEvent; 18 | use GraphAware\Neo4j\Client\Event\PostRunEvent; 19 | use GraphAware\Neo4j\Client\Event\PreRunEvent; 20 | use GraphAware\Neo4j\Client\Exception\Neo4jException; 21 | use GraphAware\Neo4j\Client\Result\ResultCollection; 22 | use GraphAware\Neo4j\Client\Schema\Label; 23 | use GraphAware\Neo4j\Client\Transaction\Transaction; 24 | use Symfony\Component\EventDispatcher\EventDispatcher; 25 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 26 | 27 | class Client implements ClientInterface 28 | { 29 | const NEOCLIENT_VERSION = '4.6.3'; 30 | 31 | /** 32 | * @var ConnectionManager 33 | */ 34 | protected $connectionManager; 35 | 36 | /** 37 | * @var EventDispatcherInterface 38 | */ 39 | protected $eventDispatcher; 40 | 41 | public function __construct(ConnectionManager $connectionManager, EventDispatcherInterface $eventDispatcher = null) 42 | { 43 | $this->connectionManager = $connectionManager; 44 | $this->eventDispatcher = null !== $eventDispatcher ? $eventDispatcher : new EventDispatcher(); 45 | } 46 | 47 | /** 48 | * Run a Cypher statement against the default database or the database specified. 49 | * 50 | * @param $query 51 | * @param null|array $parameters 52 | * @param null|string $tag 53 | * @param null|string $connectionAlias 54 | * 55 | * @throws \GraphAware\Neo4j\Client\Exception\Neo4jExceptionInterface 56 | * 57 | * @return \GraphAware\Common\Result\Result|null 58 | */ 59 | public function run($query, $parameters = null, $tag = null, $connectionAlias = null) 60 | { 61 | $connection = $this->connectionManager->getConnection($connectionAlias); 62 | $params = null !== $parameters ? $parameters : []; 63 | $statement = Statement::create($query, $params, $tag); 64 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_PRE_RUN, new PreRunEvent([$statement])); 65 | 66 | try { 67 | $result = $connection->run($query, $parameters, $tag); 68 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_POST_RUN, new PostRunEvent(ResultCollection::withResult($result))); 69 | } catch (Neo4jException $e) { 70 | $event = new FailureEvent($e); 71 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_ON_FAILURE, $event); 72 | 73 | if ($event->shouldThrowException()) { 74 | throw $e; 75 | } 76 | 77 | return; 78 | } 79 | 80 | return $result; 81 | } 82 | 83 | /** 84 | * @param string $query 85 | * @param null|array $parameters 86 | * @param null|string $tag 87 | * 88 | * @throws Neo4jException 89 | * 90 | * @return \GraphAware\Common\Result\Result 91 | */ 92 | public function runWrite($query, $parameters = null, $tag = null) 93 | { 94 | return $this->connectionManager 95 | ->getMasterConnection() 96 | ->run($query, $parameters, $tag); 97 | } 98 | 99 | /** 100 | * @deprecated since 4.0 - will be removed in 5.0 - use $client->runWrite() instead 101 | * 102 | * @param string $query 103 | * @param null|array $parameters 104 | * @param null|string $tag 105 | * 106 | * @throws Neo4jException 107 | * 108 | * @return \GraphAware\Common\Result\Result 109 | */ 110 | public function sendWriteQuery($query, $parameters = null, $tag = null) 111 | { 112 | return $this->runWrite($query, $parameters, $tag); 113 | } 114 | 115 | /** 116 | * @param string|null $tag 117 | * @param string|null $connectionAlias 118 | * 119 | * @return StackInterface 120 | */ 121 | public function stack($tag = null, $connectionAlias = null) 122 | { 123 | return Stack::create($tag, $connectionAlias); 124 | } 125 | 126 | /** 127 | * @param StackInterface $stack 128 | * 129 | * @throws Neo4jException 130 | * 131 | * @return ResultCollection|null 132 | */ 133 | public function runStack(StackInterface $stack) 134 | { 135 | $connectionAlias = $stack->hasWrites() 136 | ? $this->connectionManager->getMasterConnection()->getAlias() 137 | : $stack->getConnectionAlias(); 138 | $pipeline = $this->pipeline(null, null, $stack->getTag(), $connectionAlias); 139 | 140 | foreach ($stack->statements() as $statement) { 141 | $pipeline->push($statement->text(), $statement->parameters(), $statement->getTag()); 142 | } 143 | 144 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_PRE_RUN, new PreRunEvent($stack->statements())); 145 | 146 | try { 147 | $results = $pipeline->run(); 148 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_POST_RUN, new PostRunEvent($results)); 149 | } catch (Neo4jException $e) { 150 | $event = new FailureEvent($e); 151 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_ON_FAILURE, $event); 152 | 153 | if ($event->shouldThrowException()) { 154 | throw $e; 155 | } 156 | 157 | return null; 158 | } 159 | 160 | return $results; 161 | } 162 | 163 | /** 164 | * @param null|string $connectionAlias 165 | * 166 | * @return Transaction 167 | */ 168 | public function transaction($connectionAlias = null) 169 | { 170 | $connection = $this->connectionManager->getConnection($connectionAlias); 171 | $driverTransaction = $connection->getTransaction(); 172 | 173 | return new Transaction($driverTransaction, $this->eventDispatcher); 174 | } 175 | 176 | /** 177 | * @param null|string $query 178 | * @param null|array $parameters 179 | * @param null|string $tag 180 | * @param null|string $connectionAlias 181 | * 182 | * @return \GraphAware\Common\Driver\PipelineInterface 183 | */ 184 | private function pipeline($query = null, $parameters = null, $tag = null, $connectionAlias = null) 185 | { 186 | $connection = $this->connectionManager->getConnection($connectionAlias); 187 | 188 | return $connection->createPipeline($query, $parameters, $tag); 189 | } 190 | 191 | /** 192 | * @param string|null $conn 193 | * 194 | * @return Label[] 195 | */ 196 | public function getLabels($conn = null) 197 | { 198 | $connection = $this->connectionManager->getConnection($conn); 199 | $result = $connection->getSession()->run('CALL db.labels()'); 200 | 201 | return array_map(function (Record $record) { 202 | return new Label($record->get('label')); 203 | }, $result->records()); 204 | } 205 | 206 | /** 207 | * @deprecated since 4.0 - will be removed in 5.0 - use $client->run() instead 208 | * 209 | * @param string $query 210 | * @param null|array $parameters 211 | * @param null|string $tag 212 | * @param null|string $connectionAlias 213 | * 214 | * @return \GraphAware\Common\Result\Result 215 | */ 216 | public function sendCypherQuery($query, $parameters = null, $tag = null, $connectionAlias = null) 217 | { 218 | return $this->connectionManager 219 | ->getConnection($connectionAlias) 220 | ->run($query, $parameters, $tag); 221 | } 222 | 223 | /** 224 | * @return ConnectionManager 225 | */ 226 | public function getConnectionManager() 227 | { 228 | return $this->connectionManager; 229 | } 230 | 231 | /** 232 | * @return EventDispatcherInterface 233 | */ 234 | public function getEventDispatcher() 235 | { 236 | return $this->eventDispatcher; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/ClientBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client; 13 | 14 | use GraphAware\Common\Connection\BaseConfiguration; 15 | use GraphAware\Common\Driver\ConfigInterface; 16 | use GraphAware\Neo4j\Client\Connection\ConnectionManager; 17 | use GraphAware\Neo4j\Client\HttpDriver\Configuration; 18 | use Symfony\Component\EventDispatcher\EventDispatcher; 19 | 20 | class ClientBuilder 21 | { 22 | const PREFLIGHT_ENV_DEFAULT = 'NEO4J_DB_VERSION'; 23 | 24 | const DEFAULT_TIMEOUT = 5; 25 | 26 | const TIMEOUT_CONFIG_KEY = 'timeout'; 27 | 28 | /** 29 | * @var array 30 | */ 31 | protected $config = []; 32 | 33 | /** 34 | * @param array $config 35 | */ 36 | public function __construct(array $config = []) 37 | { 38 | $this->config['connection_manager']['preflight_env'] = self::PREFLIGHT_ENV_DEFAULT; 39 | $this->config['client_class'] = Client::class; 40 | 41 | if (!empty($config)) { 42 | $this->config = array_merge($this->config, $config); 43 | } 44 | } 45 | 46 | /** 47 | * Creates a new Client factory. 48 | * 49 | * @param array $config 50 | * 51 | * @return ClientBuilder 52 | */ 53 | public static function create($config = []) 54 | { 55 | return new static($config); 56 | } 57 | 58 | /** 59 | * Add a connection to the handled connections. 60 | * 61 | * @param string $alias 62 | * @param string $uri 63 | * @param BaseConfiguration $config 64 | * 65 | * @return ClientBuilder 66 | */ 67 | public function addConnection($alias, $uri, ConfigInterface $config = null) 68 | { 69 | //small hack for drupal 70 | if (substr($uri, 0, 7) === 'bolt://') { 71 | $parts = explode('bolt://', $uri ); 72 | if (count($parts) === 2) { 73 | $splits = explode('@', $parts[1]); 74 | $split = $splits[count($splits)-1]; 75 | if (substr($split, 0, 4) === 'ssl+') { 76 | $up = count($splits) > 1 ? $splits[0] : ''; 77 | $ups = explode(':', $up); 78 | $u = $ups[0]; 79 | $p = $ups[1]; 80 | $uri = 'bolt://'.str_replace('ssl+', '', $split); 81 | $config = \GraphAware\Bolt\Configuration::newInstance() 82 | ->withCredentials($u, $p) 83 | ->withTLSMode(\GraphAware\Bolt\Configuration::TLSMODE_REQUIRED); 84 | } 85 | } 86 | } 87 | 88 | $this->config['connections'][$alias]['uri'] = $uri; 89 | 90 | if (null !== $config) { 91 | if ($this->config['connections'][$alias]['config'] = $config); 92 | } 93 | 94 | return $this; 95 | } 96 | 97 | /** 98 | * @param string $variable 99 | */ 100 | public function preflightEnv($variable) 101 | { 102 | $this->config['connection_manager']['preflight_env'] = $variable; 103 | } 104 | 105 | /** 106 | * @param string $connectionAlias 107 | * 108 | * @return $this 109 | */ 110 | public function setMaster($connectionAlias) 111 | { 112 | if (!isset($this->config['connections']) || !array_key_exists($connectionAlias, $this->config['connections'])) { 113 | throw new \InvalidArgumentException(sprintf('The connection "%s" is not registered', (string) $connectionAlias)); 114 | } 115 | 116 | $this->config['connections'] = array_map(function ($connectionSettings) { 117 | $connectionSettings['is_master'] = false; 118 | 119 | return $connectionSettings; 120 | }, $this->config['connections']); 121 | 122 | $this->config['connections'][$connectionAlias]['is_master'] = true; 123 | 124 | return $this; 125 | } 126 | 127 | /** 128 | * @param int $timeout 129 | * 130 | * @return $this 131 | */ 132 | public function setDefaultTimeout($timeout) 133 | { 134 | $this->config[static::TIMEOUT_CONFIG_KEY] = (int) $timeout; 135 | 136 | return $this; 137 | } 138 | 139 | /** 140 | * @param string $eventName 141 | * @param mixed $callback 142 | * 143 | * @return $this 144 | */ 145 | public function registerEventListener($eventName, $callback) 146 | { 147 | $this->config['event_listeners'][$eventName][] = $callback; 148 | 149 | return $this; 150 | } 151 | 152 | /** 153 | * Builds a Client based on the connections given. 154 | * 155 | * @return ClientInterface 156 | */ 157 | public function build() 158 | { 159 | $connectionManager = new ConnectionManager(); 160 | 161 | foreach ($this->config['connections'] as $alias => $conn) { 162 | $config = 163 | isset($this->config['connections'][$alias]['config']) 164 | ? $this->config['connections'][$alias]['config'] 165 | : Configuration::create() 166 | ->withTimeout($this->getDefaultTimeout()); 167 | $connectionManager->registerConnection( 168 | $alias, 169 | $conn['uri'], 170 | $config 171 | ); 172 | 173 | if (isset($conn['is_master']) && $conn['is_master'] === true) { 174 | $connectionManager->setMaster($alias); 175 | } 176 | } 177 | 178 | $ev = null; 179 | 180 | if (isset($this->config['event_listeners'])) { 181 | $ev = new EventDispatcher(); 182 | 183 | foreach ($this->config['event_listeners'] as $k => $callbacks) { 184 | foreach ($callbacks as $callback) { 185 | $ev->addListener($k, $callback); 186 | } 187 | } 188 | } 189 | 190 | return new $this->config['client_class']($connectionManager, $ev); 191 | } 192 | 193 | /** 194 | * @return int 195 | */ 196 | private function getDefaultTimeout() 197 | { 198 | return array_key_exists(static::TIMEOUT_CONFIG_KEY, $this->config) ? $this->config[static::TIMEOUT_CONFIG_KEY] : self::DEFAULT_TIMEOUT; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/ClientInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client; 13 | 14 | use GraphAware\Common\Result\AbstractRecordCursor; 15 | use GraphAware\Neo4j\Client\Connection\ConnectionManager; 16 | use GraphAware\Neo4j\Client\Exception\Neo4jException; 17 | use GraphAware\Neo4j\Client\Result\ResultCollection; 18 | use GraphAware\Neo4j\Client\Schema\Label; 19 | use GraphAware\Neo4j\Client\Transaction\Transaction; 20 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 21 | 22 | /** 23 | * Interface ClientInterface. 24 | */ 25 | interface ClientInterface 26 | { 27 | /** 28 | * Run a Cypher statement against the default database or the database specified. 29 | * 30 | * @param $query 31 | * @param null|array $parameters 32 | * @param null|string $tag 33 | * @param null|string $connectionAlias 34 | * 35 | * @throws \GraphAware\Neo4j\Client\Exception\Neo4jExceptionInterface 36 | * 37 | * @return \GraphAware\Common\Result\Result 38 | */ 39 | public function run($query, $parameters = null, $tag = null, $connectionAlias = null); 40 | 41 | /** 42 | * @param string $query 43 | * @param null|array $parameters 44 | * @param null|string $tag 45 | * 46 | * @throws Neo4jException 47 | * 48 | * @return AbstractRecordCursor 49 | */ 50 | public function runWrite($query, $parameters = null, $tag = null); 51 | 52 | /** 53 | * @deprecated since 4.0 - will be removed in 5.0 - use $client->runWrite() instead 54 | * 55 | * @param string $query 56 | * @param null|array $parameters 57 | * @param null|string $tag 58 | * 59 | * @throws Neo4jException 60 | * 61 | * @return AbstractRecordCursor 62 | */ 63 | public function sendWriteQuery($query, $parameters = null, $tag = null); 64 | 65 | /** 66 | * @param string|null $tag 67 | * @param string|null $connectionAlias 68 | * 69 | * @return Stack 70 | */ 71 | public function stack($tag = null, $connectionAlias = null); 72 | 73 | /** 74 | * @param StackInterface $stack 75 | * 76 | * @throws Neo4jException 77 | * 78 | * @return ResultCollection|null 79 | */ 80 | public function runStack(StackInterface $stack); 81 | 82 | /** 83 | * @param null|string $connectionAlias 84 | * 85 | * @return Transaction 86 | */ 87 | public function transaction($connectionAlias = null); 88 | 89 | /** 90 | * @param string|null $conn 91 | * 92 | * @return Label[] 93 | */ 94 | public function getLabels($conn = null); 95 | 96 | /** 97 | * @deprecated since 4.0 - will be removed in 5.0 - use $client->run() instead 98 | * 99 | * @param string $query 100 | * @param null|array $parameters 101 | * @param null|string $tag 102 | * @param null|string $connectionAlias 103 | * 104 | * @return AbstractRecordCursor 105 | */ 106 | public function sendCypherQuery($query, $parameters = null, $tag = null, $connectionAlias = null); 107 | 108 | /** 109 | * @return ConnectionManager 110 | */ 111 | public function getConnectionManager(); 112 | 113 | /** 114 | * @return EventDispatcherInterface 115 | */ 116 | public function getEventDispatcher(); 117 | } 118 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client; 13 | 14 | use GraphAware\Neo4j\Client\HttpDriver\Driver; 15 | 16 | class Config 17 | { 18 | protected $defaultHttpPort = Driver::DEFAULT_HTTP_PORT; 19 | 20 | protected $defaultTcpPort = 8687; 21 | 22 | /** 23 | * @return Config 24 | */ 25 | public static function create() 26 | { 27 | return new self(); 28 | } 29 | 30 | /** 31 | * @param int $port 32 | * 33 | * @return $this 34 | */ 35 | public function withDefaultHttpPort($port) 36 | { 37 | $this->defaultHttpPort = (int) $port; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * @param int $port 44 | * 45 | * @return $this 46 | */ 47 | public function withDefaultTcpPort($port) 48 | { 49 | $this->defaultTcpPort = (int) $port; 50 | 51 | return $this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Connection/Connection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Connection; 13 | 14 | use GraphAware\Bolt\Configuration as BoltConfiguration; 15 | use GraphAware\Bolt\Driver as BoltDriver; 16 | use GraphAware\Bolt\Exception\MessageFailureException; 17 | use GraphAware\Bolt\GraphDatabase as BoltGraphDB; 18 | use GraphAware\Common\Connection\BaseConfiguration; 19 | use GraphAware\Common\Cypher\Statement; 20 | use GraphAware\Neo4j\Client\Exception\Neo4jException; 21 | use GraphAware\Neo4j\Client\HttpDriver\GraphDatabase as HttpGraphDB; 22 | use GraphAware\Neo4j\Client\StackInterface; 23 | 24 | class Connection 25 | { 26 | /** 27 | * @var string The Connection Alias 28 | */ 29 | private $alias; 30 | 31 | /** 32 | * @var string 33 | */ 34 | private $uri; 35 | 36 | /** 37 | * @var \GraphAware\Common\Driver\DriverInterface The configured driver 38 | */ 39 | private $driver; 40 | 41 | /** 42 | * @var array 43 | */ 44 | private $config; 45 | 46 | /** 47 | * @var \GraphAware\Common\Driver\SessionInterface 48 | */ 49 | private $session; 50 | 51 | /** 52 | * Connection constructor. 53 | * 54 | * @param string $alias 55 | * @param string $uri 56 | * @param BaseConfiguration|null $config 57 | */ 58 | public function __construct($alias, $uri, $config = null) 59 | { 60 | $this->alias = (string) $alias; 61 | $this->uri = (string) $uri; 62 | $this->config = $config; 63 | 64 | $this->buildDriver(); 65 | } 66 | 67 | /** 68 | * @return string 69 | */ 70 | public function getAlias() 71 | { 72 | return $this->alias; 73 | } 74 | 75 | /** 76 | * @return \GraphAware\Common\Driver\DriverInterface 77 | */ 78 | public function getDriver() 79 | { 80 | return $this->driver; 81 | } 82 | 83 | /** 84 | * @param null $query 85 | * @param array $parameters 86 | * @param null $tag 87 | * 88 | * @return \GraphAware\Common\Driver\PipelineInterface 89 | */ 90 | public function createPipeline($query = null, $parameters = [], $tag = null) 91 | { 92 | $this->checkSession(); 93 | $parameters = is_array($parameters) ? $parameters : []; 94 | 95 | return $this->session->createPipeline($query, $parameters, $tag); 96 | } 97 | 98 | /** 99 | * @param string $statement 100 | * @param array|null $parameters 101 | * @param null|string $tag 102 | * 103 | * @throws Neo4jException 104 | * 105 | * @return \GraphAware\Common\Result\Result 106 | */ 107 | public function run($statement, $parameters = null, $tag = null) 108 | { 109 | $this->checkSession(); 110 | if (empty($statement)) { 111 | throw new \InvalidArgumentException(sprintf('Expected a non-empty Cypher statement, got "%s"', $statement)); 112 | } 113 | $parameters = (array) $parameters; 114 | 115 | try { 116 | $result = $this->session->run($statement, $parameters, $tag); 117 | return $result; 118 | } catch (MessageFailureException $e) { 119 | $exception = new Neo4jException($e->getMessage()); 120 | $exception->setNeo4jStatusCode($e->getStatusCode()); 121 | 122 | throw $exception; 123 | } 124 | } 125 | 126 | /** 127 | * @param array $queue 128 | * 129 | * @return \GraphAware\Common\Result\ResultCollection 130 | */ 131 | public function runMixed(array $queue) 132 | { 133 | $this->checkSession(); 134 | $pipeline = $this->createPipeline(); 135 | 136 | foreach ($queue as $element) { 137 | if ($element instanceof StackInterface) { 138 | foreach ($element->statements() as $statement) { 139 | $pipeline->push($statement->text(), $statement->parameters(), $statement->getTag()); 140 | } 141 | } elseif ($element instanceof Statement) { 142 | $pipeline->push($element->text(), $element->parameters(), $element->getTag()); 143 | } 144 | } 145 | 146 | return $pipeline->run(); 147 | } 148 | 149 | /** 150 | * @return \GraphAware\Common\Transaction\TransactionInterface 151 | */ 152 | public function getTransaction() 153 | { 154 | $this->checkSession(); 155 | 156 | return $this->session->transaction(); 157 | } 158 | 159 | /** 160 | * @return \GraphAware\Common\Driver\SessionInterface 161 | */ 162 | public function getSession() 163 | { 164 | $this->checkSession(); 165 | 166 | return $this->session; 167 | } 168 | 169 | private function buildDriver() 170 | { 171 | $params = parse_url($this->uri); 172 | 173 | if (preg_match('/bolt/', $this->uri)) { 174 | $port = isset($params['port']) ? (int) $params['port'] : BoltDriver::DEFAULT_TCP_PORT; 175 | $uri = sprintf('%s://%s:%d', $params['scheme'], $params['host'], $port); 176 | $config = null; 177 | if (isset($params['user']) && isset($params['pass'])) { 178 | $config = BoltConfiguration::create()->withCredentials($params['user'], $params['pass']); 179 | } 180 | $this->driver = BoltGraphDB::driver($uri, $config); 181 | } elseif (preg_match('/http/', $this->uri)) { 182 | $uri = $this->uri; 183 | $this->driver = HttpGraphDB::driver($uri, $this->config); 184 | } else { 185 | throw new \RuntimeException(sprintf('Unable to build a driver from uri "%s"', $this->uri)); 186 | } 187 | } 188 | 189 | private function checkSession() 190 | { 191 | if (null === $this->session) { 192 | $this->session = $this->driver->session(); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/Connection/ConnectionManager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Connection; 13 | 14 | use GraphAware\Common\Connection\BaseConfiguration; 15 | 16 | class ConnectionManager 17 | { 18 | /** 19 | * @var array Array of all registered connections 20 | */ 21 | private $connections = []; 22 | 23 | /** 24 | * @var Connection|null 25 | */ 26 | private $master; 27 | 28 | /** 29 | * @param string $alias 30 | * @param string $uri 31 | * @param BaseConfiguration|null $config 32 | */ 33 | public function registerConnection($alias, $uri, $config = null) 34 | { 35 | $this->registerExistingConnection($alias, new Connection($alias, $uri, $config)); 36 | } 37 | 38 | /** 39 | * @param string $alias 40 | * @param Connection $connection 41 | */ 42 | public function registerExistingConnection($alias, Connection $connection) 43 | { 44 | $this->connections[$alias] = $connection; 45 | } 46 | 47 | /** 48 | * @param null $alias 49 | * 50 | * @return \GraphAware\Neo4j\Client\Connection\Connection 51 | */ 52 | public function getConnection($alias = null) 53 | { 54 | if (null === $alias) { 55 | list($a) = array_keys($this->connections); 56 | 57 | return $this->connections[$a]; 58 | } 59 | 60 | if (!array_key_exists($alias, $this->connections)) { 61 | throw new \InvalidArgumentException(sprintf('The connection "%s" is not registered', $alias)); 62 | } 63 | 64 | return $this->connections[$alias]; 65 | } 66 | 67 | /** 68 | * @param string $alias 69 | */ 70 | public function setMaster($alias) 71 | { 72 | $this->master = $this->connections[$alias]; 73 | } 74 | 75 | /** 76 | * @return Connection|null 77 | */ 78 | public function getMasterConnection() 79 | { 80 | return $this->master; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Event/FailureEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Event; 13 | 14 | use GraphAware\Neo4j\Client\Exception\Neo4jExceptionInterface; 15 | use Symfony\Component\EventDispatcher\Event; 16 | 17 | class FailureEvent extends Event 18 | { 19 | /** 20 | * @var Neo4jExceptionInterface 21 | */ 22 | protected $exception; 23 | 24 | /** 25 | * @var bool 26 | */ 27 | protected $shouldThrowException = true; 28 | 29 | /** 30 | * @param Neo4jExceptionInterface $exception 31 | */ 32 | public function __construct(Neo4jExceptionInterface $exception) 33 | { 34 | $this->exception = $exception; 35 | } 36 | 37 | /** 38 | * @return Neo4jExceptionInterface 39 | */ 40 | public function getException() 41 | { 42 | return $this->exception; 43 | } 44 | 45 | public function disableException() 46 | { 47 | $this->shouldThrowException = false; 48 | } 49 | 50 | /** 51 | * @return bool 52 | */ 53 | public function shouldThrowException() 54 | { 55 | return $this->shouldThrowException; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Event/PostRunEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Event; 13 | 14 | use GraphAware\Common\Result\ResultCollection; 15 | use Symfony\Component\EventDispatcher\Event; 16 | 17 | class PostRunEvent extends Event 18 | { 19 | /** 20 | * @var ResultCollection 21 | */ 22 | protected $results; 23 | 24 | /** 25 | * @param ResultCollection $results 26 | */ 27 | public function __construct(ResultCollection $results) 28 | { 29 | $this->results = $results; 30 | } 31 | 32 | /** 33 | * @return ResultCollection 34 | */ 35 | public function getResults() 36 | { 37 | return $this->results; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Event/PreRunEvent.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Event; 13 | 14 | use GraphAware\Common\Cypher\StatementInterface; 15 | use Symfony\Component\EventDispatcher\Event; 16 | 17 | class PreRunEvent extends Event 18 | { 19 | /** 20 | * @var StatementInterface[] 21 | */ 22 | private $statements; 23 | 24 | /** 25 | * @param StatementInterface[] $statements 26 | */ 27 | public function __construct(array $statements) 28 | { 29 | $this->statements = $statements; 30 | } 31 | 32 | /** 33 | * @return StatementInterface[] 34 | */ 35 | public function getStatements() 36 | { 37 | return $this->statements; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Exception/Neo4jException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Exception; 13 | 14 | class Neo4jException extends \Exception implements Neo4jExceptionInterface 15 | { 16 | /** 17 | * @var string 18 | */ 19 | protected $statusCode; 20 | 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function effect() 25 | { 26 | $classification = $this->classification(); 27 | 28 | switch ($classification) { 29 | case 'ClientError': 30 | return Neo4jExceptionInterface::EFFECT_ROLLBACK; 31 | case 'ClientNotification': 32 | return Neo4jExceptionInterface::EFFECT_NONE; 33 | case 'DatabaseError': 34 | return Neo4jExceptionInterface::EFFECT_ROLLBACK; 35 | case 'TransientError': 36 | return Neo4jExceptionInterface::EFFECT_ROLLBACK; 37 | default: 38 | throw new \InvalidArgumentException(sprintf('Invalid classification "%s" in "%s"', $classification, $this->getMessage())); 39 | } 40 | } 41 | 42 | /** 43 | * @param string $code 44 | */ 45 | public function setNeo4jStatusCode($code) 46 | { 47 | $this->statusCode = $code; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function classification() 54 | { 55 | $parts = explode('.', $this->statusCode); 56 | if (!isset($parts[1])) { 57 | throw new \InvalidArgumentException(sprintf('Could not parse exception classification "%"', $this->statusCode)); 58 | } 59 | 60 | return $parts[1]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Exception/Neo4jExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Exception; 13 | 14 | interface Neo4jExceptionInterface extends NeoClientExceptionInterface 15 | { 16 | const EFFECT_NONE = 'NONE'; 17 | 18 | const EFFECT_ROLLBACK = 'ROLLBACK'; 19 | 20 | /** 21 | * @return string 22 | */ 23 | public function effect(); 24 | } 25 | -------------------------------------------------------------------------------- /src/Exception/NeoClientExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Exception; 13 | 14 | interface NeoClientExceptionInterface 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /src/Formatter/RecordView.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Formatter; 13 | 14 | use GraphAware\Common\Result\RecordViewInterface; 15 | use GraphAware\Common\Type\Node; 16 | use GraphAware\Common\Type\Path; 17 | use GraphAware\Common\Type\Relationship; 18 | 19 | class RecordView implements RecordViewInterface 20 | { 21 | /** 22 | * @var array 23 | */ 24 | protected $keys = []; 25 | 26 | /** 27 | * @var array 28 | */ 29 | protected $values = []; 30 | 31 | /** 32 | * @var array 33 | */ 34 | private $keyToIndexMap = []; 35 | 36 | /** 37 | * @param array $keys 38 | * @param array $values 39 | */ 40 | public function __construct(array $keys, array $values) 41 | { 42 | $this->keys = $keys; 43 | $this->values = $values; 44 | 45 | foreach ($this->keys as $i => $k) { 46 | $this->keyToIndexMap[$k] = $i; 47 | } 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function keys() 54 | { 55 | return $this->keys; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function hasValues() 62 | { 63 | return !empty($this->values); 64 | } 65 | 66 | /** 67 | * @param string $key 68 | * 69 | * @return \GraphAware\Neo4j\Client\Formatter\Type\Node|\GraphAware\Neo4j\Client\Formatter\Type\Relationship 70 | */ 71 | public function value($key) 72 | { 73 | return $this->values[$this->keyToIndexMap[$key]]; 74 | } 75 | 76 | /** 77 | * Returns the Node for value $key. Ease IDE integration. 78 | * 79 | * @param string $key 80 | * 81 | * @throws \InvalidArgumentException When the value is not null or instance of Node 82 | * 83 | * @return \GraphAware\Neo4j\Client\Formatter\Type\Node 84 | */ 85 | public function nodeValue($key) 86 | { 87 | if (!$this->hasValue($key) || !$this->value($key) instanceof Node) { 88 | throw new \InvalidArgumentException(sprintf('value for %s is not of type %s', $key, Node::class)); 89 | } 90 | 91 | return $this->value($key); 92 | } 93 | 94 | /** 95 | * @param string $key 96 | * 97 | * @throws \InvalidArgumentException When the value is not null or instance of Relationship 98 | * 99 | * @return \GraphAware\Neo4j\Client\Formatter\Type\Relationship 100 | */ 101 | public function relationshipValue($key) 102 | { 103 | if (!$this->hasValue($key) || !$this->value($key) instanceof Relationship) { 104 | throw new \InvalidArgumentException(sprintf('value for %s is not of type %s', $key, Relationship::class)); 105 | } 106 | 107 | return $this->value($key); 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | public function pathValue($key) 114 | { 115 | if (!$this->hasValue($key) || !$this->value($key) instanceof Path) { 116 | throw new \InvalidArgumentException(sprintf('value for %s is not of type %s', $key, Path::class)); 117 | } 118 | 119 | return $this->value($key); 120 | } 121 | 122 | /** 123 | * {@inheritdoc} 124 | */ 125 | public function values() 126 | { 127 | return $this->values; 128 | } 129 | 130 | /** 131 | * {@inheritdoc} 132 | */ 133 | public function hasValue($key) 134 | { 135 | return array_key_exists($key, $this->keyToIndexMap); 136 | } 137 | 138 | /** 139 | * {@inheritdoc} 140 | */ 141 | public function valueByIndex($index) 142 | { 143 | return $this->values[$index]; 144 | } 145 | 146 | /** 147 | * @return RecordView 148 | */ 149 | public function record() 150 | { 151 | return clone $this; 152 | } 153 | 154 | /** 155 | * @param string $key 156 | * @param mixed $defaultValue 157 | * 158 | * @return \GraphAware\Neo4j\Client\Formatter\Type\Node|\GraphAware\Neo4j\Client\Formatter\Type\Relationship|mixed 159 | */ 160 | public function get($key, $defaultValue = null) 161 | { 162 | if (!isset($this->keyToIndexMap[$key]) && 2 === func_num_args()) { 163 | return $defaultValue; 164 | } 165 | 166 | return $this->value($key); 167 | } 168 | 169 | /** 170 | * {@inheritdoc} 171 | */ 172 | public function getByIndex($index) 173 | { 174 | return $this->valueByIndex($index); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Formatter/Response.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Formatter; 13 | 14 | class Response 15 | { 16 | /** 17 | * @var array 18 | */ 19 | private $rawResponse; 20 | 21 | /** 22 | * @var Result[] 23 | */ 24 | private $results; 25 | 26 | /** 27 | * @var array 28 | */ 29 | private $errors = []; 30 | 31 | /** 32 | * @param array $rawResponse 33 | */ 34 | public function setRawResponse($rawResponse) 35 | { 36 | $this->rawResponse = $rawResponse; 37 | 38 | if (isset($rawResponse['errors'])) { 39 | if (!empty($rawResponse['errors'])) { 40 | $this->errors = $rawResponse['errors'][0]; 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function getJsonResponse() 49 | { 50 | return json_encode($this->rawResponse); 51 | } 52 | 53 | /** 54 | * @return array 55 | */ 56 | public function getResponse() 57 | { 58 | return $this->rawResponse; 59 | } 60 | 61 | /** 62 | * @param Result $result 63 | */ 64 | public function addResult(Result $result) 65 | { 66 | $this->results[] = $result; 67 | } 68 | 69 | /** 70 | * @return Result 71 | */ 72 | public function getResult() 73 | { 74 | if (null !== $this->results && !$this->results instanceof Result) { 75 | reset($this->results); 76 | 77 | return $this->results[0]; 78 | } 79 | 80 | return $this->results; 81 | } 82 | 83 | /** 84 | * @return Result[] 85 | */ 86 | public function getResults() 87 | { 88 | return $this->results; 89 | } 90 | 91 | /** 92 | * @param Result $result 93 | */ 94 | public function setResult(Result $result) 95 | { 96 | $this->results = $result; 97 | } 98 | 99 | /** 100 | * @return array 101 | */ 102 | public function getErrors() 103 | { 104 | return $this->errors; 105 | } 106 | 107 | /** 108 | * @return bool 109 | */ 110 | public function hasErrors() 111 | { 112 | return !empty($this->errors); 113 | } 114 | 115 | /** 116 | * @return bool 117 | */ 118 | public function containsResults() 119 | { 120 | return isset($this->rawResponse['results']) && !empty($this->rawResponse['results']); 121 | } 122 | 123 | /** 124 | * @return bool 125 | */ 126 | public function containsRows() 127 | { 128 | return isset($this->rawResponse['results'][0]['columns']) && !empty($this->rawResponse['results']['0']['columns']); 129 | } 130 | 131 | /** 132 | * @return array 133 | */ 134 | public function getBody() 135 | { 136 | return $this->rawResponse; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Formatter/ResponseFormatter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Formatter; 13 | 14 | use GraphAware\Neo4j\Client\Exception\Neo4jException; 15 | use GraphAware\Neo4j\Client\Result\ResultCollection; 16 | 17 | class ResponseFormatter 18 | { 19 | /** 20 | * Formats the Neo4j Response. 21 | * 22 | * @param array $response 23 | * @param \GraphAware\Common\Cypher\Statement[] $statements 24 | * 25 | * @throws Neo4jException 26 | * 27 | * @return ResultCollection 28 | */ 29 | public function format(array $response, array $statements) 30 | { 31 | if (isset($response['errors'][0])) { 32 | $e = new Neo4jException($response['errors'][0]['message']); 33 | $e->setNeo4jStatusCode($response['errors'][0]['code']); 34 | 35 | throw $e; 36 | } 37 | 38 | $results = new ResultCollection(); 39 | 40 | foreach ($response['results'] as $k => $result) { 41 | $resultO = new Result($statements[$k]); 42 | $resultO->setFields($result['columns']); 43 | 44 | foreach ($result['data'] as $data) { 45 | $resultO->pushRecord($data['rest'], $data['graph']); 46 | } 47 | 48 | if (array_key_exists('stats', $result)) { 49 | $resultO->setStats($result['stats']); 50 | } 51 | 52 | $results->add($resultO, $statements[$k]->getTag()); 53 | } 54 | 55 | return $results; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Formatter/Result.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Formatter; 13 | 14 | use GraphAware\Common\Cypher\StatementInterface; 15 | use GraphAware\Common\Result\AbstractRecordCursor; 16 | use GraphAware\Common\Result\Record; 17 | use GraphAware\Neo4j\Client\Formatter\Type\Node; 18 | use GraphAware\Neo4j\Client\Formatter\Type\Path; 19 | use GraphAware\Neo4j\Client\Formatter\Type\Relationship; 20 | use GraphAware\Neo4j\Client\HttpDriver\Result\ResultSummary; 21 | use GraphAware\Neo4j\Client\HttpDriver\Result\StatementStatistics; 22 | 23 | class Result extends AbstractRecordCursor 24 | { 25 | /** 26 | * @var RecordView[] 27 | */ 28 | protected $records = []; 29 | 30 | /** 31 | * @var string[] 32 | */ 33 | protected $fields = []; 34 | 35 | /** 36 | * @var ResultSummary 37 | */ 38 | protected $resultSummary; 39 | 40 | /** 41 | * @var array 42 | */ 43 | private $graph; 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function __construct(StatementInterface $statement) 49 | { 50 | $this->resultSummary = new ResultSummary($statement); 51 | 52 | parent::__construct($statement); 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | public function size() 59 | { 60 | return count($this->records); 61 | } 62 | 63 | /** 64 | * @throws \RuntimeException When there is no record 65 | * 66 | * @return RecordView 67 | */ 68 | public function firstRecord() 69 | { 70 | if (!empty($this->records)) { 71 | return $this->records[0]; 72 | } 73 | 74 | throw new \RuntimeException('There is no records'); 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function firstRecordOrDefault($default) 81 | { 82 | if (0 === $this->size()) { 83 | return $default; 84 | } 85 | 86 | return $this->firstRecord(); 87 | } 88 | 89 | /** 90 | * @param array $fields 91 | */ 92 | public function setFields(array $fields) 93 | { 94 | $this->fields = $fields; 95 | } 96 | 97 | /** 98 | * @param array $graph 99 | */ 100 | public function setGraph(array $graph) 101 | { 102 | $this->graph = $graph; 103 | } 104 | 105 | /** 106 | * @param $data 107 | * @param $graph 108 | */ 109 | public function pushRecord($data, $graph) 110 | { 111 | $mapped = $this->array_map_deep($data, $graph); 112 | $this->records[] = new RecordView($this->fields, $mapped); 113 | } 114 | 115 | /** 116 | * @param array $stats 117 | */ 118 | public function setStats(array $stats) 119 | { 120 | $this->resultSummary->setStatistics(new StatementStatistics($stats)); 121 | } 122 | 123 | /** 124 | * @return RecordView[] 125 | */ 126 | public function getRecords() 127 | { 128 | return $this->records; 129 | } 130 | 131 | /** 132 | * @throws \RuntimeException When there is no records 133 | * 134 | * @return RecordView 135 | */ 136 | public function getRecord() 137 | { 138 | if (!empty($this->records)) { 139 | return $this->records[0]; 140 | } 141 | 142 | throw new \RuntimeException('There is no records'); 143 | } 144 | 145 | /** 146 | * @return bool 147 | */ 148 | public function hasRecord() 149 | { 150 | return !empty($this->records); 151 | } 152 | 153 | /** 154 | * @param array $array 155 | * @param array $graph 156 | * 157 | * @return array 158 | */ 159 | private function array_map_deep(array $array, array $graph) 160 | { 161 | foreach ($array as $k => $v) { 162 | if (!is_array($v)) { 163 | continue; 164 | } 165 | 166 | if (array_key_exists('metadata', $v) && isset($v['metadata']['labels'])) { 167 | $array[$k] = new Node($v['metadata']['id'], $v['metadata']['labels'], $v['data']); 168 | } elseif (array_key_exists('start', $v) && array_key_exists('type', $v)) { 169 | $array[$k] = new Relationship( 170 | $v['metadata']['id'], 171 | $v['type'], 172 | $this->extractIdFromRestUrl($v['start']), 173 | $this->extractIdFromRestUrl($v['end']), 174 | $v['data'] 175 | ); 176 | } elseif (array_key_exists('length', $v) && array_key_exists('relationships', $v) && array_key_exists('nodes', $v)) { 177 | $array[$k] = new Path( 178 | $this->getNodesFromPathMetadata($v, $graph), 179 | $this->getRelationshipsFromPathMetadata($v, $graph) 180 | ); 181 | } else { 182 | $array[$k] = $this->array_map_deep($v, $graph); 183 | } 184 | } 185 | 186 | return $array; 187 | } 188 | 189 | /** 190 | * @param string $url 191 | * 192 | * @return int 193 | */ 194 | private function extractIdFromRestUrl($url) 195 | { 196 | $expl = explode('/', $url); 197 | 198 | return (int) $expl[count($expl) - 1]; 199 | } 200 | 201 | /** 202 | * @param array $metadata 203 | * @param array $graph 204 | * 205 | * @return array 206 | */ 207 | private function getRelationshipsFromPathMetadata(array $metadata, array $graph) 208 | { 209 | $rels = []; 210 | 211 | foreach ($metadata['relationships'] as $relationship) { 212 | $relId = $this->extractIdFromRestUrl($relationship); 213 | 214 | foreach ($graph['relationships'] as $grel) { 215 | $grid = (int) $grel['id']; 216 | if ($grid === $relId) { 217 | $rels[$grid] = new Relationship( 218 | $grel['id'], 219 | $grel['type'], 220 | $grel['startNode'], 221 | $grel['endNode'], 222 | $grel['properties'] 223 | ); 224 | } 225 | } 226 | } 227 | 228 | return array_values($rels); 229 | } 230 | 231 | /** 232 | * @param array $metadata 233 | * @param array $graph 234 | * 235 | * @return array 236 | */ 237 | private function getNodesFromPathMetadata(array $metadata, array $graph) 238 | { 239 | $nodes = []; 240 | 241 | foreach ($metadata['nodes'] as $node) { 242 | $nodeId = $this->extractIdFromRestUrl($node); 243 | 244 | foreach ($graph['nodes'] as $gn) { 245 | $gnid = (int) $gn['id']; 246 | 247 | if ($gnid === $nodeId) { 248 | $nodes[$nodeId] = new Node( 249 | $gn['id'], 250 | $gn['labels'], 251 | $gn['properties'] 252 | ); 253 | } 254 | } 255 | } 256 | 257 | return array_values($nodes); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/Formatter/Type/MapAccess.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Formatter\Type; 13 | 14 | use GraphAware\Common\Type\MapAccessor; 15 | 16 | class MapAccess implements MapAccessor 17 | { 18 | /** 19 | * @var array 20 | */ 21 | protected $properties = []; 22 | 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function value($key, $default = null) 27 | { 28 | if (!array_key_exists($key, $this->properties) && 1 === func_num_args()) { 29 | throw new \InvalidArgumentException(sprintf('this object has no property with key %s', $key)); 30 | } 31 | 32 | return array_key_exists($key, $this->properties) ? $this->properties[$key] : $default; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function hasValue($key) 39 | { 40 | return array_key_exists($key, $this->properties); 41 | } 42 | 43 | /** 44 | * @return array 45 | */ 46 | public function keys() 47 | { 48 | return array_keys($this->properties); 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function get($key) 55 | { 56 | return $this->value($key); 57 | } 58 | 59 | /** 60 | * {@inheritdoc} 61 | */ 62 | public function containsKey($key) 63 | { 64 | return array_key_exists($key, $this->properties); 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | */ 70 | public function values() 71 | { 72 | return $this->properties; 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function asArray() 79 | { 80 | return $this->properties; 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | public function __get($name) 87 | { 88 | return $this->get($name); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Formatter/Type/Node.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Formatter\Type; 13 | 14 | use GraphAware\Common\Type\Node as NodeInterface; 15 | 16 | class Node extends MapAccess implements NodeInterface 17 | { 18 | /** 19 | * @var int 20 | */ 21 | protected $id; 22 | 23 | /** 24 | * @var array 25 | */ 26 | protected $labels = []; 27 | 28 | /** 29 | * @param int $id 30 | * @param array $labels 31 | * @param array $properties 32 | */ 33 | public function __construct($id, array $labels, array $properties) 34 | { 35 | $this->id = $id; 36 | $this->labels = $labels; 37 | $this->properties = $properties; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function identity() 44 | { 45 | return $this->id; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function labels() 52 | { 53 | return $this->labels; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function hasLabel($label) 60 | { 61 | return in_array($label, $this->labels, true); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Formatter/Type/Path.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Formatter\Type; 13 | 14 | use GraphAware\Common\Type\Node as NodeInterface; 15 | use GraphAware\Common\Type\Path as PathInterface; 16 | use GraphAware\Common\Type\Relationship as RelationshipInterface; 17 | 18 | class Path implements PathInterface 19 | { 20 | /** 21 | * @var Node[] 22 | */ 23 | protected $nodes; 24 | 25 | /** 26 | * @var Relationship[] 27 | */ 28 | protected $relationships; 29 | 30 | /** 31 | * @param Node[] $nodes 32 | * @param Relationship[] $relationships 33 | */ 34 | public function __construct(array $nodes, array $relationships) 35 | { 36 | $this->nodes = $nodes; 37 | $this->relationships = $relationships; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function start() 44 | { 45 | return $this->nodes[0]; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function end() 52 | { 53 | return $this->nodes[count($this->nodes) - 1]; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function length() 60 | { 61 | return count($this->relationships); 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function containsNode(NodeInterface $node) 68 | { 69 | foreach ($this->nodes as $n) { 70 | if ($n->identity() === $node->identity()) { 71 | return true; 72 | } 73 | } 74 | 75 | return false; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function containsRelationship(RelationshipInterface $relationship) 82 | { 83 | foreach ($this->relationships as $rel) { 84 | if ($rel->identity() === $relationship->identity()) { 85 | return true; 86 | } 87 | } 88 | 89 | return false; 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | public function nodes() 96 | { 97 | return $this->nodes; 98 | } 99 | 100 | /** 101 | * {@inheritdoc} 102 | */ 103 | public function relationships() 104 | { 105 | return $this->relationships; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Formatter/Type/Relationship.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Formatter\Type; 13 | 14 | use GraphAware\Common\Type\Relationship as RelationshipInterface; 15 | 16 | class Relationship extends MapAccess implements RelationshipInterface 17 | { 18 | /** 19 | * @var int 20 | */ 21 | protected $id; 22 | 23 | /** 24 | * @var string 25 | */ 26 | protected $type; 27 | 28 | /** 29 | * @var int 30 | */ 31 | protected $startNodeIdentity; 32 | 33 | /** 34 | * @var int 35 | */ 36 | protected $endNodeIdentity; 37 | 38 | /** 39 | * @param int $id 40 | * @param string $type 41 | * @param int $startNodeId 42 | * @param int $endNodeId 43 | * @param array $properties 44 | */ 45 | public function __construct($id, $type, $startNodeId, $endNodeId, array $properties = []) 46 | { 47 | $this->id = $id; 48 | $this->type = $type; 49 | $this->startNodeIdentity = $startNodeId; 50 | $this->endNodeIdentity = $endNodeId; 51 | $this->properties = $properties; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function identity() 58 | { 59 | return $this->id; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function type() 66 | { 67 | return $this->type; 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | public function hasType($type) 74 | { 75 | return $type === $this->type; 76 | } 77 | 78 | /** 79 | * @return int 80 | */ 81 | public function startNodeIdentity() 82 | { 83 | return $this->startNodeIdentity; 84 | } 85 | 86 | /** 87 | * @return int 88 | */ 89 | public function endNodeIdentity() 90 | { 91 | return $this->endNodeIdentity; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/HttpDriver/Configuration.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\HttpDriver; 13 | 14 | use GraphAware\Common\Driver\ConfigInterface; 15 | use Http\Client\HttpClient; 16 | use Http\Discovery\HttpClientDiscovery; 17 | use Http\Discovery\MessageFactoryDiscovery; 18 | use Http\Message\RequestFactory; 19 | use GraphAware\Common\Connection\BaseConfiguration; 20 | 21 | /** 22 | * @author Tobias Nyholm 23 | */ 24 | class Configuration extends BaseConfiguration implements ConfigInterface 25 | { 26 | /** 27 | * @var int 28 | * @deprecated Will be removed in 5.0 29 | */ 30 | protected $timeout; 31 | 32 | /** 33 | * @var string 34 | * @deprecated Will be removed in 5.0 35 | */ 36 | protected $curlInterface; 37 | 38 | /** 39 | * @return Configuration 40 | */ 41 | public static function create(HttpClient $httpClient = null, RequestFactory $requestFactory = null) 42 | { 43 | return new self([ 44 | 'http_client' => $httpClient ?: HttpClientDiscovery::find(), 45 | 'request_factory' => $requestFactory ?: MessageFactoryDiscovery::find(), 46 | ]); 47 | } 48 | 49 | /** 50 | * @param HttpClient $httpClient 51 | * 52 | * @return Configuration 53 | */ 54 | public function setHttpClient(HttpClient $httpClient) 55 | { 56 | return $this->setValue('http_client', $httpClient); 57 | } 58 | 59 | /** 60 | * @param RequestFactory $requestFactory 61 | * 62 | * @return Configuration 63 | */ 64 | public function setRequestFactory(RequestFactory $requestFactory) 65 | { 66 | return $this->setValue('request_factory', $requestFactory); 67 | } 68 | 69 | /** 70 | * @param int $timeout 71 | * 72 | * @return Configuration 73 | * @deprecated Will be removed in 5.0. The Timeout option will disappear. 74 | */ 75 | public function withTimeout($timeout) 76 | { 77 | return $this->setValue('timeout', $timeout); 78 | } 79 | 80 | /** 81 | * @param string $interface 82 | * 83 | * @return $this 84 | * @deprecated Will be removed in 5.0. The CurlInterface option will disappear. 85 | */ 86 | public function withCurlInterface($interface) 87 | { 88 | return $this->setValue('curl_interface', $interface); 89 | } 90 | 91 | /** 92 | * @return int 93 | * @deprecated Will be removed in 5.0 94 | */ 95 | public function getTimeout() 96 | { 97 | return $this->getValue('timeout'); 98 | } 99 | 100 | /** 101 | * @return string 102 | * @deprecated Will be removed in 5.0. 103 | */ 104 | public function getCurlInterface() 105 | { 106 | return $this->getValue('curl_interface'); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/HttpDriver/Driver.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\HttpDriver; 13 | 14 | use GraphAware\Common\Connection\BaseConfiguration; 15 | use GraphAware\Common\Driver\ConfigInterface; 16 | use GraphAware\Common\Driver\DriverInterface; 17 | use Http\Adapter\Guzzle6\Client; 18 | 19 | class Driver implements DriverInterface 20 | { 21 | const DEFAULT_HTTP_PORT = 7474; 22 | 23 | /** 24 | * @var string 25 | */ 26 | protected $uri; 27 | 28 | /** 29 | * @var Configuration 30 | */ 31 | protected $config; 32 | 33 | /** 34 | * @param string $uri 35 | * @param BaseConfiguration $config 36 | */ 37 | public function __construct($uri, ConfigInterface $config = null) 38 | { 39 | if (null !== $config && !$config instanceof BaseConfiguration) { 40 | throw new \RuntimeException(sprintf('Second argument to "%s" must be null or "%s"', __CLASS__, BaseConfiguration::class)); 41 | } 42 | 43 | $this->uri = $uri; 44 | $this->config = null !== $config ? $config : Configuration::create(); 45 | } 46 | 47 | /** 48 | * @return Session 49 | */ 50 | public function session() 51 | { 52 | return new Session($this->uri, $this->getHttpClient(), $this->config); 53 | } 54 | 55 | /** 56 | * @return string 57 | */ 58 | public function getUri() 59 | { 60 | return $this->uri; 61 | } 62 | 63 | /** 64 | * 65 | * @return \Http\Client\HttpClient 66 | */ 67 | private function getHttpClient() 68 | { 69 | $options = []; 70 | if ($this->config->hasValue('timeout')) { 71 | $options['timeout'] = $this->config->getValue('timeout'); 72 | } 73 | 74 | if ($this->config->hasValue('curl_interface')) { 75 | $options['curl'][10062] = $this->config->getValue('curl_interface'); 76 | } 77 | 78 | if (empty($options)) { 79 | return $this->config->getValue('http_client'); 80 | } 81 | 82 | // This is to keep BC. Will be removed in 5.0 83 | 84 | $options['curl'][74] = true; 85 | $options['curl'][75] = true; 86 | 87 | return Client::createWithConfig($options); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/HttpDriver/GraphDatabase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\HttpDriver; 13 | 14 | use GraphAware\Common\Connection\BaseConfiguration; 15 | use GraphAware\Common\Driver\ConfigInterface; 16 | use GraphAware\Common\GraphDatabaseInterface; 17 | 18 | class GraphDatabase implements GraphDatabaseInterface 19 | { 20 | /** 21 | * @param string $uri 22 | * @param BaseConfiguration|null $config 23 | * 24 | * @return Driver 25 | */ 26 | public static function driver($uri, ConfigInterface $config = null) 27 | { 28 | return new Driver($uri, $config); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/HttpDriver/Pipeline.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\HttpDriver; 13 | 14 | use GraphAware\Common\Cypher\Statement; 15 | use GraphAware\Common\Driver\PipelineInterface; 16 | 17 | class Pipeline implements PipelineInterface 18 | { 19 | /** 20 | * @var Session 21 | */ 22 | protected $session; 23 | 24 | /** 25 | * @var Statement[] 26 | */ 27 | protected $statements = []; 28 | 29 | /** 30 | * @param Session $session 31 | */ 32 | public function __construct(Session $session) 33 | { 34 | $this->session = $session; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function push($query, array $parameters = [], $tag = null) 41 | { 42 | $this->statements[] = Statement::create($query, $parameters, $tag); 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function run() 49 | { 50 | return $this->session->flush($this); 51 | } 52 | 53 | /** 54 | * @return Statement[] 55 | */ 56 | public function statements() 57 | { 58 | return $this->statements; 59 | } 60 | 61 | /** 62 | * @return int 63 | */ 64 | public function size() 65 | { 66 | return count($this->statements); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/HttpDriver/Result/ResultSummary.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\HttpDriver\Result; 13 | 14 | use GraphAware\Common\Cypher\StatementInterface; 15 | use GraphAware\Common\Result\ResultSummaryInterface; 16 | 17 | class ResultSummary implements ResultSummaryInterface 18 | { 19 | /** 20 | * @var StatementInterface 21 | */ 22 | protected $statement; 23 | 24 | /** 25 | * @var StatementStatistics 26 | */ 27 | protected $updateStatistics; 28 | 29 | protected $notifications; 30 | 31 | protected $type; 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function __construct(StatementInterface $statement) 37 | { 38 | $this->statement = $statement; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function statement() 45 | { 46 | return $this->statement; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function updateStatistics() 53 | { 54 | return $this->updateStatistics; 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function notifications() 61 | { 62 | return $this->notifications; 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function statementType() 69 | { 70 | return $this->type; 71 | } 72 | 73 | /** 74 | * @param StatementStatistics $statistics 75 | */ 76 | public function setStatistics(StatementStatistics $statistics) 77 | { 78 | $this->updateStatistics = $statistics; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/HttpDriver/Result/StatementStatistics.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\HttpDriver\Result; 13 | 14 | use GraphAware\Common\Result\StatementStatisticsInterface; 15 | 16 | class StatementStatistics implements StatementStatisticsInterface 17 | { 18 | /** 19 | * @var int 20 | */ 21 | protected $nodesCreated = 0; 22 | 23 | /** 24 | * @var int 25 | */ 26 | protected $nodesDeleted = 0; 27 | 28 | /** 29 | * @var int 30 | */ 31 | protected $relationshipsCreated = 0; 32 | 33 | /** 34 | * @var int 35 | */ 36 | protected $relationshipDeleted = 0; 37 | 38 | /** 39 | * @var int 40 | */ 41 | protected $propertiesSet = 0; 42 | 43 | /** 44 | * @var int 45 | */ 46 | protected $labelsAdded = 0; 47 | 48 | /** 49 | * @var int 50 | */ 51 | protected $labelsRemoved = 0; 52 | 53 | /** 54 | * @var int 55 | */ 56 | protected $indexesAdded = 0; 57 | 58 | /** 59 | * @var int 60 | */ 61 | protected $indexesRemoved = 0; 62 | 63 | /** 64 | * @var int 65 | */ 66 | protected $constraintsAdded = 0; 67 | 68 | /** 69 | * @var int 70 | */ 71 | protected $constraintsRemoved = 0; 72 | 73 | /** 74 | * @var bool 75 | */ 76 | protected $containsUpdates = false; 77 | 78 | /** 79 | * @param array $statistics 80 | */ 81 | public function __construct(array $statistics = []) 82 | { 83 | $keys = [ 84 | 'contains_updates', 'nodes_created', 'nodes_deleted', 'properties_set', 'labels_added', 'labels_removed', 85 | 'indexes_added', 'indexes_removed', 'constraints_added', 'constraints_removed', 'relationship_deleted', 86 | 'relationships_created', 87 | ]; 88 | 89 | foreach ($statistics as $key => $value) { 90 | if (!in_array($key, $keys, true)) { 91 | throw new \InvalidArgumentException(sprintf('Key %s is invalid in statement statistics', $key)); 92 | } 93 | $k = $this->toCamelCase($key); 94 | $this->$k = $value; 95 | } 96 | } 97 | 98 | /** 99 | * @return bool 100 | */ 101 | public function containsUpdates() 102 | { 103 | return (bool) $this->containsUpdates; 104 | } 105 | 106 | /** 107 | * @return int 108 | */ 109 | public function nodesCreated() 110 | { 111 | return $this->nodesCreated; 112 | } 113 | 114 | /** 115 | * @return int 116 | */ 117 | public function nodesDeleted() 118 | { 119 | return $this->nodesDeleted; 120 | } 121 | 122 | /** 123 | * @return int 124 | */ 125 | public function relationshipsCreated() 126 | { 127 | return $this->relationshipsCreated; 128 | } 129 | 130 | /** 131 | * @return int 132 | */ 133 | public function relationshipsDeleted() 134 | { 135 | return $this->relationshipDeleted; 136 | } 137 | 138 | /** 139 | * @return int 140 | */ 141 | public function propertiesSet() 142 | { 143 | return $this->propertiesSet; 144 | } 145 | 146 | /** 147 | * @return int 148 | */ 149 | public function labelsAdded() 150 | { 151 | return $this->labelsAdded; 152 | } 153 | 154 | /** 155 | * @return int 156 | */ 157 | public function labelsRemoved() 158 | { 159 | return $this->labelsRemoved; 160 | } 161 | 162 | /** 163 | * @return int 164 | */ 165 | public function indexesAdded() 166 | { 167 | return $this->indexesAdded; 168 | } 169 | 170 | /** 171 | * @return int 172 | */ 173 | public function indexesRemoved() 174 | { 175 | return $this->labelsRemoved; 176 | } 177 | 178 | /** 179 | * @return int 180 | */ 181 | public function constraintsAdded() 182 | { 183 | return $this->constraintsAdded; 184 | } 185 | 186 | /** 187 | * @return int 188 | */ 189 | public function constraintsRemoved() 190 | { 191 | return $this->constraintsRemoved; 192 | } 193 | 194 | /** 195 | * @param $key 196 | * 197 | * @return string 198 | */ 199 | private function toCamelCase($key) 200 | { 201 | list($start, $end) = explode('_', $key); 202 | $str = strtolower($start).ucfirst($end); 203 | 204 | return $str; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/HttpDriver/Session.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\HttpDriver; 13 | 14 | use GraphAware\Common\Connection\BaseConfiguration; 15 | use GraphAware\Common\Driver\ConfigInterface; 16 | use GraphAware\Common\Driver\SessionInterface; 17 | use GraphAware\Common\Transaction\TransactionInterface; 18 | use GraphAware\Neo4j\Client\Exception\Neo4jException; 19 | use GraphAware\Neo4j\Client\Formatter\ResponseFormatter; 20 | use GuzzleHttp\Client as GuzzleClient; 21 | use Http\Adapter\Guzzle6\Client; 22 | use Http\Client\Common\Plugin\ErrorPlugin; 23 | use Http\Client\Common\PluginClient; 24 | use Http\Client\Exception\HttpException; 25 | use Http\Client\HttpClient; 26 | use Http\Message\RequestFactory; 27 | use Psr\Http\Message\RequestInterface; 28 | 29 | class Session implements SessionInterface 30 | { 31 | /** 32 | * @var string 33 | */ 34 | protected $uri; 35 | 36 | /** 37 | * @var HttpClient 38 | */ 39 | protected $httpClient; 40 | 41 | /** 42 | * @var ResponseFormatter 43 | */ 44 | protected $responseFormatter; 45 | 46 | /** 47 | * @var TransactionInterface|null 48 | */ 49 | public $transaction; 50 | 51 | /** 52 | * @var Configuration 53 | */ 54 | protected $config; 55 | 56 | /** 57 | * @var RequestFactory 58 | */ 59 | private $requestFactory; 60 | 61 | /** 62 | * @param string $uri 63 | * @param GuzzleClient|HttpClient $httpClient 64 | * @param ConfigInterface $config 65 | */ 66 | public function __construct($uri, $httpClient, ConfigInterface $config) 67 | { 68 | if ($httpClient instanceof GuzzleClient) { 69 | @trigger_error('Passing a Guzzle client to Session is deprecrated. Will be removed in 5.0. Use a HTTPlug client'); 70 | $httpClient = new Client($httpClient); 71 | } elseif (!$httpClient instanceof HttpClient) { 72 | throw new \RuntimeException('Second argument to Session::__construct must be an instance of Http\Client\HttpClient.'); 73 | } 74 | 75 | if (null !== $config && !$config instanceof BaseConfiguration) { 76 | throw new \RuntimeException(sprintf('Third argument to "%s" must be null or "%s"', __CLASS__, BaseConfiguration::class)); 77 | } 78 | 79 | $this->uri = $uri; 80 | $this->httpClient = new PluginClient($httpClient, [new ErrorPlugin()]); 81 | $this->responseFormatter = new ResponseFormatter(); 82 | $this->config = $config; 83 | $this->requestFactory = $config->getValue('request_factory'); 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function run($statement, array $parameters = [], $tag = null) 90 | { 91 | $parameters = is_array($parameters) ? $parameters : []; 92 | $pipeline = $this->createPipeline($statement, $parameters, $tag); 93 | $response = $pipeline->run(); 94 | 95 | return $response->results()[0]; 96 | } 97 | 98 | /** 99 | * {@inheritdoc} 100 | */ 101 | public function close() 102 | { 103 | } 104 | 105 | /** 106 | * @return Transaction 107 | */ 108 | public function transaction() 109 | { 110 | if ($this->transaction instanceof Transaction) { 111 | throw new \RuntimeException('A transaction is already bound to this session'); 112 | } 113 | 114 | return new Transaction($this); 115 | } 116 | 117 | /** 118 | * @param string|null $query 119 | * @param array $parameters 120 | * @param string|null $tag 121 | * 122 | * @return Pipeline 123 | */ 124 | public function createPipeline($query = null, array $parameters = [], $tag = null) 125 | { 126 | $pipeline = new Pipeline($this); 127 | 128 | if (null !== $query) { 129 | $pipeline->push($query, $parameters, $tag); 130 | } 131 | 132 | return $pipeline; 133 | } 134 | 135 | /** 136 | * @param Pipeline $pipeline 137 | * 138 | * @throws \GraphAware\Neo4j\Client\Exception\Neo4jException 139 | * 140 | * @return \GraphAware\Common\Result\ResultCollection 141 | */ 142 | public function flush(Pipeline $pipeline) 143 | { 144 | $request = $this->prepareRequest($pipeline); 145 | try { 146 | $response = $this->httpClient->sendRequest($request); 147 | $data = json_decode((string) $response->getBody(), true); 148 | if (!empty($data['errors'])) { 149 | $msg = sprintf('Neo4j Exception with code "%s" and message "%s"', $data['errors'][0]['code'], $data['errors'][0]['message']); 150 | $exception = new Neo4jException($msg); 151 | $exception->setNeo4jStatusCode($data['errors'][0]['code']); 152 | 153 | throw $exception; 154 | } 155 | 156 | return $this->responseFormatter->format($data, $pipeline->statements()); 157 | } catch (HttpException $e) { 158 | $body = json_decode($e->getResponse()->getBody(), true); 159 | if (!isset($body['code'])) { 160 | throw $e; 161 | } 162 | $msg = sprintf('Neo4j Exception with code "%s" and message "%s"', $body['errors'][0]['code'], $body['errors'][0]['message']); 163 | $exception = new Neo4jException($msg, 0, $e); 164 | $exception->setNeo4jStatusCode($body['errors'][0]['code']); 165 | 166 | throw $exception; 167 | } 168 | } 169 | 170 | /** 171 | * @param Pipeline $pipeline 172 | * 173 | * @return RequestInterface 174 | */ 175 | public function prepareRequest(Pipeline $pipeline) 176 | { 177 | $statements = []; 178 | foreach ($pipeline->statements() as $statement) { 179 | $st = [ 180 | 'statement' => $statement->text(), 181 | 'resultDataContents' => ['REST', 'GRAPH'], 182 | 'includeStats' => true, 183 | ]; 184 | if (!empty($statement->parameters())) { 185 | $st['parameters'] = $this->formatParams($statement->parameters()); 186 | } 187 | $statements[] = $st; 188 | } 189 | 190 | $body = json_encode([ 191 | 'statements' => $statements, 192 | ]); 193 | $headers = [ 194 | [ 195 | 'X-Stream' => true, 196 | 'Content-Type' => 'application/json', 197 | ], 198 | ]; 199 | 200 | return $this->requestFactory->createRequest('POST', sprintf('%s/db/data/transaction/commit', $this->uri), $headers, $body); 201 | } 202 | 203 | private function formatParams(array $params) 204 | { 205 | foreach ($params as $key => $v) { 206 | if (is_array($v)) { 207 | if (empty($v)) { 208 | $params[$key] = new \stdClass(); 209 | } else { 210 | $params[$key] = $this->formatParams($params[$key]); 211 | } 212 | } 213 | } 214 | 215 | return $params; 216 | } 217 | 218 | /** 219 | * @throws Neo4jException 220 | * 221 | * @return \Psr\Http\Message\ResponseInterface 222 | */ 223 | public function begin() 224 | { 225 | $request = $this->requestFactory->createRequest('POST', sprintf('%s/db/data/transaction', $this->uri)); 226 | 227 | try { 228 | return $this->httpClient->sendRequest($request); 229 | } catch (HttpException $e) { 230 | $body = json_decode($e->getResponse()->getBody(), true); 231 | if (!isset($body['code'])) { 232 | throw $e; 233 | } 234 | $msg = sprintf('Neo4j Exception with code "%s" and message "%s"', $body['errors'][0]['code'], $body['errors'][0]['message']); 235 | $exception = new Neo4jException($msg, 0, $e); 236 | $exception->setNeo4jStatusCode($body['errors'][0]['code']); 237 | 238 | throw $exception; 239 | } 240 | } 241 | 242 | /** 243 | * @param int $transactionId 244 | * @param array $statementsStack 245 | * 246 | * @throws Neo4jException 247 | * 248 | * @return \GraphAware\Common\Result\ResultCollection 249 | */ 250 | public function pushToTransaction($transactionId, array $statementsStack) 251 | { 252 | $statements = []; 253 | foreach ($statementsStack as $statement) { 254 | $st = [ 255 | 'statement' => $statement->text(), 256 | 'resultDataContents' => ['REST', 'GRAPH'], 257 | 'includeStats' => true, 258 | ]; 259 | if (!empty($statement->parameters())) { 260 | $st['parameters'] = $this->formatParams($statement->parameters()); 261 | } 262 | $statements[] = $st; 263 | } 264 | 265 | $headers = [ 266 | 'X-Stream' => true, 267 | 'Content-Type' => 'application/json', 268 | ]; 269 | 270 | $body = json_encode([ 271 | 'statements' => $statements, 272 | ]); 273 | 274 | $request = $this->requestFactory->createRequest('POST', sprintf('%s/db/data/transaction/%d', $this->uri, $transactionId), $headers, $body); 275 | 276 | try { 277 | $response = $this->httpClient->sendRequest($request); 278 | $data = json_decode((string) $response->getBody(), true); 279 | if (!empty($data['errors'])) { 280 | $msg = sprintf('Neo4j Exception with code "%s" and message "%s"', $data['errors'][0]['code'], $data['errors'][0]['message']); 281 | $exception = new Neo4jException($msg); 282 | $exception->setNeo4jStatusCode($data['errors'][0]['code']); 283 | 284 | throw $exception; 285 | } 286 | 287 | return $this->responseFormatter->format($data, $statementsStack); 288 | } catch (HttpException $e) { 289 | $body = json_decode($e->getResponse()->getBody(), true); 290 | if (!isset($body['code'])) { 291 | throw $e; 292 | } 293 | $msg = sprintf('Neo4j Exception with code "%s" and message "%s"', $body['errors'][0]['code'], $body['errors'][0]['message']); 294 | $exception = new Neo4jException($msg, 0, $e); 295 | $exception->setNeo4jStatusCode($body['errors'][0]['code']); 296 | 297 | throw $exception; 298 | } 299 | } 300 | 301 | /** 302 | * @param int $transactionId 303 | * 304 | * @throws Neo4jException 305 | */ 306 | public function commitTransaction($transactionId) 307 | { 308 | $request = $this->requestFactory->createRequest('POST', sprintf('%s/db/data/transaction/%d/commit', $this->uri, $transactionId)); 309 | try { 310 | $response = $this->httpClient->sendRequest($request); 311 | $data = json_decode((string) $response->getBody(), true); 312 | if (!empty($data['errors'])) { 313 | $msg = sprintf('Neo4j Exception with code "%s" and message "%s"', $data['errors'][0]['code'], $data['errors'][0]['message']); 314 | $exception = new Neo4jException($msg); 315 | $exception->setNeo4jStatusCode($data['errors'][0]['code']); 316 | throw $exception; 317 | } 318 | } catch (HttpException $e) { 319 | $body = json_decode($e->getResponse()->getBody(), true); 320 | if (!isset($body['code'])) { 321 | throw $e; 322 | } 323 | $msg = sprintf('Neo4j Exception with code "%s" and message "%s"', $body['errors'][0]['code'], $body['errors'][0]['message']); 324 | $exception = new Neo4jException($msg, 0, $e); 325 | $exception->setNeo4jStatusCode($body['errors'][0]['code']); 326 | 327 | throw $exception; 328 | } 329 | } 330 | 331 | /** 332 | * @param int $transactionId 333 | * 334 | * @throws Neo4jException 335 | */ 336 | public function rollbackTransaction($transactionId) 337 | { 338 | $request = $this->requestFactory->createRequest('DELETE', sprintf('%s/db/data/transaction/%d', $this->uri, $transactionId)); 339 | 340 | try { 341 | $this->httpClient->sendRequest($request); 342 | } catch (HttpException $e) { 343 | $body = json_decode($e->getResponse()->getBody(), true); 344 | if (!isset($body['code'])) { 345 | throw $e; 346 | } 347 | $msg = sprintf('Neo4j Exception with code "%s" and message "%s"', $body['errors'][0]['code'], $body['errors'][0]['message']); 348 | $exception = new Neo4jException($msg, 0, $e); 349 | $exception->setNeo4jStatusCode($body['errors'][0]['code']); 350 | 351 | throw $exception; 352 | } 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /src/HttpDriver/Transaction.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\HttpDriver; 13 | 14 | use GraphAware\Common\Cypher\Statement; 15 | use GraphAware\Common\Transaction\TransactionInterface; 16 | use GraphAware\Neo4j\Client\Exception\Neo4jException; 17 | use GraphAware\Neo4j\Client\Exception\Neo4jExceptionInterface; 18 | 19 | class Transaction implements TransactionInterface 20 | { 21 | const OPENED = 'OPEN'; 22 | 23 | const COMMITED = 'COMMITED'; 24 | 25 | const ROLLED_BACK = 'ROLLED_BACK'; 26 | 27 | protected $state; 28 | 29 | /** 30 | * @var Session 31 | */ 32 | protected $session; 33 | 34 | /** 35 | * @var bool 36 | */ 37 | protected $closed = false; 38 | 39 | /** 40 | * @var int|null 41 | */ 42 | protected $transactionId; 43 | 44 | protected $expires; 45 | 46 | protected $pending = []; 47 | 48 | /** 49 | * @param Session $session 50 | */ 51 | public function __construct(Session $session) 52 | { 53 | $this->session = $session; 54 | $this->session->transaction = $this; 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function isOpen() 61 | { 62 | return $this->state === self::OPENED; 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function isCommited() 69 | { 70 | return $this->state === self::COMMITED; 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function isRolledBack() 77 | { 78 | return $this->state === self::ROLLED_BACK; 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public function rollback() 85 | { 86 | $this->assertNotClosed(); 87 | $this->assertStarted(); 88 | $this->session->rollbackTransaction($this->transactionId); 89 | $this->closed = true; 90 | $this->state = self::ROLLED_BACK; 91 | $this->session->transaction = null; 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function status() 98 | { 99 | return $this->state; 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | public function commit() 106 | { 107 | $this->success(); 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | public function push($query, array $parameters = [], $tag = null) 114 | { 115 | } 116 | 117 | public function getStatus() 118 | { 119 | return $this->state; 120 | } 121 | 122 | /** 123 | * {@inheritdoc} 124 | */ 125 | public function begin() 126 | { 127 | $this->assertNotStarted(); 128 | $response = $this->session->begin(); 129 | $body = json_decode($response->getBody(), true); 130 | $parts = explode('/', $body['commit']); 131 | $this->transactionId = (int) $parts[count($parts) - 2]; 132 | $this->state = self::OPENED; 133 | $this->session->transaction = $this; 134 | } 135 | 136 | /** 137 | * @param Statement $statement 138 | * 139 | * @throws Neo4jException 140 | * 141 | * @return \GraphAware\Common\Result\RecordCursorInterface 142 | */ 143 | public function run(Statement $statement) 144 | { 145 | $this->assertStarted(); 146 | try { 147 | $results = $this->session->pushToTransaction($this->transactionId, [$statement]); 148 | 149 | return $results->results()[0]; 150 | } catch (Neo4jException $e) { 151 | if ($e->effect() === Neo4jException::EFFECT_ROLLBACK) { 152 | $this->closed = true; 153 | $this->state = self::ROLLED_BACK; 154 | } 155 | 156 | throw $e; 157 | } 158 | } 159 | 160 | /** 161 | * @param array $statements 162 | * 163 | * @throws Neo4jException 164 | * 165 | * @return \GraphAware\Common\Result\ResultCollection 166 | */ 167 | public function runMultiple(array $statements) 168 | { 169 | try { 170 | return $this->session->pushToTransaction($this->transactionId, $statements); 171 | } catch (Neo4jException $e) { 172 | if ($e->effect() === Neo4jException::EFFECT_ROLLBACK) { 173 | $this->closed = true; 174 | $this->state = self::ROLLED_BACK; 175 | } 176 | 177 | throw $e; 178 | } 179 | } 180 | 181 | public function success() 182 | { 183 | $this->assertNotClosed(); 184 | $this->assertStarted(); 185 | try { 186 | $this->session->commitTransaction($this->transactionId); 187 | } catch (Neo4jException $e) { 188 | if ($e->effect() === Neo4jExceptionInterface::EFFECT_ROLLBACK) { 189 | $this->state = self::ROLLED_BACK; 190 | } 191 | 192 | throw $e; 193 | } 194 | $this->state = self::COMMITED; 195 | $this->closed = true; 196 | $this->session->transaction = null; 197 | } 198 | 199 | public function getSession() 200 | { 201 | return $this->session; 202 | } 203 | 204 | private function assertStarted() 205 | { 206 | if ($this->state !== self::OPENED) { 207 | throw new \RuntimeException('This transaction has not been started'); 208 | //$this->begin(); 209 | } 210 | } 211 | 212 | private function assertNotStarted() 213 | { 214 | if (null !== $this->state) { 215 | throw new \RuntimeException(sprintf('Can not begin transaction, Transaction State is "%s"', $this->state)); 216 | } 217 | } 218 | 219 | private function assertNotClosed() 220 | { 221 | if (false !== $this->closed) { 222 | throw new \RuntimeException('This Transaction is closed'); 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/Neo4jClientEvents.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client; 13 | 14 | final class Neo4jClientEvents 15 | { 16 | /** 17 | * This event is dispatched before a query or a stack is run. 18 | * An object of type PreRunEvent is given. 19 | */ 20 | const NEO4J_PRE_RUN = 'neo4j.pre_run'; 21 | 22 | /** 23 | * This event is dispatched after a query or stack is run. 24 | * An object of type PostRunEvent is given. 25 | */ 26 | const NEO4J_POST_RUN = 'neo4j.post_run'; 27 | 28 | /** 29 | * This event is dispatched in case of failure during the run. 30 | * An event of type FailureEvent is given. 31 | */ 32 | const NEO4J_ON_FAILURE = 'neo4j.on_failure'; 33 | } 34 | -------------------------------------------------------------------------------- /src/Result/ResultCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Result; 13 | 14 | use GraphAware\Common\Result\RecordCursorInterface; 15 | use GraphAware\Common\Result\ResultCollection as BaseResultCollection; 16 | 17 | class ResultCollection extends BaseResultCollection 18 | { 19 | /** 20 | * @var string|null 21 | */ 22 | protected $tag; 23 | 24 | /** 25 | * @param string $tag 26 | */ 27 | public function setTag($tag) 28 | { 29 | $this->tag = $tag; 30 | } 31 | 32 | /** 33 | * @return null|string 34 | */ 35 | public function getTag() 36 | { 37 | return $this->tag; 38 | } 39 | 40 | /** 41 | * @param RecordCursorInterface $result 42 | * 43 | * @return ResultCollection 44 | */ 45 | public static function withResult(RecordCursorInterface $result) 46 | { 47 | $coll = new self(); 48 | $coll->add($result); 49 | 50 | return $coll; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Schema/Label.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Schema; 13 | 14 | final class Label 15 | { 16 | /** 17 | * @var string 18 | */ 19 | private $name; 20 | 21 | /** 22 | * @param string $name 23 | */ 24 | public function __construct($name) 25 | { 26 | $this->name = $name; 27 | } 28 | 29 | /** 30 | * @return string 31 | */ 32 | public function getName() 33 | { 34 | return $this->name; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Stack.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client; 13 | 14 | use GraphAware\Common\Cypher\Statement; 15 | 16 | class Stack implements StackInterface 17 | { 18 | /** 19 | * @var null|string 20 | */ 21 | protected $tag; 22 | 23 | /** 24 | * @var string 25 | */ 26 | protected $connectionAlias; 27 | 28 | /** 29 | * @var Statement[] 30 | */ 31 | protected $statements = []; 32 | 33 | /** 34 | * @var Statement[] 35 | */ 36 | protected $preflights = []; 37 | 38 | /** 39 | * @var bool 40 | */ 41 | protected $hasWrites = false; 42 | 43 | /** 44 | * @param null $tag 45 | * @param null|string $connectionAlias 46 | */ 47 | public function __construct($tag = null, $connectionAlias = null) 48 | { 49 | $this->tag = null !== $tag ? (string) $tag : null; 50 | $this->connectionAlias = $connectionAlias; 51 | } 52 | 53 | /** 54 | * @param null|string $tag 55 | * @param null|string $connectionAlias 56 | * 57 | * @return StackInterface 58 | */ 59 | public static function create($tag = null, $connectionAlias = null) 60 | { 61 | return new static($tag, $connectionAlias); 62 | } 63 | 64 | /** 65 | * @param string $query 66 | * @param null|array $parameters 67 | * @param null|array $tag 68 | */ 69 | public function push($query, $parameters = null, $tag = null) 70 | { 71 | $params = null !== $parameters ? $parameters : []; 72 | $this->statements[] = Statement::create($query, $params, $tag); 73 | } 74 | 75 | /** 76 | * @param string $query 77 | * @param null|array $parameters 78 | * @param null|array $tag 79 | */ 80 | public function pushWrite($query, $parameters = null, $tag = null) 81 | { 82 | $params = null !== $parameters ? $parameters : []; 83 | $this->statements[] = Statement::create($query, $params, $tag); 84 | $this->hasWrites = true; 85 | } 86 | 87 | /** 88 | * @param $query 89 | * @param array|null $parameters 90 | * @param array|null $tag 91 | */ 92 | public function addPreflight($query, $parameters = null, $tag = null) 93 | { 94 | $params = null !== $parameters ? $parameters : []; 95 | $this->preflights[] = Statement::create($query, $params, $tag); 96 | } 97 | 98 | /** 99 | * @return bool 100 | */ 101 | public function hasPreflights() 102 | { 103 | return !empty($this->preflights); 104 | } 105 | 106 | /** 107 | * @return \GraphAware\Common\Cypher\Statement[] 108 | */ 109 | public function getPreflights() 110 | { 111 | return $this->preflights; 112 | } 113 | 114 | /** 115 | * @return int 116 | */ 117 | public function size() 118 | { 119 | return count($this->statements); 120 | } 121 | 122 | /** 123 | * @return Statement[] 124 | */ 125 | public function statements() 126 | { 127 | return $this->statements; 128 | } 129 | 130 | /** 131 | * @return null|string 132 | */ 133 | public function getTag() 134 | { 135 | return $this->tag; 136 | } 137 | 138 | /** 139 | * @return null|string 140 | */ 141 | public function getConnectionAlias() 142 | { 143 | return $this->connectionAlias; 144 | } 145 | 146 | /** 147 | * @return bool 148 | */ 149 | public function hasWrites() 150 | { 151 | return $this->hasWrites; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/StackInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client; 13 | 14 | use GraphAware\Common\Cypher\Statement; 15 | 16 | /** 17 | * Interface StackInterface. 18 | */ 19 | interface StackInterface 20 | { 21 | /** 22 | * @param null|string $tag 23 | * @param null|string $connectionAlias 24 | * 25 | * @return Stack 26 | */ 27 | public static function create($tag = null, $connectionAlias = null); 28 | 29 | /** 30 | * @param string $query 31 | * @param null|array $parameters 32 | * @param null|array $tag 33 | */ 34 | public function push($query, $parameters = null, $tag = null); 35 | 36 | /** 37 | * @param string $query 38 | * @param null|array $parameters 39 | * @param null|array $tag 40 | */ 41 | public function pushWrite($query, $parameters = null, $tag = null); 42 | 43 | /** 44 | * @param $query 45 | * @param array|null $parameters 46 | * @param array|null $tag 47 | */ 48 | public function addPreflight($query, $parameters = null, $tag = null); 49 | 50 | /** 51 | * @return bool 52 | */ 53 | public function hasPreflights(); 54 | 55 | /** 56 | * @return \GraphAware\Common\Cypher\Statement[] 57 | */ 58 | public function getPreflights(); 59 | 60 | /** 61 | * @return int 62 | */ 63 | public function size(); 64 | 65 | /** 66 | * @return Statement[] 67 | */ 68 | public function statements(); 69 | 70 | /** 71 | * @return null|string 72 | */ 73 | public function getTag(); 74 | 75 | /** 76 | * @return null|string 77 | */ 78 | public function getConnectionAlias(); 79 | 80 | /** 81 | * @return bool 82 | */ 83 | public function hasWrites(); 84 | } 85 | -------------------------------------------------------------------------------- /src/Transaction/Transaction.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Transaction; 13 | 14 | use GraphAware\Bolt\Exception\MessageFailureException; 15 | use GraphAware\Common\Cypher\Statement; 16 | use GraphAware\Common\Result\Result; 17 | use GraphAware\Common\Transaction\TransactionInterface; 18 | use GraphAware\Neo4j\Client\Event\FailureEvent; 19 | use GraphAware\Neo4j\Client\Event\PostRunEvent; 20 | use GraphAware\Neo4j\Client\Event\PreRunEvent; 21 | use GraphAware\Neo4j\Client\Exception\Neo4jException; 22 | use GraphAware\Neo4j\Client\Neo4jClientEvents; 23 | use GraphAware\Neo4j\Client\Result\ResultCollection; 24 | use GraphAware\Neo4j\Client\StackInterface; 25 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 26 | 27 | class Transaction 28 | { 29 | /** 30 | * @var TransactionInterface 31 | */ 32 | private $driverTransaction; 33 | 34 | /** 35 | * @var Statement[] 36 | */ 37 | protected $queue = []; 38 | 39 | /** 40 | * @var EventDispatcherInterface 41 | */ 42 | protected $eventDispatcher; 43 | 44 | /** 45 | * @param TransactionInterface $driverTransaction 46 | * @param EventDispatcherInterface $eventDispatcher 47 | */ 48 | public function __construct(TransactionInterface $driverTransaction, EventDispatcherInterface $eventDispatcher) 49 | { 50 | $this->driverTransaction = $driverTransaction; 51 | $this->eventDispatcher = $eventDispatcher; 52 | } 53 | 54 | /** 55 | * Push a statement to the queue, without actually sending it. 56 | * 57 | * @param string $statement 58 | * @param array $parameters 59 | * @param string|null $tag 60 | */ 61 | public function push($statement, array $parameters = [], $tag = null) 62 | { 63 | $this->queue[] = Statement::create($statement, $parameters, $tag); 64 | } 65 | 66 | /** 67 | * @param string $statement 68 | * @param array $parameters 69 | * @param null|string $tag 70 | * 71 | * @throws Neo4jException 72 | * 73 | * @return \GraphAware\Common\Result\Result|null 74 | */ 75 | public function run($statement, array $parameters = [], $tag = null) 76 | { 77 | if (!$this->driverTransaction->isOpen() && !in_array($this->driverTransaction->status(), ['COMMITED', 'ROLLED_BACK'], true)) { 78 | $this->driverTransaction->begin(); 79 | } 80 | $stmt = Statement::create($statement, $parameters, $tag); 81 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_PRE_RUN, new PreRunEvent([$stmt])); 82 | try { 83 | $result = $this->driverTransaction->run(Statement::create($statement, $parameters, $tag)); 84 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_POST_RUN, new PostRunEvent(ResultCollection::withResult($result))); 85 | } catch (MessageFailureException $e) { 86 | $exception = new Neo4jException($e->getMessage()); 87 | $exception->setNeo4jStatusCode($e->getStatusCode()); 88 | 89 | $event = new FailureEvent($exception); 90 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_ON_FAILURE, $event); 91 | if ($event->shouldThrowException()) { 92 | throw $exception; 93 | } 94 | return null; 95 | } 96 | 97 | return $result; 98 | } 99 | 100 | /** 101 | * Push a statements Stack to the queue, without actually sending it. 102 | * 103 | * @param \GraphAware\Neo4j\Client\StackInterface $stack 104 | */ 105 | public function pushStack(StackInterface $stack) 106 | { 107 | $this->queue[] = $stack; 108 | } 109 | 110 | /** 111 | * @param StackInterface $stack 112 | * 113 | * @throws Neo4jException 114 | * 115 | * @return ResultCollection|Result[]|null 116 | */ 117 | public function runStack(StackInterface $stack) 118 | { 119 | if (!$this->driverTransaction->isOpen() && !in_array($this->driverTransaction->status(), ['COMMITED', 'ROLLED_BACK'], true)) { 120 | $this->driverTransaction->begin(); 121 | } 122 | 123 | $sts = []; 124 | 125 | foreach ($stack->statements() as $statement) { 126 | $sts[] = $statement; 127 | } 128 | 129 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_PRE_RUN, new PreRunEvent($stack->statements())); 130 | try { 131 | $results = $this->driverTransaction->runMultiple($sts); 132 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_POST_RUN, new PostRunEvent($results)); 133 | } catch (MessageFailureException $e) { 134 | $exception = new Neo4jException($e->getMessage()); 135 | $exception->setNeo4jStatusCode($e->getStatusCode()); 136 | 137 | $event = new FailureEvent($exception); 138 | $this->eventDispatcher->dispatch(Neo4jClientEvents::NEO4J_ON_FAILURE, $event); 139 | if ($event->shouldThrowException()) { 140 | throw $exception; 141 | } 142 | return null; 143 | } 144 | 145 | return $results; 146 | } 147 | 148 | public function begin() 149 | { 150 | $this->driverTransaction->begin(); 151 | } 152 | 153 | /** 154 | * @return bool 155 | */ 156 | public function isOpen() 157 | { 158 | return $this->driverTransaction->isOpen(); 159 | } 160 | 161 | /** 162 | * @return bool 163 | */ 164 | public function isCommited() 165 | { 166 | return $this->driverTransaction->isCommited(); 167 | } 168 | 169 | /** 170 | * @return bool 171 | */ 172 | public function isRolledBack() 173 | { 174 | return $this->driverTransaction->isRolledBack(); 175 | } 176 | 177 | /** 178 | * @return string 179 | */ 180 | public function status() 181 | { 182 | return $this->driverTransaction->status(); 183 | } 184 | 185 | /** 186 | * @return mixed 187 | */ 188 | public function commit() 189 | { 190 | if (!$this->driverTransaction->isOpen() && !in_array($this->driverTransaction->status(), ['COMMITED', 'ROLLED_BACK'], true)) { 191 | $this->driverTransaction->begin(); 192 | } 193 | if (!empty($this->queue)) { 194 | $stack = []; 195 | foreach ($this->queue as $element) { 196 | if ($element instanceof StackInterface) { 197 | foreach ($element->statements() as $statement) { 198 | $stack[] = $statement; 199 | } 200 | } else { 201 | $stack[] = $element; 202 | } 203 | } 204 | 205 | $result = $this->driverTransaction->runMultiple($stack); 206 | $this->driverTransaction->commit(); 207 | $this->queue = []; 208 | 209 | return $result; 210 | } 211 | 212 | return $this->driverTransaction->commit(); 213 | } 214 | 215 | public function rollback() 216 | { 217 | return $this->driverTransaction->rollback(); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /tests/Example/ExampleTestCase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Example; 13 | 14 | use GraphAware\Neo4j\Client\ClientBuilder; 15 | 16 | abstract class ExampleTestCase extends \PHPUnit_Framework_TestCase 17 | { 18 | /** 19 | * @var \GraphAware\Neo4j\Client\Client 20 | */ 21 | protected $client; 22 | 23 | public function setUp() 24 | { 25 | $boltUrl = 'bolt://localhost'; 26 | if (isset($_ENV['NEO4J_USER'])) { 27 | $boltUrl = sprintf( 28 | 'bolt://%s:%s@%s', 29 | getenv('NEO4J_USER'), 30 | getenv('NEO4J_PASSWORD'), 31 | getenv('NEO4J_HOST') 32 | ); 33 | } 34 | 35 | $this->client = ClientBuilder::create() 36 | ->addConnection('default', $boltUrl) 37 | ->build(); 38 | } 39 | 40 | public function emptyDB() 41 | { 42 | $this->client->run('MATCH (n) DETACH DELETE n'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Example/ReadmeExampleTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Example; 13 | 14 | class ReadmeExampleTest extends ExampleTestCase 15 | { 16 | public function testReadingAResult() 17 | { 18 | $this->emptyDB(); 19 | $this->client->run('CREATE (n:Person {name: {name} }) 20 | CREATE (n2:Person {name: {friend_name} }) 21 | CREATE (n)-[:FOLLOWS]->(n2)', [ 22 | 'name' => 'Chris', 23 | 'friend_name' => 'Ales', 24 | ]); 25 | 26 | $result = $this->client->run('MATCH (n:Person)-[:FOLLOWS]->(friend) RETURN n.name as name, collect(friend) as friends'); 27 | $this->assertCount(1, $result->records()); 28 | 29 | $record = $result->firstRecord(); 30 | $this->assertEquals('Chris', $record->value('name')); 31 | $this->assertCount(1, $record->value('friends')); 32 | $this->assertEquals('Ales', $record->get('friends')[0]->get('name')); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Integration/BuildWithEventListenersIntegrationTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Integration; 13 | 14 | use GraphAware\Neo4j\Client\ClientBuilder; 15 | use GraphAware\Neo4j\Client\Exception\Neo4jExceptionInterface; 16 | use GraphAware\Neo4j\Client\Neo4jClientEvents; 17 | 18 | /** 19 | * Class BuildWithEventListenersIntegrationTest. 20 | * 21 | * @group listener 22 | */ 23 | class BuildWithEventListenersIntegrationTest extends \PHPUnit_Framework_TestCase 24 | { 25 | /** 26 | * @return string 27 | */ 28 | private function createBoltUrl() 29 | { 30 | $boltUrl = 'bolt://localhost'; 31 | if (isset($_ENV['NEO4J_USER'])) { 32 | $boltUrl = sprintf( 33 | 'bolt://%s:%s@%s', 34 | getenv('NEO4J_USER'), 35 | getenv('NEO4J_PASSWORD'), 36 | getenv('NEO4J_HOST') 37 | ); 38 | } 39 | return $boltUrl; 40 | } 41 | 42 | public function testListenersAreRegistered() 43 | { 44 | $listener = new EventListener(); 45 | $client = ClientBuilder::create() 46 | ->addConnection('default', $this->createBoltUrl()) 47 | ->registerEventListener(Neo4jClientEvents::NEO4J_PRE_RUN, [$listener, 'onPreRun']) 48 | ->registerEventListener(Neo4jClientEvents::NEO4J_POST_RUN, [$listener, 'onPostRun']) 49 | ->registerEventListener(Neo4jClientEvents::NEO4J_ON_FAILURE, [$listener, 'onFailure']) 50 | ->build(); 51 | 52 | $result = $client->run('MATCH (n) RETURN count(n)'); 53 | $this->assertTrue($listener->hookedPreRun); 54 | $this->assertTrue($listener->hookedPostRun); 55 | } 56 | 57 | public function testFailureCanBeDisabled() 58 | { 59 | $listener = new EventListener(); 60 | $client = ClientBuilder::create() 61 | ->addConnection('default', $this->createBoltUrl()) 62 | ->registerEventListener(Neo4jClientEvents::NEO4J_ON_FAILURE, [$listener, 'onFailure']) 63 | ->build(); 64 | 65 | $client->run('MATCH (n)'); 66 | $this->assertInstanceOf(Neo4jExceptionInterface::class, $listener->e); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/Integration/ClientGetExceptionIntegrationTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Integration; 13 | 14 | use GraphAware\Neo4j\Client\ClientBuilder; 15 | use GraphAware\Neo4j\Client\Exception\Neo4jException; 16 | 17 | class ClientGetExceptionIntegrationTest extends \PHPUnit_Framework_TestCase 18 | { 19 | public function testExceptionHandling() 20 | { 21 | $boltUrl = 'bolt://localhost'; 22 | if (isset($_ENV['NEO4J_USER'])) { 23 | $boltUrl = sprintf( 24 | 'bolt://%s:%s@%s', 25 | getenv('NEO4J_USER'), 26 | getenv('NEO4J_PASSWORD'), 27 | getenv('NEO4J_HOST') 28 | ); 29 | } 30 | 31 | $client = ClientBuilder::create() 32 | ->addConnection('default', $boltUrl) 33 | ->build(); 34 | 35 | $this->setExpectedException(Neo4jException::class); 36 | $result = $client->run('CREATE (n:Cool'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Integration/ClientSetupIntegrationTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Integration; 13 | 14 | use GraphAware\Bolt\Driver as BoltDriver; 15 | use GraphAware\Neo4j\Client\Client; 16 | use GraphAware\Neo4j\Client\ClientBuilder; 17 | use GraphAware\Neo4j\Client\Connection\Connection; 18 | use GraphAware\Neo4j\Client\Connection\ConnectionManager; 19 | use GraphAware\Neo4j\Client\HttpDriver\Driver as HttpDriver; 20 | use InvalidArgumentException; 21 | 22 | /** 23 | * Class ClientSetupIntegrationTest. 24 | * 25 | * @group setup 26 | */ 27 | class ClientSetupIntegrationTest extends \PHPUnit_Framework_TestCase 28 | { 29 | public function testClientSetupWithOneConnection() 30 | { 31 | $client = ClientBuilder::create() 32 | ->addConnection('default', 'bolt://localhost') 33 | ->build(); 34 | 35 | $this->assertInstanceOf(Client::class, $client); 36 | } 37 | 38 | public function testHttpDriverIsUsedForConnection() 39 | { 40 | $client = ClientBuilder::create() 41 | ->addConnection('default', 'http://localhost:7474') 42 | ->build(); 43 | 44 | $connection = $client->getConnectionManager()->getConnection('default'); 45 | $this->assertInstanceOf(HttpDriver::class, $connection->getDriver()); 46 | } 47 | 48 | public function testBoltDriverIsUsedForConnection() 49 | { 50 | $client = ClientBuilder::create() 51 | ->addConnection('default', 'bolt://localhost') 52 | ->build(); 53 | 54 | $connection = $client->getConnectionManager()->getConnection('default'); 55 | $this->assertInstanceOf(BoltDriver::class, $connection->getDriver()); 56 | } 57 | 58 | public function testTwoConnectionCanBeUsed() 59 | { 60 | $client = ClientBuilder::create() 61 | ->addConnection('http', 'http://localhost:7474') 62 | ->addConnection('bolt', 'bolt://localhost') 63 | ->build(); 64 | 65 | $this->assertInstanceOf(HttpDriver::class, $client->getConnectionManager()->getConnection('http')->getDriver()); 66 | $this->assertInstanceOf(BoltDriver::class, $client->getConnectionManager()->getConnection('bolt')->getDriver()); 67 | } 68 | 69 | public function testNullIseReturnedForMasterWhenNoMasterIsDefined() 70 | { 71 | $client = ClientBuilder::create() 72 | ->addConnection('default', 'http://localhost:7474') 73 | ->addConnection('conn2', 'http://localhost:7575') 74 | ->addConnection('conn3', 'http://localhost:7676') 75 | ->build(); 76 | 77 | $this->assertNull($client->getConnectionManager()->getMasterConnection()); 78 | } 79 | 80 | public function testICanDefineConnectionAsWriteOrRead() 81 | { 82 | $client = ClientBuilder::create() 83 | ->addConnection('default', 'http://localhost:7474') 84 | ->addConnection('conn2', 'http://localhost:7575') 85 | ->addConnection('conn3', 'http://localhost:7676') 86 | ->setMaster('conn2') 87 | ->build(); 88 | 89 | $this->assertEquals('conn2', $client->getConnectionManager()->getMasterConnection()->getAlias()); 90 | } 91 | 92 | public function testSecondIsMasterCallOverridesPreviousOne() 93 | { 94 | $client = ClientBuilder::create() 95 | ->addConnection('default', 'http://localhost:7474') 96 | ->addConnection('conn2', 'http://localhost:7575') 97 | ->addConnection('conn3', 'http://localhost:7676') 98 | ->setMaster('conn2') 99 | ->setMaster('default') 100 | ->build(); 101 | 102 | $this->assertEquals('default', $client->getConnectionManager()->getMasterConnection()->getAlias()); 103 | } 104 | 105 | public function testExceptionIsThrownWhenMasterAliasDoesntExist() 106 | { 107 | $this->setExpectedException(InvalidArgumentException::class); 108 | $client = ClientBuilder::create() 109 | ->addConnection('default', 'http://localhost:7474') 110 | ->addConnection('conn2', 'http://localhost:7575') 111 | ->addConnection('conn3', 'http://localhost:7676') 112 | ->setMaster('conn5') 113 | ->build(); 114 | } 115 | 116 | public function testSendWriteUseMasterIfAvailable() 117 | { 118 | $httpUri = 'http://localhost:7474'; 119 | if (isset($_ENV['NEO4J_USER'])) { 120 | $httpUri = sprintf( 121 | '%s://%s:%s@%s:%s', 122 | getenv('NEO4J_SCHEMA'), 123 | getenv('NEO4J_USER'), 124 | getenv('NEO4J_PASSWORD'), 125 | getenv('NEO4J_HOST'), 126 | getenv('NEO4J_PORT') 127 | ); 128 | } 129 | 130 | $connectionManager = $this->prophesize(ConnectionManager::class); 131 | $conn = new Connection('default', $httpUri, null, 5); 132 | $connectionManager->getMasterConnection()->willReturn($conn); 133 | $connectionManager->getMasterConnection()->shouldBeCalled(); 134 | 135 | $client = new Client($connectionManager->reveal()); 136 | $client->runWrite('MATCH (n) RETURN count(n)'); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tests/Integration/CombinedStatisticsIntegrationTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Integration; 13 | 14 | use GraphAware\Neo4j\Client\Stack; 15 | 16 | /** 17 | * Class CombinedStatisticsIntegrationTest. 18 | * 19 | * @group combined-stats-it 20 | */ 21 | class CombinedStatisticsIntegrationTest extends IntegrationTestCase 22 | { 23 | public function testContainsUpdatesIsMergedWithHttp() 24 | { 25 | $this->emptyDb(); 26 | $stack = Stack::create(null, 'http'); 27 | $stack->push('CREATE (n:Node)'); 28 | $stack->push('MATCH (n) RETURN n'); 29 | $results = $this->client->runStack($stack); 30 | 31 | $this->assertTrue($results->updateStatistics()->containsUpdates()); 32 | } 33 | 34 | public function testStatsAreMergedWithHttp() 35 | { 36 | $this->emptyDb(); 37 | $stack = Stack::create(null, 'http'); 38 | $stack->push('CREATE (n:Node)'); 39 | $stack->push('CREATE (n:Node)'); 40 | $results = $this->client->runStack($stack); 41 | 42 | $this->assertEquals(2, $results->updateStatistics()->nodesCreated()); 43 | $this->assertEquals(2, $results->updateStatistics()->labelsAdded()); 44 | } 45 | 46 | public function testContainsUpdatesIsMergedWithBolt() 47 | { 48 | $this->emptyDb(); 49 | $stack = Stack::create(null, 'bolt'); 50 | $stack->push('CREATE (n:Node)'); 51 | $stack->push('MATCH (n) RETURN n'); 52 | $results = $this->client->runStack($stack); 53 | 54 | $this->assertTrue($results->updateStatistics()->containsUpdates()); 55 | } 56 | 57 | public function testStatsAreMergedWithBolt() 58 | { 59 | $this->emptyDb(); 60 | $stack = Stack::create(null, 'bolt'); 61 | $stack->push('CREATE (n:Node)'); 62 | $stack->push('CREATE (n:Node)'); 63 | $results = $this->client->runStack($stack); 64 | 65 | $this->assertEquals(2, $results->updateStatistics()->nodesCreated()); 66 | $this->assertEquals(2, $results->updateStatistics()->labelsAdded()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/Integration/CypherIntegrationTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Integration; 13 | 14 | use GraphAware\Bolt\Result\Type\Node as BoltNode; 15 | use GraphAware\Bolt\Result\Type\Relationship as BoltRelationship; 16 | use GraphAware\Common\Type\Node; 17 | use GraphAware\Common\Type\Path; 18 | use GraphAware\Neo4j\Client\Formatter\Type\Node as HttpNode; 19 | use GraphAware\Neo4j\Client\Formatter\Type\Relationship as HttpRelationship; 20 | 21 | class CypherIntegrationTest extends IntegrationTestCase 22 | { 23 | public function setUp() 24 | { 25 | parent::setUp(); 26 | $this->emptyDb(); 27 | } 28 | 29 | public function testNodeIsReturned() 30 | { 31 | $query = 'CREATE (n:Node) RETURN n'; 32 | $record1 = $this->client->run($query, [], null, 'http')->firstRecord(); 33 | $this->assertInstanceOf(Node::class, $record1->get('n')); 34 | $this->assertInstanceOf(HttpNode::class, $record1->get('n')); 35 | $record2 = $this->client->run($query, [], null, 'bolt')->firstRecord(); 36 | $this->assertInstanceOf(Node::class, $record2->get('n')); 37 | $this->assertInstanceOf(BoltNode::class, $record2->get('n')); 38 | } 39 | 40 | public function testRelationshipIsReturned() 41 | { 42 | $query = 'CREATE (a)-[r:RELATES]->(b) RETURN a, r, b'; 43 | $record1 = $this->client->run($query, [], null, 'http')->firstRecord(); 44 | $record2 = $this->client->run($query, [], null, 'bolt')->firstRecord(); 45 | $this->assertInstanceOf(HttpRelationship::class, $record1->get('r')); 46 | $this->assertInstanceOf(BoltRelationship::class, $record2->get('r')); 47 | } 48 | 49 | /** 50 | * @group path 51 | */ 52 | public function testPathIsReturned() 53 | { 54 | $query = 'CREATE p=(a:Cool)-[:RELATES]->(b:NotSoCool) RETURN p'; 55 | $record1 = $this->client->run($query, [], null, 'http')->firstRecord(); 56 | $record2 = $this->client->run($query, [], null, 'bolt')->firstRecord(); 57 | $this->assertInstanceOf(Path::class, $record1->get('p')); 58 | $this->assertInstanceOf(Path::class, $record2->get('p')); 59 | } 60 | 61 | /** 62 | * @expectedException InvalidArgumentException 63 | */ 64 | public function testExceptionIsThrownOnEmptyStatement() { 65 | $query = ''; 66 | $this->client->run($query); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/Integration/EventListener.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Integration; 13 | 14 | use GraphAware\Neo4j\Client\Event\FailureEvent; 15 | use GraphAware\Neo4j\Client\Event\PostRunEvent; 16 | use GraphAware\Neo4j\Client\Event\PreRunEvent; 17 | 18 | class EventListener 19 | { 20 | public $hookedPreRun = false; 21 | 22 | public $hookedPostRun = false; 23 | 24 | public $e; 25 | 26 | public function onPreRun(PreRunEvent $event) 27 | { 28 | if (count($event->getStatements()) > 0) { 29 | $this->hookedPreRun = true; 30 | } 31 | } 32 | 33 | public function onPostRun(PostRunEvent $event) 34 | { 35 | if ($event->getResults()->size() > 0) { 36 | $this->hookedPostRun = true; 37 | } 38 | } 39 | 40 | public function onFailure(FailureEvent $event) 41 | { 42 | $this->e = $event->getException(); 43 | $event->disableException(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Integration/GetLabelsProcedureTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests; 13 | 14 | use GraphAware\Neo4j\Client\Schema\Label; 15 | use GraphAware\Neo4j\Client\Tests\Integration\IntegrationTestCase; 16 | 17 | /** 18 | * Class GetLabelsProcedureTest. 19 | * 20 | * @group procedure 21 | */ 22 | class GetLabelsProcedureTest extends IntegrationTestCase 23 | { 24 | public function testCanGetLabels() 25 | { 26 | $this->emptyDb(); 27 | $this->client->run('CREATE (:Label1), (:Label2), (:Label3)'); 28 | $result = $this->client->getLabels(); 29 | $this->assertCount(3, $result); 30 | foreach ($result as $label) { 31 | $this->assertInstanceOf(Label::class, $label); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Integration/IntegrationTestCase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Integration; 13 | 14 | use GraphAware\Neo4j\Client\ClientBuilder; 15 | 16 | class IntegrationTestCase extends \PHPUnit_Framework_TestCase 17 | { 18 | /** 19 | * @var \GraphAware\Neo4j\Client\Client 20 | */ 21 | protected $client; 22 | 23 | public function setUp() 24 | { 25 | $connections = array_merge($this->getConnections(), $this->getAdditionalConnections()); 26 | 27 | $this->client = ClientBuilder::create() 28 | ->addConnection('http', $connections['http']) 29 | ->addConnection('bolt', $connections['bolt']) 30 | ->build(); 31 | } 32 | 33 | protected function getConnections() 34 | { 35 | $httpUri = 'http://localhost:7474'; 36 | if (isset($_ENV['NEO4J_USER'])) { 37 | $httpUri = sprintf( 38 | '%s://%s:%s@%s:%s', 39 | getenv('NEO4J_SCHEMA'), 40 | getenv('NEO4J_USER'), 41 | getenv('NEO4J_PASSWORD'), 42 | getenv('NEO4J_HOST'), 43 | getenv('NEO4J_PORT') 44 | ); 45 | } 46 | 47 | $boltUrl = 'bolt://localhost'; 48 | if (isset($_ENV['NEO4J_USER'])) { 49 | $boltUrl = sprintf( 50 | 'bolt://%s:%s@%s', 51 | getenv('NEO4J_USER'), 52 | getenv('NEO4J_PASSWORD'), 53 | getenv('NEO4J_HOST') 54 | ); 55 | } 56 | 57 | return [ 58 | 'http' => $httpUri, 59 | 'bolt' => $boltUrl 60 | ]; 61 | } 62 | 63 | protected function getAdditionalConnections() 64 | { 65 | return []; 66 | } 67 | 68 | /** 69 | * Empties the graph database. 70 | * 71 | * @void 72 | */ 73 | public function emptyDb() 74 | { 75 | $this->client->run('MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE r,n', null, null); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/Integration/ResultIntegrationTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Integration; 13 | 14 | use GraphAware\Common\Type\Node; 15 | use GraphAware\Common\Type\Relationship; 16 | use InvalidArgumentException; 17 | 18 | /** 19 | * Class ResultIntegrationTest. 20 | * 21 | * @group result-it 22 | */ 23 | class ResultIntegrationTest extends IntegrationTestCase 24 | { 25 | public function testRecordReturnsNodeValue() 26 | { 27 | $this->emptyDb(); 28 | $result = $this->client->run('CREATE (n) RETURN n'); 29 | $record = $result->firstRecord(); 30 | 31 | $this->assertInstanceOf(Node::class, $record->nodeValue('n')); 32 | } 33 | 34 | public function testRecordRelationshipValue() 35 | { 36 | $this->emptyDb(); 37 | $result = $this->client->run('CREATE (n)-[r:KNOWS]->(x) RETURN n, r'); 38 | $record = $result->firstRecord(); 39 | 40 | $this->assertInstanceOf(Relationship::class, $record->get('r')); 41 | } 42 | 43 | public function testExceptionIsThrownForInvalidNodeValue() 44 | { 45 | $this->emptyDb(); 46 | $result = $this->client->run('CREATE (n) RETURN id(n) as id'); 47 | $record = $result->firstRecord(); 48 | 49 | $this->setExpectedException(InvalidArgumentException::class); 50 | $record->nodeValue('id'); 51 | } 52 | 53 | public function testExceptionIsThrownForInvalidRelationshipValue() 54 | { 55 | $this->emptyDb(); 56 | $result = $this->client->run('CREATE (n)-[r:KNOWS]->(me) RETURN id(r) as r'); 57 | $record = $result->firstRecord(); 58 | 59 | $this->setExpectedException(InvalidArgumentException::class); 60 | $record->relationshipValue('r'); 61 | } 62 | 63 | /** 64 | * @group issue54 65 | */ 66 | public function testExceptionIsThrownWhenTryingToGetRecordOnEmptyCursor() 67 | { 68 | $this->emptyDb(); 69 | $result = $this->client->run('MATCH (n) RETURN n'); 70 | $this->setExpectedException(\RuntimeException::class); 71 | $result->firstRecord(); 72 | } 73 | 74 | /** 75 | * @group issue54 76 | */ 77 | public function testExceptionIsThrownWhenTryingToGetRecordOnEmptyCursorWithGetRecord() 78 | { 79 | $this->emptyDb(); 80 | $result = $this->client->run('MATCH (n) RETURN n'); 81 | $this->setExpectedException(\RuntimeException::class); 82 | $result->getRecord(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/Integration/StatementParametersTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Integration; 13 | 14 | /** 15 | * Class StatementParametersTest. 16 | * 17 | * @group params 18 | */ 19 | class StatementParametersTest extends IntegrationTestCase 20 | { 21 | public function testEmptyArraysCanBeUsedAsNestedParameters() 22 | { 23 | $query = 'CREATE (a), (b) 24 | MERGE (a)-[r:RELATES]->(b) 25 | SET r += {fields} RETURN id(r) as id'; 26 | 27 | $params = ['a' => 30, 'b' => 31, 'fields' => []]; 28 | $result = $this->client->run($query, $params, null, 'http'); 29 | $this->assertTrue(is_numeric($result->firstRecord()->get('id'))); 30 | } 31 | 32 | public function testEmptyArraysInTransaction() 33 | { 34 | $query = 'CREATE (a), (b) 35 | MERGE (a)-[r:RELATES]->(b) 36 | SET r += {fields} RETURN id(r) as id'; 37 | 38 | $params = ['a' => 30, 'b' => 31, 'fields' => []]; 39 | $tx = $this->client->transaction('http'); 40 | $tx->push($query, $params); 41 | $results = $tx->commit(); 42 | 43 | $this->assertTrue(is_numeric($results->results()[0]->firstRecord()->get('id'))); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Integration/StatisticsIntegrationTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Integration; 13 | use GraphAware\Common\Result\StatementStatisticsInterface; 14 | 15 | /** 16 | * Class StatisticsIntegrationTest. 17 | * 18 | * @group stats-it 19 | */ 20 | class StatisticsIntegrationTest extends IntegrationTestCase 21 | { 22 | public function testNodesCreatedWithHttp() 23 | { 24 | $this->emptyDb(); 25 | $result = $this->client->run('CREATE (n)', null, null, 'http'); 26 | $summary = $result->summarize(); 27 | 28 | $this->assertEquals(1, $summary->updateStatistics()->nodesCreated()); 29 | } 30 | 31 | public function testNodesDeletedWithHttp() 32 | { 33 | $this->emptyDb(); 34 | $this->client->run('CREATE (n)'); 35 | $result = $this->client->run('MATCH (n) DETACH DELETE n', null, null, 'http'); 36 | 37 | $this->assertEquals(1, $result->summarize()->updateStatistics()->nodesDeleted()); 38 | } 39 | 40 | public function testRelationshipsCreatedWithHttp() 41 | { 42 | $this->emptyDb(); 43 | $result = $this->client->run('CREATE (n)-[:REL]->(x)', null, null, 'http'); 44 | 45 | $this->assertEquals(1, $result->summarize()->updateStatistics()->relationshipsCreated()); 46 | } 47 | 48 | public function testRelationshipsDeletedWithHttp() 49 | { 50 | $this->emptyDb(); 51 | $this->client->run('CREATE (n)-[:REL]->(x)'); 52 | $result = $this->client->run('MATCH (n) DETACH DELETE n', null, null, 'http'); 53 | 54 | $this->assertEquals(1, $result->summarize()->updateStatistics()->relationshipsDeleted()); 55 | } 56 | 57 | /** 58 | * @group bolt-stats 59 | */ 60 | public function testNodesCreatedWithBolt() 61 | { 62 | $this->emptyDb(); 63 | $result = $this->client->run('MATCH (n) RETURN count(n)', [], null, 'bolt'); 64 | $this->assertInstanceOf(StatementStatisticsInterface::class, $result->summarize()->updateStatistics()); 65 | 66 | $tx = $this->client->transaction('bolt'); 67 | $result = $tx->run('MATCH (n) RETURN count(n)'); 68 | $tx->commit(); 69 | $this->assertInstanceOf(StatementStatisticsInterface::class, $result->summarize()->updateStatistics()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Integration/TransactionIntegrationTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Integration; 13 | 14 | use GraphAware\Bolt\Exception\MessageFailureException; 15 | use GraphAware\Neo4j\Client\Exception\Neo4jException; 16 | use GraphAware\Neo4j\Client\Exception\Neo4jExceptionInterface; 17 | use GraphAware\Neo4j\Client\HttpDriver\Transaction; 18 | 19 | /** 20 | * Class TransactionIntegrationTest. 21 | * 22 | * @group tx-it 23 | */ 24 | class TransactionIntegrationTest extends IntegrationTestCase 25 | { 26 | public function testTransactionIsCommittedWithHttp() 27 | { 28 | $this->emptyDb(); 29 | $tx = $this->client->transaction('http'); 30 | $result = $tx->run('CREATE (n:Test) RETURN id(n) as id'); 31 | $this->assertTrue($result->summarize()->updateStatistics()->containsUpdates()); 32 | $this->assertEquals(Transaction::OPENED, $tx->status()); 33 | $tx->commit(); 34 | $this->assertEquals(Transaction::COMMITED, $tx->status()); 35 | $this->assertXNodesWithLabelExist('Test'); 36 | } 37 | 38 | public function testTransactionIsRolledBackWithHttp() 39 | { 40 | $this->emptyDb(); 41 | $tx = $this->client->transaction('http'); 42 | $result = $tx->run('CREATE (n:Test) RETURN id(n) as id'); 43 | $this->assertTrue($result->summarize()->updateStatistics()->containsUpdates()); 44 | $this->assertEquals(Transaction::OPENED, $tx->status()); 45 | $tx->rollback(); 46 | $this->assertEquals(Transaction::ROLLED_BACK, $tx->status()); 47 | $this->assertXNodesWithLabelExist('Test', 0); 48 | } 49 | 50 | public function testTransactionIsRolledBackInCaseOfException() 51 | { 52 | $this->emptyDb(); 53 | $tx = $this->client->transaction('http'); 54 | try { 55 | $result = $tx->run('CREATE (n:Test) RETURN x'); 56 | $this->assertEquals(1, 2); // If we reached here then there is a bug 57 | } catch (Neo4jException $e) { 58 | $this->assertEquals(Neo4jExceptionInterface::EFFECT_ROLLBACK, $e->effect()); 59 | } 60 | $this->assertEquals(Transaction::ROLLED_BACK, $tx->status()); 61 | $this->assertXNodesWithLabelExist('Test', 0); 62 | } 63 | 64 | public function testPushShouldStackUntilCommit() 65 | { 66 | $this->emptyDb(); 67 | $tx = $this->client->transaction('http'); 68 | $tx->push('CREATE (n:Test)'); 69 | $this->assertNotContains($tx->status(), [Transaction::COMMITED, Transaction::ROLLED_BACK, Transaction::OPENED]); 70 | $this->assertXNodesWithLabelExist('Test', 0); 71 | $tx->push('CREATE (n:Test)'); 72 | $this->assertXNodesWithLabelExist('Test', 0); 73 | $tx->commit(); 74 | $this->assertXNodesWithLabelExist('Test', 2); 75 | $this->assertEquals(Transaction::COMMITED, $tx->status()); 76 | } 77 | 78 | public function testTransactionIsCommittedWithBolt() 79 | { 80 | $this->emptyDb(); 81 | $tx = $this->client->transaction('bolt'); 82 | $result = $tx->run('CREATE (n:Test) RETURN id(n) as id'); 83 | $this->assertTrue($result->summarize()->updateStatistics()->containsUpdates()); 84 | //$this->assertEquals(Transaction::OPENED, $tx->status()); 85 | $tx->commit(); 86 | //$this->assertEquals(Transaction::COMMITED, $tx->status()); 87 | $this->assertXNodesWithLabelExist('Test'); 88 | } 89 | 90 | public function testTransactionIsRolledBackWithBolt() 91 | { 92 | $this->emptyDb(); 93 | $tx = $this->client->transaction('bolt'); 94 | $result = $tx->run('CREATE (n:Test) RETURN id(n) as id'); 95 | $this->assertTrue($result->summarize()->updateStatistics()->containsUpdates()); 96 | //$this->assertEquals(Transaction::OPENED, $tx->status()); 97 | $tx->rollback(); 98 | //$this->assertEquals(Transaction::ROLLED_BACK, $tx->status()); 99 | $this->assertXNodesWithLabelExist('Test', 0); 100 | } 101 | 102 | public function testTransactionIsRolledBackInCaseOfExceptionWithBolt() 103 | { 104 | $this->emptyDb(); 105 | $tx = $this->client->transaction('bolt'); 106 | try { 107 | $result = $tx->run('CREATE (n:Test) RETURN x'); 108 | $this->assertEquals(1, 2); // If we reached here then there is a bug 109 | } catch (Neo4jException $e) { 110 | $this->assertEquals(Neo4jExceptionInterface::EFFECT_ROLLBACK, $e->effect()); 111 | } 112 | //$this->assertEquals(Transaction::ROLLED_BACK, $tx->status()); 113 | $this->assertXNodesWithLabelExist('Test', 0); 114 | } 115 | 116 | public function testPushShouldStackUntilCommitWithBolt() 117 | { 118 | $this->emptyDb(); 119 | $tx = $this->client->transaction('bolt'); 120 | $tx->push('CREATE (n:Test)'); 121 | $this->assertNotContains($tx->status(), [Transaction::COMMITED, Transaction::ROLLED_BACK, Transaction::OPENED]); 122 | $this->assertXNodesWithLabelExist('Test', 0); 123 | $tx->push('CREATE (n:Test)'); 124 | $this->assertXNodesWithLabelExist('Test', 0); 125 | $tx->commit(); 126 | $this->assertXNodesWithLabelExist('Test', 2); 127 | //$this->assertEquals(Transaction::COMMITED, $tx->status()); 128 | } 129 | 130 | /** 131 | * @group tx-bug 132 | */ 133 | public function testPushAndCommitInTxWithBolt() 134 | { 135 | $this->emptyDb(); 136 | $tx = $this->client->transaction('bolt'); 137 | $tx->push('MATCH (n) RETURN count(n)'); 138 | $tx->push('MATCH (n) RETURN count(n)'); 139 | $results = $tx->commit(); 140 | $this->assertEquals(2, $results->size()); 141 | } 142 | 143 | private function assertXNodesWithLabelExist($label, $number = 1) 144 | { 145 | $query = 'MATCH (n:'.$label.') RETURN count(n) as c'; 146 | $result = $this->client->run($query, null, null, 'http'); 147 | 148 | $this->assertNotNull($result->firstRecord()); 149 | $this->assertEquals($number, $result->firstRecord()->get('c')); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /tests/Issues/DrupalIssueTest.php: -------------------------------------------------------------------------------- 1 | addConnection('default', 'bolt://neo4j:sfadfewfn;kewvljnfd@ssl+graphene.com', null); 16 | } 17 | 18 | private function addConnection($alias, $uri, $config) 19 | { 20 | if (substr($uri, 0, 7) === 'bolt://') { 21 | $parts = explode('bolt://', $uri ); 22 | if (count($parts) === 2) { 23 | $splits = explode('@', $parts[1]); 24 | $split = $splits[count($splits)-1]; 25 | if (substr($split, 0, 4) === 'ssl+') { 26 | $up = count($splits) > 1 ? $splits[0] : ''; 27 | $ups = explode(':', $up); 28 | $u = $ups[0]; 29 | $p = $ups[1]; 30 | $uri = 'bolt://'.str_replace('ssl+', '', $split); 31 | $config = \GraphAware\Bolt\Configuration::newInstance() 32 | ->withCredentials($u, $p) 33 | ->withTLSMode(\GraphAware\Bolt\Configuration::TLSMODE_REQUIRED); 34 | } 35 | } 36 | } 37 | 38 | var_dump($uri); 39 | var_dump($config); 40 | } 41 | } -------------------------------------------------------------------------------- /tests/Issues/Issue105Test.php: -------------------------------------------------------------------------------- 1 | emptyDb(); 16 | $stack = $this->client->stack(); 17 | $stack->push('CREATE (n:Node {id:1})'); 18 | $stack->push('MATCH (n:Node {id: 1}) CREATE (n2:Node {id: 2}) MERGE (n)-[r:RELATES]->(n2) RETURN id(r)'); 19 | $results = $this->client->runStack($stack); 20 | 21 | $this->assertCount(2, $results); 22 | $relId = $results->results()[1]->firstRecord()->get('id(r)'); 23 | $this->assertNotNull($relId); 24 | } 25 | 26 | public function testMatchNodeFromDbInAStack() 27 | { 28 | $this->emptyDb(); 29 | // Create Region node 30 | $this->client->run('CREATE (n:Region {name: "Picardie"})'); 31 | 32 | // Create stack, create department in first push, match department and region and relates them in second push 33 | $stack = $this->client->stack(); 34 | $stack->push('CREATE (d:Department {name:"Somme"})'); 35 | $stack->push('MATCH (d:Department {name:"Somme"}), (r:Region {name:"Picardie"}) MERGE (d)-[:IN_REGION]->(r)'); 36 | $this->client->runStack($stack); 37 | 38 | // Assert that the relationship is in the graph after stack execution 39 | 40 | $result = $this->client->run('MATCH (n:Department {name:"Somme"})-[r:IN_REGION]->(re:Region {name:"Picardie"}) RETURN n, r, re'); 41 | $this->assertEquals(1, $result->size()); 42 | $this->assertEquals('Picardie', $result->firstRecord()->nodeValue('re')->value('name')); 43 | } 44 | } -------------------------------------------------------------------------------- /tests/Issues/Issue143Test.php: -------------------------------------------------------------------------------- 1 | getConnections(), $this->getAdditionalConnections()); 19 | 20 | $this->client = ClientBuilder::create() 21 | ->addConnection('a', $connections['non-exist']) 22 | ->addConnection('http', $connections['http']) 23 | ->addConnection('bolt', $connections['bolt']) 24 | ->setMaster('bolt') 25 | ->build(); 26 | } 27 | 28 | protected function getAdditionalConnections() 29 | { 30 | return ['non-exist' => 'bolt://error:7687']; 31 | } 32 | 33 | 34 | public function testStackUsesMasterForWritesWhenOneisSet() 35 | { 36 | for ($x = 0; $x < 1000; $x++) { 37 | $stack = $this->client->stack(); 38 | $stack->pushWrite('CREATE (n)'); 39 | $this->client->runStack($stack); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /tests/Issues/Issue40Test.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Issues; 13 | 14 | use GraphAware\Neo4j\Client\Tests\Integration\IntegrationTestCase; 15 | 16 | /** 17 | * Class Issue40Test. 18 | * 19 | * @group issues 20 | */ 21 | class Issue40Test extends IntegrationTestCase 22 | { 23 | public function testIssue() 24 | { 25 | $this->emptyDb(); 26 | $this->client->run('CREATE (:BRIEF {id: 123})'); 27 | 28 | $query = 'MATCH (s:BRIEF {id:{brief_id}}) 29 | CREATE (n:BRIEFNOTECARD) 30 | SET n += {data} 31 | CREATE (n)-[:CARD_OF {order:0}]->(s) 32 | RETURN n'; 33 | 34 | $parameters = [ 35 | 'brief_id' => 123, 36 | 'data' => [ 37 | 'key_1' => 'test', 38 | 'key_2' => 'other', 39 | ], 40 | ]; 41 | 42 | $this->client->run($query, $parameters); 43 | $this->assertGraphExist('(n:BRIEF {id: 123})<-[:CARD_OF {order:0}]-(:BRIEFNOTECARD)'); 44 | } 45 | 46 | private function assertGraphExist($pattern) 47 | { 48 | $q = sprintf('MATCH %s RETURN *', $pattern); 49 | $result = $this->client->run($q); 50 | 51 | $this->assertTrue(0 !== $result->size()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Issues/ReportedIssuesTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Issues; 13 | 14 | use GraphAware\Neo4j\Client\Exception\Neo4jException; 15 | use GraphAware\Neo4j\Client\Tests\Integration\IntegrationTestCase; 16 | use Symfony\Component\Yaml\Exception\RuntimeException; 17 | 18 | class ReportedIssuesTest extends IntegrationTestCase 19 | { 20 | /** 21 | * @group issue-so-2 22 | */ 23 | public function testTryingToDeleteNodeWithRelsInTransactionShouldFail() 24 | { 25 | $this->emptyDb(); 26 | $this->createNodeWithRels(); 27 | $tx = $this->client->transaction(); 28 | $tx->push('MATCH (n:Node) DELETE n'); 29 | $this->setExpectedException(Neo4jException::class); 30 | $tx->commit(); 31 | } 32 | 33 | /** 34 | * @group issue-so-3 35 | */ 36 | public function testTryingToDeleteNodeWithRelsInTransactionShouldFailAndTxBeRolledBack() 37 | { 38 | $this->emptyDb(); 39 | $this->createNodeWithRels(); 40 | $tx = $this->client->transaction(); 41 | $tx->push('MATCH (n:Node) DELETE n'); 42 | try { 43 | $tx->commit(); 44 | // it should fail 45 | throw new RuntimeException(); 46 | } catch (Neo4jException $e) { 47 | $this->assertTrue($tx->isRolledBack()); 48 | } 49 | } 50 | 51 | private function createNodeWithRels() 52 | { 53 | $this->client->run('CREATE (n:Node)-[:REL]->(:OtherNode)'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Unit/Connection/ConnectionUnitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Unit\Connection; 13 | 14 | use GraphAware\Neo4j\Client\Connection\Connection; 15 | use GraphAware\Neo4j\Client\HttpDriver\Driver as HttpDriver; 16 | 17 | /** 18 | * @group unit 19 | * @group connection 20 | */ 21 | class ConnectionUnitTest extends \PHPUnit_Framework_TestCase 22 | { 23 | public function testConnectionInstantiation() 24 | { 25 | $connection = new Connection('default', 'http://localhost:7474', null, 5); 26 | $this->assertInstanceOf(HttpDriver::class, $connection->getDriver()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Unit/Stub/DummyDriver.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace GraphAware\Neo4j\Client\Tests\Unit\Stub; 13 | 14 | use GraphAware\Common\Driver\DriverInterface; 15 | 16 | class DummyDriver implements DriverInterface 17 | { 18 | protected $uri; 19 | 20 | public function __construct($uri) 21 | { 22 | $this->uri = $uri; 23 | } 24 | 25 | /** 26 | * @return mixed 27 | */ 28 | public function getUri() 29 | { 30 | return $this->uri; 31 | } 32 | 33 | public function session() 34 | { 35 | } 36 | } 37 | --------------------------------------------------------------------------------