stefanak-michal/bolt
Low-level PHP Bolt protocol driver (Bolt <= 6) for TCP socket communication with graph databases like Neo4j, Memgraph, Amazon Neptune, and others. Supports PHP 8.1+ and tracks official protocol message specifications across versions.
Install the package:
composer require stefanak-michal/bolt
Basic connection:
use Bolt\Bolt;
use Bolt\connection\Socket;
$connection = new Socket('localhost', 7687); // Default Neo4j Bolt port
$bolt = new Bolt($connection);
$protocol = $bolt->build(); // Auto-selects latest supported version (4.3-6)
First query:
$protocol
->hello()
->logon(['scheme' => 'basic', 'principal' => 'neo4j', 'credentials' => 'password'])
->run('MATCH (n) RETURN n LIMIT 10')
->pullAll();
Bolt class: Factory for protocol versions (default: 4.3-6).Socket/StreamSocket: Connection classes (use Socket for plain TCP, StreamSocket for SSL).Client helper: Simplified CRUD operations (see Implementation Patterns).Connection & Handshake:
$protocol = (new Bolt(new Socket('host', 7687)))
->setProtocolVersions(5.4) // Optional: Force specific version
->build();
$protocol->hello()->getResponse();
Query Execution:
// Pipeline pattern (recommended for performance)
$protocol
->run('MATCH (n) WHERE n.name = $name RETURN n', ['name' => 'Alice'])
->pullAll(); // Fetch all records at once
Transactions:
$protocol
->begin()
->run('CREATE (n:Test {id: $id})', ['id' => 1])
->commit();
Laravel Service Provider:
// config/bolt.php
'connections' => [
'neo4j' => [
'host' => env('NEO4J_HOST', 'localhost'),
'port' => env('NEO4J_PORT', 7687),
'user' => env('NEO4J_USER', 'neo4j'),
'pass' => env('NEO4J_PASS', 'password'),
'version' => '5.4',
],
];
// app/Providers/BoltServiceProvider.php
public function register()
{
$this->app->singleton('bolt', function ($app) {
$config = config('bolt.connections.neo4j');
$connection = new Socket($config['host'], $config['port']);
return (new Bolt($connection))
->setProtocolVersions($config['version'])
->build();
});
}
Query Builder Helper:
// app/Services/Neo4jQueryBuilder.php
class Neo4jQueryBuilder {
protected $protocol;
public function __construct($protocol) {
$this->protocol = $protocol;
}
public function findNodes(string $label, array $conditions = []) {
$query = "MATCH (n:$label)";
if (!empty($conditions)) {
$query .= ' WHERE ' . $this->buildConditions($conditions);
}
$query .= ' RETURN n';
return $this->protocol
->run($query, $conditions)
->pullAll();
}
}
Event Listeners:
// Listen for model events (e.g., Eloquent)
public function handle(NodeCreated $event) {
$bolt = app('bolt');
$bolt->run('CREATE (n:User {email: $email})', ['email' => $event->user->email]);
}
Batching:
// Process large datasets in chunks
$protocol->run('MATCH (n) RETURN n SKIP $skip LIMIT 100', ['skip' => 0]);
while (true) {
$records = $protocol->pull()->getResponse();
if (empty($records)) break;
// Process records...
$protocol->discard(); // Free server memory
$protocol->run('... SKIP $skip LIMIT 100', ['skip' => $skip += 100]);
}
Parameterized Queries:
// Avoid string concatenation for security/performance
$protocol->run(
'MATCH (n:User {email: $email}) RETURN n',
['email' => $user->email]
);
Protocol Version Mismatch:
Bolt 6 with Neo4j 4.x will fail.$bolt->setProtocolVersions(4.4, 5.0); // Only allow these
Connection Timeouts:
Socket class or use StreamSocket with custom timeout:
$socket = new StreamSocket('ssl://host:7687', [
'timeout' => 30, // 30 seconds
]);
Memory Leaks with Large Results:
pullAll() loads all records into memory.pull() in a loop with discard():
$protocol->run('MATCH (n) RETURN n');
while (true) {
$records = $protocol->pull()->getResponse();
if (empty($records)) break;
// Process records...
$protocol->discard();
}
Authentication Errors:
logon scheme/credentials.basic, bearer) and debug responses:
$bolt->debug(true); // Enable hex debugging
$response = $protocol->logon(['scheme' => 'basic', ...])->getResponse();
Cypher Parameter Types:
array instead of object for dictionaries).Bytes class for binary data or implement IPackListGenerator for custom types.Bolt::$debug = true; // Hex dump of all messages
$response = $protocol->getResponse();
if ($response->isSuccess()) {
// Handle success
} else {
throw new \RuntimeException($response->getFailureMessage());
}
DEBUG level. Enable in neo4j.conf:
dbms.logs.query.debug=true
Custom Connection Classes:
AbstractConnection to support custom protocols (e.g., WebSocket):
class WebSocketConnection extends AbstractConnection {
public function connect() { /* ... */ }
}
Protocol Extensions:
AbstractProtocol:
class CustomProtocol extends AbstractProtocol {
public function customMessage(array $extra = []) {
return $this->sendMessage('+Custom', $extra);
}
}
Response Decorators:
Response objects to transform data:
$response = $protocol->run('...')->pull()->getResponse();
$nodes = collect($response->getRecords())->map(function ($record) {
return (object) $record['n'];
});
1. For Bolt 6, set explicitly:
$bolt->setPackStreamVersion(2);
PipelineTooLarge.openssl extension is enabled for StreamSocket with SSL:
$socket = new StreamSocket('ssl://host:7687', [
'ssl' => [
'verify_peer' => false, // Disable for self-signed certs (not recommended)
],
]);
commit()/rollback() frequently.// Correct
$protocol->run('MATCH (n) WHERE n.id = $id', ['id' => 1]);
// Incorrect (syntax error)
$protocol
How can I help you explore Laravel packages today?