Статьи

Neo4j для PHP

Примечание: я приложил много усилий к Neo4jPHP с момента написания этого поста. Он довольно полнофункциональный и покрывает почти 100% интерфейса REST Neo4j. Любой, кто интересуется игрой с Neo4j из PHP, обязательно должен это проверить. Я хотел бы получить некоторые отзывы!

В последнее время я играю с графической базой данных Neo4j и ее применением к определенным классам задач. Графовые базы данных предназначены для решения проблем в областях, где связи данных могут быть на нескольких уровнях. Например, в реляционной базе данных очень легко ответить на вопрос «Дайте мне список всех актеров, которые были в фильме с Кевином Бэконом»:

> desc roles;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| actor_name  | varchar(100) | YES  |     | NULL    |                |
| role_name   | varchar(100) | YES  |     | NULL    |                |
| movie_title | varchar(100) | YES  |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+

> SELECT actor_name, role_name FROM roles WHERE movie_title IN (SELECT DISTINCT movie_title FROM roles WHERE actor_name='Kevin Bacon')

Извините, что я использую подзапросы (если хотите, перепишите его в JOIN в вашей голове).

Но предположим, что вы хотите получить имена всех актеров, которые были в фильме с кем-то, кто был в фильме с Кевин Бэкон. Внезапно, у вас есть еще одно СОЕДИНЕНИЕ против той же таблицы Теперь добавьте третью степень: кто-то, кто был в кино с кем-то, кто был в кино с кем-то, кто был в кино с Кевином Бэконом. По мере добавления степеней запрос становится все более громоздким, сложным в обслуживании и менее производительным.

Это именно тот тип проблемных графовых баз данных, которые предназначены для решения: поиск путей между частями данных, которые могут быть одним или несколькими отношениями, удаленными друг от друга. Они решают это очень элегантно, моделируя объекты предметной области как узлы и ребра графа (отношения в графе db графа), а затем обходя граф, используя хорошо известные и эффективные алгоритмы.

Приведенный выше пример можно очень легко смоделировать следующим образом: каждый актер — это узел, каждый фильм — это узел, а каждая роль — это отношение, идущее от актера к фильму, в котором они были:

Теперь становится очень легко найти путь от данного актера до Кевина Бэкона.

Neo4j — это графическая база данных с открытым исходным кодом, имеющая структуру лицензирования как сообщества, так и предприятия. Он поддерживает транзакции и может обрабатывать миллиарды узлов и отношений в одном экземпляре. Первоначально он был построен для встраивания в приложения Java, и большая часть документации и примеров тому доказательство. К сожалению, нет родной оболочки PHP для общения с Neo4j.

К счастью, Neo4j также имеет встроенный REST-сервер, и PHP очень хорошо использует REST-сервисы. Уже есть хорошая PHP-библиотека Neo4j REST , но я решил написать свою собственную, чтобы лучше понять, как на самом деле работает интерфейс REST. Ты сможешьзахватите это здесь, и все примеры кода ниже написаны, используя это. Концепции можно легко перенести на любой REST-клиент Neo4j.

Сначала нам нужно инициализировать соединение с базой данных. Поскольку это интерфейс REST, постоянное соединение отсутствует, и на самом деле обмен данными не происходит до тех пор, пока нам не понадобится в первый раз прочитать или записать данные:

use Everyman\Neo4j\Client,
    Everyman\Neo4j\Transport,
    Everyman\Neo4j\Node,
    Everyman\Neo4j\Relationship;

$client = new Client(new Transport('localhost', 7474));

Теперь нам нужно создать узел для каждого из наших актеров и фильмов. Это аналогично операторам INSERT, используемым для ввода данных в традиционную СУБД:

