Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Neo4J Bolt Laravel Package

graphaware/neo4j-bolt

A PHP library for communicating with Neo4j over the Bolt protocol, providing a low-level client for sending Cypher queries and handling results in PHP applications. It underpins GraphAware's Neo4j PHP integrations and supports efficient binary communication with Neo4j servers.

View on GitHub
Deep Wiki
Context7

Getting Started

graphaware/neo4j-bolt is a raw PHP driver for Neo4j's Bolt binary protocol — not a Laravel package. There is no service provider, facade, config file, or auto-discovery. You install it, then wire everything yourself. Its practical role day-to-day is to be the thin pipe between your Laravel app and a Neo4j 3.x server, wrapped inside a repository class so nothing else in the app knows it exists.

composer require graphaware/neo4j-bolt

First query:

use GraphAware\Bolt\GraphDatabase;

$driver  = GraphDatabase::createDriver('bolt://neo4j:secret@localhost:7687');
$session = $driver->session();

$result = $session->run(
    'MATCH (u:User {id: {id}}) RETURN u.name AS name',
    ['id' => 42]
);

foreach ($result->records() as $record) {
    echo $record->get('name');
}

$session->close();
$driver->close();

Order of exploration:

  1. GraphAware\Bolt\GraphDatabase::createDriver() — the only static entry point; opens the socket immediately.
  2. GraphAware\Bolt\Driver — session(), close().
  3. GraphAware\Bolt\Session — run($cypher, array $params, $tag), close().
  4. GraphAware\Bolt\Result\Result / Record — records(), get($key), values(), summarize().
  5. GraphAware\Bolt\Result\Type\{Node,Relationship,Path} — the hydrated graph objects.

Your first real use case should be one read-only query that MySQL is bad at (friends-of-friends, "users who follow brand X via any 2-hop path", fraud ring detection) hidden behind a single repository method called from one controller. Do not attempt a full domain model in Cypher on day one.

Implementation Patterns

Bind the driver once, as scoped(), never per query. createDriver() opens a TCP socket on construction, so a singleton-per-request is the baseline pattern:

// AppServiceProvider::register()
$this->app->scoped('neo4j.driver', fn ($app) => GraphDatabase::createDriver(
    config('neo4j.uri'),
    ['timeout' => config('neo4j.timeout', 5)],
    $app->make(\Psr\Log\LoggerInterface::class)
));

Prefer scoped() over singleton() — under Octane and long-running queue workers a singleton keeps a stale socket alive across requests and you get intermittent ConnectionExceptions.

Sessions are cheap, wrap them in try/finally. The universal pattern:

class Neo4jUserRepository
{
    public function __construct(private Driver $driver) {}

    public function friendsOfFriends(int $id): array
    {
        $session = $this->driver->session();
        try {
            $result = $session->run(
                'MATCH (u:User {id:{id}})-[:FOLLOWS*2]->(f) RETURN DISTINCT f.id AS id',
                ['id' => $id]
            );
            return array_map(fn ($r) => $r->get('id'), $result->records()->toArray());
        } finally {
            $session->close();
        }
    }
}

An abandoned session mid-result-stream leaves protocol state hanging; finally is non-negotiable.

Define a contract and never leak Bolt types past the repository. Records return GraphAware\Bolt\Result\Type\Node, not arrays. Map to DTOs / Eloquent models at the boundary via $node->getProperties() and $node->labels(). A Neo4jClient interface also makes the inevitable migration to laudis/neo4j-php-client a one-class change rather than an app-wide sweep.

Split reads and writes by execution context.

  • Controller/API-read traversals → injected repository.
  • Graph writes ("recommended for you" materialization, edge creation) → queued jobs. Bolt is blocking and adds latency you don't want on the request path.
  • Repeated identical traversals → Cache::remember("neo4j:u:{$id}:fof", 300, fn () => $repo->friendsOfFriends($id)). Neo4j is fast, not free.

Transactions. Some 1.x releases expose $session->transaction(); check your pinned version. If it's missing you only get single-statement atomicity, which is a strong argument for upgrading to the maintained client rather than writing compensating Cypher.

Testing. Because the factory is static and hits a socket, bind a fake implementing your Neo4jClient interface in TestCase. Reserve real integration tests for a Dockerized neo4j:3.5 so you test against the same Bolt version you ship.

Gotchas and Tips

  • Archived — treat any adoption as a fork. No PHP 8 fixes, no Bolt 5, no security patches, zero dependents. composer require may warn or fail on modern PHP; --ignore-platform-reqs is a stopgap, not a fix. From day one, open a migration ticket for laudis/neo4j-php-client (the maintained successor, same conceptual API).
  • Placeholders are {name}, not $name. Always pass values through the second argument to run(). String interpolation into Cypher is Cypher injection — identical risk profile to SQL injection, and more likely because users are used to query builders.
  • Bolt type limits. Scalars, lists, and maps only. DateTime, enums, Carbon, and Eloquent models must be converted (ISO-8601 strings / epoch ints / arrays) before passing. Unsupported objects throw a ProtocolException with a vague message.
  • Integers. Bolt Ints are signed 64-bit; PHP ints on 64-bit builds are fine. Watch for float coercion of large IDs and null vs 0 in optional MATCHes.
  • Exceptions to catch distinctly. ConnectionException (socket down, auth failure) vs ProtocolException (unsupported type, Bolt version mismatch) vs client exceptions (Cypher syntax/semantic errors). Map them in your repository so callers can see ConnectionException → retryable, others → 500. Never blindly retry writes; you'll duplicate edges.
  • Result streaming is lazy. $result->records() returns a collection that may hold unread messages on the wire. Call summarize() or iterate fully before closing the session, or you can get protocol desync on the next query on the same session.
  • Auth/TLS are inside the URI. bolt://user:pass@host:7687 covers basic auth. TLS requires bolt+tls:// or explicit config flags depending on version — verify against your Neo4j server, not the docs, which trail the archived code.
  • No connection pool, no retries, no health check. Add a php artisan neo4j:ping command that does RETURN 1 and wire it into your readiness probe. Add a short timeout in the config array (see ['timeout' => 5]) and a circuit breaker around any call from a request path.
  • Bolt protocol version pins the pair. A Neo4j server upgrade that bumps the Bolt handshake will hard-fail this driver. Pin Neo4j and Neo4j-bolt together in composer.lock and in your Docker image, and add a CI job that runs one query against the exact server version you deploy.
  • Extension points are thin. The driver accepts a Psr\Log\LoggerInterface and a config array — that's the whole surface. Query-level metrics, span logging, and slow-query detection must be built in your repository wrapper (time $session->run(), log $cypher with param keys but redacted values, emit to your metrics backend).
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console