├── .gitignore
├── src
├── Protocol
│ ├── V1
│ │ ├── Signatures.php
│ │ ├── Response.php
│ │ ├── Transaction.php
│ │ └── Session.php
│ ├── Message
│ │ ├── PullAllMessage.php
│ │ ├── DiscardAllMessage.php
│ │ ├── AckFailureMessage.php
│ │ ├── RawMessage.php
│ │ ├── MessageInterface.php
│ │ ├── InitMessage.php
│ │ ├── FailureMessage.php
│ │ ├── SuccessMessage.php
│ │ ├── RunMessage.php
│ │ ├── AbstractMessage.php
│ │ └── RecordMessage.php
│ ├── SessionInterface.php
│ ├── ChunkWriter.php
│ ├── Pipeline.php
│ ├── AbstractSession.php
│ ├── Constants.php
│ └── SessionRegistry.php
├── Exception
│ ├── BoltExceptionInterface.php
│ ├── IOException.php
│ ├── HandshakeException.php
│ ├── BoltOutOfBoundsException.php
│ ├── SerializationException.php
│ ├── BoltInvalidArgumentException.php
│ └── MessageFailureException.php
├── Misc
│ └── Helper.php
├── IO
│ ├── AbstractIO.php
│ ├── IoInterface.php
│ ├── Socket.php
│ └── StreamSocket.php
├── GraphDatabase.php
├── BoltEvents.php
├── Result
│ ├── Type
│ │ ├── Node.php
│ │ ├── UnboundRelationship.php
│ │ ├── MapAccess.php
│ │ ├── Relationship.php
│ │ └── Path.php
│ ├── ResultSummary.php
│ └── Result.php
├── PackStream
│ ├── Structure
│ │ ├── MessageStructure.php
│ │ └── Structure.php
│ ├── BytesWalker.php
│ ├── Serializer.php
│ ├── StreamChannel.php
│ └── Packer.php
├── Record
│ └── RecordView.php
├── Driver.php
└── Configuration.php
├── tests
├── Result
│ ├── DummyMA.php
│ └── MapAccessUnitTest.php
├── Unit
│ ├── Protocol
│ │ └── Message
│ │ │ └── MessageUnitTest.php
│ └── PackStream
│ │ └── PackingUnpackingTest.php
├── Integration
│ ├── Packing
│ │ ├── PackingListIntegrationTest.php
│ │ ├── PackingFloatsIntegrationTest.php
│ │ ├── PackingMapsIntegrationTest.php
│ │ ├── PackingTextIntegrationTest.php
│ │ ├── PackingGraphStructureIntegrationTest.php
│ │ └── PackingIntegersIntegrationTest.php
│ ├── HandshakeIntegrationTest.php
│ ├── EmptyArraysHandlingTest.php
│ ├── RealLifeUseCasesITest.php
│ ├── IssuesIntegrationTest.php
│ ├── TransactionIntegrationTest.php
│ └── ExceptionDispatchTest.php
├── Documentation
│ └── DocumentationTest.php
├── IntegrationTestCase.php
├── Example
│ └── MovieExampleTest.php
└── TCK
│ └── TCK9TypesTest.php
├── behat.yml
├── .travis.yml
├── phpunit.xml.dist
├── features
├── bootstrap
│ ├── BoltTypeSystemContext.php
│ ├── ResultMetadataContext.php
│ └── ChunkingDechunkingContext.php
├── result
│ └── tck-01-result-metadata.feature
└── chunking-dechunking
│ └── tck-chunking-dechunking.feature
├── composer.json
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | test.php
3 | composer.lock
4 | phpunit.xml
5 | .idea/
--------------------------------------------------------------------------------
/src/Protocol/V1/Signatures.php:
--------------------------------------------------------------------------------
1 | properties = $properties;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 ]
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/.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"
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 | ./tests
17 |
18 |
19 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/Unit/Protocol/Message/MessageUnitTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(AbstractMessage::class, $message);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/features/bootstrap/BoltTypeSystemContext.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | [](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 |
--------------------------------------------------------------------------------
/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/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/Protocol/V1/Transaction.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace GraphAware\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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/features/bootstrap/ChunkingDechunkingContext.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------