Skip to content

Commit 38527dc

Browse files
Merge pull request #6 from stefanak-michal/up_to_v4.1
Refactored to support versioning. Updated up to bolt protocol version 4.1.
2 parents b651350 + 65ed483 commit 38527dc

File tree

20 files changed

+1089
-245
lines changed

20 files changed

+1089
-245
lines changed

Bolt.php

Lines changed: 196 additions & 219 deletions
Large diffs are not rendered by default.

PackStream/IPacker.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Bolt\PackStream;
4+
5+
/**
6+
* Interface IPacker
7+
*
8+
* @author Michal Stefanak
9+
* @link https://github.com/stefanak-michal/Bolt
10+
* @package Bolt\PackStream
11+
*/
12+
interface IPacker
13+
{
14+
/**
15+
* @param $signature
16+
* @param mixed ...$params
17+
* @return string
18+
*/
19+
public function pack($signature, ...$params): string;
20+
}

PackStream/IUnpacker.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Bolt\PackStream;
4+
5+
/**
6+
* Interface IUnpacker
7+
*
8+
* @author Michal Stefanak
9+
* @link https://github.com/stefanak-michal/Bolt
10+
* @package Bolt\PackStream
11+
*/
12+
interface IUnpacker
13+
{
14+
/**
15+
* @param string $msg
16+
* @param int $signature
17+
* @return mixed
18+
*/
19+
public function unpack(string $msg, int &$signature = 0);
20+
}

Packer.php renamed to PackStream/v1/Packer.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
<?php
22

3-
namespace Bolt;
3+
namespace Bolt\PackStream\v1;
44

55
use Exception;
6+
use Bolt\PackStream\IPacker;
67

