🦁

Laravelでグラフデータベース(Docker+Neo4J)を使う

2023/06/01に公開

はじめに

概要としてはLaravelでneo4j-php-clientというライブラリを用いてNeo4Jを操作する手順を書いたもの。

一時期グラフデータベースというものに興味があって、そのとき調べた内容を整理していきたい。

グラフデータベースというのはNoSQLの一種であり、RDBMSが苦手とするエンティティ同士の複雑な関係の扱いが得意とされている。
例えば RDBMSである人のフォロワーのフォロワーまで辿ろうとするとテーブルをJOINで結合する処理が複数回発生するという問題があるが、グラフデータベースはこのような処理を得意としている。

Neo4Jというのはオープンソースのグラフデータベースとしてよく知られているもので、SQLとは異なるCypherというクエリ言語を使ってデータベースの操作を行う。
wikipediaによると、00年代前半から存在する枯れた技術(?)ではあるが、2022年にもバージョン5へのアップデートが行われている。

Neo4JやCypherのクエリの使い方は別の機会に上げるとして、
今回はdocker環境でLaravelアプリケーションから接続するところまで記載。

docker-compose.yml

    image: neo4j
    ports:
        - 7474:7474
        - 7687:7687
    volumes: 
        - ./volumes/neo4j/data:/data
        - ./volumes/neo4j/logs:/logs
        - ./volumes/neo4j/conf:/conf
    environment:
        - dbms.connector.bolt.listen_address=:7688
        - dbms.connector.bolt.advertised_address=:7688
    container_name: neo4j

7474ポートがブラウザ上で動作するクライアントアプリを開くポートで、7687がデータベースそのもののポートである。

ブラウザで初期設定を済ます

http://localhost:7474を開くと初回はデータベースとの接続設定が表示される。

次にクライアントの初期パスワードを変更する。
このとき「neo4j」という文字列は使えないので注意。

Laravelでの準備

ライブラリの種類

Laravelで動かせるNeo4J用のドライバはneo4j-php-clientNeoEloquentの2種類が存在する。

neo4j-php-clientは、クラスのインスタンスにクエリを渡すことでNeo4Jのグラフデータベースを操作できるシンプルなライブラリであり、Laravel以外のphp環境でも動かせると思われる。
NeoEloquentはLaravel用に作られたライブラリで、試していないがgithub上のイントロダクションを見る限りではモデルクラスにNeoEloquentクラスを継承して使用する。

基本的にはMySQLなどのRDBMSを使って、複雑な関係を分析したいときにNeo4Jを使う」というユースケースを想定するのであれば前者の方が使うやすいと思われる。

今回は学習コストの低そうなneo4j-php-clientを使用してみた。

ライブラリのインストール

composerからインストールする

composer require laudis/neo4j-php-client

使い方

インスタンスの生成

docker-composeでneo4jというサービス名で7687番ポートにneo4jコンテナを立てた場合、下記のように任意の箇所でClientBuilderインスタンスを生成することで接続が可能。

use Laudis\Neo4j\ClientBuilder;
use Laudis\Neo4j\Databags\Statement;

class Hogehoge
{
      $client = ClientBuilder::create()->withDriver('default', 'bolt://username:password@service_name:7687')->build();
}

ドライバを扱う方法

Neo4Jのドライバを操作する方法は下記3パターン。

  • 自動コミット
  • 手動でトランザクションを行う
  • writeTransactionメソッド(クロージャ)を使う

公式推薦なのは3つめのwriteTransactionメソッドを使うことであるが、
一番簡単なのは自動コミットを使う方法なのでまずはそちらがおすすめ。

以下、自動コミットとwriteTransactionメソッドを使った方法だけ説明。
(手動トランザクションは使っていないので省略)

自動コミット

公式のものを少し改変。

ClientBuilderインスタンスを作ってNeo4Jとの接続を確立し、runStatementsメソッドにてStatementインスタンスを渡す。


use Laudis\Neo4j\ClientBuilder;
use Laudis\Neo4j\Databags\Statement;

class Hogehoge
{
    $client = ClientBuilder::create()->withDriver('default', 'bolt://neo4j:password@neo4j:7687')->build();

    $client->runStatements([
            Statement::create(
                'MERGE (user:User {email: $email})', 
                ['email' => 'abc@hotmail.com']
            )
        ]);
}

この方法の欠点はクエリの文法ミスがあってもエラーが返ってこずに、正常終了したかのような振る舞いをすること。

Transactionメソッド(クロージャ)で記述する方法

公式で推薦されている方法。

自動コミットのrunStatementメソッドではreturnで返したノードが受け取れないようだが、こちらの方法であればreturnしたノードを受け取ることができる。

例えば、「データベース上の全てのノードをnとして取得し、nをuserという名前でreturn返す」場合は下記のようになる。

$query =  'MATCH (n) return n as user';

$result = $client->writeTransaction(static function (TransactionInterface $tsx) use ($query) {
       $result = $tsx->run($query);
       return $result;
});

dd($result[0]->get('user')->getProperty('email'));
// >> abc@hotmail.com

結果は配列として返ってくるので、foreachを使うか取り出したい配列のキーを指定。
getメソッドでuserノードが選択され、getPropertyメソッドでノード内の特定のプロパティの値を取り出すことができる。

【おまけ】全てのノード・エッジを削除したい場合

最初の方は手探りで色々なクエリを試していくことになると思うので、一括削除のクエリを知っておくと便利。

MATCH (n) DETACH DELETE n

上記のクエリは「まず全てのノードにマッチし、関連づけを解除した上でマッチしたノードを削除する」というクエリ。

以上。

Discussion