laudis/neo4j-php-client
Typed Neo4j PHP client/driver with Bolt and Neo4j (auto-routed) support. Intuitive, extensible API with easy configuration, built with input from the official driver team and validated via Neo4j Testkit for reliability.
Installation:
composer require laudis/neo4j-php-client
Ensure your project meets the requirements: PHP ≥7.4, Neo4j ≥3.5, and extensions bcmath, json, and sockets.
Basic Client Setup: Configure the client with a Bolt driver (recommended for production):
use Laudis\Neo4j\ClientBuilder;
$client = ClientBuilder::create()
->withDriver('bolt', 'bolt+s://neo4j:password@localhost:7687')
->withDefaultDriver('bolt')
->build();
First Query: Execute a simple Cypher query to verify connectivity:
$result = $client->run('RETURN 1 AS one');
echo $result->first()->get('one'); // Output: 1
First Use Case: Create a node and return its properties:
$result = $client->writeTransaction(function ($tx) {
return $tx->run('CREATE (n:User {name: $name}) RETURN n', ['name' => 'John Doe'])
->first()
->get('n');
});
Use transaction functions for idempotent operations (recommended for most use cases):
// Idempotent write transaction
$userId = Uuid::v4();
$client->writeTransaction(function ($tx) use ($userId) {
$tx->run('MERGE (u:User {id: $id}) SET u.name = $name', [
'id' => $userId,
'name' => 'Alice'
]);
});
Always use parameters to avoid injection and improve readability:
$email = 'user@example.com';
$client->run('MATCH (u:User {email: $email}) RETURN u', ['email' => $email]);
Iterate over results or fetch specific columns:
// Fetch all users
$users = $client->run('MATCH (u:User) RETURN u');
foreach ($users as $user) {
$node = $user->get('u');
echo $node->getProperty('name');
}
// Fetch a single value
$count = $client->run('MATCH (u:User) RETURN count(u) AS total')->first()->get('total');
For advanced control (e.g., custom rollback logic):
$tx = $client->beginTransaction();
try {
$tx->run('CREATE (n:Test)');
$tx->commit();
} catch (\Exception $e) {
$tx->rollback();
throw $e;
}
Use runStatements for batch operations:
$statements = [
Statement::create('CREATE (n:User {id: $id})', ['id' => 1]),
Statement::create('CREATE (n:User {id: $id})', ['id' => 2]),
];
$client->runStatements($statements);
Store the client in a service provider or singleton:
// app/Providers/Neo4jServiceProvider.php
public function register()
{
$this->app->singleton('neo4j', function () {
return ClientBuilder::create()
->withDriver('bolt', config('neo4j.uri'))
->withDefaultDriver('bolt')
->build();
});
}
Use dependency injection in controllers:
public function __construct(private ClientInterface $neo4j) {}
public function index()
{
$users = $this->neo4j->run('MATCH (u:User) RETURN u');
return view('users.index', compact('users'));
}
Non-Idempotent Transactions: Avoid side effects outside the transaction (e.g., incrementing counters). Retries will fail if the operation isn’t repeatable.
// ❌ Bad: Non-idempotent
$client->writeTransaction(function ($tx) use (&$counter) {
$counter++; // Side effect!
$tx->run('CREATE (n:Test)');
});
// ✅ Good: Idempotent
$client->writeTransaction(function ($tx) {
$tx->run('CREATE (n:Test)');
});
Parameter Type Ambiguity:
Empty arrays default to CypherList. Use ParameterHelper for clarity:
$client->run('UNWIND $list RETURN count(*)', [
'list' => ParameterHelper::asList([]) // Explicitly a list
]);
Connection Timeouts:
Bolt connections may hang. Use neo4j:// for auto-routing in clusters:
$client = ClientBuilder::create()
->withDriver('neo4j', 'neo4j://cluster.example.com')
->build();
Result Caching: The driver doesn’t cache results. For frequent queries, consider:
CALL db.cache.clear() for full cache invalidation.Large Result Sets:
Avoid RETURN * or unbounded LIMIT. Use pagination:
$offset = 0;
$limit = 100;
$users = $client->run(
'MATCH (u:User) RETURN u SKIP $offset LIMIT $limit',
['offset' => $offset, 'limit' => $limit]
);
Enable Logging: Configure the driver to log queries and responses:
$client = ClientBuilder::create()
->withDriver('bolt', 'bolt://localhost', null, [
'logging' => [
'level' => Logger::DEBUG,
'handler' => new StreamHandler('php://stdout'),
],
])
->build();
Query Profiling:
Use PROFILE to analyze slow queries:
$result = $client->run('PROFILE MATCH (u:User) RETURN u');
$summary = $result->getSummary();
echo $summary->getPlanDescription();
Error Handling:
Catch Neo4jException for driver-specific errors:
try {
$client->run('INVALID CYPHER');
} catch (Neo4jException $e) {
echo $e->getCode(); // Neo4j status code (e.g., 400 for syntax errors)
}
Custom Formatters: Override result formatting (e.g., for JSON APIs):
$client = ClientBuilder::create()
->withFormatter(new class implements ResultFormatterInterface {
public function formatResult(ResultInterface $result): ResultInterface {
// Custom logic (e.g., convert nodes to arrays)
return $result;
}
})
->build();
Middleware: Intercept queries for logging/auditing:
$client = ClientBuilder::create()
->withMiddleware(function ($session) {
return new class($session) implements SessionInterface {
public function run(string $query, array $params = []): ResultInterface {
// Log query before execution
logger()->debug("Executing: $query", ['params' => $params]);
return $this->session->run($query, $params);
}
};
})
->build();
Custom Types:
Extend CypherType for domain-specific types:
class UserType extends CypherType {
public function serialize($value): string {
return json_encode($value);
}
public function deserialize(string $value): array {
return json_decode($value, true);
}
}
Driver Aliases:
Use aliases to switch between environments (e.g., dev, prod):
$client = ClientBuilder::create()
->withDriver('bolt:dev', 'bolt://dev.example.com')
->withDriver('bolt:prod', 'bolt://prod.example.com')
->withDefaultDriver('bolt:dev')
->build();
Bookmarks: Resume transactions from a specific point (useful for retries):
$client = ClientBuilder::create()
->withDriver('bolt', 'bolt://localhost', null, [
'bookmarks' => ['bookmark1', 'bookmark2'],
])
->build();
Max Connection Pool Size: Limit connections to avoid overload:
$client = ClientBuilder::create()
->withDriver('bolt', 'bolt://localhost', null, [
'max_connection_pool_size' =>
How can I help you explore Laravel packages today?