├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── behat.yml ├── build ├── bolt ├── install-jdk8.sh └── install-neo.sh ├── composer.json ├── features ├── bootstrap │ ├── BoltTypeSystemContext.php │ ├── ChunkingDechunkingContext.php │ └── ResultMetadataContext.php ├── chunking-dechunking │ └── tck-chunking-dechunking.feature └── result │ └── tck-01-result-metadata.feature ├── phpunit.xml.dist ├── src ├── BoltEvents.php ├── Configuration.php ├── Driver.php ├── Exception │ ├── BoltExceptionInterface.php │ ├── BoltInvalidArgumentException.php │ ├── BoltOutOfBoundsException.php │ ├── HandshakeException.php │ ├── IOException.php │ ├── MessageFailureException.php │ └── SerializationException.php ├── GraphDatabase.php ├── IO │ ├── AbstractIO.php │ ├── IoInterface.php │ ├── Socket.php │ └── StreamSocket.php ├── Misc │ └── Helper.php ├── PackStream │ ├── BytesWalker.php │ ├── Packer.php │ ├── Serializer.php │ ├── StreamChannel.php │ ├── Structure │ │ ├── MessageStructure.php │ │ └── Structure.php │ └── Unpacker.php ├── Protocol │ ├── AbstractSession.php │ ├── ChunkWriter.php │ ├── Constants.php │ ├── Message │ │ ├── AbstractMessage.php │ │ ├── AckFailureMessage.php │ │ ├── DiscardAllMessage.php │ │ ├── FailureMessage.php │ │ ├── InitMessage.php │ │ ├── MessageInterface.php │ │ ├── PullAllMessage.php │ │ ├── RawMessage.php │ │ ├── RecordMessage.php │ │ ├── RunMessage.php │ │ └── SuccessMessage.php │ ├── Pipeline.php │ ├── SessionInterface.php │ ├── SessionRegistry.php │ └── V1 │ │ ├── Response.php │ │ ├── Session.php │ │ ├── Signatures.php │ │ └── Transaction.php ├── Record │ └── RecordView.php └── Result │ ├── Result.php │ ├── ResultSummary.php │ └── Type │ ├── MapAccess.php │ ├── Node.php │ ├── Path.php │ ├── Relationship.php │ └── UnboundRelationship.php └── tests ├── Documentation └── DocumentationTest.php ├── Example └── MovieExampleTest.php ├── Integration ├── EmptyArraysHandlingTest.php ├── ExceptionDispatchTest.php ├── HandshakeIntegrationTest.php ├── IssuesIntegrationTest.php ├── Packing │ ├── PackingFloatsIntegrationTest.php │ ├── PackingGraphStructureIntegrationTest.php │ ├── PackingIntegersIntegrationTest.php │ ├── PackingListIntegrationTest.php │ ├── PackingMapsIntegrationTest.php │ └── PackingTextIntegrationTest.php ├── RealLifeUseCasesITest.php └── TransactionIntegrationTest.php ├── IntegrationTestCase.php ├── Result ├── DummyMA.php └── MapAccessUnitTest.php ├── TCK └── TCK9TypesTest.php └── Unit ├── PackStream └── PackingUnpackingTest.php └── Protocol └── Message └── MessageUnitTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | test.php 3 | composer.lock 4 | phpunit.xml 5 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.6 4 | - 7.0 5 | 6 | before_install: 7 | - sudo apt-get update > /dev/null 8 | # install Oracle JDK8 9 | - sh -c ./build/install-jdk8.sh 10 | # install and launch neo4j 11 | - sh -c ./build/install-neo.sh 12 | - composer install --prefer-source --no-interaction 13 | - composer self-update 14 | 15 | script: 16 | - vendor/bin/phpunit --exclude-group fail 17 | - vendor/bin/behat 18 | 19 | notifications: 20 | email: "christophe@graphaware.com" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 GraphAware Ltd 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 furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | 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. -------------------------------------------------------------------------------- /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 | --- 6 | 7 | ## Neo4j Bolt PHP 8 | 9 | PHP low level Driver for Neo4j's Bolt Remoting Protocol 10 | 11 | [![Build Status](https://travis-ci.org/graphaware/neo4j-bolt-php.svg?branch=master)](https://travis-ci.org/graphaware/neo4j-bolt-php) 12 | 13 | --- 14 | 15 | ### References : 16 | 17 | * PHP Client embedding Bolt along with the http driver (recommended way of using Neo4j in PHP) : https://github.com/graphaware/neo4j-php-client 18 | * Neo4j 3.0 : http://neo4j.com/docs 19 | 20 | ### Requirements: 21 | 22 | * PHP5.6+ 23 | * Neo4j3.0 24 | * PHP Sockets extension available 25 | * `bcmath` extension 26 | * `mbstring` extension 27 | 28 | ### Installation 29 | 30 | Require the package in your dependencies : 31 | 32 | ```bash 33 | composer require graphaware/neo4j-bolt 34 | ``` 35 | 36 | ### Setting up a driver and creating a session 37 | 38 | ```php 39 | 40 | use GraphAware\Bolt\GraphDatabase; 41 | 42 | $driver = GraphDatabase::driver("bolt://localhost"); 43 | $session = $driver->session(); 44 | ``` 45 | 46 | ### Sending a Cypher statement 47 | 48 | ```php 49 | $session = $driver->session(); 50 | $session->run("CREATE (n)"); 51 | $session->close(); 52 | 53 | // with parameters : 54 | 55 | $session->run("CREATE (n) SET n += {props}", ['name' => 'Mike', 'age' => 27]); 56 | ``` 57 | 58 | ### Empty Arrays 59 | 60 | Due to lack of Collections types in php, there is no way to distinguish when an empty array 61 | should be treated as equivalent Java List or Map types. 62 | 63 | Therefore you can use a wrapper around arrays for type safety : 64 | 65 | ```php 66 | use GraphAware\Common\Collections; 67 | 68 | $query = 'MERGE (n:User {id: {id} }) 69 | WITH n 70 | UNWIND {friends} AS friend 71 | MERGE (f:User {id: friend.name}) 72 | MERGE (f)-[:KNOWS]->(n)'; 73 | 74 | $params = ['id' => 'me', 'friends' => Collections::asList([])]; 75 | $this->getSession()->run($query, $params); 76 | 77 | // Or 78 | 79 | $query = 'MERGE (n:User {id: {id} }) 80 | WITH n 81 | UNWIND {friends}.users AS friend 82 | MERGE (f:User {id: friend.name}) 83 | MERGE (f)-[:KNOWS]->(n)'; 84 | 85 | $params = ['id' => 'me', 'friends' => Collections::asMap([])]; 86 | $this->getSession()->run($query, $params); 87 | 88 | ``` 89 | 90 | ### TLS Encryption 91 | 92 | In order to enable TLS support, you need to set the configuration option to `REQUIRED`, here an example : 93 | 94 | ```php 95 | $config = \GraphAware\Bolt\Configuration::newInstance() 96 | ->withCredentials('bolttest', 'L7n7SfTSj0e6U') 97 | ->withTLSMode(\GraphAware\Bolt\Configuration::TLSMODE_REQUIRED); 98 | 99 | $driver = \GraphAware\Bolt\GraphDatabase::driver('bolt://hobomjfhocgbkeenl.dbs.graphenedb.com:24786', $config); 100 | $session = $driver->session(); 101 | ``` 102 | 103 | ### License 104 | 105 | Copyright (c) 2015-2016 GraphAware Ltd 106 | 107 | Permission is hereby granted, free of charge, to any person obtaining a copy 108 | of this software and associated documentation files (the "Software"), to deal 109 | in the Software without restriction, including without limitation the rights 110 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 111 | copies of the Software, and to permit persons to whom the Software is furnished 112 | to do so, subject to the following conditions: 113 | 114 | The above copyright notice and this permission notice shall be included in all 115 | copies or substantial portions of the Software. 116 | 117 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 118 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 119 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 120 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 121 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 122 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 123 | THE SOFTWARE. 124 | 125 | --- 126 | -------------------------------------------------------------------------------- /behat.yml: -------------------------------------------------------------------------------- 1 | default: 2 | suites: 3 | chunking_features: 4 | paths: [ %paths.base%/features/chunking-dechunking ] 5 | contexts: [ ChunkingDechunkingContext ] 6 | result_features: 7 | paths: [ %paths.base%/features/result ] 8 | contexts: [ ResultMetadataContext ] 9 | bolt_type_features: 10 | paths: [ %paths.base%/features/bolt_type_system ] 11 | contexts: [ BoltTypeSystemContext ] -------------------------------------------------------------------------------- /build/bolt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 3 | /usr/bin/php $DIR/bin/bolt.php bolt:run "$@" -------------------------------------------------------------------------------- /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-bolt", 3 | "description": "Neo4j Bolt Binary Protocol PHP Driver", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Christophe Willemsen", 9 | "email": "christophe@graphaware.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">= 5.6", 14 | "ext-bcmath": "*", 15 | "ext-mbstring": "*", 16 | "symfony/event-dispatcher": "^2.7|^3.0|^4.0", 17 | "graphaware/neo4j-common": "^3.4", 18 | "myclabs/php-enum": "^1.4" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "GraphAware\\Bolt\\": "src/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "GraphAware\\Bolt\\Tests\\": "tests/" 28 | } 29 | }, 30 | "require-dev": { 31 | "phpunit/phpunit": "^4.8", 32 | "symfony/stopwatch": "^2.7", 33 | "behat/behat": "~3.0.4" 34 | }, 35 | "minimum-stability": "dev" 36 | } 37 | -------------------------------------------------------------------------------- /features/bootstrap/BoltTypeSystemContext.php: -------------------------------------------------------------------------------- 1 | driver = \GraphAware\Bolt\GraphDatabase::driver("bolt://localhost"); 40 | $this->driver->session()->run("MATCH (n) DETACH DELETE n"); 41 | } 42 | 43 | /** 44 | * @Given a String of size :arg1 45 | */ 46 | public function aStringOfSize($arg1) 47 | { 48 | $this->value = str_repeat('a', $arg1); 49 | } 50 | 51 | /** 52 | * @When the driver asks the server to echo this value back 53 | */ 54 | public function theDriverAsksTheServerToEchoThisValueBack() 55 | { 56 | $session = $this->driver->session(); 57 | $this->result = $session->run("RETURN {value} as value", ['value' => $this->value]); 58 | } 59 | 60 | /** 61 | * @Then the result returned from the server should be a single record with a single value 62 | */ 63 | public function theResultReturnedFromTheServerShouldBeASingleRecordWithASingleValue() 64 | { 65 | Assert::assertCount(1, $this->result->records()); 66 | Assert::assertCount(1, $this->result->getRecord()->values()); 67 | } 68 | 69 | /** 70 | * @Then the value given in the result should be the same as what was sent 71 | */ 72 | public function theValueGivenInTheResultShouldBeTheSameAsWhatWasSent() 73 | { 74 | Assert::assertEquals($this->value, $this->result->getRecord()->value('value')); 75 | } 76 | 77 | /** 78 | * @Given a List of size :arg1 and type Null 79 | */ 80 | public function aListOfSizeAndTypeNull($arg1) 81 | { 82 | $this->value = $this->buildList($arg1, null); 83 | } 84 | 85 | /** 86 | * @Given a List of size :arg1 and type Boolean 87 | */ 88 | public function aListOfSizeAndTypeBoolean($arg1) 89 | { 90 | $this->value = $this->buildList($arg1, true); 91 | } 92 | 93 | /** 94 | * @Given a List of size :arg1 and type Integer 95 | */ 96 | public function aListOfSizeAndTypeInteger($arg1) 97 | { 98 | $this->value = $this->buildList($arg1, 1); 99 | } 100 | 101 | /** 102 | * @Given a List of size :arg1 and type Float 103 | */ 104 | public function aListOfSizeAndTypeFloat($arg1) 105 | { 106 | $this->value = $this->buildList($arg1, 1.1); 107 | } 108 | 109 | /** 110 | * @Given a List of size :arg1 and type String 111 | */ 112 | public function aListOfSizeAndTypeString($arg1) 113 | { 114 | $this->value = $this->buildList($arg1, "GraphAware is Awesome"); 115 | } 116 | 117 | /** 118 | * @Given a Map of size :arg1 and type Null 119 | */ 120 | public function aMapOfSizeAndTypeNull($arg1) 121 | { 122 | $this->value = $this->buildMap($arg1, null); 123 | } 124 | 125 | /** 126 | * @Given a Map of size :arg1 and type Boolean 127 | */ 128 | public function aMapOfSizeAndTypeBoolean($arg1) 129 | { 130 | $this->value = $this->buildMap($arg1, true); 131 | } 132 | 133 | /** 134 | * @Given a Map of size :arg1 and type Integer 135 | */ 136 | public function aMapOfSizeAndTypeInteger($arg1) 137 | { 138 | $this->value = $this->buildMap($arg1, 12); 139 | } 140 | 141 | /** 142 | * @Given a Map of size :arg1 and type Float 143 | */ 144 | public function aMapOfSizeAndTypeFloat($arg1) 145 | { 146 | $this->value = $this->buildMap($arg1, 1.23); 147 | } 148 | 149 | /** 150 | * @Given a Map of size :arg1 and type String 151 | */ 152 | public function aMapOfSizeAndTypeString($arg1) 153 | { 154 | $this->value = $this->buildMap($arg1, "GraphAware Rocks !"); 155 | } 156 | 157 | /** 158 | * @Given a Node with great amount of properties and labels 159 | */ 160 | public function aNodeWithGreatAmountOfPropertiesAndLabels() 161 | { 162 | $labels = []; 163 | $identifier = strtoupper('Label' . sha1(microtime(true) . rand(0,10000))); 164 | $labels[] = $identifier; 165 | foreach (range(0, 100) as $i) { 166 | $labels[] = 'Label' . $i; 167 | } 168 | 169 | $properties = $this->buildMap(1000, 'http://graphaware.com'); 170 | $this->value = ['labels' => $labels, 'properties' => $properties, 'identifier' => $identifier]; 171 | $session = $this->driver->session(); 172 | $statement = 'CREATE (n:' . implode(':', $labels) . ') SET n += {props}'; 173 | $session->run($statement, ['props' => $properties]); 174 | } 175 | 176 | /** 177 | * @When the driver asks the server to echo this node back 178 | */ 179 | public function theDriverAsksTheServerToEchoThisNodeBack() 180 | { 181 | $session = $this->driver->session(); 182 | $this->result = $session->run("MATCH (n:" . $this->value['identifier'] . ") RETURN n LIMIT 1"); 183 | } 184 | 185 | /** 186 | * @Then the node value given in the result should be the same as what was sent 187 | */ 188 | public function theNodeValueGivenInTheResultShouldBeTheSameAsWhatWasSent() 189 | { 190 | $node = $this->result->getRecord()->value('n'); 191 | Assert::assertCount(count($this->value['labels']), $node->labels()); 192 | Assert::assertEquals($this->value['properties'], $node->values()); 193 | } 194 | 195 | /** 196 | * @Given a path P of size :arg1 197 | */ 198 | public function aPathPOfSize($arg1) 199 | { 200 | $startIdentifier = 'start ' . sha1(microtime(true) . rand(0, 10000)); 201 | $endIdentifier = 'end' . sha1(microtime(true) . rand(0, 10000)); 202 | $session = $this->driver->session(); 203 | $session->run("CREATE INDEX ON :Node(i)"); 204 | $session->run("MATCH (n) DETACH DELETE n"); 205 | $session->run(" 206 | UNWIND range(0, 1001) as i 207 | CREATE (z:Node) SET z.i = i 208 | WITH z, i 209 | MATCH (n:Node) WHERE n.i = i-1 210 | MERGE (n)-[:REL]->(z)"); 211 | $session->run("MATCH (n:Node {i:1001}), (z:Node {id:1002}) MERGE (n)-[:REL]->(z)"); 212 | } 213 | 214 | /** 215 | * @When the driver asks the server to echo this path back 216 | */ 217 | public function theDriverAsksTheServerToEchoThisPathBack() 218 | { 219 | $session = $this->driver->session(); 220 | $this->result = $session->run("MATCH (n:Node {i:0}), (z:Node {i: 1001}) 221 | MATCH p=(n)-[*]->(z) RETURN p"); 222 | } 223 | 224 | /** 225 | * @Then the path value given in the result should be the same as what was sent 226 | */ 227 | public function thePathValueGivenInTheResultShouldBeTheSameAsWhatWasSent() 228 | { 229 | Assert::assertEquals(1001, $this->result->getRecord()->value('p')->length()); 230 | } 231 | 232 | private function buildList($size, $value) 233 | { 234 | $list = []; 235 | foreach (range(0, $size) as $x) { 236 | $list[] = $value; 237 | } 238 | 239 | return $list; 240 | } 241 | 242 | private function buildMap($size, $value) 243 | { 244 | $map = []; 245 | foreach (range(0, $size) as $x) { 246 | $map['key' . $x] = $x; 247 | } 248 | 249 | return $map; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /features/bootstrap/ResultMetadataContext.php: -------------------------------------------------------------------------------- 1 | driver = \GraphAware\Bolt\GraphDatabase::driver("bolt://localhost"); 50 | } 51 | 52 | /** 53 | * @When I run a statement 54 | */ 55 | public function iRunAStatement() 56 | { 57 | $session = $this->driver->session(); 58 | $this->result = $session->run("CREATE (n:Node) RETURN n"); 59 | } 60 | 61 | /** 62 | * @When I summarize it 63 | */ 64 | public function iSummarizeIt() 65 | { 66 | $this->summary = $this->result->summarize(); 67 | } 68 | 69 | /** 70 | * @Then I should get a Result Summary back 71 | */ 72 | public function iShouldGetAResultSummaryBack() 73 | { 74 | Assert::assertInstanceOf(ResultSummaryInterface::class, $this->summary); 75 | } 76 | 77 | /** 78 | * @When I request a statement from it 79 | */ 80 | public function iRequestAStatementFromIt() 81 | { 82 | $this->statement = $this->summary->statement(); 83 | } 84 | 85 | /** 86 | * @Then I should get a Statement back 87 | */ 88 | public function iShouldGetAStatementBack() 89 | { 90 | Assert::assertInstanceOf(StatementInterface::class, $this->statement); 91 | } 92 | 93 | /** 94 | * @When I run a statement with text :arg1 95 | */ 96 | public function iRunAStatementWithText($arg1) 97 | { 98 | $session = $this->driver->session(); 99 | $this->result = $session->run($arg1); 100 | } 101 | 102 | /** 103 | * @Then I can request the statement text and the text should be :arg1 104 | */ 105 | public function iCanRequestTheStatementTextAndTheTextShouldBe($arg1) 106 | { 107 | Assert::assertEquals($arg1, $this->statement->text()); 108 | } 109 | 110 | /** 111 | * @Then the statement parameters should be a map 112 | */ 113 | public function theStatementParametersShouldBeAMap() 114 | { 115 | Assert::assertInternalType("array", $this->statement->parameters()); 116 | } 117 | 118 | /** 119 | * @When I request the update statistics 120 | */ 121 | public function iRequestTheUpdateStatistics() 122 | { 123 | $this->statistics = $this->summary->updateStatistics(); 124 | } 125 | 126 | /** 127 | * @Then I should get the UpdateStatistics back 128 | */ 129 | public function iShouldGetTheUpdatestatisticsBack() 130 | { 131 | Assert::assertInstanceOf(StatementStatisticsInterface::class, $this->statistics); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /features/chunking-dechunking/tck-chunking-dechunking.feature: -------------------------------------------------------------------------------- 1 | Feature: Driver Chunking and Dechunking Test 2 | 3 | It is important that the types that are sent over Bolt are not corrupted. 4 | These Scenarios will send very large types or collections of types so that Bolts chunking and dechunking 5 | functionallity is used. 6 | Similar to to the type system feature scenarios these scenarios will echo these large values and make sure that the 7 | returning values are the same. 8 | 9 | Echoing to the server can be done by using the cypher statement "RETURN ", 10 | or "RETURN {value}" with value provided via a parameter. 11 | It is recommended to test each supported way of sending statements that the driver provides while running these 12 | cucumber scenarios. 13 | 14 | Scenario: should echo very long string 15 | Given a String of size 10000 16 | When the driver asks the server to echo this value back 17 | Then the result returned from the server should be a single record with a single value 18 | And the value given in the result should be the same as what was sent 19 | 20 | Scenario Outline: should echo very long list 21 | Given a List of size 1000 and type 22 | When the driver asks the server to echo this value back 23 | Then the result returned from the server should be a single record with a single value 24 | And the value given in the result should be the same as what was sent 25 | Examples: 26 | | Type | 27 | | Null | 28 | | Boolean | 29 | | Integer | 30 | | Float | 31 | | String | 32 | 33 | Scenario Outline: should echo very long map 34 | Given a Map of size 1000 and type 35 | When the driver asks the server to echo this value back 36 | Then the result returned from the server should be a single record with a single value 37 | And the value given in the result should be the same as what was sent 38 | Examples: 39 | | Type | 40 | | Null | 41 | | Boolean | 42 | | Integer | 43 | | Float | 44 | | String | 45 | 46 | Scenario: should echo very large node 47 | Given a Node with great amount of properties and labels 48 | When the driver asks the server to echo this node back 49 | Then the result returned from the server should be a single record with a single value 50 | And the node value given in the result should be the same as what was sent 51 | 52 | Scenario: Should echo very long path 53 | Given a path P of size 1001 54 | When the driver asks the server to echo this path back 55 | Then the result returned from the server should be a single record with a single value 56 | And the path value given in the result should be the same as what was sent -------------------------------------------------------------------------------- /features/result/tck-01-result-metadata.feature: -------------------------------------------------------------------------------- 1 | Feature: Basic Result Metadata API 2 | 3 | Scenario: Summarize Result 4 | Given there is a driver configured with the "localhost" uri 5 | When I run a statement 6 | And I summarize it 7 | Then I should get a Result Summary back 8 | 9 | Scenario: Access Statement 10 | Given there is a driver configured with the "localhost" uri 11 | When I run a statement 12 | And I summarize it 13 | And I request a statement from it 14 | Then I should get a Statement back 15 | 16 | Scenario: Examine Statement 17 | Given there is a driver configured with the "localhost" uri 18 | When I run a statement with text "MATCH (n) RETURN count(n)" 19 | And I summarize it 20 | And I request a statement from it 21 | Then I can request the statement text and the text should be "MATCH (n) RETURN count(n)" 22 | And the statement parameters should be a map 23 | 24 | Scenario: Access Update Statistics 25 | Given there is a driver configured with the "localhost" uri 26 | When I run a statement with text "CREATE (n) RETURN n" 27 | And I summarize it 28 | And I request the update statistics 29 | Then I should get the UpdateStatistics back -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/BoltEvents.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\Bolt; 13 | 14 | final class BoltEvents 15 | { 16 | /** 17 | * This event is dispatched before the handshake is performed with the server. 18 | * Listeners will receive a PreHandshakeEvent instance containing 19 | * the versions supported by BoltPHP. 20 | */ 21 | const PRE_HANDSHAKE = 'bolt.pre_handshake'; 22 | 23 | /** 24 | * This event is dispatched after the handshake is performed with the server. 25 | * Listeners will receive a PostHandshakeEvent instance containing 26 | * the version defined by the server. 27 | */ 28 | const POST_HANDSHAKE = 'bolt.post_handshake'; 29 | 30 | /** 31 | * This event is dispatched when an exception is raised. 32 | * Listeners will received a ExeptionEvent instance. 33 | */ 34 | const EXCEPTION = 'bolt.exception'; 35 | } 36 | -------------------------------------------------------------------------------- /src/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\Bolt; 13 | 14 | use GraphAware\Common\Driver\ConfigInterface; 15 | use GraphAware\Common\Connection\BaseConfiguration; 16 | 17 | /** 18 | * @author Tobias Nyholm 19 | */ 20 | class Configuration extends BaseConfiguration implements ConfigInterface 21 | { 22 | const TLSMODE_REQUIRED = 'REQUIRED'; 23 | const TLSMODE_REJECTED = 'REJECTED'; 24 | 25 | /** 26 | * @var array 27 | * 28 | * @deprecated 29 | */ 30 | protected $credentials; 31 | 32 | /** 33 | * @deprecated 34 | */ 35 | protected $username; 36 | 37 | /** 38 | * @deprecated 39 | */ 40 | protected $password; 41 | 42 | /** 43 | * @var string 44 | * 45 | * @deprecated 46 | */ 47 | protected $bindtoInterface; 48 | 49 | /** 50 | * @var int 51 | * 52 | * @deprecated 53 | */ 54 | protected $timeout; 55 | 56 | /** 57 | * @deprecated 58 | */ 59 | protected $tlsMode; 60 | 61 | /** 62 | * Create a new configuration with default values. 63 | * 64 | * @return self 65 | */ 66 | public static function create() 67 | { 68 | return new self([ 69 | 'user' => 'null', 70 | 'password' => 'null', 71 | 'bind_to_interface' => 'null', 72 | 'timeout' => 5, 73 | 'credentials' => ['null', 'null'], 74 | ]); 75 | } 76 | 77 | /** 78 | * @return Configuration 79 | * 80 | * @deprecated Will be removed in 2.0. Use Configuration::create 81 | */ 82 | public static function newInstance() 83 | { 84 | $config = self::create(); 85 | 86 | $config->username = 'null'; 87 | $config->password = 'null'; 88 | $config->credentials = ['null', 'null']; 89 | $config->bindtoInterface = 'null'; 90 | $config->timeout = 5; 91 | 92 | return $config; 93 | } 94 | 95 | /** 96 | * @param string $username 97 | * @param string $password 98 | * 99 | * @return Configuration 100 | */ 101 | public function withCredentials($username, $password) 102 | { 103 | if (null === $username || null === $password) { 104 | // No change if credentials or null 105 | return $this; 106 | } 107 | 108 | $new = $this->setValue('username', $username) 109 | ->setValue('password', $password) 110 | ->setValue('credentials', [$username, $password]); 111 | 112 | // To keep BC 113 | $new->username = $username; 114 | $new->password = $password; 115 | $new->credentials = [$username, $password]; 116 | 117 | return $new; 118 | } 119 | 120 | /** 121 | * @param string $interface 122 | * 123 | * @return Configuration 124 | */ 125 | public function bindToInterface($interface) 126 | { 127 | $new = $this->setValue('bind_to_interface', $interface); 128 | 129 | // To keep BC 130 | $new->bindtoInterface = $interface; 131 | 132 | return $new; 133 | } 134 | 135 | /** 136 | * @param int $timeout 137 | * 138 | * @return Configuration 139 | */ 140 | public function withTimeout($timeout) 141 | { 142 | $new = $this->setValue('timeout', $timeout); 143 | 144 | // To keep BC 145 | $new->timeout = $timeout; 146 | 147 | return $new; 148 | } 149 | 150 | /** 151 | * @return array 152 | * 153 | * @deprecated Will be removed in 2.0. Use Configuration::getValue('credentials') 154 | */ 155 | public function getCredentials() 156 | { 157 | return $this->getValue('credentials'); 158 | } 159 | 160 | /** 161 | * @return string 162 | * 163 | * @deprecated Will be removed in 2.0. Use Configuration::getValue('bind_to_interface') 164 | */ 165 | public function getBindtoInterface() 166 | { 167 | return $this->getValue('bind_to_interface'); 168 | } 169 | 170 | /** 171 | * @return int 172 | * 173 | * @deprecated Will be removed in 2.0. Use Configuration::getValue('timeout') 174 | */ 175 | public function getTimeout() 176 | { 177 | return $this->getValue('timeout'); 178 | } 179 | 180 | /** 181 | * @return mixed 182 | * 183 | * @deprecated Will be removed in 2.0. Use Configuration::getValue('username') 184 | */ 185 | public function getUsername() 186 | { 187 | return $this->getValue('username'); 188 | } 189 | 190 | /** 191 | * @return mixed 192 | * 193 | * @deprecated Will be removed in 2.0. Use Configuration::getValue('password') 194 | */ 195 | public function getPassword() 196 | { 197 | return $this->getValue('password'); 198 | } 199 | 200 | /** 201 | * @param $mode 202 | * 203 | * @return Configuration 204 | */ 205 | public function withTLSMode($mode) 206 | { 207 | $new = $this->setValue('tls_mode', $mode); 208 | 209 | // To keep BC 210 | $new->tlsMode = $mode; 211 | 212 | return $new; 213 | } 214 | 215 | /** 216 | * @return mixed 217 | * 218 | * @deprecated Will be removed in 2.0. Use Configuration::getValue('tls_mode') 219 | */ 220 | public function getTlsMode() 221 | { 222 | return $this->getValue('tls_mode'); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/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\Bolt; 13 | 14 | use GraphAware\Bolt\Exception\IOException; 15 | use GraphAware\Bolt\IO\StreamSocket; 16 | use GraphAware\Bolt\Protocol\SessionRegistry; 17 | use GraphAware\Bolt\PackStream\Packer; 18 | use GraphAware\Bolt\Protocol\V1\Session; 19 | use GraphAware\Common\Driver\DriverInterface; 20 | use Symfony\Component\EventDispatcher\EventDispatcher; 21 | use GraphAware\Bolt\Exception\HandshakeException; 22 | 23 | class Driver implements DriverInterface 24 | { 25 | const VERSION = '1.5.4'; 26 | 27 | const DEFAULT_TCP_PORT = 7687; 28 | 29 | /** 30 | * @var StreamSocket 31 | */ 32 | protected $io; 33 | 34 | /** 35 | * @var EventDispatcher 36 | */ 37 | protected $dispatcher; 38 | 39 | /** 40 | * @var SessionRegistry 41 | */ 42 | protected $sessionRegistry; 43 | 44 | /** 45 | * @var bool 46 | */ 47 | protected $versionAgreed = false; 48 | 49 | /** 50 | * @var Session 51 | */ 52 | protected $session; 53 | 54 | /** 55 | * @var array 56 | */ 57 | protected $credentials; 58 | 59 | /** 60 | * @return string 61 | */ 62 | public static function getUserAgent() 63 | { 64 | return 'GraphAware-BoltPHP/'.self::VERSION; 65 | } 66 | 67 | /** 68 | * @param string $uri 69 | * @param Configuration|null $configuration 70 | */ 71 | public function __construct($uri, Configuration $configuration = null) 72 | { 73 | $this->credentials = null !== $configuration ? $configuration->getValue('credentials', []) : []; 74 | /* 75 | $ctx = stream_context_create(array()); 76 | define('CERTS_PATH', 77 | '/Users/ikwattro/dev/_graphs/3.0-M02-NIGHTLY/conf'); 78 | $ssl_options = array( 79 | 'cafile' => CERTS_PATH . '/cacert.pem', 80 | 'local_cert' => CERTS_PATH . '/ssl/snakeoil.pem', 81 | 'peer_name' => 'example.com', 82 | 'allow_self_signed' => true, 83 | 'verify_peer' => true, 84 | 'capture_peer_cert' => true, 85 | 'capture_peer_cert_chain' => true, 86 | 'disable_compression' => true, 87 | 'SNI_enabled' => true, 88 | 'verify_depth' => 1 89 | ); 90 | foreach ($ssl_options as $k => $v) { 91 | stream_context_set_option($ctx, 'ssl', $k, $v); 92 | } 93 | */ 94 | 95 | $config = null !== $configuration ? $configuration : Configuration::create(); 96 | $parsedUri = parse_url($uri); 97 | $host = isset($parsedUri['host']) ? $parsedUri['host'] : $parsedUri['path']; 98 | $port = isset($parsedUri['port']) ? $parsedUri['port'] : static::DEFAULT_TCP_PORT; 99 | $this->dispatcher = new EventDispatcher(); 100 | $this->io = StreamSocket::withConfiguration($host, $port, $config, $this->dispatcher); 101 | $this->sessionRegistry = new SessionRegistry($this->io, $this->dispatcher); 102 | $this->sessionRegistry->registerSession(Session::class); 103 | } 104 | 105 | /** 106 | * @return Session 107 | */ 108 | public function session() 109 | { 110 | if (null !== $this->session) { 111 | return $this->session; 112 | } 113 | 114 | if (!$this->versionAgreed) { 115 | $this->versionAgreed = $this->handshake(); 116 | } 117 | 118 | $this->session = $this->sessionRegistry->getSession($this->versionAgreed, $this->credentials); 119 | 120 | return $this->session; 121 | } 122 | 123 | /** 124 | * @return int 125 | * 126 | * @throws HandshakeException 127 | */ 128 | public function handshake() 129 | { 130 | $packer = new Packer(); 131 | 132 | if (!$this->io->isConnected()) { 133 | $this->io->reconnect(); 134 | } 135 | 136 | $msg = ''; 137 | $msg .= chr(0x60).chr(0x60).chr(0xb0).chr(0x17); 138 | 139 | foreach (array(1, 0, 0, 0) as $v) { 140 | $msg .= $packer->packBigEndian($v, 4); 141 | } 142 | 143 | try { 144 | $this->io->write($msg); 145 | $rawHandshakeResponse = $this->io->read(4); 146 | $response = unpack('N', $rawHandshakeResponse); 147 | $version = $response[1]; 148 | 149 | if (0 === $version) { 150 | $this->throwHandshakeException(sprintf('Handshake Exception. Unable to negotiate a version to use. Proposed versions were %s', 151 | json_encode(array(1, 0, 0, 0)))); 152 | } 153 | 154 | return $version; 155 | } catch (IOException $e) { 156 | $this->throwHandshakeException($e->getMessage()); 157 | } 158 | } 159 | 160 | /** 161 | * @param string $message 162 | */ 163 | private function throwHandshakeException($message) 164 | { 165 | throw new HandshakeException($message); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/Exception/BoltExceptionInterface.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\Bolt\Exception; 13 | 14 | interface BoltExceptionInterface 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /src/Exception/BoltInvalidArgumentException.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\Bolt\Exception; 13 | 14 | class BoltInvalidArgumentException extends \InvalidArgumentException implements BoltExceptionInterface 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /src/Exception/BoltOutOfBoundsException.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\Bolt\Exception; 13 | 14 | class BoltOutOfBoundsException extends \OutOfBoundsException implements BoltExceptionInterface 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /src/Exception/HandshakeException.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\Bolt\Exception; 13 | 14 | class HandshakeException extends \RuntimeException implements BoltExceptionInterface 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /src/Exception/IOException.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\Bolt\Exception; 13 | 14 | class IOException extends \Exception implements BoltExceptionInterface 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /src/Exception/MessageFailureException.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\Bolt\Exception; 13 | 14 | class MessageFailureException extends \RuntimeException implements BoltExceptionInterface 15 | { 16 | protected $statusCode; 17 | 18 | public function setStatusCode($code) 19 | { 20 | $this->statusCode = $code; 21 | } 22 | 23 | public function getStatusCode() 24 | { 25 | return $this->statusCode; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Exception/SerializationException.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\Bolt\Exception; 13 | 14 | class SerializationException extends \InvalidArgumentException implements BoltExceptionInterface 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /src/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\Bolt; 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(self::formatUri($uri), $config); 29 | } 30 | 31 | /** 32 | * @param string $uri 33 | * 34 | * @return string 35 | */ 36 | private static function formatUri($uri) 37 | { 38 | return str_replace('bolt://', '', $uri); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/IO/AbstractIO.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\Bolt\IO; 13 | 14 | use GraphAware\Bolt\Exception\IOException; 15 | 16 | abstract class AbstractIO implements IoInterface 17 | { 18 | /** 19 | * @return bool 20 | * 21 | * @throws IOException 22 | */ 23 | public function assertConnected() 24 | { 25 | if (!$this->isConnected()) { 26 | return $this->connect(); 27 | } 28 | 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/IO/IoInterface.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\Bolt\IO; 13 | 14 | use GraphAware\Bolt\Exception\IOException; 15 | 16 | interface IoInterface 17 | { 18 | /** 19 | * @param string $data 20 | * 21 | * @throws IOException 22 | */ 23 | public function write($data); 24 | 25 | /** 26 | * @param int $n 27 | * 28 | * @return string 29 | * 30 | * @throws IOException 31 | */ 32 | public function read($n); 33 | 34 | /** 35 | * @param int $sec 36 | * @param int $usec 37 | * 38 | * @return int 39 | */ 40 | public function select($sec, $usec); 41 | 42 | /** 43 | * @return bool 44 | * 45 | * @throws IOException 46 | */ 47 | public function connect(); 48 | 49 | /** 50 | * @return bool 51 | * 52 | * @throws IOException 53 | */ 54 | public function reconnect(); 55 | 56 | /** 57 | * @return bool 58 | */ 59 | public function isConnected(); 60 | 61 | /** 62 | * @return bool 63 | */ 64 | public function close(); 65 | } 66 | -------------------------------------------------------------------------------- /src/IO/Socket.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\Bolt\IO; 13 | 14 | use GraphAware\Bolt\Exception\IOException; 15 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 16 | 17 | class Socket extends AbstractIO 18 | { 19 | /** 20 | * @var string 21 | */ 22 | private $host; 23 | 24 | /** 25 | * @var int 26 | */ 27 | private $port; 28 | 29 | /** 30 | * @var resource|null 31 | */ 32 | private $socket; 33 | 34 | /** 35 | * @var int 36 | */ 37 | private $timeout; 38 | 39 | /** 40 | * @var null|EventDispatcherInterface 41 | */ 42 | protected $dispatcher; 43 | 44 | /** 45 | * Socket constructor. 46 | * 47 | * @param string $host 48 | * @param int $port 49 | * @param int $timeout 50 | * @param EventDispatcherInterface|null $dispatcher 51 | */ 52 | public function __construct($host, $port, $timeout = 5, EventDispatcherInterface $dispatcher = null) 53 | { 54 | $this->host = $host; 55 | $this->port = $port; 56 | $this->timeout = $timeout; 57 | $this->dispatcher = $dispatcher; 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function connect() 64 | { 65 | $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 66 | 67 | if (!socket_connect($this->socket, $this->host, $this->port)) { 68 | $errno = socket_last_error($this->socket); 69 | $errstr = socket_strerror($errno); 70 | throw new IOException( 71 | sprintf('Error connecting to server "%s": %s', 72 | $errno, 73 | $errstr 74 | ), $errno 75 | ); 76 | } 77 | 78 | socket_set_block($this->socket); 79 | socket_set_option($this->socket, SOL_TCP, TCP_NODELAY, 1); 80 | //socket_set_option($this->socket, SOL_SOCKET, SO_PASSCRED 81 | socket_set_option($this->socket, SOL_SOCKET, SO_KEEPALIVE, 1); 82 | socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $this->timeout, 'usec' => 0)); 83 | socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, array('sec' => $this->timeout, 'usec' => 0)); 84 | 85 | return true; 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | public function close() 92 | { 93 | if (is_resource($this->socket)) { 94 | echo 'closing'; 95 | socket_close($this->socket); 96 | } 97 | $this->socket = null; 98 | 99 | return true; 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | public function reconnect() 106 | { 107 | $this->close(); 108 | 109 | return $this->connect(); 110 | } 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | public function write($data) 116 | { 117 | $len = mb_strlen($data, 'ASCII'); 118 | 119 | while (true) { 120 | // Null sockets are invalid, throw exception 121 | if (is_null($this->socket)) { 122 | throw new IOException(sprintf( 123 | 'Socket was null! Last SocketError was: %s', 124 | socket_strerror(socket_last_error()) 125 | )); 126 | } 127 | 128 | $sent = socket_write($this->socket, $data, $len); 129 | if ($sent === false) { 130 | throw new IOException(sprintf( 131 | 'Error sending data. Last SocketError: %s', 132 | socket_strerror(socket_last_error()) 133 | )); 134 | } 135 | 136 | // Check if the entire message has been sent 137 | if ($sent < $len) { 138 | // If not sent the entire message. 139 | // Get the part of the message that has not yet been sent as message 140 | $data = mb_substr($data, $sent, mb_strlen($data, 'ASCII') - $sent, 'ASCII'); 141 | // Get the length of the not sent part 142 | $len -= $sent; 143 | } else { 144 | break; 145 | } 146 | } 147 | } 148 | 149 | /** 150 | * {@inheritdoc} 151 | */ 152 | public function select($sec, $usec) 153 | { 154 | // not implemented yet 155 | } 156 | 157 | /** 158 | * {@inheritdoc} 159 | */ 160 | public function read($n) 161 | { 162 | $res = ''; 163 | $read = 0; 164 | 165 | $buf = socket_read($this->socket, $n); 166 | 167 | while ($read < $n && $buf !== '' && $buf !== false) { 168 | $read += mb_strlen($buf, 'ASCII'); 169 | $res .= $buf; 170 | $buf = socket_read($this->socket, $n - $read); 171 | } 172 | 173 | if (mb_strlen($res, 'ASCII') != $n) { 174 | throw new IOException(sprintf( 175 | 'Error reading data. Received %s instead of expected %s bytes', 176 | mb_strlen($res, 'ASCII'), 177 | $n 178 | )); 179 | } 180 | 181 | return $res; 182 | } 183 | 184 | /** 185 | * {@inheritdoc} 186 | */ 187 | public function isConnected() 188 | { 189 | return is_resource($this->socket); 190 | } 191 | 192 | /** 193 | * @return null|resource 194 | */ 195 | public function getSocket() 196 | { 197 | return $this->socket; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/IO/StreamSocket.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\Bolt\IO; 13 | 14 | use GraphAware\Bolt\Configuration; 15 | use GraphAware\Bolt\Exception\IOException; 16 | use GraphAware\Bolt\Misc\Helper; 17 | use Symfony\Component\EventDispatcher\EventDispatcher; 18 | 19 | class StreamSocket extends AbstractIO 20 | { 21 | /** 22 | * @var string 23 | */ 24 | protected $protocol; 25 | 26 | /** 27 | * @var string 28 | */ 29 | protected $host; 30 | 31 | /** 32 | * @var int 33 | */ 34 | protected $port; 35 | 36 | /** 37 | * @var array|null 38 | */ 39 | protected $context; 40 | 41 | /** 42 | * @var bool 43 | */ 44 | protected $keepAlive; 45 | 46 | /** 47 | * @var null|EventDispatcher 48 | */ 49 | protected $eventDispatcher; 50 | 51 | /** 52 | * @var int 53 | */ 54 | protected $timeout = 5; 55 | 56 | /** 57 | * @var resource|null 58 | */ 59 | private $sock; 60 | 61 | private $configuration; 62 | 63 | /** 64 | * @param string $host 65 | * @param int $port 66 | * @param array|null $context 67 | * @param bool $keepAlive 68 | * @param EventDispatcher|null $eventDispatcher 69 | */ 70 | public function __construct($host, $port, $context = null, $keepAlive = false, EventDispatcher $eventDispatcher = null, Configuration $configuration = null) 71 | { 72 | $this->host = $host; 73 | $this->port = $port; 74 | $this->context = $context; 75 | $this->keepAlive = $keepAlive; 76 | $this->eventDispatcher = $eventDispatcher; 77 | $this->protocol = 'tcp'; 78 | 79 | $this->context = null !== $context ? $context : stream_context_create(); 80 | $this->configuration = $configuration; 81 | 82 | /* 83 | if (is_null($this->context)) { 84 | $this->context = stream_context_create(); 85 | } else { 86 | $this->protocol = 'ssl'; 87 | } 88 | */ 89 | //stream_set_blocking($this->sock, false); 90 | } 91 | 92 | public static function withConfiguration($host, $port, Configuration $configuration, EventDispatcher $eventDispatcher = null) 93 | { 94 | $context = null; 95 | if (null !== $configuration->getValue('bind_to_interface')) { 96 | $context = stream_context_create([ 97 | 'socket' => [ 98 | 'bindto' => $configuration->getValue('bind_to_interface') 99 | ] 100 | ]); 101 | } 102 | 103 | return new self($host, $port, $context, false, $eventDispatcher, $configuration); 104 | } 105 | 106 | /** 107 | * {@inheritdoc} 108 | */ 109 | public function write($data) 110 | { 111 | //echo \GraphAware\Bolt\Misc\Helper::prettyHex($data) . PHP_EOL; 112 | $this->assertConnected(); 113 | $written = 0; 114 | $len = mb_strlen($data, 'ASCII'); 115 | 116 | while ($written < $len) { 117 | $buf = fwrite($this->sock, $data); 118 | 119 | if ($buf === false) { 120 | throw new IOException('Error writing data'); 121 | } 122 | 123 | if ($buf === 0 && feof($this->sock)) { 124 | throw new IOException('Broken pipe or closed connection'); 125 | } 126 | 127 | $written += $buf; 128 | } 129 | } 130 | 131 | /** 132 | * {@inheritdoc} 133 | */ 134 | public function read($n) 135 | { 136 | if (null === $n) { 137 | return $this->readAll(); 138 | } 139 | $this->assertConnected(); 140 | $read = 0; 141 | $data = ''; 142 | 143 | while ($read < $n) { 144 | $buffer = fread($this->sock, ($n - $read)); 145 | //var_dump(\GraphAware\Bolt\Misc\Helper::prettyHex($buffer)); 146 | // check '' later for non-blocking mode use case 147 | if ($buffer === false || '' === $buffer) { 148 | throw new IOException('Error receiving data'); 149 | } 150 | 151 | $read += mb_strlen($buffer, 'ASCII'); 152 | $data .= $buffer; 153 | } 154 | 155 | return $data; 156 | } 157 | 158 | /** 159 | * @param int $l 160 | * 161 | * @return string 162 | */ 163 | public function readChunk($l = 8192) 164 | { 165 | $data = stream_socket_recvfrom($this->sock, $l); 166 | //echo Helper::prettyHex($data); 167 | 168 | return $data; 169 | } 170 | 171 | /** 172 | * {@inheritdoc} 173 | */ 174 | public function select($sec, $usec) 175 | { 176 | $r = array($this->sock); 177 | $w = $e = null; 178 | $result = stream_select($r, $w, $e, $sec, $usec); 179 | 180 | return $result; 181 | } 182 | 183 | /** 184 | * {@inheritdoc} 185 | */ 186 | public function connect() 187 | { 188 | $errstr = $errno = null; 189 | 190 | $remote = sprintf( 191 | '%s://%s:%s', 192 | $this->protocol, 193 | $this->host, 194 | $this->port 195 | ); 196 | 197 | $this->sock = stream_socket_client( 198 | $remote, 199 | $errno, 200 | $errstr, 201 | $this->timeout, 202 | STREAM_CLIENT_CONNECT, 203 | $this->context 204 | ); 205 | 206 | if (false === $this->sock) { 207 | throw new IOException(sprintf( 208 | 'Error to connect to the server(%s) : "%s"', $errno, $errstr 209 | )); 210 | } 211 | 212 | if ($this->shouldEnableCrypto()) { 213 | $result = stream_socket_enable_crypto($this->sock, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT); 214 | if (true !== $result) { 215 | throw new \RuntimeException(sprintf('Unable to enable crypto on socket')); 216 | } 217 | } 218 | 219 | stream_set_read_buffer($this->sock, 0); 220 | 221 | return true; 222 | } 223 | 224 | /** 225 | * {@inheritdoc} 226 | */ 227 | public function close() 228 | { 229 | if (is_resource($this->sock)) { 230 | fclose($this->sock); 231 | } 232 | 233 | $this->sock = null; 234 | 235 | return true; 236 | } 237 | 238 | /** 239 | * {@inheritdoc} 240 | */ 241 | public function reconnect() 242 | { 243 | $this->close(); 244 | 245 | return $this->connect(); 246 | } 247 | 248 | /** 249 | * {@inheritdoc} 250 | */ 251 | public function isConnected() 252 | { 253 | return is_resource($this->sock); 254 | } 255 | 256 | /** 257 | * @return string 258 | */ 259 | private function readAll() 260 | { 261 | stream_set_blocking($this->sock, false); 262 | $r = array($this->sock); 263 | $w = $e = []; 264 | $data = ''; 265 | $continue = true; 266 | 267 | while ($continue) { 268 | $select = stream_select($r, $w, $e, 0, 10000); 269 | 270 | if (0 === $select) { 271 | stream_set_blocking($this->sock, true); 272 | 273 | return $data; 274 | } 275 | 276 | $buffer = stream_get_contents($this->sock, 8192); 277 | 278 | if ($buffer === '') { 279 | stream_select($r, $w, $e, null, null); 280 | } 281 | 282 | $r = array($this->sock); 283 | $data .= $buffer; 284 | } 285 | 286 | return $data; 287 | } 288 | 289 | public function shouldEnableCrypto() 290 | { 291 | if (null !== $this->configuration && $this->configuration->getValue('tls_mode') === Configuration::TLSMODE_REQUIRED) { 292 | return true; 293 | } 294 | 295 | return false; 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/Misc/Helper.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\Bolt\Misc; 13 | 14 | class Helper 15 | { 16 | /** 17 | * @param string $raw Binary content 18 | * 19 | * @return string 20 | */ 21 | public static function prettyHex($raw) 22 | { 23 | $split = str_split(bin2hex($raw), 2); 24 | 25 | return implode(':', $split); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/PackStream/BytesWalker.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\Bolt\PackStream; 13 | 14 | use GraphAware\Bolt\Protocol\Message\RawMessage; 15 | 16 | class BytesWalker 17 | { 18 | /** 19 | * @var int 20 | */ 21 | protected $position; 22 | 23 | /** 24 | * @var string 25 | */ 26 | protected $bytes; 27 | 28 | /** 29 | * @var string 30 | */ 31 | protected $encoding; 32 | 33 | /** 34 | * @param RawMessage $message 35 | * @param int $position 36 | * @param string $encoding 37 | */ 38 | public function __construct(RawMessage $message, $position = 0, $encoding = 'ASCII') 39 | { 40 | $this->bytes = $message->getBytes(); 41 | $this->position = $position; 42 | $this->encoding = $encoding; 43 | } 44 | 45 | /** 46 | * @param int $n 47 | * 48 | * @return string 49 | */ 50 | public function read($n) 51 | { 52 | $n = (int) $n; 53 | $raw = substr($this->bytes, $this->position, $n); 54 | $this->position += $n; 55 | 56 | return $raw; 57 | } 58 | 59 | /** 60 | * @param int $n 61 | * 62 | * @throws \OutOfBoundsException 63 | */ 64 | public function forward($n) 65 | { 66 | $n = (int) $n; 67 | if (($this->position + $n) > $this->getLength()) { 68 | throw new \OutOfBoundsException(sprintf('No more bytes to read')); 69 | } 70 | 71 | $this->position += $n; 72 | } 73 | 74 | /** 75 | * @param int $n 76 | * 77 | * @throws \OutOfBoundsException 78 | */ 79 | public function setPosition($n) 80 | { 81 | $n = (int) $n; 82 | if ($n > $this->getLength()) { 83 | throw new \OutOfBoundsException(sprintf('Require position out of bound')); 84 | } 85 | 86 | $this->position = $n; 87 | } 88 | 89 | /** 90 | * @param int $n 91 | * 92 | * @throws \InvalidArgumentException 93 | */ 94 | public function rewind($n) 95 | { 96 | $n = (int) $n; 97 | if ($n > $this->position) { 98 | throw new \InvalidArgumentException(sprintf('You try to rewind %d characters, but current position is %d', 99 | $n, 100 | $this->position 101 | )); 102 | } 103 | 104 | $this->position -= $n; 105 | } 106 | 107 | /** 108 | * @return int 109 | */ 110 | public function getLength() 111 | { 112 | return mb_strlen($this->bytes, $this->encoding); 113 | } 114 | 115 | /** 116 | * @return int 117 | */ 118 | public function getPosition() 119 | { 120 | return $this->position; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/PackStream/Packer.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\Bolt\PackStream; 13 | 14 | use GraphAware\Bolt\Exception\BoltInvalidArgumentException; 15 | use GraphAware\Bolt\Exception\BoltOutOfBoundsException; 16 | use GraphAware\Bolt\Exception\SerializationException; 17 | use GraphAware\Bolt\Protocol\Constants; 18 | use GraphAware\Common\Collection\ArrayList; 19 | use GraphAware\Common\Collection\CollectionInterface; 20 | use GraphAware\Common\Collection\Map; 21 | 22 | class Packer 23 | { 24 | /** 25 | * @param $v 26 | * 27 | * @return string 28 | */ 29 | public function pack($v) 30 | { 31 | $stream = ''; 32 | if (is_string($v)) { 33 | $stream .= $this->packText($v); 34 | } elseif ($v instanceof CollectionInterface && $v instanceof Map) { 35 | $stream .= $this->packMap($v->getElements()); 36 | } elseif ($v instanceof CollectionInterface && $v instanceof ArrayList) { 37 | $stream .= $this->packList($v->getElements()); 38 | } 39 | elseif (is_array($v)) { 40 | $stream .= ($this->isList($v) && !empty($v)) ? $this->packList($v) : $this->packMap($v); 41 | } elseif (is_float($v)) { 42 | $stream .= $this->packFloat($v); 43 | } elseif (is_int($v)) { 44 | $stream .= $this->packInteger($v); 45 | } elseif (is_null($v)) { 46 | $stream .= chr(Constants::MARKER_NULL); 47 | } elseif (true === $v) { 48 | $stream .= chr(Constants::MARKER_TRUE); 49 | } elseif (false === $v) { 50 | $stream .= chr(Constants::MARKER_FALSE); 51 | } elseif (is_float($v)) { 52 | // if it is 64 bit integers casted to float 53 | $r = $v + $v; 54 | if ('double' === gettype($r)) { 55 | $stream .= $this->packInteger($v); 56 | } 57 | } else { 58 | throw new BoltInvalidArgumentException(sprintf('Could not pack the value %s', $v)); 59 | } 60 | 61 | return $stream; 62 | } 63 | 64 | /** 65 | * @param int $length 66 | * @param $signature 67 | * 68 | * @return string 69 | */ 70 | public function packStructureHeader($length, $signature) 71 | { 72 | $stream = ''; 73 | $packedSig = chr($signature); 74 | if ($length < Constants::SIZE_TINY) { 75 | $stream .= chr(Constants::STRUCTURE_TINY + $length); 76 | $stream .= $packedSig; 77 | 78 | return $stream; 79 | } 80 | 81 | if ($length < Constants::SIZE_MEDIUM) { 82 | $stream .= chr(Constants::STRUCTURE_MEDIUM); 83 | $stream .= $this->packUnsignedShortShort($length); 84 | $stream .= $packedSig; 85 | 86 | return $stream; 87 | } 88 | 89 | if ($length < Constants::SIZE_LARGE) { 90 | $stream .= chr(Constants::STRUCTURE_LARGE); 91 | $stream .= $this->packSignedShort($length); 92 | $stream .= $packedSig; 93 | 94 | return $stream; 95 | } 96 | 97 | throw new SerializationException(sprintf('Unable pack the size "%d" of the structure, Out of bound !', $length)); 98 | } 99 | 100 | /** 101 | * @param int $length 102 | * 103 | * @return string 104 | */ 105 | public function getStructureMarker($length) 106 | { 107 | $length = (int) $length; 108 | $bytes = ''; 109 | 110 | if ($length < Constants::SIZE_TINY) { 111 | $bytes .= chr(Constants::STRUCTURE_TINY + $length); 112 | } elseif ($length < Constants::SIZE_MEDIUM) { 113 | // do 114 | } elseif ($length < Constants::SIZE_LARGE) { 115 | // do 116 | } else { 117 | throw new SerializationException(sprintf('Unable to get a Structure Marker for size %d', $length)); 118 | } 119 | 120 | return $bytes; 121 | } 122 | 123 | /** 124 | * @param array $array 125 | * 126 | * @return string 127 | */ 128 | public function packList(array $array) 129 | { 130 | $size = count($array); 131 | $b = $this->getListSizeMarker($size); 132 | foreach ($array as $k => $v) { 133 | $b .= $this->pack($v); 134 | } 135 | 136 | return $b; 137 | } 138 | 139 | /** 140 | * @param array $array 141 | * 142 | * @return bool 143 | */ 144 | public function isList(array $array) 145 | { 146 | foreach ($array as $k => $v) { 147 | if (!is_int($k)) { 148 | return false; 149 | } 150 | } 151 | 152 | return true; 153 | } 154 | 155 | /** 156 | * @param $size 157 | * 158 | * @return string 159 | */ 160 | public function getListSizeMarker($size) 161 | { 162 | $b = ''; 163 | 164 | if ($size < Constants::SIZE_TINY) { 165 | $b .= chr(Constants::LIST_TINY + $size); 166 | 167 | return $b; 168 | } 169 | 170 | if ($size < Constants::SIZE_8) { 171 | $b .= chr(Constants::LIST_8); 172 | $b .= $this->packUnsignedShortShort($size); 173 | 174 | return $b; 175 | } 176 | 177 | if ($b < Constants::SIZE_16) { 178 | $b .= chr(Constants::LIST_16); 179 | $b .= $this->packUnsignedShort($size); 180 | 181 | return $b; 182 | } 183 | 184 | if ($b < Constants::SIZE_32) { 185 | $b .= chr(Constants::LIST_32); 186 | $b .= $this->packUnsignedLong($size); 187 | 188 | return $b; 189 | } 190 | 191 | throw new SerializationException(sprintf('Unable to create marker for List size %d', $size)); 192 | } 193 | 194 | /** 195 | * @param array $array 196 | * 197 | * @return string 198 | */ 199 | public function packMap(array $array) 200 | { 201 | $size = count($array); 202 | $b = ''; 203 | $b .= $this->getMapSizeMarker($size); 204 | 205 | foreach ($array as $k => $v) { 206 | $b .= $this->pack($k); 207 | $b .= $this->pack($v); 208 | } 209 | 210 | return $b; 211 | } 212 | 213 | /** 214 | * @param $v 215 | * 216 | * @return int|string 217 | */ 218 | public function packFloat($v) 219 | { 220 | $str = chr(Constants::MARKER_FLOAT); 221 | 222 | return $str.strrev(pack('d', $v)); 223 | } 224 | 225 | /** 226 | * @param int $size 227 | * 228 | * @return string 229 | */ 230 | public function getMapSizeMarker($size) 231 | { 232 | $b = ''; 233 | 234 | if ($size < Constants::SIZE_TINY) { 235 | $b .= chr(Constants::MAP_TINY + $size); 236 | 237 | return $b; 238 | } 239 | 240 | if ($size < Constants::SIZE_8) { 241 | $b .= chr(Constants::MAP_8); 242 | $b .= $this->packUnsignedShortShort($size); 243 | 244 | return $b; 245 | } 246 | 247 | if ($size < Constants::SIZE_16) { 248 | $b .= chr(Constants::MAP_16); 249 | $b .= $this->packUnsignedShort($size); 250 | 251 | return $b; 252 | } 253 | 254 | if ($size < Constants::SIZE_32) { 255 | $b .= chr(Constants::MAP_32); 256 | $b .= $this->packUnsignedLong($size); 257 | 258 | return $b; 259 | } 260 | 261 | throw new SerializationException(sprintf('Unable to pack Array with size %d. Out of bound !', $size)); 262 | } 263 | 264 | /** 265 | * @param $value 266 | * 267 | * @return string 268 | * 269 | * @throws \OutOfBoundsException 270 | */ 271 | public function packText($value) 272 | { 273 | $length = strlen($value); 274 | $b = ''; 275 | 276 | if ($length < 16) { 277 | $b .= chr(Constants::TEXT_TINY + $length); 278 | $b .= $value; 279 | 280 | return $b; 281 | } 282 | 283 | if ($length < 256) { 284 | $b .= chr(Constants::TEXT_8); 285 | $b .= $this->packUnsignedShortShort($length); 286 | $b .= $value; 287 | 288 | return $b; 289 | } 290 | 291 | if ($length < 65536) { 292 | $b .= chr(Constants::TEXT_16); 293 | $b .= $this->packUnsignedShort($length); 294 | $b .= $value; 295 | 296 | return $b; 297 | } 298 | 299 | if ($length < 2147483643) { 300 | $b .= chr(Constants::TEXT_32); 301 | $b .= $this->packUnsignedLong($length); 302 | $b .= $value; 303 | 304 | return $b; 305 | } 306 | 307 | throw new \OutOfBoundsException(sprintf('String size overflow, Max PHP String size is %d, you gave a string of size %d', 308 | 2147483647, 309 | $length)); 310 | } 311 | 312 | /** 313 | * @return string 314 | */ 315 | public function getRunSignature() 316 | { 317 | return chr(Constants::SIGNATURE_RUN); 318 | } 319 | 320 | /** 321 | * @return string 322 | */ 323 | public function getEndSignature() 324 | { 325 | return str_repeat(chr(Constants::MISC_ZERO), 2); 326 | } 327 | 328 | /** 329 | * @param $stream 330 | * 331 | * @return string 332 | */ 333 | public function getSizeMarker($stream) 334 | { 335 | $size = mb_strlen($stream, 'ASCII'); 336 | 337 | return pack('n', $size); 338 | } 339 | 340 | /** 341 | * @param $value 342 | * 343 | * @return string 344 | * 345 | * @throws BoltOutOfBoundsException 346 | */ 347 | public function packInteger($value) 348 | { 349 | $pow15 = pow(2, 15); 350 | $pow31 = pow(2, 31); 351 | $b = ''; 352 | 353 | if ($value > -16 && $value < 128) { 354 | //$b .= chr(Constants::INT_8); 355 | //$b .= $this->packBigEndian($value, 2); 356 | return $this->packSignedShortShort($value); 357 | } 358 | 359 | if ($value > -129 && $value < -16) { 360 | $b .= chr(Constants::INT_8); 361 | $b .= $this->packSignedShortShort($value); 362 | 363 | return $b; 364 | } 365 | 366 | if ($value < -16 && $value > -129) { 367 | $b .= chr(Constants::INT_8); 368 | $b .= $this->packSignedShortShort($value); 369 | 370 | return $b; 371 | } 372 | 373 | if ($value < -128 && $value > -32769) { 374 | $b .= chr(Constants::INT_16); 375 | $b .= $this->packBigEndian($value, 2); 376 | 377 | return $b; 378 | } 379 | 380 | if ($value >= -16 && $value < 128) { 381 | return $this->packSignedShortShort($value); 382 | } 383 | 384 | if ($value > 127 && $value < $pow15) { 385 | $b .= chr(Constants::INT_16); 386 | $b .= pack('n', $value); 387 | 388 | return $b; 389 | } 390 | 391 | if ($value > 32767 && $value < $pow31) { 392 | $b .= chr(Constants::INT_32); 393 | $b .= pack('N', $value); 394 | 395 | return $b; 396 | } 397 | 398 | // 32 INTEGERS MINUS 399 | if ($value >= (-1 * abs(pow(2, 31))) && $value < (-1 * abs(pow(2, 15)))) { 400 | $b .= chr(Constants::INT_32); 401 | $b .= pack('N', $value); 402 | 403 | return $b; 404 | } 405 | 406 | // 64 INTEGERS POS 407 | if ($value >= pow(2, 31) && $value < pow(2, 63)) { 408 | $b .= chr(Constants::INT_64); 409 | //$b .= $this->packBigEndian($value, 8); 410 | $b .= pack('J', $value); 411 | 412 | return $b; 413 | } 414 | 415 | // 64 INTEGERS MINUS 416 | if ($value >= ((-1 * abs(pow(2, 63))) - 1) && $value < (-1 * abs(pow(2, 31)))) { 417 | $b .= chr(Constants::INT_64); 418 | $b .= pack('J', $value); 419 | 420 | return $b; 421 | } 422 | 423 | throw new BoltOutOfBoundsException(sprintf('Out of bound value, max is %d and you give %d', PHP_INT_MAX, $value)); 424 | } 425 | 426 | /** 427 | * @param int $integer 428 | * 429 | * @return string 430 | */ 431 | public function packUnsignedShortShort($integer) 432 | { 433 | return pack('C', $integer); 434 | } 435 | 436 | /** 437 | * @param int $integer 438 | * 439 | * @return string 440 | */ 441 | public function packSignedShortShort($integer) 442 | { 443 | return pack('c', $integer); 444 | } 445 | 446 | /** 447 | * @param int $integer 448 | * 449 | * @return string 450 | */ 451 | public function packSignedShort($integer) 452 | { 453 | $p = pack('s', $integer); 454 | $v = ord($p); 455 | 456 | return $v >> 32; 457 | } 458 | 459 | /** 460 | * @param int $integer 461 | * 462 | * @return string 463 | */ 464 | public function packUnsignedShort($integer) 465 | { 466 | return pack('n', $integer); 467 | } 468 | 469 | /** 470 | * @param int $integer 471 | * 472 | * @return string 473 | */ 474 | public function packUnsignedLong($integer) 475 | { 476 | return pack('N', $integer); 477 | } 478 | 479 | /** 480 | * @param int $value 481 | * 482 | * @return string 483 | */ 484 | public function packUnsignedLongLong($value) 485 | { 486 | return pack('J', $value); 487 | } 488 | 489 | /** 490 | * @param string $value 491 | * 492 | * @return bool 493 | */ 494 | public function isShortShort($value) 495 | { 496 | if (in_array($value, range(-16, 127))) { 497 | return true; 498 | } 499 | 500 | return false; 501 | } 502 | 503 | /** 504 | * @param int $integer 505 | * 506 | * @return bool 507 | */ 508 | public function isShort($integer) 509 | { 510 | $min = 128; 511 | $max = 32767; 512 | $minMin = -129; 513 | $minMax = -32768; 514 | 515 | return in_array($integer, range($min, $max)) || in_array($integer, range($minMin, $minMax)); 516 | } 517 | 518 | /** 519 | * @param int $x 520 | * @param int $bytes 521 | * 522 | * @return array 523 | * 524 | * @throws BoltInvalidArgumentException 525 | */ 526 | public function packBigEndian($x, $bytes) 527 | { 528 | if (($bytes <= 0) || ($bytes % 2)) { 529 | throw new BoltInvalidArgumentException(sprintf('Expected bytes count must be multiply of 2, %s given', $bytes)); 530 | } 531 | 532 | if (!is_int($x)) { 533 | throw new BoltInvalidArgumentException('Only integer values are supported'); 534 | } 535 | 536 | $ox = $x; 537 | $isNeg = false; 538 | 539 | if ($x < 0) { 540 | $isNeg = true; 541 | $x = abs($x); 542 | } 543 | 544 | if ($isNeg) { 545 | $x = bcadd($x, -1, 0); 546 | } //in negative domain starting point is -1, not 0 547 | $res = array(); 548 | 549 | for ($b = 0; $b < $bytes; $b += 2) { 550 | $chnk = (int) bcmod($x, 65536); 551 | $x = bcdiv($x, 65536, 0); 552 | $res[] = pack('n', $isNeg ? ~$chnk : $chnk); 553 | } 554 | 555 | if ($x || ($isNeg && ($chnk & 0x8000))) { 556 | throw new BoltOutOfBoundsException(sprintf('Overflow detected while attempting to pack %s into %s bytes', $ox, $bytes)); 557 | } 558 | 559 | return implode(array_reverse($res)); 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /src/PackStream/Serializer.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\Bolt\PackStream; 13 | 14 | use GraphAware\Bolt\PackStream\Structure\MessageStructure; 15 | use GraphAware\Bolt\Protocol\Message\AbstractMessage; 16 | use GraphAware\Bolt\Protocol\Message\FailureMessage; 17 | use GraphAware\Bolt\Protocol\Message\RawMessage; 18 | use GraphAware\Bolt\Protocol\Message\RecordMessage; 19 | use GraphAware\Bolt\Protocol\Message\SuccessMessage; 20 | 21 | class Serializer 22 | { 23 | /** 24 | * @var Packer 25 | */ 26 | protected $packer; 27 | 28 | /** 29 | * @var Unpacker 30 | */ 31 | protected $unpacker; 32 | 33 | /** 34 | * Serializer constructor. 35 | * 36 | * @param Packer $packer 37 | * @param Unpacker $unpacker 38 | */ 39 | public function __construct(Packer $packer, Unpacker $unpacker) 40 | { 41 | $this->packer = $packer; 42 | $this->unpacker = $unpacker; 43 | } 44 | 45 | /** 46 | * @param AbstractMessage $message 47 | */ 48 | public function serialize(AbstractMessage $message) 49 | { 50 | $buffer = ''; 51 | $buffer .= $this->packer->packStructureHeader($message->getFieldsLength(), $message->getSignature()); 52 | 53 | foreach ($message->getFields() as $field) { 54 | $buffer .= $this->packer->pack($field); 55 | } 56 | 57 | $message->setSerialization($buffer); 58 | } 59 | 60 | /** 61 | * @param RawMessage $message 62 | * 63 | * @return Structure\Structure 64 | */ 65 | public function deserialize(RawMessage $message) 66 | { 67 | return $this->unpacker->unpackRaw($message); 68 | } 69 | 70 | /** 71 | * @param MessageStructure $structure 72 | * @param RawMessage $rawMessage 73 | * 74 | * @return SuccessMessage 75 | */ 76 | public function convertStructureToSuccessMessage(MessageStructure $structure, RawMessage $rawMessage) 77 | { 78 | $message = new SuccessMessage($structure->getElements()[0]); 79 | $message->setSerialization($rawMessage->getBytes()); 80 | 81 | return $message; 82 | } 83 | 84 | /** 85 | * @param MessageStructure $structure 86 | * @param RawMessage $rawMessage 87 | * 88 | * @return RecordMessage 89 | */ 90 | public function convertStructureToRecordMessage(MessageStructure $structure, RawMessage $rawMessage) 91 | { 92 | $message = new RecordMessage($structure->getElements()[0]); 93 | $message->setSerialization($rawMessage->getBytes()); 94 | 95 | return $message; 96 | } 97 | 98 | /** 99 | * @param MessageStructure $structure 100 | * @param RawMessage $rawMessage 101 | * 102 | * @return FailureMessage 103 | */ 104 | public function convertStructureToFailureMessage(MessageStructure $structure, RawMessage $rawMessage) 105 | { 106 | $message = new FailureMessage($structure->getElements()[0]); 107 | $message->setSerialization($rawMessage->getBytes()); 108 | 109 | return $message; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/PackStream/StreamChannel.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\Bolt\PackStream; 13 | 14 | use GraphAware\Bolt\Protocol\Message\RawMessage; 15 | 16 | class StreamChannel 17 | { 18 | const ENCODING = 'ASCII'; 19 | 20 | /** 21 | * @var int 22 | */ 23 | protected $position = 0; 24 | 25 | /** 26 | * @var string 27 | */ 28 | protected $bytes; 29 | 30 | /** 31 | * @var int 32 | */ 33 | protected $length = 0; 34 | 35 | /** 36 | * @var \GraphAware\Bolt\IO\AbstractIO 37 | */ 38 | protected $io; 39 | 40 | /** 41 | * @param \GraphAware\Bolt\IO\AbstractIO $io 42 | */ 43 | public function __construct($io) 44 | { 45 | if ($io instanceof RawMessage) { 46 | $this->bytes = $io->getBytes(); 47 | $this->length = strlen($this->bytes); 48 | } else { 49 | $this->io = $io; 50 | //$this->io->assumeNonBlocking(); 51 | } 52 | } 53 | 54 | /** 55 | * @param int $n 56 | * 57 | * @return string 58 | */ 59 | public function read($n) 60 | { 61 | if (0 === $n) { 62 | return ''; 63 | } 64 | 65 | $remaining = ($n - $this->length) + $this->position; 66 | 67 | while ($remaining > 0) { 68 | //$this->io->wait(); 69 | if ($this->io->shouldEnableCrypto()) { 70 | $new = $this->io->read($remaining); 71 | } else { 72 | $new = $this->io->readChunk($remaining); 73 | } 74 | $this->bytes .= $new; 75 | $remaining -= strlen($new); 76 | } 77 | 78 | $this->length = strlen($this->bytes); 79 | $data = substr($this->bytes, $this->position, $n); 80 | $this->position += $n; 81 | 82 | return $data; 83 | } 84 | 85 | /** 86 | * @param int $n 87 | */ 88 | public function forward($n) 89 | { 90 | $n = (int) $n; 91 | 92 | if (($this->position + $n) > $this->length) { 93 | throw new \OutOfBoundsException(sprintf('No more bytes to read')); 94 | } 95 | 96 | $this->position += $n; 97 | } 98 | 99 | /** 100 | * @param int $n 101 | */ 102 | public function setPosition($n) 103 | { 104 | $n = (int) $n; 105 | 106 | if ($n > $this->length) { 107 | throw new \OutOfBoundsException(sprintf('Require position out of bound')); 108 | } 109 | 110 | $this->position = $n; 111 | } 112 | 113 | /** 114 | * @param int $n 115 | */ 116 | public function rewind($n) 117 | { 118 | $n = (int) $n; 119 | if ($n > $this->position) { 120 | throw new \InvalidArgumentException(sprintf('You try to rewind %d characters, but current position is %d', 121 | $n, 122 | $this->position 123 | )); 124 | } 125 | 126 | $this->position -= $n; 127 | } 128 | 129 | /** 130 | * @return int 131 | */ 132 | public function getPosition() 133 | { 134 | return $this->position; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/PackStream/Structure/MessageStructure.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\Bolt\PackStream\Structure; 13 | 14 | class MessageStructure 15 | { 16 | /** 17 | * @var string 18 | */ 19 | protected $signature; 20 | 21 | /** 22 | * @var int 23 | */ 24 | protected $size; 25 | 26 | /** 27 | * @var array 28 | */ 29 | protected $elements; 30 | 31 | /** 32 | * @param string $signature 33 | * @param int $size 34 | */ 35 | public function __construct($signature, $size) 36 | { 37 | $this->signature = $signature; 38 | $this->size = $size; 39 | } 40 | 41 | /** 42 | * @param $element 43 | */ 44 | public function addElement($element) 45 | { 46 | $this->elements[] = $element; 47 | } 48 | 49 | /** 50 | * @return mixed 51 | */ 52 | public function getElements() 53 | { 54 | return $this->elements[0]; 55 | } 56 | 57 | /** 58 | * @return bool 59 | */ 60 | public function isSuccess() 61 | { 62 | return 'SUCCESS' === $this->signature; 63 | } 64 | 65 | /** 66 | * @return bool 67 | */ 68 | public function isFailure() 69 | { 70 | return 'FAILURE' === $this->signature; 71 | } 72 | 73 | /** 74 | * @return bool 75 | */ 76 | public function isIgnored() 77 | { 78 | return 'IGNORED' === $this->signature; 79 | } 80 | 81 | /** 82 | * @return bool 83 | */ 84 | public function isRecord() 85 | { 86 | return 'RECORD' === $this->signature; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/PackStream/Structure/Structure.php: -------------------------------------------------------------------------------- 1 | signature = $signature; 25 | $this->size = (int) $size; 26 | } 27 | 28 | /** 29 | * @param $elt 30 | */ 31 | public function addElement($elt) 32 | { 33 | $this->elements[] = $elt; 34 | } 35 | 36 | /** 37 | * @param array $elts 38 | */ 39 | public function setElements($elts) 40 | { 41 | $this->elements = $elts; 42 | } 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function getSignature() 48 | { 49 | return $this->signature; 50 | } 51 | 52 | /** 53 | * @return array 54 | */ 55 | public function getElements() 56 | { 57 | if (in_array($this->signature, $this->types())) { 58 | return $this->elements; 59 | } 60 | 61 | return $this->elements[0]; 62 | } 63 | 64 | /** 65 | * @return int 66 | */ 67 | public function getSize() 68 | { 69 | return $this->size; 70 | } 71 | 72 | /** 73 | * @return array 74 | */ 75 | public function getValue() 76 | { 77 | return $this->elements; 78 | } 79 | 80 | /** 81 | * @return bool 82 | */ 83 | public function isSuccess() 84 | { 85 | return 'SUCCESS' === $this->signature; 86 | } 87 | 88 | /** 89 | * @return bool 90 | */ 91 | public function isRecord() 92 | { 93 | return 'RECORD' === $this->signature; 94 | } 95 | 96 | /** 97 | * @return bool 98 | */ 99 | public function isFailure() 100 | { 101 | return 'FAILURE' === $this->signature; 102 | } 103 | 104 | /** 105 | * @return bool 106 | */ 107 | public function hasFields() 108 | { 109 | return array_key_exists('fields', $this->getElements()); 110 | } 111 | 112 | /** 113 | * @return array 114 | */ 115 | public function getFields() 116 | { 117 | return $this->hasFields() ? $this->getElements()['fields'] : []; 118 | } 119 | 120 | /** 121 | * @return bool 122 | */ 123 | public function hasStatistics() 124 | { 125 | return array_key_exists('stats', $this->getElements()); 126 | } 127 | 128 | /** 129 | * @return array 130 | */ 131 | public function getStatistics() 132 | { 133 | return $this->hasStatistics() ? $this->getElements()['stats'] : []; 134 | } 135 | 136 | /** 137 | * @return bool 138 | */ 139 | public function hasType() 140 | { 141 | return array_key_exists('type', $this->getElements()); 142 | } 143 | 144 | /** 145 | * @return array 146 | */ 147 | public function getType() 148 | { 149 | return $this->hasType() ? $this->getElements()['type'] : []; 150 | } 151 | 152 | /** 153 | * @return array 154 | */ 155 | private function types() 156 | { 157 | return ['NODE', 'RELATIONSHIP', 'PATH', 'UNBOUND_RELATIONSHIP']; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Protocol/AbstractSession.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\Bolt\Protocol; 13 | 14 | use GraphAware\Bolt\IO\AbstractIO; 15 | use GraphAware\Bolt\PackStream\Serializer; 16 | use GraphAware\Bolt\PackStream\StreamChannel; 17 | use GraphAware\Bolt\PackStream\Unpacker; 18 | use GraphAware\Bolt\PackStream\Packer; 19 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 20 | 21 | abstract class AbstractSession implements SessionInterface 22 | { 23 | /** 24 | * @var AbstractIO 25 | */ 26 | protected $io; 27 | 28 | /** 29 | * @var EventDispatcherInterface 30 | */ 31 | protected $dispatcher; 32 | 33 | /** 34 | * @var Serializer 35 | */ 36 | protected $serializer; 37 | 38 | /** 39 | * @var Packer 40 | */ 41 | protected $packer; 42 | 43 | /** 44 | * @var Unpacker 45 | */ 46 | protected $unpacker; 47 | 48 | /** 49 | * @var ChunkWriter 50 | */ 51 | protected $writer; 52 | 53 | /** 54 | * @var StreamChannel 55 | */ 56 | protected $streamChannel; 57 | 58 | /** 59 | * @param AbstractIO $io 60 | * @param EventDispatcherInterface $dispatcher 61 | */ 62 | public function __construct(AbstractIO $io, EventDispatcherInterface $dispatcher) 63 | { 64 | $this->io = $io; 65 | $this->dispatcher = $dispatcher; 66 | $this->packer = new Packer(); 67 | $this->streamChannel = new StreamChannel($io); 68 | $this->unpacker = new Unpacker($this->streamChannel); 69 | $this->serializer = new Serializer($this->packer, $this->unpacker); 70 | $this->writer = new ChunkWriter($this->io, $this->packer); 71 | } 72 | 73 | /** 74 | * @return Serializer 75 | */ 76 | public function getSerializer() 77 | { 78 | return $this->serializer; 79 | } 80 | 81 | /** 82 | * @return ChunkWriter 83 | */ 84 | public function getWriter() 85 | { 86 | return $this->writer; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Protocol/ChunkWriter.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\Bolt\Protocol; 13 | 14 | use GraphAware\Bolt\IO\AbstractIO; 15 | use GraphAware\Bolt\PackStream\Packer; 16 | 17 | class ChunkWriter 18 | { 19 | const MAX_CHUNK_SIZE = 8192; 20 | 21 | /** 22 | * @var AbstractIO 23 | */ 24 | protected $io; 25 | 26 | /** 27 | * @var Packer 28 | */ 29 | protected $packer; 30 | 31 | /** 32 | * @param AbstractIO $io 33 | * @param Packer $packer 34 | */ 35 | public function __construct(AbstractIO $io, Packer $packer) 36 | { 37 | $this->io = $io; 38 | $this->packer = $packer; 39 | } 40 | 41 | /** 42 | * @param \GraphAware\Bolt\Protocol\Message\AbstractMessage[] $messages 43 | */ 44 | public function writeMessages(array $messages) 45 | { 46 | $raw = ''; 47 | 48 | foreach ($messages as $msg) { 49 | $chunkData = $msg->getSerialization(); 50 | $chunks = $this->splitChunk($chunkData); 51 | foreach ($chunks as $chunk) { 52 | $raw .= $this->packer->getSizeMarker($chunk); 53 | $raw .= $chunk; 54 | } 55 | $raw .= $this->packer->getEndSignature(); 56 | } 57 | 58 | $this->io->write($raw); 59 | } 60 | 61 | /** 62 | * @param string $data 63 | * 64 | * @return array 65 | */ 66 | public function splitChunk($data) 67 | { 68 | return str_split($data, self::MAX_CHUNK_SIZE); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Protocol/Constants.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\Bolt\Protocol; 13 | 14 | class Constants 15 | { 16 | // SIGNATURES 17 | 18 | const SIGNATURE_RUN = 0x10; 19 | 20 | const SIGNATURE_PULL_ALL = 0x3f; 21 | 22 | const SIGNATURE_DISCARD_ALL = 0x2f; 23 | 24 | const SIGNATURE_SUCCESS = 0x70; 25 | 26 | const SIGNATURE_INIT = 0x01; 27 | 28 | const SIGNATURE_FAILURE = 0x7f; 29 | 30 | const SIGNATURE_ACK_FAILURE = 0x0f; 31 | 32 | const SIGNATURE_IGNORE = 0x7E; 33 | 34 | const SIGNATURE_RECORD = 0x71; 35 | 36 | const SIGNATURE_NODE = 0x4e; 37 | 38 | const SIGNATURE_RELATIONSHIP = 0x52; 39 | 40 | const SIGNATURE_PATH = 0x50; 41 | 42 | const SIGNATURE_UNBOUND_RELATIONSHIP = 0x72; 43 | 44 | // STRUCTURES 45 | 46 | const STRUCTURE_TINY = 0xb0; 47 | 48 | const STRUCTURE_MEDIUM = 0xdc; 49 | 50 | const STRUCTURE_LARGE = 0xdd; 51 | 52 | // TEXTS 53 | 54 | const TEXT_TINY = 0x80; 55 | 56 | const TEXT_8 = 0xd0; 57 | 58 | const TEXT_16 = 0xd1; 59 | 60 | const TEXT_32 = 0xd2; 61 | 62 | // INTEGERS 63 | 64 | const INT_8 = 0xc8; 65 | 66 | const INT_16 = 0xc9; 67 | 68 | const INT_32 = 0xca; 69 | 70 | const INT_64 = 0xcb; 71 | 72 | // MAPS 73 | 74 | const MAP_TINY = 0xa0; 75 | 76 | const MAP_8 = 0xd8; 77 | 78 | const MAP_16 = 0xd9; 79 | 80 | const MAP_32 = 0xda; 81 | 82 | // LISTS 83 | 84 | const LIST_TINY = 0x90; 85 | 86 | const LIST_8 = 0xd4; 87 | 88 | const LIST_16 = 0xd5; 89 | 90 | const LIST_32 = 0xd6; 91 | 92 | // SIZES 93 | 94 | const SIZE_TINY = 16; 95 | 96 | const SIZE_8 = 256; 97 | 98 | const SIZE_16 = 65536; 99 | 100 | const SIZE_32 = 4294967295; 101 | 102 | const SIZE_MEDIUM = 256; 103 | 104 | const SIZE_LARGE = 65536; 105 | 106 | // FLOAT 107 | 108 | const MARKER_FLOAT = 0xc1; 109 | 110 | // MISC 111 | 112 | const MISC_ZERO = 0x00; 113 | 114 | const MARKER_NULL = 0xc0; 115 | 116 | const MARKER_TRUE = 0xc3; 117 | 118 | const MARKER_FALSE = 0xc2; 119 | } 120 | -------------------------------------------------------------------------------- /src/Protocol/Message/AbstractMessage.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\Bolt\Protocol\Message; 13 | 14 | abstract class AbstractMessage implements MessageInterface 15 | { 16 | /** 17 | * @var string 18 | */ 19 | protected $signature; 20 | 21 | /** 22 | * @var array 23 | */ 24 | protected $fields = []; 25 | 26 | /** 27 | * @var bool 28 | */ 29 | protected $isSerialized = false; 30 | 31 | /** 32 | * @var null 33 | */ 34 | protected $serialization = null; 35 | 36 | /** 37 | * @param string $signature 38 | * @param array $fields 39 | */ 40 | public function __construct($signature, array $fields = array()) 41 | { 42 | $this->signature = $signature; 43 | $this->fields = $fields; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function getSignature() 50 | { 51 | return $this->signature; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function getFields() 58 | { 59 | return $this->fields; 60 | } 61 | 62 | public function getFieldsLength() 63 | { 64 | return count($this->fields); 65 | } 66 | 67 | public function setSerialization($stream) 68 | { 69 | $this->serialization = $stream; 70 | $this->isSerialized = true; 71 | } 72 | 73 | public function getSerialization() 74 | { 75 | return $this->serialization; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function isSuccess() 82 | { 83 | return $this->getMessageType() === 'SUCCESS'; 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function isFailure() 90 | { 91 | return $this->getMessageType() === 'FAILURE'; 92 | } 93 | 94 | /** 95 | * @return bool 96 | */ 97 | public function isRecord() 98 | { 99 | return $this->getMessageType() === 'RECORD'; 100 | } 101 | 102 | /** 103 | * @return bool 104 | */ 105 | public function hasFields() 106 | { 107 | return !empty($this->fields); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Protocol/Message/AckFailureMessage.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\Bolt\Protocol\Message; 13 | 14 | use GraphAware\Bolt\Protocol\Constants; 15 | 16 | class AckFailureMessage extends AbstractMessage 17 | { 18 | const MESSAGE_TYPE = 'ACK_FAILURE'; 19 | 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function __construct() 24 | { 25 | parent::__construct(Constants::SIGNATURE_ACK_FAILURE); 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function getMessageType() 32 | { 33 | return self::MESSAGE_TYPE; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Protocol/Message/DiscardAllMessage.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\Bolt\Protocol\Message; 13 | 14 | use GraphAware\Bolt\Protocol\Constants; 15 | 16 | class DiscardAllMessage extends AbstractMessage 17 | { 18 | const MESSAGE_TYPE = 'DISCARD_ALL'; 19 | 20 | public function __construct() 21 | { 22 | parent::__construct(Constants::SIGNATURE_DISCARD_ALL); 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function getMessageType() 29 | { 30 | return self::MESSAGE_TYPE; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Protocol/Message/FailureMessage.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\Bolt\Protocol\Message; 13 | 14 | use GraphAware\Bolt\Protocol\Constants; 15 | 16 | class FailureMessage extends AbstractMessage 17 | { 18 | const MESSAGE_TYPE = 'FAILURE'; 19 | 20 | /** 21 | * @var string 22 | */ 23 | protected $code; 24 | 25 | /** 26 | * @var string 27 | */ 28 | protected $message; 29 | 30 | /** 31 | * @param $map 32 | */ 33 | public function __construct($map) 34 | { 35 | parent::__construct(Constants::SIGNATURE_FAILURE); 36 | $this->code = $map->get('code')->__toString(); 37 | $this->message = $map->get('message')->__toString(); 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function getMessageType() 44 | { 45 | return self::MESSAGE_TYPE; 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getCode() 52 | { 53 | return $this->code; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function getMessage() 60 | { 61 | return $this->message; 62 | } 63 | 64 | /** 65 | * @return string 66 | */ 67 | public function getFullMessage() 68 | { 69 | return $this->code.' : '.$this->message; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Protocol/Message/InitMessage.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\Bolt\Protocol\Message; 13 | 14 | use GraphAware\Bolt\Protocol\Constants; 15 | 16 | class InitMessage extends AbstractMessage 17 | { 18 | const MESSAGE_TYPE = 'INIT'; 19 | 20 | /** 21 | * @param string $userAgent 22 | * @param array $credentials 23 | */ 24 | public function __construct($userAgent, array $credentials) 25 | { 26 | $authToken = array(); 27 | 28 | if (isset($credentials[1]) && null !== $credentials[1]) { 29 | $authToken = [ 30 | 'scheme' => 'basic', 31 | 'principal' => $credentials[0], 32 | 'credentials' => $credentials[1], 33 | ]; 34 | } 35 | 36 | parent::__construct(Constants::SIGNATURE_INIT, array($userAgent, $authToken)); 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function getMessageType() 43 | { 44 | return self::MESSAGE_TYPE; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Protocol/Message/MessageInterface.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\Bolt\Protocol\Message; 13 | 14 | interface MessageInterface 15 | { 16 | /** 17 | * @return string 18 | */ 19 | public function getSignature(); 20 | 21 | /** 22 | * @return string 23 | */ 24 | public function getMessageType(); 25 | 26 | /** 27 | * @return array 28 | */ 29 | public function getFields(); 30 | 31 | /** 32 | * @return bool 33 | */ 34 | public function isSuccess(); 35 | 36 | /** 37 | * @return bool 38 | */ 39 | public function isFailure(); 40 | 41 | /* 42 | public function isIgnored(); 43 | 44 | public function isRecord(); 45 | */ 46 | } 47 | -------------------------------------------------------------------------------- /src/Protocol/Message/PullAllMessage.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\Bolt\Protocol\Message; 13 | 14 | use GraphAware\Bolt\Protocol\Constants; 15 | 16 | class PullAllMessage extends AbstractMessage 17 | { 18 | const MESSAGE_TYPE = 'PULL_ALL'; 19 | 20 | public function __construct() 21 | { 22 | parent::__construct(Constants::SIGNATURE_PULL_ALL); 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function getMessageType() 29 | { 30 | return self::MESSAGE_TYPE; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Protocol/Message/RawMessage.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\Bolt\Protocol\Message; 13 | 14 | class RawMessage 15 | { 16 | protected $bytes = ''; 17 | 18 | /** 19 | * @param string $bytes 20 | */ 21 | public function __construct($bytes) 22 | { 23 | $this->bytes = $bytes; 24 | } 25 | 26 | /** 27 | * @return int 28 | */ 29 | public function getLength() 30 | { 31 | return mb_strlen($this->bytes, 'ASCII'); 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function getBytes() 38 | { 39 | return $this->bytes; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Protocol/Message/RecordMessage.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\Bolt\Protocol\Message; 13 | 14 | use GraphAware\Bolt\Protocol\Constants; 15 | use GraphAware\Common\Result\RecordViewInterface; 16 | 17 | class RecordMessage extends AbstractMessage implements RecordViewInterface 18 | { 19 | const MESSAGE_TYPE = 'RECORD'; 20 | 21 | protected $values; 22 | 23 | public function __construct($list) 24 | { 25 | parent::__construct(Constants::SIGNATURE_RECORD); 26 | $this->values = $list; 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function getMessageType() 33 | { 34 | return self::MESSAGE_TYPE; 35 | } 36 | 37 | public function getValues() 38 | { 39 | return $this->values; 40 | } 41 | 42 | public function keys() 43 | { 44 | // TODO: Implement keys() method. 45 | } 46 | 47 | public function hasValues() 48 | { 49 | // TODO: Implement hasValues() method. 50 | } 51 | 52 | public function value($key) 53 | { 54 | // TODO: Implement value() method. 55 | } 56 | 57 | public function values() 58 | { 59 | // TODO: Implement values() method. 60 | } 61 | 62 | public function valueByIndex($index) 63 | { 64 | // TODO: Implement valueByIndex() method. 65 | } 66 | 67 | public function record() 68 | { 69 | // TODO: Implement record() method. 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function get($key) 76 | { 77 | // TODO: Implement get() method. 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function hasValue($key) 84 | { 85 | // TODO: Implement hasValue() method. 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | public function nodeValue($key) 92 | { 93 | // TODO: Implement nodeValue() method. 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | public function relationshipValue($key) 100 | { 101 | // TODO: Implement relationshipValue() method. 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | public function pathValue($key) 108 | { 109 | // TODO: Implement pathValue() method. 110 | } 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | public function getByIndex($index) 116 | { 117 | // TODO: Implement getByIndex() method. 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Protocol/Message/RunMessage.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\Bolt\Protocol\Message; 13 | 14 | use GraphAware\Bolt\Protocol\Constants; 15 | 16 | class RunMessage extends AbstractMessage 17 | { 18 | const MESSAGE_TYPE = 'RUN'; 19 | 20 | /** 21 | * @var string 22 | */ 23 | protected $statement; 24 | 25 | /** 26 | * @var array 27 | */ 28 | protected $params; 29 | 30 | /** 31 | * @var null|string 32 | */ 33 | protected $tag; 34 | 35 | /** 36 | * @param string $statement 37 | * @param array $params 38 | * @param null|string $tag 39 | */ 40 | public function __construct($statement, array $params = array(), $tag = null) 41 | { 42 | parent::__construct(Constants::SIGNATURE_RUN); 43 | $this->fields = array($statement, $params); 44 | $this->statement = $statement; 45 | $this->params = $params; 46 | $this->tag = $tag; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function getMessageType() 53 | { 54 | return self::MESSAGE_TYPE; 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function getFields() 61 | { 62 | return array($this->statement, $this->params); 63 | } 64 | 65 | /** 66 | * @return string 67 | */ 68 | public function getStatement() 69 | { 70 | return $this->statement; 71 | } 72 | 73 | /** 74 | * @return array 75 | */ 76 | public function getParams() 77 | { 78 | return $this->params; 79 | } 80 | 81 | /** 82 | * @return null|string 83 | */ 84 | public function getTag() 85 | { 86 | return $this->tag; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Protocol/Message/SuccessMessage.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\Bolt\Protocol\Message; 13 | 14 | use GraphAware\Bolt\PackStream\Structure\Map; 15 | use GraphAware\Bolt\Protocol\Constants; 16 | 17 | class SuccessMessage extends AbstractMessage 18 | { 19 | const MESSAGE_TYPE = 'SUCCESS'; 20 | 21 | /** 22 | * @var array 23 | */ 24 | protected $map; 25 | 26 | /** 27 | * @param array $map 28 | */ 29 | public function __construct($map) 30 | { 31 | parent::__construct(Constants::SIGNATURE_SUCCESS); 32 | $this->map = $map; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function getMessageType() 39 | { 40 | return self::MESSAGE_TYPE; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getFields() 47 | { 48 | return $this->map['fields']; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function hasFields() 55 | { 56 | return array_key_exists('fields', $this->map); 57 | } 58 | 59 | /** 60 | * @return bool 61 | */ 62 | public function hasStatistics() 63 | { 64 | return array_key_exists('stats', $this->map); 65 | } 66 | 67 | /** 68 | * @return array 69 | */ 70 | public function getStatistics() 71 | { 72 | return $this->map['stats']; 73 | } 74 | 75 | /** 76 | * @return bool 77 | */ 78 | public function hasType() 79 | { 80 | return array_key_exists('type', $this->map); 81 | } 82 | 83 | /** 84 | * @return mixed 85 | */ 86 | public function getType() 87 | { 88 | return $this->map['type']; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Protocol/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\Bolt\Protocol; 13 | 14 | use GraphAware\Bolt\Exception\BoltInvalidArgumentException; 15 | use GraphAware\Bolt\Protocol\Message\PullAllMessage; 16 | use GraphAware\Bolt\Protocol\Message\RunMessage; 17 | use GraphAware\Bolt\Protocol\V1\Session; 18 | use GraphAware\Common\Driver\PipelineInterface; 19 | use GraphAware\Common\Result\ResultCollection; 20 | 21 | class Pipeline implements PipelineInterface 22 | { 23 | /** 24 | * @var Session 25 | */ 26 | protected $session; 27 | 28 | /** 29 | * @var RunMessage[] 30 | */ 31 | protected $messages = []; 32 | 33 | /** 34 | * @param Session $session 35 | */ 36 | public function __construct(Session $session) 37 | { 38 | $this->session = $session; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function push($query, array $parameters = array(), $tag = null) 45 | { 46 | if (null === $query) { 47 | throw new BoltInvalidArgumentException('Statement cannot be null'); 48 | } 49 | $this->messages[] = new RunMessage($query, $parameters, $tag); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function run() 56 | { 57 | $pullAllMessage = new PullAllMessage(); 58 | $batch = []; 59 | $resultCollection = new ResultCollection(); 60 | 61 | foreach ($this->messages as $message) { 62 | $result = $this->session->run($message->getStatement(), $message->getParams(), $message->getTag()); 63 | $resultCollection->add($result); 64 | } 65 | 66 | return $resultCollection; 67 | } 68 | 69 | /** 70 | * @return RunMessage[] 71 | */ 72 | public function getMessages() 73 | { 74 | return $this->messages; 75 | } 76 | 77 | /** 78 | * @return bool 79 | */ 80 | public function isEmpty() 81 | { 82 | return empty($this->messages); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Protocol/SessionInterface.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\Bolt\Protocol; 13 | 14 | use GraphAware\Common\Driver\SessionInterface as BaseSessionInterface; 15 | 16 | interface SessionInterface extends BaseSessionInterface 17 | { 18 | /** 19 | * @return string 20 | */ 21 | public static function getProtocolVersion(); 22 | 23 | /** 24 | * @param string $statement 25 | * @param array $parameters 26 | * @param null|string $tag 27 | * 28 | * @return \GraphAware\Bolt\Result\Result 29 | */ 30 | public function run($statement, array $parameters = array(), $tag = null); 31 | 32 | /** 33 | * @param Pipeline $pipeline 34 | * 35 | * @return mixed 36 | */ 37 | public function runPipeline(Pipeline $pipeline); 38 | 39 | /** 40 | * @param null|string $query 41 | * @param array $parameters 42 | * @param null|string $tag 43 | * 44 | * @return Pipeline 45 | */ 46 | public function createPipeline($query = null, array $parameters = array(), $tag = null); 47 | 48 | /** 49 | * @return \GraphAware\Bolt\Protocol\V1\Transaction 50 | */ 51 | public function transaction(); 52 | } 53 | -------------------------------------------------------------------------------- /src/Protocol/SessionRegistry.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\Bolt\Protocol; 13 | 14 | use GraphAware\Bolt\IO\AbstractIO; 15 | use GraphAware\Common\Driver\SessionInterface; 16 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 17 | 18 | class SessionRegistry 19 | { 20 | /** 21 | * @var AbstractIO 22 | */ 23 | protected $io; 24 | 25 | /** 26 | * @var EventDispatcherInterface 27 | */ 28 | protected $dispatcher; 29 | 30 | /** 31 | * @var array 32 | */ 33 | protected $sessions = []; 34 | 35 | /** 36 | * @param AbstractIO $io 37 | * @param EventDispatcherInterface $dispatcher 38 | */ 39 | public function __construct(AbstractIO $io, EventDispatcherInterface $dispatcher) 40 | { 41 | $this->io = $io; 42 | $this->dispatcher = $dispatcher; 43 | } 44 | 45 | /** 46 | * @param string $sessionClass 47 | */ 48 | public function registerSession($sessionClass) 49 | { 50 | $v = (int) $sessionClass::getProtocolVersion(); 51 | 52 | if (array_key_exists($v, $this->sessions)) { 53 | throw new \RuntimeException(sprintf('There is already a Session registered for supporting Version#%d', $v)); 54 | } 55 | 56 | $this->sessions[$v] = $sessionClass; 57 | } 58 | 59 | /** 60 | * @return array 61 | */ 62 | public function getSupportedVersions() 63 | { 64 | return array_keys($this->sessions); 65 | } 66 | 67 | /** 68 | * @param int $version 69 | * 70 | * @return bool 71 | */ 72 | public function supportsVersion($version) 73 | { 74 | return array_key_exists((int) $version, $this->sessions); 75 | } 76 | 77 | /** 78 | * @param int $version 79 | * @param array $credentials 80 | * 81 | * @return SessionInterface 82 | */ 83 | public function getSession($version, array $credentials) 84 | { 85 | $v = (int) $version; 86 | 87 | if (!$this->supportsVersion($v)) { 88 | throw new \InvalidArgumentException(sprintf('No session registered supporting Version %d', $v)); 89 | } 90 | $class = $this->sessions[$v]; 91 | 92 | return new $class($this->io, $this->dispatcher, $credentials); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Protocol/V1/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\Bolt\Protocol\V1; 13 | 14 | use GraphAware\Bolt\Exception\MessageFailureException; 15 | 16 | class Response 17 | { 18 | /** 19 | * @var bool 20 | */ 21 | protected $completed = false; 22 | 23 | /** 24 | * @var array 25 | */ 26 | protected $records = []; 27 | 28 | /** 29 | * @var array 30 | */ 31 | protected $metadata = []; 32 | 33 | /** 34 | * @param $metadata 35 | */ 36 | public function onSuccess($metadata) 37 | { 38 | $this->completed = true; 39 | $this->metadata[] = $metadata; 40 | } 41 | 42 | /** 43 | * @param $metadata 44 | */ 45 | public function onRecord($metadata) 46 | { 47 | $this->records[] = $metadata; 48 | } 49 | 50 | /** 51 | * @return array 52 | */ 53 | public function getRecords() 54 | { 55 | return $this->records; 56 | } 57 | 58 | /** 59 | * @param $metadata 60 | * 61 | * @throws MessageFailureException 62 | */ 63 | public function onFailure($metadata) 64 | { 65 | $this->completed = true; 66 | $e = new MessageFailureException($metadata->getElements()['message']); 67 | $e->setStatusCode($metadata->getElements()['code']); 68 | 69 | throw $e; 70 | } 71 | 72 | public function getMetadata() 73 | { 74 | return $this->metadata; 75 | } 76 | 77 | /** 78 | * @return bool 79 | */ 80 | public function isCompleted() 81 | { 82 | return $this->completed; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Protocol/V1/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\Bolt\Protocol\V1; 13 | 14 | use GraphAware\Bolt\Driver; 15 | use GraphAware\Bolt\Exception\BoltInvalidArgumentException; 16 | use GraphAware\Bolt\IO\AbstractIO; 17 | use GraphAware\Bolt\Protocol\AbstractSession; 18 | use GraphAware\Bolt\Protocol\Message\AbstractMessage; 19 | use GraphAware\Bolt\Protocol\Message\AckFailureMessage; 20 | use GraphAware\Bolt\Protocol\Message\InitMessage; 21 | use GraphAware\Bolt\Protocol\Message\PullAllMessage; 22 | use GraphAware\Bolt\Protocol\Message\RawMessage; 23 | use GraphAware\Bolt\Protocol\Message\RunMessage; 24 | use GraphAware\Bolt\Protocol\Pipeline; 25 | use GraphAware\Bolt\Exception\MessageFailureException; 26 | use GraphAware\Bolt\Result\Result as CypherResult; 27 | use GraphAware\Common\Cypher\Statement; 28 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 29 | 30 | class Session extends AbstractSession 31 | { 32 | const PROTOCOL_VERSION = 1; 33 | 34 | /** 35 | * @var bool 36 | */ 37 | public $isInitialized = false; 38 | 39 | /** 40 | * @var Transaction|null 41 | */ 42 | public $transaction; 43 | 44 | /** 45 | * @var array 46 | */ 47 | protected $credentials; 48 | 49 | /** 50 | * @param AbstractIO $io 51 | * @param EventDispatcherInterface $dispatcher 52 | * @param array $credentials 53 | */ 54 | public function __construct(AbstractIO $io, EventDispatcherInterface $dispatcher, array $credentials) 55 | { 56 | parent::__construct($io, $dispatcher); 57 | 58 | $this->credentials = $credentials; 59 | $this->init(); 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public static function getProtocolVersion() 66 | { 67 | return self::PROTOCOL_VERSION; 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | public function run($statement, array $parameters = array(), $tag = null) 74 | { 75 | if (null === $statement) { 76 | //throw new BoltInvalidArgumentException("Statement cannot be null"); 77 | } 78 | $messages = array( 79 | new RunMessage($statement, $parameters), 80 | ); 81 | 82 | $messages[] = new PullAllMessage(); 83 | $this->sendMessages($messages); 84 | 85 | $runResponse = new Response(); 86 | $r = $this->unpacker->unpack(); 87 | 88 | if ($r->isSuccess()) { 89 | $runResponse->onSuccess($r); 90 | } elseif ($r->isFailure()) { 91 | try { 92 | $runResponse->onFailure($r); 93 | } catch (MessageFailureException $e) { 94 | // server ignores the PULL ALL 95 | $this->handleIgnore(); 96 | $this->sendMessage(new AckFailureMessage()); 97 | // server success for ACK FAILURE 98 | $r2 = $this->handleSuccess(); 99 | throw $e; 100 | } 101 | } 102 | 103 | $pullResponse = new Response(); 104 | 105 | while (!$pullResponse->isCompleted()) { 106 | $r = $this->unpacker->unpack(); 107 | 108 | if ($r->isRecord()) { 109 | $pullResponse->onRecord($r); 110 | } 111 | 112 | if ($r->isSuccess()) { 113 | $pullResponse->onSuccess($r); 114 | } 115 | 116 | if ($r->isFailure()) { 117 | $pullResponse->onFailure($r); 118 | } 119 | } 120 | 121 | $cypherResult = new CypherResult(Statement::create($statement, $parameters, $tag)); 122 | $cypherResult->setFields($runResponse->getMetadata()[0]->getElements()); 123 | 124 | foreach ($pullResponse->getRecords() as $record) { 125 | $cypherResult->pushRecord($record); 126 | } 127 | 128 | $pullMeta = $pullResponse->getMetadata(); 129 | 130 | if (isset($pullMeta[0])) { 131 | if (isset($pullMeta[0]->getElements()['stats'])) { 132 | $cypherResult->setStatistics($pullResponse->getMetadata()[0]->getElements()['stats']); 133 | } else { 134 | $cypherResult->setStatistics([]); 135 | } 136 | } 137 | 138 | return $cypherResult; 139 | } 140 | 141 | /** 142 | * {@inheritdoc} 143 | */ 144 | public function runPipeline(Pipeline $pipeline) 145 | { 146 | } 147 | 148 | /** 149 | * {@inheritdoc} 150 | */ 151 | public function createPipeline($query = null, array $parameters = [], $tag = null) 152 | { 153 | return new Pipeline($this); 154 | } 155 | 156 | /** 157 | * @param string $statement 158 | * @param array $parameters 159 | * @param null|string $tag 160 | * 161 | * @return CypherResult 162 | */ 163 | public function recv($statement, array $parameters = array(), $tag = null) 164 | { 165 | $runResponse = new Response(); 166 | $r = $this->unpacker->unpack(); 167 | $shouldThrow = false; 168 | if ($r->isFailure()) { 169 | try { 170 | $runResponse->onFailure($r); 171 | } catch (MessageFailureException $e) { 172 | // server ignores the PULL ALL 173 | $this->handleIgnore(); 174 | $this->handleIgnore(); 175 | $this->sendMessage(new AckFailureMessage()); 176 | $this->handleIgnore(); 177 | // server success for ACK FAILURE 178 | $r2 = $this->handleSuccess(); 179 | $shouldThrow = $e; 180 | } 181 | } 182 | 183 | if ($shouldThrow !== false) { 184 | throw $shouldThrow; 185 | } 186 | 187 | if ($r->isSuccess()) { 188 | $runResponse->onSuccess($r); 189 | } 190 | 191 | $pullResponse = new Response(); 192 | 193 | while (!$pullResponse->isCompleted()) { 194 | $r = $this->unpacker->unpack(); 195 | 196 | if ($r->isRecord()) { 197 | $pullResponse->onRecord($r); 198 | } 199 | 200 | if ($r->isSuccess()) { 201 | $pullResponse->onSuccess($r); 202 | } 203 | } 204 | 205 | $cypherResult = new CypherResult(Statement::create($statement, $parameters, $tag)); 206 | $cypherResult->setFields($runResponse->getMetadata()[0]->getElements()); 207 | 208 | foreach ($pullResponse->getRecords() as $record) { 209 | $cypherResult->pushRecord($record); 210 | } 211 | 212 | if (null !== $pullResponse && array_key_exists(0, $pullResponse->getMetadata())) { 213 | $metadata = $pullResponse->getMetadata()[0]->getElements(); 214 | $stats = array_key_exists('stats', $metadata) ? $metadata['stats'] : array(); 215 | $cypherResult->setStatistics($stats); 216 | } 217 | 218 | return $cypherResult; 219 | } 220 | 221 | /** 222 | * @throws \Exception 223 | */ 224 | public function init() 225 | { 226 | $this->io->assertConnected(); 227 | $ua = Driver::getUserAgent(); 228 | $this->sendMessage(new InitMessage($ua, $this->credentials)); 229 | $responseMessage = $this->receiveMessageInit(); 230 | 231 | if ($responseMessage->getSignature() != 'SUCCESS') { 232 | throw new \Exception('Unable to INIT'); 233 | } 234 | 235 | $this->isInitialized = true; 236 | } 237 | 238 | /** 239 | * @return \GraphAware\Bolt\PackStream\Structure\Structure 240 | */ 241 | public function receiveMessageInit() 242 | { 243 | $bytes = ''; 244 | $chunkHeader = $this->io->read(2); 245 | list(, $chunkSize) = unpack('n', $chunkHeader); 246 | $nextChunkLength = $chunkSize; 247 | 248 | do { 249 | if ($nextChunkLength) { 250 | $bytes .= $this->io->read($nextChunkLength); 251 | } 252 | 253 | list(, $next) = unpack('n', $this->io->read(2)); 254 | $nextChunkLength = $next; 255 | } while ($nextChunkLength > 0); 256 | 257 | $rawMessage = new RawMessage($bytes); 258 | 259 | $message = $this->serializer->deserialize($rawMessage); 260 | 261 | if ($message->getSignature() === 'FAILURE') { 262 | $msg = sprintf('Neo4j Exception "%s" with code "%s"', $message->getElements()['message'], $message->getElements()['code']); 263 | $e = new MessageFailureException($msg); 264 | $e->setStatusCode($message->getElements()['code']); 265 | $this->sendMessage(new AckFailureMessage()); 266 | 267 | throw $e; 268 | } 269 | 270 | return $message; 271 | } 272 | 273 | /** 274 | * @return \GraphAware\Bolt\PackStream\Structure\Structure 275 | */ 276 | public function receiveMessage() 277 | { 278 | $bytes = ''; 279 | 280 | $chunkHeader = $this->io->read(2); 281 | list(, $chunkSize) = unpack('n', $chunkHeader); 282 | $nextChunkLength = $chunkSize; 283 | 284 | do { 285 | if ($nextChunkLength) { 286 | $bytes .= $this->io->read($nextChunkLength); 287 | } 288 | 289 | list(, $next) = unpack('n', $this->io->read(2)); 290 | $nextChunkLength = $next; 291 | } while ($nextChunkLength > 0); 292 | 293 | $rawMessage = new RawMessage($bytes); 294 | $message = $this->serializer->deserialize($rawMessage); 295 | 296 | if ($message->getSignature() === 'FAILURE') { 297 | $msg = sprintf('Neo4j Exception "%s" with code "%s"', $message->getElements()['message'], $message->getElements()['code']); 298 | $e = new MessageFailureException($msg); 299 | $e->setStatusCode($message->getElements()['code']); 300 | 301 | throw $e; 302 | } 303 | 304 | return $message; 305 | } 306 | 307 | /** 308 | * @param \GraphAware\Bolt\Protocol\Message\AbstractMessage $message 309 | */ 310 | public function sendMessage(AbstractMessage $message) 311 | { 312 | $this->sendMessages(array($message)); 313 | } 314 | 315 | /** 316 | * @param \GraphAware\Bolt\Protocol\Message\AbstractMessage[] $messages 317 | */ 318 | public function sendMessages(array $messages) 319 | { 320 | foreach ($messages as $message) { 321 | $this->serializer->serialize($message); 322 | } 323 | 324 | $this->writer->writeMessages($messages); 325 | } 326 | 327 | /** 328 | * {@inheritdoc} 329 | */ 330 | public function close() 331 | { 332 | $this->io->close(); 333 | $this->isInitialized = false; 334 | } 335 | 336 | /** 337 | * {@inheritdoc} 338 | */ 339 | public function transaction() 340 | { 341 | if ($this->transaction instanceof Transaction) { 342 | throw new \RuntimeException('A transaction is already bound to this session'); 343 | } 344 | 345 | return new Transaction($this); 346 | } 347 | 348 | private function handleSuccess() 349 | { 350 | return $this->handleMessage('SUCCESS'); 351 | } 352 | 353 | private function handleIgnore() 354 | { 355 | $this->handleMessage('IGNORED'); 356 | } 357 | 358 | private function handleMessage($messageType) 359 | { 360 | $message = $this->unpacker->unpack(); 361 | if ($messageType !== $message->getSignature()) { 362 | throw new \RuntimeException(sprintf('Expected an %s message, got %s', $messageType, $message->getSignature())); 363 | } 364 | 365 | return $message; 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/Protocol/V1/Signatures.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\Bolt\Protocol\V1; 13 | 14 | use GraphAware\Bolt\Exception\MessageFailureException; 15 | use GraphAware\Common\Cypher\Statement; 16 | use GraphAware\Common\Transaction\TransactionInterface; 17 | 18 | class Transaction implements TransactionInterface 19 | { 20 | private static $NO_ROLLBACK_STATUS_CODE = 'ClientNotification'; 21 | 22 | const OPENED = 'OPEN'; 23 | 24 | const COMMITED = 'COMMITED'; 25 | 26 | const ROLLED_BACK = 'TRANSACTION_ROLLED_BACK'; 27 | 28 | /** 29 | * @var string|null 30 | */ 31 | protected $state; 32 | 33 | /** 34 | * @var Session 35 | */ 36 | protected $session; 37 | 38 | /** 39 | * @var bool 40 | */ 41 | protected $closed = false; 42 | 43 | /** 44 | * @param Session $session 45 | */ 46 | public function __construct(Session $session) 47 | { 48 | $this->session = $session; 49 | $this->session->transaction = $this; 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function isOpen() 56 | { 57 | return $this->state === self::OPENED; 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function isCommited() 64 | { 65 | return $this->state === self::COMMITED; 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function isRolledBack() 72 | { 73 | return $this->state === self::ROLLED_BACK; 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | public function rollback() 80 | { 81 | $this->assertNotClosed(); 82 | $this->assertStarted(); 83 | $this->session->run('ROLLBACK'); 84 | $this->closed = true; 85 | $this->state = self::ROLLED_BACK; 86 | $this->session->transaction = null; 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public function status() 93 | { 94 | return $this->getStatus(); 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | */ 100 | public function commit() 101 | { 102 | $this->success(); 103 | } 104 | 105 | /** 106 | * {@inheritdoc} 107 | */ 108 | public function push($query, array $parameters = array(), $tag = null) 109 | { 110 | // 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | public function begin() 117 | { 118 | $this->assertNotStarted(); 119 | $this->session->run('BEGIN'); 120 | $this->state = self::OPENED; 121 | } 122 | 123 | /** 124 | * {@inheritdoc} 125 | */ 126 | public function run(Statement $statement) 127 | { 128 | try { 129 | return $this->session->run($statement->text(), $statement->parameters(), $statement->getTag()); 130 | } catch (MessageFailureException $e) { 131 | $spl = explode('.', $e->getStatusCode()); 132 | if (self::$NO_ROLLBACK_STATUS_CODE !== $spl[1]) { 133 | $this->state = self::ROLLED_BACK; 134 | $this->closed = true; 135 | } 136 | throw $e; 137 | } 138 | } 139 | 140 | /** 141 | * @return string 142 | */ 143 | public function getStatus() 144 | { 145 | return $this->state; 146 | } 147 | 148 | /** 149 | * @param Statement[] $statements 150 | * 151 | * @return \GraphAware\Common\Result\ResultCollection 152 | */ 153 | public function runMultiple(array $statements) 154 | { 155 | $pipeline = $this->session->createPipeline(); 156 | 157 | foreach ($statements as $statement) { 158 | $pipeline->push($statement->text(), $statement->parameters(), $statement->getTag()); 159 | } 160 | 161 | return $pipeline->run(); 162 | } 163 | 164 | public function success() 165 | { 166 | $this->assertNotClosed(); 167 | $this->assertStarted(); 168 | $this->session->run('COMMIT'); 169 | $this->state = self::COMMITED; 170 | $this->closed = true; 171 | $this->session->transaction = null; 172 | } 173 | 174 | /** 175 | * @return Session 176 | */ 177 | public function getSession() 178 | { 179 | return $this->session; 180 | } 181 | 182 | private function assertStarted() 183 | { 184 | if ($this->state !== self::OPENED) { 185 | throw new \RuntimeException('This transaction has not been started'); 186 | } 187 | } 188 | 189 | private function assertNotStarted() 190 | { 191 | if (null !== $this->state) { 192 | throw new \RuntimeException(sprintf('Can not begin transaction, Transaction State is "%s"', $this->state)); 193 | } 194 | } 195 | 196 | private function assertNotClosed() 197 | { 198 | if (false !== $this->closed) { 199 | throw new \RuntimeException('This Transaction is closed'); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/Record/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\Bolt\Record; 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 $key 68 | * 69 | * @return mixed|\GraphAware\Bolt\Result\Type\Node|\GraphAware\Bolt\Result\Type\Relationship|\GraphAware\Bolt\Result\Type\Path 70 | */ 71 | public function value($key) 72 | { 73 | return $this->values[$this->keyToIndexMap[$key]]; 74 | } 75 | 76 | /** 77 | * @param string $key 78 | * @param mixed $defaultValue 79 | * 80 | * @return \GraphAware\Bolt\Result\Type\Node|\GraphAware\Bolt\Result\Type\Path|\GraphAware\Bolt\Result\Type\Relationship|mixed 81 | */ 82 | public function get($key, $defaultValue = null) 83 | { 84 | if (!isset($this->keyToIndexMap[$key]) && 2 === func_num_args()) { 85 | return $defaultValue; 86 | } 87 | 88 | return $this->value($key); 89 | } 90 | 91 | /** 92 | * Returns the Node for value $key. Ease IDE integration. 93 | * 94 | * @param $key 95 | * 96 | * @return \GraphAware\Bolt\Result\Type\Node 97 | * 98 | * @throws \InvalidArgumentException When the value is not null or instance of Node 99 | */ 100 | public function nodeValue($key) 101 | { 102 | if (!isset($this->keyToIndexMap[$key]) || !$this->values[$this->keyToIndexMap[$key]] instanceof Node) { 103 | throw new \InvalidArgumentException(sprintf('value for %s is not of type %s', $key, 'NODE')); 104 | } 105 | 106 | return $this->value($key); 107 | } 108 | 109 | /** 110 | * @param $key 111 | * 112 | * @return \GraphAware\Bolt\Result\Type\Relationship 113 | * 114 | * @throws \InvalidArgumentException When the value is not null or instance of Relationship 115 | */ 116 | public function relationshipValue($key) 117 | { 118 | if (!isset($this->keyToIndexMap[$key]) || !$this->values[$this->keyToIndexMap[$key]] instanceof Relationship) { 119 | throw new \InvalidArgumentException(sprintf('value for %s is not of type %s', $key, 'RELATIONSHIP')); 120 | } 121 | 122 | return $this->value($key); 123 | } 124 | 125 | /** 126 | * @param $key 127 | * 128 | * @return \GraphAware\Bolt\Result\Type\Path 129 | * 130 | * @throws \InvalidArgumentException When the value is not null or instance of Path 131 | */ 132 | public function pathValue($key) 133 | { 134 | if (!isset($this->keyToIndexMap[$key]) || !$this->values[$this->keyToIndexMap[$key]] instanceof Path) { 135 | throw new \InvalidArgumentException(sprintf('value for %s is not of type %s', $key, 'PATH')); 136 | } 137 | 138 | return $this->value($key); 139 | } 140 | 141 | /** 142 | * {@inheritdoc} 143 | */ 144 | public function values() 145 | { 146 | return $this->values; 147 | } 148 | 149 | /** 150 | * {@inheritdoc} 151 | */ 152 | public function hasValue($key) 153 | { 154 | return array_key_exists($key, $this->keyToIndexMap); 155 | } 156 | 157 | /** 158 | * {@inheritdoc} 159 | */ 160 | public function valueByIndex($index) 161 | { 162 | return $this->values[$index]; 163 | } 164 | 165 | /** 166 | * {@inheritdoc} 167 | */ 168 | public function getByIndex($index) 169 | { 170 | return $this->valueByIndex($index); 171 | } 172 | 173 | /** 174 | * @return RecordView 175 | */ 176 | public function record() 177 | { 178 | return clone $this; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/Result/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\Bolt\Result; 13 | 14 | use GraphAware\Bolt\PackStream\Structure\Structure; 15 | use GraphAware\Bolt\Record\RecordView; 16 | use GraphAware\Bolt\Result\Type\Node; 17 | use GraphAware\Bolt\Result\Type\Path; 18 | use GraphAware\Bolt\Result\Type\Relationship; 19 | use GraphAware\Bolt\Result\Type\UnboundRelationship; 20 | use GraphAware\Common\Cypher\StatementInterface; 21 | use GraphAware\Common\Result\AbstractRecordCursor; 22 | use GraphAware\Common\Result\Record; 23 | use RuntimeException; 24 | 25 | class Result extends AbstractRecordCursor 26 | { 27 | /** 28 | * @var RecordView[] 29 | */ 30 | protected $records = []; 31 | 32 | /** 33 | * @var array 34 | */ 35 | protected $fields; 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function __construct(StatementInterface $statement) 41 | { 42 | $this->resultSummary = new ResultSummary($statement); 43 | 44 | parent::__construct($statement); 45 | } 46 | 47 | /** 48 | * @param Structure $structure 49 | */ 50 | public function pushRecord(Structure $structure) 51 | { 52 | $elts = $this->array_map_deep($structure->getElements()); 53 | $this->records[] = new RecordView($this->fields, $elts); 54 | } 55 | 56 | /** 57 | * @return RecordView[] 58 | */ 59 | public function getRecords() 60 | { 61 | return $this->records; 62 | } 63 | 64 | /** 65 | * @return RecordView 66 | * 67 | * @throws \RuntimeException When there is no record. 68 | */ 69 | public function getRecord() 70 | { 71 | if (count($this->records) < 1) { 72 | throw new \RuntimeException('There is no record'); 73 | } 74 | 75 | return $this->records[0]; 76 | } 77 | 78 | /** 79 | * @param array $fields 80 | */ 81 | public function setFields(array $fields) 82 | { 83 | $this->fields = $fields['fields']; 84 | } 85 | 86 | /** 87 | * @param array $stats 88 | */ 89 | public function setStatistics(array $stats) 90 | { 91 | $this->resultSummary->setStatistics($stats); 92 | } 93 | 94 | /** 95 | * @param $type 96 | */ 97 | public function setType($type) 98 | { 99 | $this->type = $type; 100 | } 101 | 102 | /** 103 | * @return ResultSummary 104 | */ 105 | public function summarize() 106 | { 107 | return $this->resultSummary; 108 | } 109 | 110 | public function position() 111 | { 112 | // TODO: Implement position() method. 113 | } 114 | 115 | public function skip() 116 | { 117 | // TODO: Implement skip() method. 118 | } 119 | 120 | private function array_map_deep(array $array) 121 | { 122 | foreach ($array as $k => $v) { 123 | if ($v instanceof Structure && $v->getSignature() === 'NODE') { 124 | $elts = $v->getElements(); 125 | $array[$k] = new Node($elts[0], $elts[1], $elts[2]); 126 | } elseif ($v instanceof Structure && $v->getSignature() === 'RELATIONSHIP') { 127 | $elts = $v->getElements(); 128 | $array[$k] = new Relationship($elts[0], $elts[1], $elts[2], $elts[3], $elts[4]); 129 | } elseif ($v instanceof Structure && $v->getSignature() === 'UNBOUND_RELATIONSHIP') { 130 | $elts = $v->getElements(); 131 | $array[$k] = new UnboundRelationship($elts[0], $elts[1], $elts[2]); 132 | } elseif ($v instanceof Structure && $v->getSignature() === 'PATH') { 133 | $elts = $v->getElements(); 134 | $array[$k] = new Path($this->array_map_deep($elts[0]), $this->array_map_deep($elts[1]), $this->array_map_deep($elts[2])); 135 | } elseif ($v instanceof Structure) { 136 | $array[$k] = $this->array_map_deep($v->getElements()); 137 | } elseif (is_array($v)) { 138 | $array[$k] = $this->array_map_deep($v); 139 | } 140 | } 141 | 142 | return $array; 143 | } 144 | 145 | /** 146 | * {@inheritdoc} 147 | */ 148 | public function size() 149 | { 150 | return count($this->records); 151 | } 152 | 153 | /** 154 | * @return RecordView 155 | * @throws \RuntimeException When there is no record 156 | */ 157 | public function firstRecord() 158 | { 159 | if (!empty($this->records)) { 160 | return $this->records[0]; 161 | } 162 | 163 | throw new RuntimeException('There is no record'); 164 | } 165 | 166 | /** 167 | * {@inheritdoc} 168 | */ 169 | public function firstRecordOrDefault($default) 170 | { 171 | if (0 === $this->size()) { 172 | return $default; 173 | } 174 | 175 | return $this->firstRecord(); 176 | } 177 | 178 | 179 | } 180 | -------------------------------------------------------------------------------- /src/Result/ResultSummary.php: -------------------------------------------------------------------------------- 1 | statement = $statement; 28 | } 29 | 30 | /** 31 | * @return StatementInterface 32 | */ 33 | public function statement() 34 | { 35 | return $this->statement; 36 | } 37 | 38 | /** 39 | * @return StatementStatistics|null 40 | */ 41 | public function updateStatistics() 42 | { 43 | return $this->updateStatistics; 44 | } 45 | 46 | /** 47 | * @return \GraphAware\Common\Cypher\StatementType 48 | */ 49 | public function statementType() 50 | { 51 | return $this->statement->statementType(); 52 | } 53 | 54 | /** 55 | * @param array $stats 56 | */ 57 | public function setStatistics(array $stats) 58 | { 59 | // Difference between http format and binary format of statistics 60 | foreach ($stats as $k => $v) { 61 | $nk = str_replace('-', '_', $k); 62 | $stats[$nk] = $v; 63 | unset($stats[$k]); 64 | } 65 | 66 | $this->updateStatistics = new StatementStatistics($stats); 67 | } 68 | 69 | public function notifications() 70 | { 71 | // TODO: Implement notifications() method. 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Result/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\Bolt\Result\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 | * {@inheritdoc} 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 __get($name) 63 | { 64 | return $this->get($name); 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | */ 70 | public function containsKey($key) 71 | { 72 | return array_key_exists($key, $this->properties); 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function values() 79 | { 80 | return $this->properties; 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | public function asArray() 87 | { 88 | return $this->properties; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Result/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\Bolt\Result\Type; 13 | 14 | use GraphAware\Common\Type\Node as BaseNodeInterface; 15 | 16 | class Node extends MapAccess implements BaseNodeInterface 17 | { 18 | /** 19 | * @var int 20 | */ 21 | protected $identity; 22 | 23 | /** 24 | * @var array 25 | */ 26 | protected $labels; 27 | 28 | /** 29 | * @param int $identity 30 | * @param array $labels 31 | * @param array $properties 32 | */ 33 | public function __construct($identity, array $labels = [], array $properties = []) 34 | { 35 | $this->identity = $identity; 36 | $this->labels = $labels; 37 | $this->properties = $properties; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function identity() 44 | { 45 | return $this->identity; 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); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Result/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\Bolt\Result\Type; 13 | 14 | use GraphAware\Common\Type\Node as NodeInterface; 15 | use GraphAware\Common\Type\Path as BasePathInterface; 16 | use GraphAware\Common\Type\Relationship as RelationshipInterface; 17 | 18 | class Path implements BasePathInterface 19 | { 20 | /** 21 | * @var NodeInterface[] 22 | */ 23 | protected $nodes; 24 | 25 | /** 26 | * @var UnboundRelationship[] 27 | */ 28 | protected $relationships; 29 | 30 | /** 31 | * @var \int[] 32 | */ 33 | protected $sequence; 34 | 35 | /** 36 | * @param NodeInterface[] $nodes 37 | * @param UnboundRelationship[] $relationships 38 | * @param int[] $sequence 39 | */ 40 | public function __construct(array $nodes, array $relationships, array $sequence) 41 | { 42 | $this->nodes = $nodes; 43 | $this->relationships = $relationships; 44 | $this->sequence = $sequence; 45 | } 46 | 47 | /** 48 | * @return NodeInterface 49 | */ 50 | public function start() 51 | { 52 | return $this->nodes[0]; 53 | } 54 | 55 | /** 56 | * @return Node 57 | */ 58 | public function end() 59 | { 60 | return $this->nodes[count($this->nodes) - 1]; 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function length() 67 | { 68 | return count($this->relationships); 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function containsNode(NodeInterface $node) 75 | { 76 | return in_array($node, $this->nodes); 77 | } 78 | 79 | /** 80 | * {@inheritdoc} 81 | */ 82 | public function containsRelationship(RelationshipInterface $relationship) 83 | { 84 | return in_array($relationship, $this->relationships); 85 | } 86 | 87 | /** 88 | * @return Node[] 89 | */ 90 | public function nodes() 91 | { 92 | return $this->nodes; 93 | } 94 | 95 | /** 96 | * @return UnboundRelationship[] 97 | */ 98 | public function relationships() 99 | { 100 | return $this->relationships; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Result/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\Bolt\Result\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 $identity; 22 | 23 | /** 24 | * @var int 25 | */ 26 | protected $startNodeIdentity; 27 | 28 | /** 29 | * @var int 30 | */ 31 | protected $endNodeIdentity; 32 | 33 | /** 34 | * @var string 35 | */ 36 | protected $type; 37 | 38 | /** 39 | * @param int $identity 40 | * @param int $startNodeIdentity 41 | * @param int $endNodeIdentity 42 | * @param string $type 43 | * @param array $properties 44 | */ 45 | public function __construct($identity, $startNodeIdentity, $endNodeIdentity, $type, array $properties = array()) 46 | { 47 | $this->identity = $identity; 48 | $this->startNodeIdentity = $startNodeIdentity; 49 | $this->endNodeIdentity = $endNodeIdentity; 50 | $this->type = $type; 51 | $this->properties = $properties; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function identity() 58 | { 59 | return $this->identity; 60 | } 61 | 62 | /** 63 | * @return int 64 | */ 65 | public function startNodeIdentity() 66 | { 67 | return $this->startNodeIdentity; 68 | } 69 | 70 | /** 71 | * @return int 72 | */ 73 | public function endNodeIdentity() 74 | { 75 | return $this->endNodeIdentity; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function type() 82 | { 83 | return $this->type; 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function hasType($type) 90 | { 91 | return $this->type === $type; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Result/Type/UnboundRelationship.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\Bolt\Result\Type; 13 | 14 | use GraphAware\Common\Type\Relationship as BaseRelationshipInterface; 15 | 16 | class UnboundRelationship extends MapAccess implements BaseRelationshipInterface 17 | { 18 | /** 19 | * @var string 20 | */ 21 | protected $identity; 22 | 23 | /** 24 | * @var string 25 | */ 26 | protected $type; 27 | 28 | /** 29 | * @param string $identity 30 | * @param string $type 31 | * @param string array $properties 32 | */ 33 | public function __construct($identity, $type, array $properties) 34 | { 35 | $this->identity = $identity; 36 | $this->type = $type; 37 | $this->properties = $properties; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function identity() 44 | { 45 | return $this->identity; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function type() 52 | { 53 | return $this->type; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function hasType($type) 60 | { 61 | return $this->type === $type; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Documentation/DocumentationTest.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\Bolt\Tests\Documentation; 13 | 14 | use GraphAware\Bolt\GraphDatabase; 15 | use GraphAware\Common\Driver\DriverInterface; 16 | 17 | /** 18 | * Class DocumentationTest 19 | * 20 | * @group documentation 21 | */ 22 | class DocumentationTest extends \PHPUnit_Framework_TestCase 23 | { 24 | public function testSetup() 25 | { 26 | /** 27 | * Creating a driver 28 | */ 29 | 30 | $driver = GraphDatabase::driver("bolt://localhost"); 31 | $this->assertInstanceOf(DriverInterface::class, $driver); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Example/MovieExampleTest.php: -------------------------------------------------------------------------------- 1 | emptyDB(); 18 | $q = <<(matrix1) 26 | CREATE (keanu)-[:ACTS_IN { role : 'Neo' }]->(matrix2) 27 | CREATE (keanu)-[:ACTS_IN { role : 'Neo' }]->(matrix3) 28 | CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix1) 29 | CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix2) 30 | CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix3) 31 | CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix1) 32 | CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix2) 33 | CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix3) 34 | QUERY; 35 | 36 | $this->getSession()->run($q); 37 | } 38 | 39 | public function testGetSimpleNode() 40 | { 41 | $q = 'MATCH (m:Movie {title: {title}}) RETURN m;'; 42 | $p = ['title' => 'The Matrix']; 43 | 44 | $result = $this->getSession()->run($q, $p); 45 | 46 | $this->assertCount(1, $result->getRecords()); 47 | $this->assertTrue(in_array('Movie', $result->firstRecord()->value('m')->labels())); 48 | } 49 | 50 | /** 51 | * Fix issue #2 52 | * 53 | * @link https://github.com/graphaware/neo4j-bolt-php/issues/2 54 | */ 55 | public function testRecordViewThrowsExceptionWhenKeyDoesntExist() 56 | { 57 | $query = 'MATCH (m:Movie {title: {title} }) RETURN m'; 58 | $result = $this->getSession()->run($query, ['title' => 'The Matrix']); 59 | $record = $result->firstRecord(); 60 | 61 | $movieNode = $record->nodeValue('m'); 62 | $this->assertInstanceOf(Node::class, $movieNode); 63 | 64 | $this->setExpectedException(\InvalidArgumentException::class); 65 | $record->nodeValue('z'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Integration/EmptyArraysHandlingTest.php: -------------------------------------------------------------------------------- 1 | emptyDB(); 19 | $query = 'MERGE (n:User {id: {id} }) 20 | WITH n 21 | UNWIND {friends} AS friend 22 | MERGE (f:User {id: friend.name}) 23 | MERGE (f)-[:KNOWS]->(n)'; 24 | 25 | $params = ['id' => 'me', 'friends' => Collections::asList([])]; 26 | 27 | $session = $this->getSession(); 28 | $session->run($query, $params); 29 | 30 | $result = $session->run('MATCH (n:User) RETURN count(n) AS c'); 31 | $this->assertEquals(1, $result->firstRecord()->get('c')); 32 | } 33 | 34 | public function testEmptyArrayAsMapIsHandled() 35 | { 36 | $this->emptyDB(); 37 | $query = 'MERGE (n:User {id: {id} }) 38 | WITH n 39 | UNWIND {friends}.users AS friend 40 | MERGE (f:User {id: friend.name}) 41 | MERGE (f)-[:KNOWS]->(n)'; 42 | 43 | $params = ['id' => 'me', 'friends' => Collections::asMap([])]; 44 | $session = $this->getSession(); 45 | 46 | $session->run($query, $params); 47 | 48 | $result = $session->run('MATCH (n:User) RETURN count(n) AS c'); 49 | $this->assertEquals(1, $result->firstRecord()->get('c')); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Integration/ExceptionDispatchTest.php: -------------------------------------------------------------------------------- 1 | getSession(); 22 | 23 | $this->setExpectedException(MessageFailureException::class); 24 | $session->run("CREATE (n:)"); 25 | 26 | try { 27 | $session->run("CR"); 28 | } catch (MessageFailureException $e) { 29 | $this->assertEquals('Neo.ClientError.Statement.SyntaxError', $e->getStatusCode()); 30 | } 31 | } 32 | 33 | public function testNeo4jStatusCodeIsAvalailble() 34 | { 35 | try { 36 | $this->getSession()->run("CR"); 37 | } catch (MessageFailureException $e) { 38 | $this->assertEquals('Neo.ClientError.Statement.SyntaxError', $e->getStatusCode()); 39 | } 40 | } 41 | 42 | public function testMessageFailuresAreHandled() 43 | { 44 | $session = $this->getSession(); 45 | 46 | try { 47 | $session->run('CR'); 48 | } catch (MessageFailureException $e) { 49 | // 50 | } 51 | 52 | $result = $session->run('CREATE (n) RETURN n'); 53 | $this->assertTrue($result->firstRecord()->get('n') instanceof Node); 54 | } 55 | 56 | public function testMessageFailuresInTransactionsAreHandled() 57 | { 58 | $session = $this->getSession(); 59 | $tx = $session->transaction(); 60 | 61 | try { 62 | $tx->run(Statement::create('CR')); 63 | } catch (MessageFailureException $e) { 64 | // 65 | } 66 | $result = $session->run('CREATE (n) RETURN n'); 67 | $this->assertTrue($result->firstRecord()->get('n') instanceof Node); 68 | } 69 | 70 | public function testMessageFailuresAreHandledInSequence() 71 | { 72 | $session = $this->getSession(); 73 | $this->createConstraint('User', 'id'); 74 | $session->run('MATCH (n:User) DETACH DELETE n'); 75 | $session->run('CREATE (n:User {id:1})'); 76 | $this->setExpectedException(MessageFailureException::class); 77 | $session->run('CREATE (n:User {id:1})'); 78 | } 79 | 80 | public function testMessageFailuresAreHandledInPipelines() 81 | { 82 | $session = $this->getSession(); 83 | $session->run('CREATE CONSTRAINT ON (u:User) ASSERT u.id IS UNIQUE'); 84 | $session->run('MATCH (n:User) DETACH DELETE n'); 85 | $session->run('CREATE (n:User {id:1})'); 86 | $pipeline = $session->createPipeline(); 87 | $pipeline->push('CREATE (n:User {id:3})'); 88 | $pipeline->push('CREATE (n:User {id:4})'); 89 | $pipeline->push('CREATE (n:User {id:1})'); 90 | $pipeline->push('CREATE (n:User {id:5})'); 91 | $this->setExpectedException(MessageFailureException::class); 92 | $pipeline->run(); 93 | } 94 | 95 | /** 96 | * @group issue-111 97 | */ 98 | public function testPipelineWithConstraintCreation() 99 | { 100 | $session = $this->getSession(); 101 | $session->run('MATCH (n) DETACH DELETE n'); 102 | $session->run('CREATE (n:User {id:1})'); 103 | $pipeline = $session->createPipeline(); 104 | $pipeline->push('CREATE CONSTRAINT ON (u:User) ASSERT u.id IS UNIQUE'); 105 | $pipeline->push('CREATE (n:User {id:1})'); 106 | $this->setExpectedException(MessageFailureException::class); 107 | $pipeline->run(); 108 | } 109 | 110 | /** 111 | * @group exception-pipeline 112 | */ 113 | public function testPipelinesCanBeRunAfterFailure() 114 | { 115 | $session = $this->getSession(); 116 | $this->createConstraint('User', 'id'); 117 | $session->run('MATCH (n:User) DETACH DELETE n'); 118 | $session->run('CREATE (n:User {id:1})'); 119 | $pipeline = $session->createPipeline(); 120 | $pipeline->push('CREATE (n:User {id:3})'); 121 | $pipeline->push('CREATE (n:User {id:4})'); 122 | $pipeline->push('CREATE (n:User {id:1})'); 123 | $pipeline->push('CREATE (n:User {id:5})'); 124 | try { 125 | $pipeline->run(); 126 | } catch (MessageFailureException $e) { 127 | // 128 | } 129 | 130 | $pipeline = $session->createPipeline(); 131 | $pipeline->push('MATCH (n) DETACH DELETE n'); 132 | $pipeline->push('CREATE (n) RETURN n'); 133 | $results = $pipeline->run(); 134 | $this->assertEquals(2, $results->size()); 135 | } 136 | 137 | public function testConstraintViolationInTransaction() 138 | { 139 | $session = $this->getSession(); 140 | 141 | $session->run('MATCH (n) DETACH DELETE n'); 142 | $this->createConstraint('User', 'id'); 143 | $this->createConstraint('User', 'login'); 144 | $session->run('MERGE (n:User {login: "ikwattro", id:1})'); 145 | $session->run('MERGE (n:User {login: "jexp", id: 2})'); 146 | 147 | $tx = $session->createPipeline(); 148 | $tx->push('MERGE (n:User {id:3}) SET n.login = "bachmanm"'); 149 | $tx->push('MERGE (n:User {id:2}) SET n.login = "ikwattro"'); 150 | $tx->push('MERGE (n:User {id:4}) SET n.login = "ale"'); 151 | 152 | try { 153 | $tx->run(); 154 | // should fail 155 | $this->assertFalse(true); 156 | } catch (\Exception $e) { 157 | $this->assertTrue(true); 158 | } 159 | } 160 | 161 | private function createConstraint($label, $key) 162 | { 163 | try { 164 | $this->getSession()->run(sprintf('CREATE CONSTRAINT ON (n:`%s`) ASSERT n.`%s` IS UNIQUE', $label, $key)); 165 | } catch (MessageFailureException $e) { 166 | if ($e->getStatusCode() === 'Neo.ClientError.Schema.IndexAlreadyExists') { 167 | $this->dropIndex($label, $key); 168 | $this->createConstraint($label, $key); 169 | } 170 | 171 | throw $e; 172 | } 173 | } 174 | 175 | private function dropIndex($label, $key) 176 | { 177 | $this->getSession()->run(sprintf('DROP INDEX ON :%s(%s)', $label, $key)); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /tests/Integration/HandshakeIntegrationTest.php: -------------------------------------------------------------------------------- 1 | getSession(); 20 | $this->assertInstanceOf(SessionInterface::class, $session); 21 | $this->assertEquals(1, $session::getProtocolVersion()); 22 | } 23 | 24 | public function testErrorIsThrownWhenNoVersionCanBeAgreed() 25 | { 26 | // needs some refactoring for mocking the session registry 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Integration/IssuesIntegrationTest.php: -------------------------------------------------------------------------------- 1 | emptyDB(); 18 | // create node with 22 properties 19 | 20 | $props = []; 21 | for ($i = 0; $i < 22; ++$i) { 22 | $props['prop'.$i] = $i; 23 | } 24 | $this->assertCount(22, $props); 25 | $this->getSession()->run('CREATE (n:IssueNode) SET n = {props} RETURN n', ['props' => $props]); 26 | } 27 | 28 | /** 29 | * @group context-interface 30 | */ 31 | public function testBindToInterface() 32 | { 33 | $config = Configuration::create() 34 | ->bindToInterface('0:0'); 35 | $driver = GraphDatabase::driver('bolt://localhost:7687', $config); 36 | $result = $driver->session()->run('MATCH (n) RETURN count(n)'); 37 | $this->assertInstanceOf(Result::class, $result); 38 | } 39 | 40 | /** 41 | * @see https://github.com/graphaware/neo4j-php-client/issues/60 42 | * @group issue60 43 | */ 44 | public function testIssue60() 45 | { 46 | $this->emptyDB(); 47 | $timestamp = time() * 1000; 48 | 49 | $session = $this->getSession(); 50 | $result = $session->run('CREATE (n:Node {time: {time} }) RETURN n.time as t', ['time' => $timestamp]); 51 | $this->assertEquals($timestamp, $result->firstRecord()->get('t')); 52 | 53 | $this->emptyDB(); 54 | $time = 1475921198602; 55 | $session->run('CREATE (n:Node) SET n.time = {time}', ['time' => $time]); 56 | $result = $session->run('MATCH (n:Node) RETURN n.time as t'); 57 | $this->assertEquals($time, $result->firstRecord()->get('t')); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Integration/Packing/PackingFloatsIntegrationTest.php: -------------------------------------------------------------------------------- 1 | emptyDB(); 18 | } 19 | 20 | public function testPackingFloatsPositive() 21 | { 22 | $session = $this->getSession(); 23 | 24 | for ($x = 1; $x < 1000; ++$x) { 25 | $result = $session->run("CREATE (n:Float) SET n.prop = {x} RETURN n.prop as x", ['x' => $x/100]); 26 | $this->assertEquals($x/100, $result->getRecord()->value('x')); 27 | } 28 | } 29 | 30 | public function testPackingFloatsNegative() 31 | { 32 | $session = $this->getSession(); 33 | 34 | for ($x = -1; $x > -1000; --$x) { 35 | $result = $session->run("CREATE (n:Float) SET n.prop = {x} RETURN n.prop as x", ['x' => $x/100]); 36 | $this->assertEquals($x/100, $result->getRecord()->value('x')); 37 | } 38 | } 39 | 40 | public function testPi() 41 | { 42 | $result = $this->getSession()->run("CREATE (n:Float) SET n.prop = {x} RETURN n.prop as x", ['x' => pi()]); 43 | $this->assertEquals(pi(), $result->getRecord()->value('x')); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Integration/Packing/PackingGraphStructureIntegrationTest.php: -------------------------------------------------------------------------------- 1 | emptyDB(); 21 | } 22 | 23 | /** 24 | * @group structure-node 25 | */ 26 | public function testUnpackingNode() 27 | { 28 | $result = $this->getSession()->run("CREATE (n:Node) SET n.time = {t}, n.desc = {d} RETURN n", ['t' => time(), 'd' => 'GraphAware is awesome !']); 29 | 30 | $this->assertTrue($result->getRecord()->value('n') instanceof Node); 31 | $this->assertEquals('GraphAware is awesome !', $result->getRecord()->value('n')->value('desc')); 32 | } 33 | 34 | public function testUnpackingUnboundRelationship() 35 | { 36 | $result = $this->getSession()->run("CREATE (n:Node)-[r:RELATES_TO {since: 1992}]->(b:Node) RETURN r"); 37 | $record = $result->getRecord(); 38 | 39 | $this->assertTrue($record->value('r') instanceof Relationship); 40 | $this->assertEquals(1992, $record->value('r')->value('since')); 41 | } 42 | 43 | public function testUnpackingNodesCollection() 44 | { 45 | $session = $this->getSession(); 46 | $session->run("FOREACH (x in range(1,3) | CREATE (n:Node {id: x}))"); 47 | $result = $session->run("MATCH (n:Node) RETURN collect(n) as nodes"); 48 | 49 | $this->assertCount(3, $result->getRecord()->value('nodes')); 50 | foreach ($result->getRecord()->value('nodes') as $node) { 51 | /** @var \GraphAware\Bolt\Result\Type\Node $node */ 52 | $this->assertTrue(in_array('Node', $node->labels())); 53 | } 54 | } 55 | 56 | /** 57 | * @group path 58 | */ 59 | public function testUnpackingPaths() 60 | { 61 | $session = $this->getSession(); 62 | $session->run("MATCH (n) DETACH DELETE n"); 63 | $session->run("CREATE (a:A {k: 'v'})-[:KNOWS]->(b:B {k:'v2'})-[:LIKES]->(c:C {k:'v3'})<-[:KNOWS]-(a)"); 64 | $result = $session->run("MATCH p=(a:A)-[r*]->(b) RETURN p, length(p) as l"); 65 | 66 | $this->assertInstanceOf(Path::class, $result->getRecord()->value('p')); 67 | $this->assertInternalType('integer', $result->getRecord()->value('l')); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Integration/Packing/PackingIntegersIntegrationTest.php: -------------------------------------------------------------------------------- 1 | emptyDB(); 21 | $this->getSession()->run("CREATE INDEX ON :Integer(value)"); 22 | } 23 | 24 | /** 25 | * @group tinyint 26 | */ 27 | public function testTinyIntegersPacking() 28 | { 29 | $this->doRangeTest(0, 127); 30 | } 31 | 32 | public function testMinTinyIntegersPacking() 33 | { 34 | $this->doRangeTest(-16, -1); 35 | } 36 | 37 | public function testMinIntegers8Packing() 38 | { 39 | $this->doRangeTest(-128, -17); 40 | } 41 | 42 | public function testInt8IntegersPacking() 43 | { 44 | $this->doRangeTest(128, 1000); 45 | } 46 | 47 | public function testInt8IntegersPackingEnd() 48 | { 49 | $this->doRangeTest(32000, 32768); 50 | } 51 | 52 | public function testInt16Packing() 53 | { 54 | $this->doRangeTest(32768, 34000); 55 | } 56 | 57 | public function testMinIntegers16Packing() 58 | { 59 | $this->doRangeTest(-200, -129); 60 | } 61 | 62 | public function testMinIntegers16PackingEnd() 63 | { 64 | $this->doRangeTest(-32768, -32700); 65 | } 66 | 67 | public function testMinIntegers32Packing() 68 | { 69 | $this->doRangeTest(-33000, -32769); 70 | } 71 | 72 | public function testMinIntegers32End() 73 | { 74 | $min = (-1*abs(pow(2, 31))); 75 | $this->doRangeTest($min, $min + 100); 76 | } 77 | 78 | /** 79 | * @group 64 80 | */ 81 | public function testMinIntegers64() 82 | { 83 | $max = (-1*abs(pow(2, 31)))-1; 84 | $this->doRangeTest($max - 100, $max); 85 | } 86 | 87 | /** 88 | * @group 64 89 | */ 90 | public function testMin64IntegersEnd() 91 | { 92 | $min = -1*abs(pow(2, 63)); 93 | $this->doRangeTest((int) $min, (int) $min+1); 94 | } 95 | 96 | /** 97 | * @group 64 98 | */ 99 | public function test64Integers() 100 | { 101 | $min = pow(2, 31); 102 | $this->doRangeTest($min, $min+100); 103 | } 104 | 105 | /** 106 | * @group 64 107 | */ 108 | public function test64IntegersEnd() 109 | { 110 | $this->emptyDB(); 111 | $max = (int) pow(2, 63); 112 | $this->doRangeTest($max-1000, $max); 113 | } 114 | 115 | private function doRangeTest($min, $max) 116 | { 117 | $range = range($min, $max); 118 | $session = $this->getSession(); 119 | 120 | foreach ($range as $i) { 121 | $q = 'CREATE (n:Integer) SET n.value = {value}'; 122 | $session->run($q, ['value' => $i]); 123 | } 124 | 125 | foreach ($range as $i) { 126 | $response = $session->run('MATCH (n:Integer) WHERE n.value = {value} RETURN n.value', ['value' => $i]); 127 | $this->assertCount(1, $response->getRecords()); 128 | $this->assertEquals($i, $response->getRecord()->value('n.value')); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /tests/Integration/Packing/PackingListIntegrationTest.php: -------------------------------------------------------------------------------- 1 | emptyDB(); 13 | } 14 | 15 | public function testPackingList32() 16 | { 17 | $session = $this->getSession(); 18 | 19 | $session->run('UNWIND range(1, 40000) AS i CREATE (n:TestList {id: i})'); 20 | $result = $session->run('MATCH (n:TestList) RETURN collect(n.id) AS list'); 21 | $this->assertCount(40000, $result->firstRecord()->get('list')); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Integration/Packing/PackingMapsIntegrationTest.php: -------------------------------------------------------------------------------- 1 | emptyDB(); 21 | } 22 | 23 | public function testMapTiny() 24 | { 25 | $this->doRangeTest(1, 15); 26 | } 27 | 28 | public function testMap8() 29 | { 30 | $this->doRangeTest(16, 18); 31 | $this->doRangeTest(253, 255); 32 | } 33 | 34 | public function testMap16() 35 | { 36 | $this->doRangeTest(1024, 1026); 37 | } 38 | 39 | /** 40 | * @group fail 41 | */ 42 | public function testMap16High() 43 | { 44 | $this->doRangeTest(65533, 65535); 45 | } 46 | 47 | private function doRangeTest($min, $max) 48 | { 49 | $query = 'CREATE (n:MapTest) SET n += {props} RETURN n'; 50 | $session = $this->getSession(); 51 | 52 | for ($i = $min; $i < $max; ++$i) { 53 | $parameters = []; 54 | foreach (range(1, $i) as $x) { 55 | $parameters['prop' . $x] = $i; 56 | } 57 | $result = $session->run($query, ['props' => $parameters]); 58 | $node = $result->firstRecord()->nodeValue('n'); 59 | $this->assertEquals($i, count($node->values())); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Integration/Packing/PackingTextIntegrationTest.php: -------------------------------------------------------------------------------- 1 | emptyDB(); 21 | 22 | $this->getSession()->run("CREATE INDEX ON :Text(value)"); 23 | } 24 | 25 | /** 26 | * @group text-tiny 27 | */ 28 | public function testTinyTextPacking() 29 | { 30 | $this->doRangeTest(1, 15); 31 | } 32 | 33 | /** 34 | * @group text8 35 | */ 36 | public function testText8Packing() 37 | { 38 | $this->doRangeTest(16, 255); 39 | } 40 | 41 | /** 42 | * @group text16 43 | */ 44 | public function testText16Packing() 45 | { 46 | $this->doRangeTest(256, 356); 47 | $this->doRangeTest(1024, 1026); 48 | $this->doRangeTest(2048, 2050); 49 | //$this->doRangeTest(16351, 16383); 50 | //$this->doRangeTest(65500, 65535); 51 | } 52 | 53 | /** 54 | * @group text32 55 | * @group fail 56 | * @group stringx 57 | */ 58 | public function testText32Packing() 59 | { 60 | //$this->markTestSkipped("Neo4j3.0M02 has issues with 64 bits texts"); 61 | //$this->doRangeTest(65537, 65537); 62 | //$this->doRangeTest(500000, 500000); 63 | } 64 | 65 | public function doRangeTest($min, $max) 66 | { 67 | $session = $this->getSession(); 68 | 69 | $q = 'CREATE (n:Text) SET n.value = {value}'; 70 | 71 | foreach (range($min, $max) as $i) { 72 | $txt = str_repeat('a', $i); 73 | $session->run($q, array('value' => $txt)); 74 | } 75 | 76 | $q = 'MATCH (n:Text) WHERE n.value = {value} RETURN n.value as x'; 77 | 78 | foreach (range($min, $max) as $i) { 79 | $txt = str_repeat('a', $i); 80 | $response = $session->run($q, ['value' => $txt]); 81 | $this->assertCount(1, $response->getRecords()); 82 | $this->assertEquals($txt, $response->getRecord()->value('x')); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/Integration/RealLifeUseCasesITest.php: -------------------------------------------------------------------------------- 1 | emptyDB(); 20 | $batches = []; 21 | for ($i = 1; $i < 10; ++$i) { 22 | $batches[] = $this->createBatch($i); 23 | } 24 | 25 | $query = 'UNWIND {batches} as batch 26 | MERGE (p:Person {id: batch.id}) 27 | SET p += batch.props 28 | WITH p, batch 29 | UNWIND batch.prev as prev 30 | MERGE (o:Person {id: prev}) 31 | MERGE (p)-[:KNOWS]->(o)'; 32 | 33 | $this->getSession()->run($query, ['batches' => $batches]); 34 | } 35 | 36 | /** 37 | * @group stats 38 | */ 39 | public function testResultSummaryReturnsStats() 40 | { 41 | $this->emptyDB(); 42 | $session = $this->getSession(); 43 | $result = $session->run('MATCH (n) RETURN count(n)'); 44 | $this->assertInstanceOf(StatementStatisticsInterface::class, $result->summarize()->updateStatistics()); 45 | 46 | $tx = $session->transaction(); 47 | $tx->begin(); 48 | $result = $tx->run(Statement::create('CREATE (n)')); 49 | $tx->commit(); 50 | $this->assertInstanceOf(StatementStatisticsInterface::class, $result->summarize()->updateStatistics()); 51 | } 52 | 53 | private function createBatch($i) 54 | { 55 | $batch = [ 56 | 'id' => $i, 57 | 'props' => [ 58 | 'login' => sprintf('login%d', $i) 59 | ], 60 | 'prev' => $i > 0 ? range(1, 10) : [] 61 | ]; 62 | 63 | return $batch; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Integration/TransactionIntegrationTest.php: -------------------------------------------------------------------------------- 1 | emptyDB(); 22 | 23 | $statements = array(); 24 | 25 | for ($i = 0; $i < 5; ++$i) { 26 | $statements[] = Statement::create('CREATE (n:Test)'); 27 | } 28 | 29 | $tx = $this->getSession()->transaction(); 30 | $tx->begin(); 31 | $tx->runMultiple($statements); 32 | $tx->commit(); 33 | $this->assertXNodesWithTestLabelExist(5); 34 | } 35 | 36 | public function testRunSingle() 37 | { 38 | $this->emptyDB(); 39 | 40 | $tx = $this->getSession()->transaction(); 41 | $result = $tx->run(Statement::create('CREATE (n) RETURN id(n) as id')); 42 | $this->assertTrue($result->firstRecord()->hasValue('id')); 43 | } 44 | 45 | public function testManualRollbackOnException() 46 | { 47 | $this->emptyDB(); 48 | 49 | $session = $this->getSession(); 50 | $tx = $session->transaction(); 51 | try { 52 | $tx->run(Statement::create("BLA BLA BLA")); 53 | } catch (MessageFailureException $e) { 54 | // 55 | } 56 | $result = $session->run('CREATE (n) RETURN n'); 57 | $this->assertTrue($result->firstRecord()->get('n') instanceof Node); 58 | $this->assertEquals(TransactionState::ROLLED_BACK, $tx->status()); 59 | } 60 | 61 | /** 62 | * @group tx-tag-multiple-fix 63 | */ 64 | public function testRunMultipleInTransactionWithTags() 65 | { 66 | $this->emptyDB(); 67 | 68 | $statements = array(); 69 | for ($i = 0; $i < 5; ++$i) { 70 | $statements[] = Statement::create('CREATE (n:Test) RETURN n', [], sprintf('statement_%d', $i)); 71 | } 72 | 73 | $tx = $this->getSession()->transaction(); 74 | $results = $tx->runMultiple($statements); 75 | $this->assertEquals('statement_0', $results->results()[0]->statement()->getTag()); 76 | } 77 | 78 | private function assertXNodesWithTestLabelExist($number = 1) 79 | { 80 | $result = $this->getSession()->run("MATCH (n:Test) RETURN count(n) as c"); 81 | 82 | $this->assertEquals($number, $result->firstRecord()->get('c')); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/IntegrationTestCase.php: -------------------------------------------------------------------------------- 1 | driver = GraphDatabase::driver("bolt://localhost"); 20 | } 21 | 22 | /** 23 | * @return \GraphAware\Bolt\Driver 24 | */ 25 | protected function getDriver() 26 | { 27 | return $this->driver; 28 | } 29 | 30 | /** 31 | * @return \Graphaware\Bolt\Protocol\SessionInterface 32 | */ 33 | protected function getSession() 34 | { 35 | return $this->driver->session(); 36 | } 37 | 38 | /** 39 | * Empty the database 40 | */ 41 | protected function emptyDB() 42 | { 43 | $this->getSession()->run('MATCH (n) DETACH DELETE n'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Result/DummyMA.php: -------------------------------------------------------------------------------- 1 | properties = $properties; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Result/MapAccessUnitTest.php: -------------------------------------------------------------------------------- 1 | 'value1')); 16 | $this->assertEquals('value1', $map->value('key1')); 17 | $this->assertEquals('value2', $map->value('not_exist', 'value2')); 18 | } 19 | 20 | public function testExceptionIsThrownIfNotDefaultGiven() 21 | { 22 | $map = new DummyMA(array('key' => 'val')); 23 | $this->setExpectedException(\InvalidArgumentException::class); 24 | $map->value('not_exist'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/TCK/TCK9TypesTest.php: -------------------------------------------------------------------------------- 1 | 20 | * |null| 21 | * |boolean| 22 | * |int| 23 | * |float| 24 | * |string| 25 | * |list| 26 | * |map| 27 | * 28 | * When I pass the value as parameter "x" to a "RETURN {x}" statement 29 | * Then I should receive the same value in the result 30 | * 31 | */ 32 | public function testSafeEscapedProvision() 33 | { 34 | // null 35 | $this->assertEquals(null, $this->runValue(null)); 36 | 37 | // boolean 38 | $this->assertEquals(true, $this->runValue(true)); 39 | $this->assertEquals(false, $this->runValue(false)); 40 | 41 | // int 42 | $this->assertEquals(1, $this->runValue(1)); 43 | $this->assertEquals(10000, $this->runValue(10000)); 44 | $this->assertEquals(1000000000, $this->runValue(1000000000)); 45 | 46 | // float 47 | $this->assertEquals(1.0, $this->runValue(1.0)); 48 | $this->assertEquals(pi(), $this->runValue(pi())); 49 | 50 | // string 51 | $this->assertEquals('GraphAware is awesome !', $this->runValue('GraphAware is awesome !')); 52 | 53 | // list 54 | $this->assertEquals(array(0,1,2), $this->runValue(array(0,1,2))); 55 | $this->assertEquals(array("one", "two", "three"), $this->runValue(array("one", "two", "three"))); 56 | 57 | // map 58 | $this->assertEquals(['zone' => 1, 'code' => 'neo.TransientError'], $this->runValue(['zone' => 1, 'code' => 'neo.TransientError'])); 59 | } 60 | 61 | /** 62 | * Scenario: To handle a value of any type returned within a Cypher result 63 | * Given a Result containing a value of type 64 | * |null| 65 | * |boolean| 66 | * |string| 67 | * |float| 68 | * |int| 69 | * |list| 70 | * |map| 71 | * |node| 72 | * |relationship| 73 | * |path| 74 | * When I extract the value from the Result 75 | * Then it should be mapped to appropriate language-idiomatic value 76 | */ 77 | public function testResultTypes() 78 | { 79 | $session = $this->getSession(); 80 | 81 | // null 82 | $result = $session->run("CREATE (n) RETURN n.key as nilKey"); 83 | $this->assertEquals(null, $result->getRecord()->value('nilKey')); 84 | 85 | // collection of null 86 | $result = $session->run("UNWIND range(0, 2) as r CREATE (n) RETURN collect(n.x) as x"); 87 | $this->assertInternalType('array', $result->getRecord()->value('x')); 88 | foreach ($result->getRecord()->value('x') as $v) { 89 | $this->assertEquals(null, $v); 90 | } 91 | 92 | // boolean 93 | $result = $session->run("CREATE (n) RETURN id(n) = id(n) as bool, id(n) = 'a' as bool2"); 94 | $this->assertEquals(true, $result->getRecord()->value('bool')); 95 | $this->assertEquals(false, $result->getRecord()->value('bool2')); 96 | 97 | // collection of booleans 98 | $result = $session->run("UNWIND range(0, 2) as r CREATE (n) SET n.x = (id(n) = id(n)) RETURN collect(n.x) as x"); 99 | $this->assertInternalType('array', $result->getRecord()->value('x')); 100 | foreach ($result->getRecord()->value('x') as $v) { 101 | $this->assertEquals(true, $v); 102 | } 103 | 104 | // string 105 | $result = $session->run("CREATE (n {k: {value}}) RETURN n.k as v", ['value' => 'text']); 106 | $this->assertEquals('text', $result->getRecord()->value('v')); 107 | 108 | // float 109 | $result = $session->run("CREATE (n {k: {value}}) RETURN n.k as v", ['value' => 1.38]); 110 | $this->assertEquals(1.38, $result->getRecord()->value('v')); 111 | 112 | // collection of floats 113 | $result = $session->run("UNWIND range(0,2) as r CREATE (n:X) SET n.k = (id(n) / 100.0f) RETURN collect(n.k) as x"); 114 | $this->assertCount(3, $result->getRecord()->value('x')); 115 | foreach ($result->getRecord()->value('x') as $v) { 116 | $this->assertInternalType('float', $v); 117 | } 118 | 119 | // int 120 | $result = $session->run("CREATE (n) RETURN id(n) as id"); 121 | $this->assertInternalType('int', $result->getRecord()->value('id')); 122 | $this->assertTrue($result->getRecord()->value('id') >= 0); 123 | 124 | // collection of integers 125 | $result = $session->run("UNWIND range(0, 2) as r CREATE (n:X) SET n.k = id(n) RETURN collect(n.k) as x"); 126 | $this->assertCount(3, $result->getRecord()->value('x')); 127 | foreach ($result->getRecord()->value('x') as $v) { 128 | $this->assertInternalType('int', $v); 129 | } 130 | 131 | // list 132 | $result = $session->run("CREATE (n:Person:Male) RETURN labels(n) as l"); 133 | $this->assertInternalType('array', $result->getRecord()->value('l')); 134 | $this->assertTrue(array_key_exists(0, $result->getRecord()->value('l'))); 135 | $this->assertTrue(array_key_exists(1, $result->getRecord()->value('l'))); 136 | 137 | // collection of list 138 | $result = $session->run("UNWIND range(0, 2) as r CREATE (n:X) RETURN collect(labels(n)) as x"); 139 | $this->assertCount(3, $result->getRecord()->value('x')); 140 | foreach ($result->getRecord()->value('x') as $v) { 141 | $this->assertInternalType('array', $v); 142 | $this->assertEquals('X', $v[0]); 143 | } 144 | 145 | // map 146 | $result = $session->run("CREATE (n:Node) RETURN {id: id(n), labels: labels(n)} as map"); 147 | $this->assertInternalType('array', $result->getRecord()->value('map')); 148 | $this->assertTrue(array_key_exists('id', $result->getRecord()->value('map'))); 149 | $this->assertTrue(array_key_exists('labels', $result->getRecord()->value('map'))); 150 | $this->assertInternalType('int', $result->getRecord()->value('map')['id']); 151 | $this->assertInternalType('array', $result->getRecord()->value('map')['labels']); 152 | 153 | // collection of map 154 | $result = $session->run("UNWIND range(0, 2) as r CREATE (n:X) RETURN collect({labels: labels(n)}) as x"); 155 | $this->assertCount(3, $result->getRecord()->value('x')); 156 | foreach ($result->getRecord()->value('x') as $v) { 157 | $this->assertInternalType('array', $v); 158 | $this->assertArrayHasKey('labels', $v); 159 | $this->assertEquals('X', $v['labels'][0]); 160 | } 161 | 162 | // node 163 | $result = $session->run("CREATE (n:Node) RETURN n"); 164 | $this->assertInstanceOf(Node::class, $result->getRecord()->value('n')); 165 | $this->assertTrue($result->getRecord()->value('n')->hasLabel('Node')); 166 | 167 | // collection of nodes 168 | $result = $session->run("UNWIND range(0,2) as r CREATE (n:Node {value: r}) RETURN collect(n) as n"); 169 | $this->assertCount(3, $result->getRecord()->value('n')); 170 | foreach ($result->getRecord()->value('n') as $k => $n) { 171 | $this->assertInstanceOf(Node::class, $n); 172 | $this->assertEquals($k, $n->value('value')); 173 | } 174 | 175 | // map 176 | $result = $session->run("CREATE (n:X) RETURN {created: n} as x"); 177 | $this->assertInstanceOf(Node::class, $result->getRecord()->value('x')['created']); 178 | 179 | // collection of map> 180 | $result = $session->run("UNWIND range(0, 2) as r CREATE (n:X) WITH collect(n) as n RETURN collect({nodes: n}) as x"); 181 | $this->assertCount(1, $result->getRecord()->value('x')); 182 | $this->assertArrayHasKey('nodes', $result->getRecord()->value('x')[0]); 183 | $this->assertCount(3, $result->getRecord()->value('x')[0]['nodes']); 184 | $this->assertInstanceOf(Node::class, $result->getRecord()->value('x')[0]['nodes'][0]); 185 | 186 | // relationship 187 | $result = $session->run("CREATE (n:X)-[r:REL]->(z:X) RETURN r"); 188 | $this->assertInstanceOf(Relationship::class, $result->getRecord()->value('r')); 189 | $this->assertEquals('REL', $result->getRecord()->value('r')->type()); 190 | $this->assertTrue($result->getRecord()->value('r')->hasType('REL')); 191 | 192 | // path 193 | $result = $session->run("CREATE p=(n:X)-[:REL]->(o:Y)-[:REL]->(i:Z) RETURN p"); 194 | $this->assertInstanceOf(Path::class, $result->getRecord()->value('p')); 195 | $this->assertCount(3, $result->getRecord()->value('p')->nodes()); 196 | $this->assertCount(2, $result->getRecord()->value('p')->relationships()); 197 | $this->assertEquals(2, $result->getRecord()->value('p')->length()); 198 | foreach ($result->getRecord()->value('p')->nodes() as $node) { 199 | $this->assertInstanceOf(Node::class, $node); 200 | } 201 | foreach ($result->getRecord()->value('p')->relationships() as $rel) { 202 | $this->assertInstanceOf(Relationship::class, $rel); 203 | } 204 | } 205 | 206 | private function runValue($value) 207 | { 208 | return $this->getSession() 209 | ->run("RETURN {x} as x", ['x' => $value]) 210 | ->getRecord() 211 | ->value('x'); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /tests/Unit/PackStream/PackingUnpackingTest.php: -------------------------------------------------------------------------------- 1 | unpacker = new Unpacker(new StreamChannel(new StreamSocket("bolt://localhost", 7687))); 35 | $this->packer = new Packer(); 36 | } 37 | 38 | public function testPackingNull() 39 | { 40 | $w = $this->getWalkerForBinary(chr(Constants::MARKER_NULL)); 41 | $nullElement = null; 42 | $this->assertEquals($nullElement, $this->unpacker->unpackElement($w)); 43 | $this->assertEquals(chr(Constants::MARKER_NULL), $this->packer->pack(null)); 44 | } 45 | 46 | public function testPackingTrue() 47 | { 48 | $w = $this->getWalkerForBinary(chr(Constants::MARKER_TRUE)); 49 | $trueElt = true; 50 | $this->assertEquals($trueElt, $this->unpacker->unpackElement($w)); 51 | $this->assertEquals(chr(Constants::MARKER_TRUE), $this->packer->pack(true)); 52 | } 53 | 54 | public function testPackingFalse() 55 | { 56 | $w = $this->getWalkerForBinary(chr(Constants::MARKER_FALSE)); 57 | $elt = false; 58 | $this->assertEquals($elt, $this->unpacker->unpackElement($w)); 59 | $this->assertEquals(chr(Constants::MARKER_FALSE), $this->packer->pack(false)); 60 | } 61 | 62 | public function testPackingTinyText() 63 | { 64 | $text = 'TinyText'; 65 | $length = strlen($text); 66 | $binary = chr(Constants::TEXT_TINY + $length) . $text; 67 | $w = $this->getWalkerForBinary($binary); 68 | $elt = $text; 69 | $this->assertEquals($elt, $this->unpacker->unpackElement($w)); 70 | $this->assertEquals($binary, $this->packer->pack($text)); 71 | } 72 | 73 | public function testPackingText8() 74 | { 75 | $text = str_repeat('a', (Constants::SIZE_8)-1); 76 | $length = strlen($text); 77 | $binary = chr(Constants::TEXT_8) . $this->packer->packUnsignedShortShort($length) . $text; 78 | $w = $this->getWalkerForBinary($binary); 79 | $this->assertEquals($text, $this->unpacker->unpackElement($w)); 80 | $this->assertEquals($binary, $this->packer->pack($text)); 81 | } 82 | 83 | public function testPackingText16() 84 | { 85 | $text = str_repeat("a", (Constants::SIZE_16)-1); 86 | $length = strlen($text); 87 | $bin = chr(Constants::TEXT_16) . $this->packer->packUnsignedShort($length) . $text; 88 | $w = $this->getWalkerForBinary($bin); 89 | $this->assertEquals($text, $this->unpacker->unpackElement($w)); 90 | $this->assertEquals($bin, $this->packer->pack($text)); 91 | } 92 | 93 | /** 94 | * @group sig 95 | */ 96 | public function testGetSignature() 97 | { 98 | $bytes = hex2bin("b170a0"); 99 | $raw = new RawMessage($bytes); 100 | $walker = new BytesWalker($raw); 101 | //$walker->forward(1); 102 | 103 | //$sig = $this->unpacker->getSignature($walker); 104 | //$this->assertEquals('SUCCESS', $sig); 105 | } 106 | 107 | /** 108 | * @param string $binary 109 | * @param int $pos 110 | * 111 | * @return BytesWalker 112 | */ 113 | protected function getWalkerForBinary($binary = '', $pos = 0) 114 | { 115 | return new BytesWalker(new RawMessage($binary), $pos); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/Unit/Protocol/Message/MessageUnitTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(AbstractMessage::class, $message); 21 | } 22 | } 23 | --------------------------------------------------------------------------------