Skip to content

Commit 3d6253d

Browse files
committed
initial commit
0 parents  commit 3d6253d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+11790
-0
lines changed

.editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_size = 4
7+
indent_style = space
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true

.gitignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.php_cs.cache
2+
bin/*
3+
cache/
4+
neoclient.yml
5+
test.php
6+
tests/_reports
7+
tests/database_settings.yml
8+
vendor/
9+
phpunit.xml
10+
.phpunit.result.cache
11+
.idea/

.php_cs

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Laudis Neo4j package.
7+
*
8+
* (c) Laudis technologies <http://laudis.tech>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
$header = <<<'EOF'
15+
This file is part of the Laudis Neo4j package.
16+
17+
(c) Laudis technologies <http://laudis.tech>
18+
19+
For the full copyright and license information, please view the LICENSE
20+
file that was distributed with this source code.
21+
EOF;
22+
23+
try {
24+
$finder = PhpCsFixer\Finder::create()
25+
->in(__DIR__.'/src')
26+
->in(__DIR__.'/tests');
27+
} catch (Throwable $e) {
28+
echo $e->getMessage()."\n";
29+
30+
exit(1);
31+
}
32+
33+
return PhpCsFixer\Config::create()
34+
->setRules([
35+
'@Symfony' => true,
36+
37+
'array_syntax' => ['syntax' => 'short'],
38+
'header_comment' => ['header' => $header],
39+
'linebreak_after_opening_tag' => true,
40+
'ordered_imports' => true,
41+
'phpdoc_order' => true,
42+
'phpdoc_to_comment' => false,
43+
'yoda_style' => false,
44+
])
45+
->setFinder($finder)
46+
;

Dockerfile

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
FROM php:7.4-cli
2+
RUN apt-get update && apt-get install -y \
3+
libfreetype6-dev \
4+
libjpeg62-turbo-dev \
5+
libmcrypt-dev \
6+
libpng-dev \
7+
libzip-dev \
8+
zip \
9+
unzip \
10+
&& docker-php-ext-install -j$(nproc) gd sockets bcmath \
11+
&& pecl install ds \
12+
&& docker-php-ext-enable ds
13+
14+
ARG WITH_XDEBUG=false
15+
16+
RUN if [ $WITH_XDEBUG = "true" ] ; then \
17+
pecl install channel://pecl.php.net/xdebug-3.0.1; \
18+
docker-php-ext-enable xdebug; \
19+
fi;
20+
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
21+
22+
COPY composer.json composer.lock phpunit.xml.dist phpunit-bootstrap.php psalm.xml .php_cs ./
23+
COPY src/ src/
24+
COPY tests/ tests/
25+
COPY tools/ tools/
26+
27+
28+
RUN composer install && \
29+
composer install --working-dir=tools/php-cs-fixer && \
30+
composer install --working-dir=tools/psalm
31+
32+
33+
34+

Jenkinsfile

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
pipeline {
2+
agent any
3+
4+
stages {
5+
stage('Build') {
6+
steps {
7+
sh 'docker build -t php-neo4j:static-analysis .'
8+
sh 'docker-compose -f docker/docker-compose-4.2.yml build'
9+
sh 'docker-compose -f docker/docker-compose-4.1.yml build'
10+
sh 'docker-compose -f docker/docker-compose-4.0.yml build'
11+
sh 'docker-compose -f docker/docker-compose-3.5.yml build'
12+
sh 'docker-compose -f docker/docker-compose-2.3.yml build'
13+
sh 'docker-compose -f docker/docker-compose-php-7.4.yml build'
14+
sh 'docker build -t php-neo4j:static-analysis .'
15+
}
16+
}
17+
stage('Static Analysis') {
18+
steps {
19+
sh 'docker run php-neo4j:static-analysis tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --dry-run'
20+
sh 'docker run php-neo4j:static-analysis tools/psalm/vendor/bin/psalm --show-info=true'
21+
}
22+
}
23+
stage('Test') {
24+
steps {
25+
sh 'docker-compose -f docker/docker-compose-4.2.yml run client php vendor/bin/phpunit'
26+
sh 'docker-compose -f docker/docker-compose-4.1.yml run client php vendor/bin/phpunit'
27+
sh 'docker-compose -f docker/docker-compose-4.0.yml run client php vendor/bin/phpunit'
28+
sh 'docker-compose -f docker/docker-compose-3.5.yml run client php vendor/bin/phpunit'
29+
sh 'docker-compose -f docker/docker-compose-2.3.yml run client php vendor/bin/phpunit'
30+
sh 'docker-compose -f docker/docker-compose-php-7.4.yml run client php vendor/bin/phpunit'
31+
}
32+
}
33+
stage('Deploy') {
34+
steps {
35+
echo 'Deploying....'
36+
}
37+
}
38+
}
39+
}

LICENSE

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2020 laudis
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
# Laudis Neo4j PHP Client
2+
3+
## Installation and basic usage
4+
5+
### installing
6+
7+
Install via composer:
8+
9+
```bash
10+
composer require laudis/neo4j-php-client
11+
```
12+
13+
If you want to use the http protocol but haven't installed any of the psr7, psr17 and psr18 implementations yet:
14+
15+
```bash
16+
composer require guzzlehttp/guzzle guzzlehttp/psr7 http-interop/http-factory-guzzle
17+
```
18+
19+
### Initializing client
20+
21+
```php
22+
$client = Laudis\Neo4j\ClientBuilder::create()
23+
->addHttpConnection('backup', 'http://neo4j:password@localhost')
24+
->addBoltConnection('default', 'neo4j:password@localhost')
25+
->setDefaultConnection('default')
26+
->build();
27+
```
28+
29+
The default connection is the first registered connection, unless it is overridden with the `setDefaultConnection` method.
30+
31+
### Sending a Cypher Query
32+
33+
Sending a query is done by sending the cypher with optional parameters and a connection alias
34+
35+
```php
36+
$client->run(
37+
'MERGE (user {email: $email})', //The query is a required parameter
38+
['email' => '[email protected]'], //Parameters can be optionally added
39+
'backup' //The default connection can be overridden
40+
);
41+
```
42+
43+
Or by using a statement object
44+
45+
```php
46+
use Laudis\Neo4j\Databags\Statement;
47+
$statement = new Statement('MERGE (user {email: $email})', ['email' => '[email protected]']);
48+
$client->runStatement($statement, 'default');
49+
```
50+
51+
### Reading a Result
52+
53+
A result is a simple vector, with hashmaps representing a record.
54+
55+
```php
56+
foreach ($client->run('UNWIND range(1, 9) as x RETURN x') as $item) {
57+
echo $item->get('x');
58+
}
59+
```
60+
will echo `123456789`
61+
62+
The Map representing the Record can only contain null, scalar or array values. Each array can then only contain null, scalar or array values, ad infinitum.
63+
64+
## Diving Deeper
65+
66+
### Running multiple queries at once
67+
68+
You can run multiple statements at once by simple wrapping a bunch of statements in an iterable object and passing it to the `runStatements` method.
69+
70+
```php
71+
use Laudis\Neo4j\Databags\Statement;
72+
73+
$results = $client->runStatements([
74+
Statement::create('MATCH (x) RETURN x LIMIT 100'),
75+
Statement::create('MERGE (x:Person {email: $email})', ['email' => '[email protected]'])
76+
]);
77+
```
78+
79+
The results of each query will be wrapped in another vector:
80+
81+
```php
82+
$results->first(); //Contain the first result vector
83+
$results->get(0); //Contain the first result vector
84+
$result->get(1); //Contains the second result vector
85+
```
86+
87+
### Opening a transaction
88+
89+
Transactions can be started by opening one with the client.
90+
```php
91+
use Laudis\Neo4j\Databags\Statement;
92+
93+
$tsx = $client->openTransaction(
94+
// This is an optional set of statements to execute while opening the transaction
95+
[Statement::create('MERGE (x:Person({email: $email})', ['email' => '[email protected]'])],
96+
'backup' // This is the optional connection alias
97+
);
98+
```
99+
100+
**Note that the optional set of statements will not have their result returned to you, as the transaction will be returned instead**
101+
102+
You can then further execute statements on the transaction.
103+
104+
```php
105+
$result = $tsx->runStatement('MATCH (x) RETURN x LIMIT 100');
106+
$result = $tsx->runStatement(Statement::create('MATCH (x) RETURN x LIMIT 100'));
107+
$results = $tsx->runStatements([Statement::create('MATCH (x) RETURN x LIMIT 100')]);
108+
```
109+
110+
They can be committed or rolled back at will:
111+
112+
```php
113+
$tsx->rollback();
114+
```
115+
116+
```php
117+
$tsx->commit([Statement::create('MATCH (x) RETURN x LIMIT 100')]);
118+
```
119+
120+
*Note that the optional set of statements provided while comitting the transaction will not return the results.
121+
122+
### Providing custom injections
123+
124+
Each connection can be configured with custom injections.
125+
126+
```php
127+
use GuzzleHttp\Client;
128+
use GuzzleHttp\Handler\CurlHandler;
129+
use GuzzleHttp\HandlerStack;
130+
use GuzzleHttp\Middleware;
131+
use Laudis\Neo4j\Network\Bolt\BoltInjections;use Laudis\Neo4j\Network\Http\HttpInjections;
132+
133+
$client = Laudis\Neo4j\ClientBuilder::create()
134+
->addHttpConnection('backup', 'http://neo4j:password@localhost', HttpInjections::create()->withClient(static function () {
135+
$handler = HandlerStack::create(new CurlHandler());
136+
$handler->push(Middleware::cookies());
137+
return new Client(['handler' => $handler]);
138+
}))
139+
->addBoltConnection('default', 'neo4j:password@localhost', BoltInjections::create()->withDatabase('tags'))
140+
->build();
141+
```
142+
143+
Custom injections can be wrapped around a callable for lazy initialization as can be found in the example above.
144+
145+
## Final Remarks
146+
147+
### Filosophy
148+
149+
This client tries to strike a balance between extensibility, performance and clean code. All classes are marked final but where there is an interface, injections and decorators can be used.
150+
151+
I also chose not to implement custom resultsets but use the php-ds extension or polyfill instead. This is because these datastructures are a lot more capable than I will ever be able to make them. Php ds has a consistent interface, works nicely with psalm, has all features you can really want from a simple container and is incredibly fast.
152+
153+
Flexibility is maintained where possible by making all parameters iterables if they are a container of sorts. This means you can pass parameters as an array, \Ds\Map or any other object which implements the \Iterator or \IteratorAggregate. These are all valid:
154+
155+
```php
156+
// Vanilla flavour
157+
$client->run('MATCH (x {slug: $slug})', ['slug' => 'a']);
158+
// php-ds implementation
159+
$client->run('MATCH (x {slug: $slug})', new \Ds\Map(['slug' => 'a']));
160+
// laravel style
161+
$client->run('MATCH (x {slug: $slug})', collect(['slug' => 'a']));
162+
```
163+
164+
### Neo4j Version Support
165+
166+
| **Version** | **Tested** |
167+
|-------------|-------------|
168+
| 2.3 | Yes |
169+
| 3.0 + | Yes |
170+
| 4.0 + | Yes |
171+
172+
### Neo4j Feature Support
173+
174+
| **Feature** | **Supported?** |
175+
|----------------------|----------------|
176+
| Auth | Yes |
177+
| Transactions | Yes |
178+
| Http Protocol | Yes |
179+
| Bolt Protocol | Yes |
180+
| Cluster | Roadmap |
181+
| Graph Representation | Roadmap |
182+
183+
## Requirements
184+
185+
* PHP >= 7.4
186+
* A Neo4j database (minimum version 2.3)
187+
* ext-bcmath *
188+
* ext-sockets *
189+
* ext-json **
190+
* ext-ds ***
191+
192+
(*) Needed to implement the bolt protocol
193+
194+
(**) Needed to implement the http protocol
195+
196+
(***) Needed for optimal performance
197+
198+
## Roadmap
199+
200+
### Cluster support
201+
202+
Version 2.0 will have cluster support. The interface for this is not yet set in stone.
203+
204+
### Support for graph representation instead of simple records.
205+
206+
Version 2.0 will have graph representation suppport. The inteface for this is not yet set in stone, but will be somthing akin to this:
207+
208+
```php
209+
$graph = $client->graphOf('MATCH (x:Article) - [:ContainsReferenceTo] -> (y:Article)');
210+
211+
$node = $graph->enter(HasLabel::create('Article')->and(HasAttribute::create('slug', 'neo4j-is-awesome')));
212+
213+
foreach ($node->relationships() as $relationship) {
214+
if (!$relationship->endNode()->relationships()->isEmpty()) {
215+
echo 'multi level reference detected' . "\n";
216+
}
217+
}
218+
```

0 commit comments

Comments
 (0)