Skip to content

Commit 04fc9c8

Browse files
Merge pull request #134 from neo4j-php/beta_v7
v7
2 parents a256e7f + 04ddeaa commit 04fc9c8

File tree

99 files changed

+1250
-1336
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+1250
-1336
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ cert/
66
*.lock
77
*.cache
88
phpunit.dev.xml
9+
temp/
10+
tmp/

README.md

Lines changed: 73 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
# Bolt
44

5-
PHP library for communication with graph database over TCP socket with Bolt protocol specification. Bolt protocol was
6-
created by [Neo4j](https://neo4j.com/) and documentation is available
7-
at [https://www.neo4j.com/](https://www.neo4j.com/docs/bolt/current/). This library is aimed to be low level, support
5+
PHP library for communication with graph database over TCP socket with Bolt protocol specification. Bolt protocol was created by [Neo4j](https://neo4j.com/) and documentation is available at [https://www.neo4j.com/](https://www.neo4j.com/docs/bolt/current/). This library is aimed to be low level, support
86
all available versions and keep up with protocol messages architecture and specifications.
97

108
[![](https://img.shields.io/github/stars/stefanak-michal/Bolt)](https://github.com/neo4j-php/Bolt/stargazers)
@@ -16,28 +14,24 @@ all available versions and keep up with protocol messages architecture and speci
1614

1715
<a href='https://jb.gg/OpenSourceSupport' target='_blank'><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main) logo." width="100" /></a>
1816

19-
## Version support
17+
## :label: Version support
2018

2119
We are trying to keep up and this library supports **Bolt <= 5.4**.
2220

2321
https://www.neo4j.com/docs/bolt/current/bolt-compatibility/
2422

25-
## Requirements
23+
## :white_check_mark: Requirements
2624

27-
Keep up with [PHP supported versions](https://www.php.net/supported-versions.php) means we are at **PHP^8**.
25+
This library keep up with [PHP supported versions](https://www.php.net/supported-versions.php) what means it is at **PHP^8.1**.
2826

29-
_If you need support for PHP < 7.4 you can use latest v3.x release and if you need support for PHP 7.4 you can use v5.x.
30-
Not all new features are implement backwards and this readme is updated to latest released version._
31-
32-
### Extensions
27+
### PHP Extensions
3328

3429
- [mbstring](https://www.php.net/manual/en/book.mbstring.php)
3530
- [sockets](https://www.php.net/manual/en/book.sockets.php) (optional) - Required when you use Socket connection class
3631
- [openssl](https://www.php.net/manual/en/book.openssl.php) (optional) - Required when you use StreamSocket connection
3732
class with enabled SSL
38-
- [phpunit](https://phpunit.de/) (development)
3933

40-
## Installation
34+
## :floppy_disk: Installation
4135

4236
You can use composer or download this repository from github and manually implement it.
4337

@@ -55,47 +49,14 @@ Run the following command in your project to install the latest applicable versi
5549
2. Unpack
5650
3. Copy content of `src` directory into your project
5751

58-
## Usage
52+
## :desktop_computer: Usage
5953

60-
Concept of usage is based on Bolt messages. Available protocol methods depends on Bolt version. Communication works
61-
in [pipeline](https://www.neo4j.com/docs/bolt/current/bolt/message/#pipelining) and you can chain multiple Bolt messages
62-
before fetching response from the server.
54+
Concept of usage is based on Bolt messages. Bolt messages are mapped 1:1 as protocol methods. Available protocol methods depends on Bolt version. Communication works in [pipeline](https://www.neo4j.com/docs/bolt/current/bolt/message/#pipelining) and you can chain multiple Bolt messages before consuming response from the server.
6355

64-
Main `Bolt` class serves as Factory design pattern and it returns instance of protocol class by requested Bolt version. Query execution and fetching response is split in two methods. First message `run` is for
65-
sending queries. Second message `pull` is for fetching response from last executed query on database.
66-
Response from database for Bolt message `pull` always contains n+1 rows because last entry is `success` message with
56+
Main `Bolt` class serves as Factory design pattern and it returns instance of protocol class by requested Bolt version. Basic usage consist of query execution and fetching response which is split in two methods. First message `run` is for sending queries. Second message `pull` is for fetching response from executed query on database. Response from database for Bolt message `pull` always contains n+1 rows because last entry is `success` message with
6757
meta informations.
6858

69-
More info about available Bolt messages: https://www.neo4j.com/docs/bolt/current/bolt/message/
70-
71-
### Example
72-
73-
```php
74-
// Create connection class and specify target host and port.
75-
$conn = new \Bolt\connection\Socket('127.0.0.1', 7687);
76-
// Create new Bolt instance and provide connection object.
77-
$bolt = new \Bolt\Bolt($conn);
78-
// Set requested protocol versions
79-
$bolt->setProtocolVersions(5.1, 5, 4.4);
80-
// Build and get protocol version instance which creates connection and executes handshake.
81-
$protocol = $bolt->build();
82-
// Connect and login into database
83-
$protocol->hello();
84-
$protocol->logon(['scheme' => 'basic', 'principal' => 'neo4j', 'credentials' => 'neo4j']);
85-
86-
// Pipeline two messages. One to execute query with parameters and second to pull records.
87-
$protocol
88-
->run('RETURN $a AS num, $b AS str', ['a' => 123, 'b' => 'text'])
89-
->pull();
90-
91-
// Fetch waiting server responses for pipelined messages.
92-
foreach ($protocol->getResponses() as $response) {
93-
// $response is instance of \Bolt\protocol\Response.
94-
// First response is SUCCESS message for RUN message.
95-
// Second response is RECORD message for PULL message.
96-
// Third response is SUCCESS message for PULL message.
97-
}
98-
```
59+
:information_source: More info about available Bolt messages: https://www.neo4j.com/docs/bolt/current/bolt/message/
9960

10061
### Available methods
10162

@@ -132,12 +93,12 @@ foreach ($protocol->getResponses() as $response) {
13293
| pullAll | @see pull | |
13394
| discardAll | @see discard | |
13495

135-
Many methods accept argument called `$extra`. This argument can contain any of key-value by Bolt specification. This
96+
Multiple methods accept argument called `$extra`. This argument can contain any of key-value by Bolt specification. This
13697
argument was extended during Neo4j development which means the content of it changed. You should keep in mind what
13798
version you are working with when using this argument. You can read more about extra parameter in Bolt documentation
13899
where you can look into your version and bolt message.
139100

140-
Annotation of methods in protocol classes contains direct link to specific version and message from mentioned
101+
:information_source: Annotation of methods in protocol classes contains direct link to specific version and message from mentioned
141102
documentation website.
142103

143104
### Authentification
@@ -179,51 +140,77 @@ of data. To learn more you can
179140
check [performance test](https://github.com/neo4j-php/Bolt/blob/master/tests/PerformanceTest.php)
180141
or [packer test](https://github.com/neo4j-php/Bolt/blob/master/tests/PackStream/v1/PackerTest.php).
181142

182-
Structures `Node`, `Relationship`, `UnboundRelationship` and `Path` cannot be used as parameter. They are available only
143+
:warning: Structures `Node`, `Relationship`, `UnboundRelationship` and `Path` cannot be used as parameter. They are available only
183144
as received data from database.
184145

185-
Server state is not available from server but we assume it. Library contains `\Bolt\helpers\ServerState` and you can get
186-
used instance of this class with `$bolt->serverState` or `$protocol->serverState` (after you call `build()`).
146+
### Example
187147

188-
### Autoload
148+
```php
149+
// Choose and create connection class and specify target host and port.
150+
$conn = new \Bolt\connection\Socket('127.0.0.1', 7687);
151+
// Create new Bolt instance and provide connection object.
152+
$bolt = new \Bolt\Bolt($conn);
153+
// Set requested protocol versions ..you can add up to 4 versions
154+
$bolt->setProtocolVersions(5.4);
155+
// Build and get protocol version instance which creates connection and executes handshake.
156+
$protocol = $bolt->build();
189157

190-
Directory `src` contains autoload file which accepts only Bolt library namespaces. Main Bolt namespace points to this
191-
directory. If you have installed this project with composer, you have to load `vendor/autoload.php`.
158+
// Initialize communication with database
159+
$response = $protocol->hello()->getResponse();
160+
// verify $response for successful initialization
192161

193-
## Server state
162+
// Login into database
163+
$response = $protocol->logon(['scheme' => 'basic', 'principal' => 'neo4j', 'credentials' => 'neo4j'])->getResponse();
164+
// verify $response for successful login
194165

195-
If assumed server state is different than expected, library does not throw exception. This logic is silent but you can
196-
change it and if you would like to implement own logic when assumed server state is different than expected you can
197-
assign callable into class property `$serverState->expectedServerStateMismatchCallback`.
166+
// Pipeline two messages. One to execute query with parameters and second to pull records.
167+
$protocol
168+
->run('RETURN $a AS num, $b AS str', ['a' => 123, 'b' => 'text'])
169+
->pull();
170+
171+
// Fetch waiting server responses for pipelined messages.
172+
foreach ($protocol->getResponses() as $response) {
173+
// $response is instance of \Bolt\protocol\Response.
174+
// First response is SUCCESS message for RUN message.
175+
// Second response is RECORD message for PULL message.
176+
// Third response is SUCCESS message for PULL message.
177+
}
178+
```
198179

199-
## Connection
180+
### Autoload
200181

201-
Bolt class constructor accepts connection argument. This argument has to be instance of class which implements
202-
IConnection interface. Currently exists two predefined classes `Socket` and `StreamSocket`.
182+
Directory `src` contains autoload file which accepts only Bolt library namespaces. Main Bolt namespace points to this
183+
directory. If you have installed this project with composer, you have to load `vendor/autoload.php`.
184+
185+
## :chains: Connection
203186

204-
_We provide two connection classes. `Socket` was created first and it has better memory usage. `StreamSocket` was made
205-
because of need to accept TLS._
187+
Bolt class constructor accepts connection argument. This argument has to be instance of class which implements IConnection interface. Library offers few options.
206188

207189
**\Bolt\connection\Socket**
208190

209-
This class use php extension sockets. More informations
191+
This class use php extension sockets and has better memory usage. More informations
210192
here: [https://www.php.net/manual/en/book.sockets.php](https://www.php.net/manual/en/book.sockets.php)
211193

212194
**\Bolt\connection\StreamSocket**
213195

214196
This class uses php stream functions. Which is a part of php and there is no extensions needed. More informations
215197
here: [https://www.php.net/manual/en/ref.stream.php](https://www.php.net/manual/en/ref.stream.php)
216198

217-
StreamSocket besides of implemented methods from interface has method to configure SSL. When you want to activate SSL
199+
StreamSocket besides of implemented methods from interface has method to configure SSL. SSL option requires php extension openssl. When you want to activate SSL
218200
you have to call method `setSslContextOptions`. This method accept array by php specification available
219201
here: [https://www.php.net/manual/en/context.ssl.php](https://www.php.net/manual/en/context.ssl.php).
220202

221-
_If you want to use it, you have to enable openssl php extension._
203+
**\Bolt\connection\PStreamSocket**
204+
205+
This class extends StreamSocket and adds support for persistent connections. Upon reuse of connection remaining buffer is consumed and message RESET is automatically sent. PHP is stateless therefore using this connection class requires storing meta information about active TCP connection. Default storage is `\Bolt\helper\FileCache` which you can change with method `setCache` (PSR-16 Simple Cache).
206+
207+
:warning: If your system reuse persistent connection and meta information about it was lost for some reason, your attemt to connect will end with ConnectionTimeoutException. Repeated attempt to connect will succeed.
208+
209+
## :lock: SSL
222210

223211
### Neo4j Aura
224212

225-
Connecting to Aura requires encryption which is provided with SSL. To connect to Aura you have to use `StreamSocket`
226-
connection class and enable SSL.
213+
Connecting to Aura requires encryption which is provided with SSL. To connect to Aura you have to use `StreamSocket` connection class and enable SSL.
227214

228215
```php
229216
$conn = new \Bolt\connection\StreamSocket('helloworld.databases.neo4j.io');
@@ -251,18 +238,22 @@ $conn->setSslContextOptions([
251238
$bolt = new \Bolt\Bolt($conn);
252239
```
253240

254-
You can also take a look at my article on how to implement SSL for Neo4j running on localhost
241+
:bookmark: You can also take a look at my article on how to implement SSL for Neo4j running on localhost
255242
at [Neo4j and self signed certificate](https://ko-fi.com/post/Neo4j-and-self-signed-certificate-on-Windows-S6S2I0KQT).
256243

257-
### Timeout
244+
## :stopwatch: Timeout
258245

259246
Connection class constructor contains `$timeout` argument. This timeout is for established socket connection. To set up
260247
timeout for establishing socket connection itself you have to set ini directive `default_socket_timeout`.
261248

262249
_Setting up ini directive isn't part of connection class because function `ini_set` can be disabled on production
263250
environments for security reasons._
264251

265-
## Another solutions
252+
## :vertical_traffic_light: Server state
253+
254+
Server state is not reported by server but it is evaluated by received response. You can access current state through property `$protocol->serverState`. This property is updated with every call `getResponse(s)`.
255+
256+
## :pushpin: More solutions
266257

267258
If you need simple class to cover basic functionality you can
268259
use: [neo4j-bolt-wrapper](https://packagist.org/packages/stefanak-michal/neo4j-bolt-wrapper)
@@ -273,3 +264,11 @@ on: [php-client](https://packagist.org/packages/laudis/neo4j-php-client)
273264
PDO implementation is available at [pdo-bolt](https://github.com/stefanak-michal/pdo-bolt)
274265

275266
More informations can be found at: https://neo4j.com/developer/php/
267+
268+
## :recycle: Old versions
269+
270+
If you need support for end-of-life PHP versions, here is a short info list. Not all new features are implement backwards and this readme is updated to latest released version.
271+
272+
* PHP < 7.4 - v3.x
273+
* PHP 7.4 - v5.x
274+
* PHP 8.0 - v6.x

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
"license": "MIT",
99
"minimum-stability": "stable",
1010
"require": {
11-
"php": "^8",
12-
"ext-mbstring": "*"
11+
"php": "^8.1",
12+
"ext-mbstring": "*",
13+
"psr/simple-cache": "^3.0"
1314
},
1415
"require-dev": {
1516
"phpunit/phpunit": "^9"

phpunit.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
</testsuites>
1616
<php>
1717
<var name="NEO_USER" value="neo4j"/>
18-
<var name="NEO_PASS" value="nothing"/>
18+
<var name="NEO_PASS" value="nothing123"/>
1919
</php>
2020
</phpunit>

src/Bolt.php

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
use Bolt\error\ConnectException;
66
use Bolt\error\BoltException;
7-
use Bolt\protocol\{AProtocol, ServerState};
7+
use Bolt\protocol\AProtocol;
8+
use Bolt\enum\{Signature, ServerState};
89
use Bolt\connection\IConnection;
910

1011
/**
@@ -22,11 +23,10 @@ final class Bolt
2223
private int $packStreamVersion = 1;
2324

2425
public static bool $debug = false;
25-
public ServerState $serverState;
2626

2727
public function __construct(private IConnection $connection)
2828
{
29-
$this->setProtocolVersions(5, 4.4);
29+
$this->setProtocolVersions(5.4, 5, 4.4);
3030
}
3131

3232
/**
@@ -35,27 +35,69 @@ public function __construct(private IConnection $connection)
3535
*/
3636
public function build(): AProtocol
3737
{
38-
$this->serverState = new ServerState();
39-
$this->serverState->is(ServerState::DISCONNECTED, ServerState::DEFUNCT);
38+
$protocol = null;
4039

4140
try {
4241
if (!$this->connection->connect()) {
4342
throw new ConnectException('Connection failed');
4443
}
4544

46-
$version = $this->handshake();
45+
if ($this->connection instanceof \Bolt\connection\PStreamSocket) {
46+
$protocol = $this->persistentBuild();
47+
}
4748

48-
$protocolClass = "\\Bolt\\protocol\\V" . str_replace('.', '_', $version);
49-
if (!class_exists($protocolClass)) {
50-
throw new ConnectException('Requested Protocol version (' . $version . ') not yet implemented');
49+
if (empty($protocol)) {
50+
$protocol = $this->normalBuild();
5151
}
52-
} catch (ConnectException $e) {
53-
$this->serverState->set(ServerState::DEFUNCT);
52+
} catch (BoltException $e) {
53+
$this->connection->disconnect();
5454
throw $e;
5555
}
5656

57-
$this->serverState->set(ServerState::CONNECTED);
58-
return new $protocolClass($this->packStreamVersion, $this->connection, $this->serverState);
57+
if ($this->connection instanceof \Bolt\connection\PStreamSocket) {
58+
$this->connection->getCache()->set($this->connection->getIdentifier(), $protocol->getVersion());
59+
}
60+
61+
return $protocol;
62+
}
63+
64+
private function normalBuild(): AProtocol
65+
{
66+
$version = $this->handshake();
67+
68+
$protocolClass = "\\Bolt\\protocol\\V" . str_replace('.', '_', $version);
69+
if (!class_exists($protocolClass)) {
70+
throw new ConnectException('Requested Protocol version (' . $version . ') not yet implemented');
71+
}
72+
73+
$protocol = new $protocolClass($this->packStreamVersion, $this->connection);
74+
$protocol->serverState = version_compare($version, '5.1', '>=') ? ServerState::NEGOTIATION : ServerState::CONNECTED;
75+
return $protocol;
76+
}
77+
78+
private function persistentBuild(): ?AProtocol
79+
{
80+
$version = $this->connection->getCache()->get($this->connection->getIdentifier());
81+
if (empty($version)) {
82+
return null;
83+
}
84+
85+
$protocolClass = "\\Bolt\\protocol\\V" . str_replace('.', '_', $version);
86+
if (!class_exists($protocolClass)) {
87+
throw new ConnectException('Requested Protocol version (' . $version . ') not yet implemented');
88+
}
89+
90+
/** @var AProtocol $protocol */
91+
$protocol = new $protocolClass($this->packStreamVersion, $this->connection);
92+
$protocol->serverState = ServerState::INTERRUPTED;
93+
94+
if ($protocol->reset()->getResponse()->signature != Signature::SUCCESS) {
95+
$this->connection->disconnect();
96+
$this->connection->connect();
97+
return null;
98+
}
99+
100+
return $protocol;
59101
}
60102

61103
public function setProtocolVersions(int|float|string ...$v): Bolt
@@ -72,12 +114,6 @@ public function setPackStreamVersion(int $version = 1): Bolt
72114
return $this;
73115
}
74116

75-
public function setConnection(IConnection $connection): Bolt
76-
{
77-
$this->connection = $connection;
78-
return $this;
79-
}
80-
81117
/**
82118
* @link https://www.neo4j.com/docs/bolt/current/bolt/handshake/
83119
* @throws BoltException

0 commit comments

Comments
 (0)