78
/**
8-
* Class Packer
9-
* Pack bolt messages
9+
* Class Packer of PackStream version 1
1010
*
1111
* @author Michal Stefanak
12+
* @link https://github.com/stefanak-michal/Bolt
13+
* @package Bolt\PackStream\v1
1214
*/
13-
class Packer
15+
class Packer implements IPacker
1416
{
1517
private const SMALL = 16;
1618
private const MEDIUM = 256;
@@ -24,7 +26,7 @@ class Packer
2426
* @return string
2527
* @throws Exception
2628
*/
27-
public function pack($signature, ...$params)
29+
public function pack($signature, ...$params): string
2830
{
2931
$output = '';
3032

Unpacker.php renamed to PackStream/v1/Unpacker.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
<?php
22

3-
namespace Bolt;
3+
namespace Bolt\PackStream\v1;
44

55
use Bolt\structures\{
66
Node,
77
Path,
88
Relationship,
99
UnboundRelationship
1010
};
11+
use Bolt\PackStream\IUnpacker;
1112
use Exception;
1213

1314
/**
14-
* Class Unpacker
15-
* Unpack bolt messages
15+
* Class Unpacker of PackStream version 1
1616
*
1717
* @author Michal Stefanak
18+
* @link https://github.com/stefanak-michal/Bolt
19+
* @package Bolt\PackStream\v1
1820
*/
19-
class Unpacker
21+
class Unpacker implements IUnpacker
2022
{
2123
/**
2224
* @var string

README.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
# Bolt
2-
Bolt protocol library over TCP socket. Bolt protocol is primary used for communication with [Neo4j](https://neo4j.com/) Graph database. The documentation is available at https://boltprotocol.org/v1/
2+
Bolt protocol library over TCP socket. Bolt protocol is primary used for communication with [Neo4j](https://neo4j.com/) Graph database. The documentation is available at [https://7687.org/](https://7687.org/).
33

44
## Supported version
5-
Bolt < 4.0
5+
Bolt <= 4.1
66

7-
Neo4j version 4.0 is out for some time and I'm sorry to tell you, but this software won't work with it. Reason is outdated documentation for Bolt protocol handled by Neo4j team, which is still available only for version 1.0. Suddenly Neo4j 4.0 drops support for Bolt V1.
7+
| Neo4j Version | Bolt 1 | Bolt 2 | Bolt 3 | Bolt 4.0 | Bolt 4.1 |
8+
|:-------------:|:------:|:------:|:------:|:--------:|:--------:|
9+
| 3.0 | x | | | | |
10+
| 3.1 | x | | | | |
11+
| 3.2 | x | | | | |
12+
| 3.3 | x | | | | |
13+
| 3.4 | (x) | x | | | |
14+
| 3.5 | | (x) | x | | |
15+
| 4.0 | | | (x) | x | |
16+
| 4.1 | | | (x) | (x) | x |
17+
18+
<sup>The (x) denotes that support could be removed in next version of Neo4j.</sup>
819

920
## Requirements
1021
PHP >= 7.1
@@ -13,10 +24,10 @@ extensions:
1324
- mbstring https://www.php.net/manual/en/book.mbstring.php
1425

1526
## Usage
16-
See ``index.php`` file. It contains few examples how you can use this library. All files are loaded with require_once at the beginning of file, because this example doesn't contain autoloader. Of course you need to set up your username and password.
27+
See ``index.php`` file. It contains few examples how you can use this library. Of course you need to set up your username and password. This repository contains simple `autoload.php` file.
1728

1829
## Exceptions
19-
Throwing exceptions is default behaviour. If you want, you can assign own callable error handler to ``\Bolt\Bolt::$errorHandler``. It's called on error and methods (init, run, pullAll, ...) will return false.
30+
Throwing exceptions is default behaviour. If you want, you can assign own callable error handler to ``\Bolt\Bolt::$errorHandler``. It's called on error and methods (init, run, pullAll, ...) will therefore return false.
2031

2132
## Author note
2233
I really like Neo4j and I wanted to use it with PHP. But after I looked on official php library, I was really disappointed. Too much dependencies. I don't like if I need to install 10 things because of one. First I decided to use HTTP API for communication, but it wasn't fast enough. I went through bolt protocol documentation and I said to myself, why not to create own simpler library?

Socket.php

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
namespace Bolt;
4+
5+
use Bolt\PackStream\IUnpacker;
6+
use Exception;
7+
8+
/**
9+
* Static class Socket
10+
*
11+
* @author Michal Stefanak
12+
* @link https://github.com/stefanak-michal/Bolt
13+
* @package Bolt
14+
*/
15+
final class Socket
16+
{
17+
/**
18+
* @var resource
19+
*/
20+
public static $socket;
21+
22+
/**
23+
* @param string $ip
24+
* @param int $port
25+
* @param int $timeout
26+
* @throws Exception
27+
*/
28+
public static function initialize(string $ip, int $port, int $timeout)
29+
{
30+
if (is_resource(self::$socket))
31+
return;
32+
33+
if (!extension_loaded('sockets')) {
34+
Bolt::error('PHP Extension sockets not enabled');
35+
return;
36+
}
37+
38+
self::$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
39+
if (!is_resource(self::$socket)) {
40+
Bolt::error('Cannot create socket');
41+
return;
42+
}
43+
44+
socket_set_block(Socket::$socket);
45+
socket_set_option(Socket::$socket, SOL_TCP, TCP_NODELAY, 1);
46+
socket_set_option(Socket::$socket, SOL_SOCKET, SO_KEEPALIVE, 1);
47+
socket_set_option(Socket::$socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $timeout, 'usec' => 0]);
48+
socket_set_option(Socket::$socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $timeout, 'usec' => 0]);
49+
50+
$conn = socket_connect(self::$socket, $ip, $port);
51+
if (!$conn) {
52+
$code = socket_last_error(Socket::$socket);
53+
Bolt::error(socket_strerror($code), $code);
54+
return;
55+
}
56+
}
57+
58+
/**
59+
* Write to socket
60+
* @param string $buffer
61+
* @throws Exception
62+
*/
63+
public static function write(string $buffer)
64+
{
65+
if (!is_resource(self::$socket)) {
66+
Bolt::error('Not initialized socket');
67+
return;
68+
}
69+
70+
$size = mb_strlen($buffer, '8bit');
71+
$sent = 0;
72+
73+
if (Bolt::$debug)
74+
Bolt::printHex($buffer);
75+
76+
while ($sent < $size) {
77+
$sent = socket_write(self::$socket, $buffer, $size);
78+
if ($sent === false) {
79+
$code = socket_last_error(self::$socket);
80+
Bolt::error(socket_strerror($code), $code);
81+
return;
82+
}
83+
84+
$buffer = mb_strcut($buffer, $sent, null, '8bit');
85+
$size -= $sent;
86+
}
87+
}
88+
89+
/**
90+
* Read unpacked from socket
91+
* @param IUnpacker $unpacker
92+
* @return mixed
93+
* @throws Exception
94+
*/
95+
public static function read(IUnpacker $unpacker)
96+
{
97+
if (!is_resource(self::$socket)) {
98+
Bolt::error('Not initialized socket');
99+
return;
100+
}
101+
102+
$msg = '';
103+
while (true) {
104+
$header = self::readBuffer(2);
105+
if (ord($header[0]) == 0x00 && ord($header[1]) == 0x00)
106+
break;
107+
$length = unpack('n', $header)[1] ?? 0;
108+
$msg .= self::readBuffer($length);
109+
}
110+
111+
$output = null;
112+
$signature = 0;
113+
if (!empty($msg)) {
114+
if (Bolt::$debug)
115+
Bolt::printHex($msg, false);
116+
117+
try {
118+
$output = $unpacker->unpack($msg, $signature);
119+
} catch (Exception $ex) {
120+
Bolt::error($ex->getMessage());
121+
}
122+
}
123+
124+
return [$signature, $output];
125+
}
126+
127+
/**
128+
* Read buffer from socket
129+
* @param int $length
130+
* @return string
131+
*/
132+
public static function readBuffer(int $length = 2048): string
133+
{
134+
$output = '';
135+
do {
136+
$output .= socket_read(self::$socket, $length - mb_strlen($output, '8bit'), PHP_BINARY_READ);
137+
} while (mb_strlen($output, '8bit') < $length);
138+
return $output;
139+
}
140+
}

autoload.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
define('BASE_PATH', dirname(__DIR__));
4+
define('DS', DIRECTORY_SEPARATOR);
5+
6+
spl_autoload_register(function ($name) {
7+
$parts = explode("\\", $name);
8+
$parts = array_filter($parts);
9+
10+
/*
11+
* namespace calls
12+
*/
13+
14+
//compose standart namespaced path to file
15+
$path = BASE_PATH . DS . implode(DS, $parts) . '.php';
16+
if (file_exists($path)) {
17+
require_once $path;
18+
return;
19+
}
20+
});

index.php

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
<?php
22

3-
require_once 'Bolt.php';
4-
require_once 'Packer.php';
5-
require_once 'Unpacker.php';
6-
require_once 'structures' . DIRECTORY_SEPARATOR . 'Node.php';
7-
require_once 'structures' . DIRECTORY_SEPARATOR . 'Relationship.php';
8-
require_once 'structures' . DIRECTORY_SEPARATOR . 'Path.php';
9-
require_once 'structures' . DIRECTORY_SEPARATOR . 'UnboundRelationship.php';
3+
/**
4+
* Bolt protocol library
5+
* This index.php file serve as usage preview and functional test. It can be removed.
6+
*
7+
* @author Michal Stefanak
8+
* @link https://github.com/stefanak-michal/Bolt
9+
*/
10+
11+
require_once 'autoload.php';
1012

1113
set_time_limit(3);
1214

1315
$user = 'neo4j';
1416
$password = 'nothing';
1517

18+
//\Bolt\Bolt::$debug = true;
19+
1620
try {
21+
1722
$neo4j = new \Bolt\Bolt();
23+
$neo4j->setProtocolVersions(4.1);
24+
1825
if (!$neo4j->init('MyClient/1.0', $user, $password)) {
1926
throw new Exception('Wrong login');
2027
}
@@ -26,30 +33,52 @@
2633
}
2734

2835
//test record
29-
$res = $neo4j->pullAll();
36+
$res = $neo4j->pull();
3037
if (($res[0][0] ?? 0) != 1 || ($res[0][1] ?? 0) != 2) {
3138
throw new Exception('Wrong record');
3239
}
3340

3441

3542
//test node create
3643
$neo4j->run('CREATE (a:Test) RETURN a, ID(a)');
37-
$created = $neo4j->pullAll();
44+
$created = $neo4j->pull();
3845
if (!($created[0][0] instanceof \Bolt\structures\Node)) {
3946
throw new Exception('Unsuccussful node create');
4047
}
4148

49+
//get neo4j version to use right placeholders
50+
$neo4j->run('call dbms.components() yield versions unwind versions as version return version');
51+
$neo4jVersion = $neo4j->pull()[0][0] ?? '';
52+
$t = version_compare($neo4jVersion, '4') == -1;
53+
4254
//test delete created node
43-
$neo4j->run('MATCH (a:Test) WHERE ID(a) = {a} DELETE a', [
55+
$neo4j->run('MATCH (a:Test) WHERE ID(a) = ' . ($t ? '{a}' : '$a') . ' DELETE a', [
4456
'a' => $created[0][1]
4557
]);
46-
$res = $neo4j->pullAll();
58+
$res = $neo4j->pull();
4759
if (($res[0]['stats']['nodes-deleted'] ?? 0) != 1) {
4860
throw new Exception('Unsuccussful node delete');
4961
}
5062

63+
//transaction
64+
if ($neo4j->getProtocolVersion() >= 3) {
65+
$neo4j->begin();
66+
$neo4j->run('CREATE (a:Test) RETURN a, ID(a)');
67+
$created = $neo4j->pull();
68+
$neo4j->rollback();
69+
70+
$neo4j->run('MATCH (a:Test) WHERE ID(a) = ' . ($t ? '{a}' : '$a') . ' RETURN COUNT(a)', [
71+
'a' => $created[0][1]
72+
]);
73+
$res = $neo4j->pull();
74+
if ($res[0][0] != 0)
75+
throw new Exception('Unsuccussful transaction rollback');
76+
}
77+
78+
unset($neo4j);
79+
80+
echo '<br><br>Test successful';
5181

52-
echo 'Test successful';
5382
} catch (Exception $e) {
5483
var_dump($e->getMessage());
5584
}

0 commit comments

Comments
 (0)