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 Bundle Laravel Package

neo4j/neo4j-bundle

A Symfony bundle that integrates Neo4j graph databases into PHP applications. It configures the Neo4j client, wires services for dependency injection, and provides convenient access to graph database queries and transactions from Symfony projects.

View on GitHub
Deep Wiki
Context7

Getting Started

This is not a Laravel package — it's a Symfony Bundle (neo4j/neo4j-bundle, source at neo4j-php/neo4j-symfony) that wires the laudis/neo4j-php-client into Symfony's DI container. Laravel's package discovery will not find it, and it cannot be registered in config/app.php because it declares Symfony\Component\HttpKernel\Bundle\Bundle. The realistic Laravel path is to depend on the underlying client and reuse the bundle's patterns.

On a Symfony project (the intended target):

  1. composer require neo4j/neo4j-bundle
  2. Register Neo4j\Neo4jBundle\Neo4jBundle::class => ['all' => true] in config/bundles.php.
  3. Configure a connection in config/packages/neo4j.yaml (keys have shifted across releases — check the installed version's README):
neo4j:
  default_connection: default
  connections:
    default:
      dsn: '%env(NEO4J_DSN)%' # bolt://neo4j:password@localhost:7687
  1. Inject Neo4j\Client\ClientInterface (or the DriverInterface) into a service and run a first statement.

On a Laravel project (the pragmatic route):

  1. composer require laudis/neo4j-php-client — same driver the bundle wraps.
  2. Add NEO4J_DSN=bolt://neo4j:secret@localhost:7687 to .env and config/neo4j.php returning ['dsn' => env('NEO4J_DSN')].
  3. Bind a singleton in a Neo4jServiceProvider:
$this->app->singleton(ClientInterface::class, function () {
    return ClientBuilder::create()
        ->withDriver('default', config('neo4j.dsn'))
        ->withDefaultDriver('default')
        ->build();
});
  1. Create a Neo4j facade over the singleton so you can write Neo4j::run('MATCH (n) RETURN count(n)').

First use case: a graph sidecar next to Eloquent — user follows, product recommendations, permission hierarchies, or audit lineage. Keep relational data in MySQL/Postgres and treat Neo4j as an index you rebuild from domain events; that's the lowest-risk first integration.

Implementation Patterns

Connection and client access. The bundle exposes the client through Symfony's autowiring aliases on ClientInterface and DriverInterface. In Laravel, mirror that: bind both interfaces, and always resolve the client from the container rather than constructing it in a controller — the driver owns a connection pool that must outlive a single request.

Session-per-unit-of-work. Do not run queries directly on the client unless you're comfortable with ephemeral sessions. The pattern the bundle encourages and that translates cleanly to Laravel:

$session = Neo4j::driver()->createSession();
try {
    $session->run('CREATE (u:User {id: $id})', ['id' => $user->id]);
} finally {
    $session->close();
}

Wire this into a small Neo4jRepository base class or a Neo4j::transaction(fn ($tx) => ...) helper so session lifecycle is never duplicated in controllers.

Syncing Eloquent to the graph. The highest-value pattern is write-through via observers or domain events dispatched to a queue:

User::created(fn ($user) => SyncUserToGraph::dispatch($user->id));

Inside the job, use MERGE (not CREATE) so re-delivery from a queue is idempotent: MERGE (u:User {id: $id}) SET u.name = $name. Never let a graph write failure roll back the relational transaction — the graph is a derived view, and queue retries will heal it.

Schema as code. There is no migration system. Keep a database/neo4j/schema.cypher (or an Artisan command neo4j:schema) with idempotent statements:

CREATE CONSTRAINT user_id IF NOT EXISTS FOR (u:User) REQUIRE u.id IS UNIQUE;
CREATE INDEX user_email IF NOT EXISTS FOR (u:User) ON (u.email);

Run this on deploy. Without a uniqueness constraint on your sync key, MERGE silently produces duplicates under concurrency.

Reads that earn their keep. Don't use the graph for straightforward lookups — Eloquent is better. Use Cypher for variable-depth traversal, shortest path, and recommendation queries where the join count would explode in SQL. Return flat maps (.name, .id) rather than whole nodes so result hydration stays cheap.

Instrumentation belongs at session/transaction level. Since 1.2.0 the bundle routes sessions and transactions through the same stopwatch, logger and event bus it already used for the client, so Symfony's profiler shows per-session and per-transaction spans rather than one opaque client call. That's a useful blueprint: in Laravel, decorate your bound ClientInterface (or your Neo4j facade/Neo4jRepository base class) so that session creation, transaction commit and each run() are timed and dispatched as Laravel events. If you only instrument the client you will see one number for an entire request, which is rarely the query that hurt you.

Testing. Spin up Neo4j via Testcontainers (testcontainers/testcontainers-php) or a fixed test database, and wrap each test in an explicit transaction you roll back. Never point the test suite at NEO4J_DSN from .env — add a separate NEO4J_TEST_DSN and fail loudly if they match.

Gotchas and Tips

  • The bundle name is a trap. neo4j/neo4j-bundle is the Composer name; the repo is neo4j-php/neo4j-symfony. It's Symfony-only. Budget for writing roughly 40 lines of Laravel glue rather than expecting a drop-in.
  • Password encoding in the DSN. Passwords with @, /, : or % must be URL-encoded, or auth fails with a confusing "unauthorized" that looks like a server problem. Prefer the builder's withBasicAuth() over string DSNs when credentials come from a secret manager.
  • Lazy connection. The driver doesn't connect until the first query. A misconfigured host surfaces at the first run(), not at boot — so add a neo4j:ping Artisan/console command and call it in your health check and post-deploy smoke test.
  • Results are lazy streams. The client's Result yields records as you iterate. If you run a second query on the same session before consuming the first result, you can deadlock or truncate. Consume (or ->toArray() on small results) before issuing the next statement.
  • Transaction scoping. Auto-commit mode is fine for single writes; use explicit beginTransaction() for multi-statement writes. Long-lived transactions hold locks and can starve the pool — keep them under a few hundred milliseconds and never wrap an HTTP call inside one.
  • Pool sizing. The driver keeps a connection pool per driver instance. In Laravel Octane or a queue worker, the singleton will be reused across requests — that's desirable, but tune maxConnectionPoolSize and make sure you're not also lazily creating drivers in a helper.
  • No auto-migrations. Constraints and indexes are your responsibility, and creating a constraint on a large existing label can be slow. Run schema changes as a deploy step with IF NOT EXISTS, never inline at request time.
  • Multi-database (Neo4j 4+). The default neo4j database is often wrong in clustered or Aura deployments. Specify the database when creating the session, or you'll write to an unexpected store.
  • Version drift. The bundle's config tree keys (drivers/connections/dsn placement) have changed between major releases. Pin the version and read that tag's README rather than the blog posts — 1.2.0 is additive over 1.1.x (extra stopwatch/logging/event-bus coverage for sessions and transactions), so config written for 1.1.x still applies, but that is not a guarantee across majors.
  • Extension points. On Symfony, you can override services via container aliases and swap the client builder with a decorated factory — this is how you'd inject a custom PSR-3 logger or a retry-enabled driver, and the same decorators are now what feed the event bus for sessions and transactions. In Laravel the equivalent hooks are the service provider bindings plus ClientBuilder::withDefaultLogger(); there's no event bus to subscribe to, so if you want per-query events you own the decorator. That decorator is also the cheapest place to add retry/backoff for transient Neo4jExceptions.
  • Debugging. Enable Cypher logging via the client's logger (withDefaultLogger($psrLogger)) and watch slow-query logs on the server side (dbms.logs.query.enabled=true, dbms.logs.query.threshold=100ms). On Symfony, 1.2.0 means the profiler panel now breaks timings down by session and transaction — read that before reaching for server-side logging. In Laravel you have no profiler panel, so log at minimum: query text hash, parameters count (not values), duration, and whether it ran in a transaction. Most "the graph is slow" complaints are missing indexes, not the driver.
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