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

pandawan-technology/neo4j-bundle

Pandawan Neo4j Bundle integrates the Neo4j graph database into Symfony/PHP applications, providing configuration and services to connect, query, and manage graph data. Useful for projects needing graph relationships, traversal, and Cypher-backed persistence.

View on GitHub
Deep Wiki
Context7

Getting Started

First, do not composer require this. The README opens with "NOT MAINTAINED ANYMORE" and redirects to neo4j-contrib/neo4j-symfony, and the package is a Symfony bundle — pulling it into a Laravel app drags in symfony/http-kernel, symfony/dependency-injection, and a bootstrapping model that does not exist in Laravel. Its 1 star and 0 dependents mean you are the only person who will ever file a bug against it. Treat the repo as reference source, not a dependency.

Where to look first (30 minutes, timeboxed):

  1. DependencyInjection/Neo4jExtension.php — the config tree: connection names, driver URI, auth, and the clients map. This is the entire contract of the bundle.
  2. Resources/config/services.yml — how the client is constructed and aliased. This is the only design decision worth copying: a named client service per configured connection, built once and injected.
  3. The profiler/logging wiring — the bundle advertises query profiling; note where it hooks the client (a decorator), because you will reimplement that in Laravel.

That is the whole package. Everything else is Symfony plumbing you will not reuse.

The Laravel-shaped first use case — a graph of User nodes with FOLLOWS edges, backed by the maintained driver directly:

composer require graphaware/neo4j-php-client
// config/neo4j.php
return [
    'default' => env('NEO4J_CONNECTION', 'default'),
    'connections' => [
        'default' => [
            'uri'      => env('NEO4J_URI', 'bolt://neo4j:[email protected]:7687'),
            'database' => env('NEO4J_DATABASE', 'neo4j'),
        ],
    ],
];
// app/Providers/Neo4jServiceProvider.php
class Neo4jServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->mergeConfigFrom(config_path('neo4j.php'), 'neo4j');

        // scoped(), not singleton() — see Gotchas (Octane).
        $this->app->scoped(Client::class, function ($app) {
            $cfg = $app['config']->get('neo4j.connections.'.config('neo4j.default'));

            return ClientBuilder::create()
                ->addConnection('default', $cfg['uri'])
                ->setDefaultTimeout(30)
                ->build();
        });
    }

    public function boot(): void
    {
        $this->publishes([config_path('neo4j.php') => config_path('neo4j.php')], 'neo4j-config');
    }
}
// first query
app(Client::class)->run(
    'MERGE (u:User {id: $id}) RETURN u',
    ['id' => $userId]
);

If the target stack is genuinely Symfony, stop reading this repo and evaluate neo4j-contrib/neo4j-symfony against your Symfony version matrix instead. For Laravel, the driver + a thin provider is the package.

Implementation Patterns

1. Map the bundle's multi-connection YAML to Laravel config. The bundle's one real feature is named clients. Preserve that shape so multiple graph connections (e.g. analytics, identity) stay addressable:

public function register(): void
{
    $this->app->scoped('neo4j.connection.'.$name, fn () => ...);
    // or a small ConnectionManager that lazily builds by name
}

Keep a Neo4jManager with a connection(string $name = null): Client method if you run more than one graph. Otherwise a single scoped Client is enough and the manager is over-engineering.

2. Repository-per-aggregate, thin. Do not leak the client into controllers or models. One class per graph aggregate, constructor-injected, Cypher inline, parameters always bound:

final class FollowGraph
{
    public function __construct(private Client $client) {}

    public function follow(int $from, int $to): void
    {
        $this->client->run(
            'MATCH (a:User {id: $from}), (b:User {id: $to})
             MERGE (a)-[:FOLLOWS]->(b)',
            ['from' => $from, 'to' => $to]
        );
    }

    public function followers(int $id, int $limit = 50): array
    {
        return $this->client->run(
            'MATCH (:User {id: $id})<-[:FOLLOWS]-(f:User)
             RETURN f.id AS id LIMIT $limit',
            ['id' => $id, 'limit' => $limit]
        )->toArray();
    }
}

This is exactly what the bundle's service-injection pattern gives you in Symfony — you are just re-implementing it in four lines of provider code.

3. Reimplement the profiling hook as a decorator. The bundle's logging support is a wrapper around the client. Do the same, bind the decorator in the container, and gate it on app()->isLocal():

final class LoggingClient implements ClientInterface
{
    public function __construct(private ClientInterface $inner) {}