$keanu = new Node($client);
$keanu->setProperty('name', 'Keanu Reeves')->save();
$laurence = new Node($client);
$laurence->setProperty('name', 'Laurence Fishburne')->save();
$jennifer = new Node($client);
$jennifer->setProperty('name', 'Jennifer Connelly')->save();
$kevin = new Node($client);
$kevin->setProperty('name', 'Kevin Bacon')->save();

$matrix = new Node($client);
$matrix->setProperty('title', 'The Matrix')->save();
$higherLearning = new Node($client);
$higherLearning->setProperty('title', 'Higher Learning')->save();
$mysticRiver = new Node($client);
$mysticRiver->setProperty('title', 'Mystic River')->save();

Каждый узел имеет методы `setProperty` и` getProperty`, которые позволяют хранить произвольные данные на узле. Связь с сервером не происходит до вызова `save ()`, который должен вызываться для каждого узла.

Связать актера с фильмом означает установить отношения между ними. В терминах RDBMS взаимосвязь заменяет таблицу соединений или столбец внешнего ключа. В этом примере отношение всегда начинается с актера, указывающего на фильм, и помечается как отношение типа «действовал»:

$keanu->relateTo($matrix, 'IN')->save();
$laurence->relateTo($matrix, 'IN')->save();

$laurence->relateTo($higherLearning, 'IN')->save();
$jennifer->relateTo($higherLearning, 'IN')->save();

$laurence->relateTo($mysticRiver, 'IN')->save();
$kevin->relateTo($mysticRiver, 'IN')->save();

Вызов `relateTo` возвращает объект Relationship, который похож на узел, в котором могут храниться произвольные свойства. Каждое отношение также сохраняется в базе данных.

Направление отношений совершенно произвольно; пути могут быть найдены независимо от того, в каком направлении указывает связь. Вы можете использовать любую семантику, имеющую смысл для вашей проблемной области. В приведенном выше примере имеет смысл, что актер находится «в» фильме, но с такой же легкостью можно сказать, что в фильме «есть» актер. Одни и те же два узла могут иметь несколько взаимосвязей друг с другом, с разными направлениями, типами и свойствами.

Все отношения установлены, и теперь мы готовы найти связи между любым действующим лицом в нашей системе и Кевином Бэконом. Обратите внимание, что максимальная длина пути будет 12 (6 степеней разделения, умноженные на 2 узла для каждого градуса; узел актера и узел фильма.)

$path = $keanu->findPathsTo($kevin)
    ->setMaxDepth(12)
    ->getSinglePath();

foreach ($path as $i => $node) {
    if ($i % 2 == 0) {
        echo $node->getProperty('name');
        if ($i+1 != count($path)) {
            echo " was in\n";
        }
    } else {
        echo "\t" . $node->getProperty('title') . " with\n";
    }
}

Путь — это упорядоченный массив узлов. Узлы чередуются между актером и фильмом.

Вы также можете выполнить типичные запросы типа «найти все похожие фильмы»:

echo $laurence->getProperty('name') . " was in:\n";
$relationships = $laurence->getRelationships('IN');
foreach ($relationships as $relationship) {
    $movie = $relationship->getEndNode();
    echo "\t" . $movie->getProperty('title') . "\n";
}

`getRelationships` возвращает массив всех отношений, которые имеет узел, опционально ограничивая только отношения данного типа. Также возможно указать только входящие или исходящие отношения. Мы настроили наши данные таким образом, чтобы все отношения «IN» были от актера к фильму, поэтому мы знаем, что конечным узлом любого отношения «IN» является фильм.

В интерфейсе REST есть больше возможностей, включая индексирование узлов и отношений, запросы и обход (что позволяет более сложные способы поиска пути). Поддержка транзакций / пакетных операций над REST пока помечена как «экспериментальная». Я надеюсь добавить оболочки для большего количества этой функциональности в ближайшее время. Я также буду публиковать больше о различных типах проблем, которые могут быть очень элегантно решены с помощью графических баз данных.

В следующем посте мы расскажем немного подробнее о поиске путей и некоторых доступных алгоритмах.