    public function run(string $statement, array $params = [])
    {
        $t = hrtime(true);
        try {
            return $this->inner->run($statement, $params);
        } finally {
            Log::channel('neo4j')->debug('cypher', [
                'ms'     => (hrtime(true) - $t) / 1e6,
                'cypher' => $statement,
                'params' => $params, // redact PII here
            ]);
        }
    }
}

Bind ClientInterface to the decorator when config('app.debug') is true, otherwise bind directly.

4. Health check as an Artisan command. Every integration needs one and the bundle does not ship it:

Artisan::command('neo4j:ping', function () {
    $this->app->make(Client::class)->run('RETURN 1 AS ok');
    $this->info('neo4j ok');
})->purpose('Verify Neo4j connectivity');

Wire it into your deploy pre-flight and your /health endpoint via Queue::fake-style checks so container health reflects graph health.

5. Testing: swap the binding, don't hit a server. Because everything resolves through the container, tests are clean:

$this->app->instance(Client::class, $fake);

For integration tests, spin up Neo4j with Testcontainers (or a docker-compose service) and point the connection URI at it via env — the config layer already supports that. Do not attempt to unit-test Cypher; test the repository against a real instance and assert on results.

6. Queue workers and Octane: reset per unit of work. If you use singleton(), the client holds a stale Bolt connection across jobs. Use scoped() (Laravel resets it between Octane requests) and, for queue workers, close the driver in a JobProcessed listener so long-running workers do not leak sockets during a Neo4j restart.

7. Graceful degradation. Graph features are usually non-critical relative to your relational core. Wrap repository calls in a circuit breaker (resilience helpers or a small Retry/fallback) so a Neo4j outage degrades recommendations/recommendation panels rather than 500-ing the app.

Gotchas and Tips

  • The package is a redirect, not a component. Every hour spent adapting its Symfony service definitions to Laravel's container is an hour spent rebuilding what three lines of app->scoped() already give you. If you find yourself writing a Symfony\Component\DependencyInjection\ContainerInterface shim, stop — you have gone off the rails.
  • No Laravel-native anything. No service provider, no facade, no publishable config, no config/ conventions. Laravel's container does not read the bundle's services.yml, so out of the box this package does literally nothing in a Laravel app.
  • Version drift is the real hazard, not the code you read. An unmaintained bundle's composer.json constraints will pin ancient Symfony versions and, transitively, an old graphaware/neo4j-php-client whose Bolt protocol version may not talk to Neo4j 5.x. Symptom: composer require resolves a strange set, or the server rejects the handshake. Check the target Neo4j server version before anything else.
  • No CVE path. No maintainer, no security contact, no issue triage. If a transitive dependency is flagged by your supply-chain scanner, you own the fork. If your org runs an SCA gate, this package fails it — do not fight it.
  • Bind to an interface, not the concrete client. If you ever swap graphaware/neo4j-php-client for neo4j/neo4j-php-client, you want one line changed in your provider, not a grep across the codebase. Type-hint your own Neo4jClient interface, implement it around the driver.
  • Always parameterise Cypher — never interpolate. Graph queries are string-built by nature; treat Cypher exactly like SQL. This is the single most common production defect in graph integrations and the bundle does nothing to help.
  • Profiling overhead. The bundle's logging hooks are on by default in the original wiring; if you copy that, gate it on app.debug. Neo4j queries are chatty and per-query Log::debug in production will flood the log and dominate wall time.
  • Connection lifecycle in long-running processes. Octane, queue workers, and php artisan serve all keep processes alive. A cached Client will hold a dead socket after a database restart and every call will fail until the worker recycles. scoped() + a health check in the supervisor loop (or a JobProcessed teardown) is the fix.
  • Config quirks carried over from the bundle: it expects driver URIs of the form bolt://user:pass@host:port or bolt+routing://. If you copy a URI into .env and quote it wrong, auth failures surface as opaque connection errors. The ping command above is how you tell the difference between "wrong password" and "server down".
  • Extension points? None worth using. The bundle's services.yml is editable in the vendor directory but any such edit is lost on composer update. Forking an abandoned repo to add a feature means you have adopted a maintenance burden nobody signed off on. Do not.
  • Migration note if you inherit it. There is no installed base to migrate from (0 dependents), so any migration is manual: map each named bundle service to a Laravel config connection, move the injection sites to your repository classes, and delete the Symfony dependency tree. Budget a day, not a week — the surface area is small precisely because nobody used it.
  • Step back and justify the graph store. If Neo4j is incidental to your product, a Postgres recursive CTE or a pg_edge table solves the same queries with one fewer runtime, one fewer driver, and one fewer thing abandoned on you. The bundle being abandoned is a data point about the PHP graph ecosystem's thinness — weight it in the architecture decision, not just the dependency decision.
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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels