diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..5ab54ef
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,44 @@
+name: Main
+
+on: [push, pull_request]
+
+jobs:
+ phpunit:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php: [8.0, 8.1]
+ name: PHP ${{ matrix.php }}
+ steps:
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+ tools: php-cs-fixer:3
+
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Run PHP CS Fixer
+ run: php-cs-fixer fix --diff --dry-run
+
+ - name: Install
+ uses: "ramsey/composer-install@v2"
+
+ - name: Run tests
+ run: ./vendor/bin/phpunit
+ services:
+ neo4j:
+ image: neo4j
+ ports:
+ - 7687:7687
+ - 7474:7474
+ env:
+ NEO4J_AUTH: none
+ options: >-
+ --health-cmd "wget http://localhost:7474 || exit 1"
+ --health-interval 1s
+ --health-timeout 10s
+ --health-retries 20
+ --health-start-period 3s
diff --git a/.gitignore b/.gitignore
index 2d24543..27eaed8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
vendor/
composer.lock
-.DS_Store
\ No newline at end of file
+.DS_Store
+.phpunit.result.cache
diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
new file mode 100644
index 0000000..a685465
--- /dev/null
+++ b/.php-cs-fixer.php
@@ -0,0 +1,18 @@
+in(__DIR__.'/src')
+ ->in(__DIR__.'/tests')
+ ->in(__DIR__.'/_demo')
+;
+
+$config = new PhpCsFixer\Config();
+
+return $config
+ ->setRules([
+ '@PSR12' => true,
+ '@Symfony' => true,
+ ])
+ ->setUsingCache(false)
+ ->setFinder($finder)
+;
diff --git a/.styleci.yml b/.styleci.yml
deleted file mode 100644
index 9faf6cc..0000000
--- a/.styleci.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-preset: symfony
-
-finder:
- exclude:
- - "build"
- - "vendor"
-
-enabled:
- - short_array_syntax
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 556bb70..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-language: php
-php:
- - 7.0
- - 7.1
-
-before_install:
- - sudo apt-get update > /dev/null
- # install Oracle JDK8
- - sh -c ./build/install-jdk8.sh
- # install and launch neo4j
- - sh -c ./build/install-neo.sh
- - composer install --prefer-source --no-interaction
- - composer self-update
-
-script:
- - vendor/bin/phpunit
-
-notifications:
- email: "christophe@graphaware.com"
\ No newline at end of file
diff --git a/README.md b/README.md
index 20421d8..4b78e62 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
GraphAware Reco4PHP is a library for building complex recommendation engines atop Neo4j.
-[![Build Status](https://travis-ci.org/graphaware/neo4j-php-client.svg)](https://travis-ci.org/graphaware/reco4php)
+[![Build Status](https://github.com/graphaware/reco4php/workflows/main/badge.svg)](https://github.com/graphaware/reco4php/actions)
Features:
@@ -15,8 +15,8 @@ Features:
Requirements:
-* PHP7.0+
-* Neo4j 2.2.6+ (Neo4j 3.0+ recommended)
+* PHP8.0+
+* Neo4j 3.5 / 4.0+
The library imposes a specific recommendation engine architecture, which has emerged from our experience building recommendation
engines and solves the architectural challenge to run recommendation engines remotely via Cypher.
@@ -79,16 +79,18 @@ The dataset is publicly available here : http://grouplens.org/datasets/movielens
Once downloaded and extracted the archive, you can run the following Cypher statements for importing the dataset, just adapt the file urls to match your actual path to the files :
+> **_NOTE:_** This is Cypher version 4.4 syntax.
+
```
-CREATE CONSTRAINT ON (m:Movie) ASSERT m.id IS UNIQUE;
-CREATE CONSTRAINT ON (g:Genre) ASSERT g.name IS UNIQUE;
-CREATE CONSTRAINT ON (u:User) ASSERT u.id IS UNIQUE;
+CREATE CONSTRAINT FOR (m:Movie) REQUIRE m.id IS UNIQUE;
+CREATE CONSTRAINT FOR (g:Genre) REQUIRE g.name IS UNIQUE;
+CREATE CONSTRAINT FOR (u:User) REQUIRE u.id IS UNIQUE;
```
```
LOAD CSV WITH HEADERS FROM "file:///Users/ikwattro/dev/movielens/movies.csv" AS row
WITH row
-MERGE (movie:Movie {id: toInt(row.movieId)})
+MERGE (movie:Movie {id: toInteger(row.movieId)})
ON CREATE SET movie.title = row.title
WITH movie, row
UNWIND split(row.genres, '|') as genre
@@ -98,16 +100,19 @@ MERGE (movie)-[:HAS_GENRE]->(g)
```
-USING PERIODIC COMMIT 500
-LOAD CSV WITH HEADERS FROM "file:///Users/ikwattro/dev/movielens/ratings.csv" AS row
+:auto LOAD CSV WITH HEADERS FROM "file:///Users/ikwattro/dev/movielens/ratings.csv" AS row
WITH row
-MATCH (movie:Movie {id: toInt(row.movieId)})
-MERGE (user:User {id: toInt(row.userId)})
-MERGE (user)-[r:RATED]->(movie)
-ON CREATE SET r.rating = toInt(row.rating), r.timestamp = toInt(row.timestamp)
+LIMIT 500
+CALL {
+ WITH row
+ MATCH (movie:Movie {id: toInteger(row.movieId)})
+ MERGE (user:User {id: toInteger(row.userId)})
+ MERGE (user)-[r:RATED]->(movie)
+ ON CREATE SET r.rating = toInteger(row.rating), r.timestamp = toInteger(row.timestamp)
+} IN TRANSACTIONS
```
-For the purpose of the example, we will assume we are recommending movies for the User with ID 460.
+For the purpose of the example, we will assume we are recommending movies for the User with ID 4.
### Installation
@@ -127,39 +132,72 @@ In order to recommend movies people should watch, you have decided that we shoul
* Find movies rated by people who rated the same movies than me, but that I didn't rated yet
As told before, the `reco4php` recommendation engine framework makes all the plumbing so you only have to concentrate on the business logic, that's why it provides base class that you should extend and just implement
-the methods of the upper interfaces, here is how you would create your first discovery engine :
+the methods of the upper interfaces, here are how you would create your first discovery engines :
```php
(m)<-[:RATED]-(o)
WITH distinct o
MATCH (o)-[:RATED]->(reco)
RETURN distinct reco LIMIT 500';
- return Statement::create($query, ['id' => $input->identity()]);
+ return Statement::create($query, ['id' => $input->getId()]);
}
- public function name()
+ public function name(): string
{
- return "rated_by_others";
+ return 'rated_by_others';
}
}
```
-The `discoveryMethod` method should return a `Statement` object containing the query for finding recommendations,
+```php
+(movie)-[:HAS_GENRE]->(genre)
+ WITH distinct genre, sum(r.rating) as score
+ ORDER BY score DESC
+ LIMIT 15
+ MATCH (genre)<-[:HAS_GENRE]-(reco)
+ RETURN reco
+ LIMIT 200';
+
+ return Statement::create($query, ['id' => $input->getId()]);
+ }
+}
+```
+
+The `discoveryQuery` method should return a `Statement` object containing the query for finding recommendations,
the `name` method should return a string describing the name of your engine (this is mostly for logging purposes).
The query here has some logic, we don't want to return as candidates all the movies found, as in the initial dataset it would be 10k+, so imagine what it would be on a 100M dataset. So we are summing the score
@@ -181,19 +219,19 @@ As an example of a filter, we will filter the movies that were produced before t
namespace GraphAware\Reco4PHP\Tests\Example\Filter;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Filter\Filter;
+use Laudis\Neo4j\Types\Node;
class ExcludeOldMovies implements Filter
{
- public function doInclude(Node $input, Node $item)
+ public function doInclude(Node $input, Node $item): bool
{
- $title = $item->value("title");
+ $title = (string) $item->getProperty('title');
preg_match('/(?:\()\d+(?:\))/', $title, $matches);
if (isset($matches[0])) {
- $y = str_replace('(','',$matches[0]);
- $y = str_replace(')','', $y);
+ $y = str_replace('(', '', $matches[0]);
+ $y = str_replace(')', '', $y);
$year = (int) $y;
if ($year < 1999) {
return false;
@@ -218,22 +256,22 @@ Of course we do not want to recommend movies that the current user has already r
namespace GraphAware\Reco4PHP\Tests\Example\Filter;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Filter\BaseBlacklistBuilder;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\Node;
class AlreadyRatedBlackList extends BaseBlacklistBuilder
{
- public function blacklistQuery(Node $input)
+ public function blacklistQuery(Node $input): Statement
{
- $query = 'MATCH (input) WHERE id(input) = {inputId}
+ $query = 'MATCH (input) WHERE id(input) = $inputId
MATCH (input)-[:RATED]->(movie)
RETURN movie as item';
- return Statement::create($query, ['inputId' => $input->identity()]);
+ return Statement::create($query, ['inputId' => $input->getId()]);
}
- public function name()
+ public function name(): string
{
return 'already_rated';
}
@@ -252,39 +290,39 @@ nodes against the blacklists provided.
namespace GraphAware\Reco4PHP\Tests\Example\PostProcessing;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Result\Record;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Post\RecommendationSetPostProcessor;
use GraphAware\Reco4PHP\Result\Recommendation;
use GraphAware\Reco4PHP\Result\Recommendations;
use GraphAware\Reco4PHP\Result\SingleScore;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\CypherMap;
+use Laudis\Neo4j\Types\Node;
class RewardWellRated extends RecommendationSetPostProcessor
{
- public function buildQuery(Node $input, Recommendations $recommendations)
+ public function buildQuery(Node $input, Recommendations $recommendations): Statement
{
- $query = 'UNWIND {ids} as id
+ $query = 'UNWIND $ids as id
MATCH (n) WHERE id(n) = id
MATCH (n)<-[r:RATED]-(u)
RETURN id(n) as id, sum(r.rating) as score';
$ids = [];
foreach ($recommendations->getItems() as $item) {
- $ids[] = $item->item()->identity();
+ $ids[] = $item->item()->getId();
}
return Statement::create($query, ['ids' => $ids]);
}
- public function postProcess(Node $input, Recommendation $recommendation, Record $record)
+ public function postProcess(Node $input, Recommendation $recommendation, CypherMap $result): void
{
- $recommendation->addScore($this->name(), new SingleScore($record->get('score'), 'total_ratings_relationships'));
+ $recommendation->addScore($this->name(), new SingleScore((float) $result->get('score'), 'total_ratings_relationships'));
}
- public function name()
+ public function name(): string
{
- return "reward_well_rated";
+ return 'reward_well_rated';
}
}
```
@@ -299,44 +337,46 @@ Now that our components are created, we need to build effectively our recommenda
namespace GraphAware\Reco4PHP\Tests\Example;
use GraphAware\Reco4PHP\Engine\BaseRecommendationEngine;
+use GraphAware\Reco4PHP\Tests\Example\Discovery\FromSameGenreILike;
+use GraphAware\Reco4PHP\Tests\Example\Discovery\RatedByOthers;
use GraphAware\Reco4PHP\Tests\Example\Filter\AlreadyRatedBlackList;
use GraphAware\Reco4PHP\Tests\Example\Filter\ExcludeOldMovies;
use GraphAware\Reco4PHP\Tests\Example\PostProcessing\RewardWellRated;
-use GraphAware\Reco4PHP\Tests\Example\Discovery\RatedByOthers;
class ExampleRecommendationEngine extends BaseRecommendationEngine
{
- public function name()
+ public function name(): string
{
- return "example";
+ return 'user_movie_reco';
}
- public function discoveryEngines()
+ public function discoveryEngines(): array
{
- return array(
- new RatedByOthers()
- );
+ return [
+ new RatedByOthers(),
+ new FromSameGenreILike(),
+ ];
}
- public function blacklistBuilders()
+ public function blacklistBuilders(): array
{
- return array(
- new AlreadyRatedBlackList()
- );
+ return [
+ new AlreadyRatedBlackList(),
+ ];
}
- public function postProcessors()
+ public function postProcessors(): array
{
- return array(
- new RewardWellRated()
- );
+ return [
+ new RewardWellRated(),
+ ];
}
- public function filters()
+ public function filters(): array
{
- return array(
- new ExcludeOldMovies()
- );
+ return [
+ new ExcludeOldMovies(),
+ ];
}
}
```
@@ -351,32 +391,25 @@ namespace GraphAware\Reco4PHP\Tests\Example;
use GraphAware\Reco4PHP\Context\SimpleContext;
use GraphAware\Reco4PHP\RecommenderService;
+use GraphAware\Reco4PHP\Result\Recommendations;
class ExampleRecommenderService
{
- /**
- * @var \GraphAware\Reco4PHP\RecommenderService
- */
- protected $service;
+ protected RecommenderService $service;
/**
* ExampleRecommenderService constructor.
- * @param string $databaseUri
*/
- public function __construct($databaseUri)
+ public function __construct(string $databaseUri)
{
$this->service = RecommenderService::create($databaseUri);
$this->service->registerRecommendationEngine(new ExampleRecommendationEngine());
}
- /**
- * @param int $id
- * @return \GraphAware\Reco4PHP\Result\Recommendations
- */
- public function recommendMovieForUserWithId($id)
+ public function recommendMovieForUserWithId(int $id): Recommendations
{
$input = $this->service->findInputBy('User', 'id', $id);
- $recommendationEngine = $this->service->getRecommender("user_movie_reco");
+ $recommendationEngine = $this->service->getRecommender('user_movie_reco');
return $recommendationEngine->recommend($input, new SimpleContext());
}
@@ -390,9 +423,14 @@ The `recommend()` method on a recommendation engine will returns you a `Recommen
Each score is inserted so you can easily inspect why such recommendation has been produced, example :
```php
+recommendMovieForUserWithId(460);
+use GraphAware\Reco4PHP\Tests\Example\ExampleRecommenderService;
+
+$recommender = new ExampleRecommenderService('bolt://localhost:7687');
+$recommendations = $recommender->recommendMovieForUserWithId(4);
print_r($recommendations->getItems(1));
@@ -400,18 +438,59 @@ Array
(
[0] => GraphAware\Reco4PHP\Result\Recommendation Object
(
- [item:protected] => GraphAware\Bolt\Result\Type\Node Object
+ [item:protected] => Laudis\Neo4j\Types\Node Object
(
- [identity:protected] => 13248
- [labels:protected] => Array
+ [id:Laudis\Neo4j\Types\Node:private] => 2700
+ [labels:Laudis\Neo4j\Types\Node:private] => Laudis\Neo4j\Types\CypherList Object
(
- [0] => Movie
+ [keyCache:protected] => Array
+ (
+ [0] => 0
+ )
+
+ [cache:protected] => Array
+ (
+ [0] => Movie
+ )
+
+ [cacheLimit:Laudis\Neo4j\Types\AbstractCypherSequence:private] => 9223372036854775807
+ [currentPosition:protected] => 0
+ [generatorPosition:protected] => 1
+ [generator:protected] => ArrayIterator Object
+ (
+ [storage:ArrayIterator:private] => Array
+ (
+ )
+
+ )
+
)
- [properties:protected] => Array
+ [properties:Laudis\Neo4j\Types\Node:private] => Laudis\Neo4j\Types\CypherMap Object
(
- [id] => 2571
- [title] => Matrix, The (1999)
+ [keyCache:protected] => Array
+ (
+ [0] => id
+ [1] => title
+ )
+
+ [cache:protected] => Array
+ (
+ [id] => 3578
+ [title] => Gladiator (2000)
+ )
+
+ [cacheLimit:Laudis\Neo4j\Types\AbstractCypherSequence:private] => 9223372036854775807
+ [currentPosition:protected] => 0
+ [generatorPosition:protected] => 2
+ [generator:protected] => ArrayIterator Object
+ (
+ [storage:ArrayIterator:private] => Array
+ (
+ )
+
+ )
+
)
)
@@ -420,13 +499,12 @@ Array
(
[rated_by_others] => GraphAware\Reco4PHP\Result\Score Object
(
- [score:protected] => 1067
+ [score:protected] => 1
[scores:protected] => Array
(
[0] => GraphAware\Reco4PHP\Result\SingleScore Object
(
- [score:GraphAware\Reco4PHP\Result\SingleScore:private] => 1067
- [reason:GraphAware\Reco4PHP\Result\SingleScore:private] =>
+ [score:GraphAware\Reco4PHP\Result\SingleScore:private] => 1
)
)
@@ -435,13 +513,13 @@ Array
[reward_well_rated] => GraphAware\Reco4PHP\Result\Score Object
(
- [score:protected] => 261
+ [score:protected] => 9
[scores:protected] => Array
(
[0] => GraphAware\Reco4PHP\Result\SingleScore Object
(
- [score:GraphAware\Reco4PHP\Result\SingleScore:private] => 261
- [reason:GraphAware\Reco4PHP\Result\SingleScore:private] =>
+ [score:GraphAware\Reco4PHP\Result\SingleScore:private] => 9
+ [reason:GraphAware\Reco4PHP\Result\SingleScore:private] => total_ratings_relationships
)
)
@@ -450,8 +528,9 @@ Array
)
- [totalScore:protected] => 261
+ [totalScore:protected] => 10
)
+
)
```
### License
diff --git a/UPGRADE.md b/UPGRADE.md
new file mode 100644
index 0000000..a1442aa
--- /dev/null
+++ b/UPGRADE.md
@@ -0,0 +1,76 @@
+# Upgrades
+
+## 2.0 to 3.0
+
+```diff
+- use GraphAware\Common\Type\Node;
+- use GraphAware\Common\Type\NodeInterface;
++ use Laudis\Neo4j\Types\Node;
+
+- $node->identity()
++ $node->getId()
+
+- $node->get('login')
++ $node->getProperty('login')
+
+- $node->hasValue($key)
++ $node->getProperties()->hasKey($key)
+
+- $node->value($key)
++ $node->getProperty($key)
+```
+
+```diff
+- use GraphAware\Common\Cypher\StatementInterface;
+- use GraphAware\Common\Cypher\Statement;
++ use Laudis\Neo4j\Databags\Statement;
+```
+
+```diff
+- use GraphAware\Common\Result\Record;
+- use GraphAware\Common\Result\RecordViewInterface;
++ use Laudis\Neo4j\Types\CypherMap;
+
+- RecordViewInterface $record
+- Record $record
++ CypherMap $result
+
+- $record->hasValue($key)
++ $result->hasKey($key)
+
+- $record->value($key)
++ $result->get($key)
+```
+
+
+```diff
+- use GraphAware\Common\Result\ResultCollection;
++ use GraphAware\Reco4PHP\Result\ResultCollection;
+```
+
+```diff
+- use GraphAware\Common\Result\Result;
++ use Laudis\Neo4j\Types\CypherList;
++ use Laudis\Neo4j\Types\CypherMap;
+
+- Result $result
++ CypherList $results
+
+- foreach ($result->records() as $record) {
++ /** @var CypherMap $result */
++ foreach ($results as $result) {
+
+- $result->getRecord()
++ $results->first()
+
+- $result->firstRecord()
++ $results->first()
+```
+
+```diff
+- use GraphAware\Common\Result\RecordCursorInterface;
++ use Laudis\Neo4j\Types\CypherList;
+
+- RecordCursorInterface $result
++ CypherList $results
+```
diff --git a/_algo-examples/cosine-similarity.php b/_algo-examples/cosine-similarity.php
index d4bea8e..15864d7 100644
--- a/_algo-examples/cosine-similarity.php
+++ b/_algo-examples/cosine-similarity.php
@@ -11,7 +11,7 @@
$db = new DatabaseService("http://neo4j:error@localhost:7474");
$driver = $db->getDriver();
-$knn = new KNNModelBuilder(null, new CosineSimilarity());
+$knn = new KNNModelBuilder(new CosineSimilarity());
$s = microtime(true);
@@ -55,4 +55,4 @@
}
}
$e2 = $stopwatch->stop("simil");
-echo $e2->getDuration() . PHP_EOL;
\ No newline at end of file
+echo $e2->getDuration() . PHP_EOL;
diff --git a/_demo/Github/FollowedByFollowers.php b/_demo/Github/FollowedByFollowers.php
new file mode 100644
index 0000000..706a43b
--- /dev/null
+++ b/_demo/Github/FollowedByFollowers.php
@@ -0,0 +1,34 @@
+(reco)
+ WHERE size((follower)-[:FOLLOWS]->()) < $max_follows
+ RETURN reco, count(*) as score
+ LIMIT 100';
+
+ return Statement::create($query, ['id' => $input->getId(), 'max_follows' => 200]);
+ }
+
+ public function buildScore(Node $input, Node $item, CypherMap $result, Context $context): SingleScore
+ {
+ return new SingleScore((float) $result->get('score'));
+ }
+}
diff --git a/_demo/github/PenalizeTooMuchFollowers.php b/_demo/Github/PenalizeTooMuchFollowers.php
similarity index 62%
rename from _demo/github/PenalizeTooMuchFollowers.php
rename to _demo/Github/PenalizeTooMuchFollowers.php
index d2a0629..f67eca2 100644
--- a/_demo/github/PenalizeTooMuchFollowers.php
+++ b/_demo/Github/PenalizeTooMuchFollowers.php
@@ -2,41 +2,37 @@
namespace GraphAware\Reco4PHP\Demo\Github;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Result\Record;
-use GraphAware\Common\Type\Node;
-use GraphAware\Common\Type\NodeInterface;
use GraphAware\Reco4PHP\Post\RecommendationSetPostProcessor;
use GraphAware\Reco4PHP\Result\Recommendation;
use GraphAware\Reco4PHP\Result\Recommendations;
-use GraphAware\Reco4PHP\Result\Score;
use GraphAware\Reco4PHP\Result\SingleScore;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\CypherMap;
+use Laudis\Neo4j\Types\Node;
class PenalizeTooMuchFollowers extends RecommendationSetPostProcessor
{
- public function name()
+ public function name(): string
{
return 'too_much_followers';
}
- public function buildQuery(NodeInterface $input, Recommendations $recommendations)
+ public function buildQuery(Node $input, Recommendations $recommendations): Statement
{
$ids = [];
foreach ($recommendations->getItems() as $recommendation) {
- $ids[] = $recommendation->item()->identity();
+ $ids[] = $recommendation->item()->getId();
}
- $query = 'UNWIND {ids} as id
+ $query = 'UNWIND $ids as id
MATCH (n) WHERE id(n) = id
RETURN id, size((n)<-[:FOLLOWS]-()) as followersCount';
return Statement::create($query, ['ids' => $ids]);
-
}
- public function postProcess(Node $input, Recommendation $recommendation, Record $record)
+ public function postProcess(Node $input, Recommendation $recommendation, CypherMap $result): void
{
- $recommendation->addScore($this->name(), new SingleScore(- $record->get('followersCount') / 50));
+ $recommendation->addScore($this->name(), new SingleScore(-(int) $result->get('followersCount') / 50));
}
-
-}
\ No newline at end of file
+}
diff --git a/_demo/Github/RecommendationEngine.php b/_demo/Github/RecommendationEngine.php
new file mode 100644
index 0000000..75b1c8e
--- /dev/null
+++ b/_demo/Github/RecommendationEngine.php
@@ -0,0 +1,28 @@
+(repo)<-[:CONTRIBUTED_TO]-(reco)
+ RETURN reco, count(*) as score';
+
+ return Statement::create($query, ['id' => $input->getId()]);
+ }
+
+ public function buildScore(Node $input, Node $item, CypherMap $result, Context $context): SingleScore
+ {
+ return new SingleScore((float) $result->get('score') * 10);
+ }
+}
diff --git a/_demo/github/FollowedByFollowers.php b/_demo/github/FollowedByFollowers.php
deleted file mode 100644
index ce40169..0000000
--- a/_demo/github/FollowedByFollowers.php
+++ /dev/null
@@ -1,34 +0,0 @@
-(reco)
- WHERE size((follower)-[:FOLLOWS]->()) < {max_follows}
- RETURN reco, count(*) as score
- LIMIT 100';
-
- return Statement::create($query, ['id' => $input->identity(), 'max_follows' => 200]);
- }
-
- public function buildScore(NodeInterface $input, NodeInterface $item, RecordViewInterface $record)
- {
- return new SingleScore($record->get('score'));
- }
-
-
-}
\ No newline at end of file
diff --git a/_demo/github/RecommendationEngine.php b/_demo/github/RecommendationEngine.php
deleted file mode 100644
index 45c832b..0000000
--- a/_demo/github/RecommendationEngine.php
+++ /dev/null
@@ -1,28 +0,0 @@
-(repo)<-[:CONTRIBUTED_TO]-(reco)
- RETURN reco, count(*) as score';
-
- return Statement::create($query, ['id' => $input->identity()]);
- }
-
- public function buildScore(NodeInterface $input, NodeInterface $item, RecordViewInterface $record)
- {
- return new SingleScore($record->get('score') * 10);
- }
-
-
-}
\ No newline at end of file
diff --git a/build/install-jdk8.sh b/build/install-jdk8.sh
deleted file mode 100755
index 8b08600..0000000
--- a/build/install-jdk8.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-# Get dependencies (for adding repos)
-sudo apt-get install -y python-software-properties
-sudo add-apt-repository -y ppa:webupd8team/java
-sudo apt-get update
-
-# install oracle jdk 8
-sudo apt-get install -y oracle-java8-installer
-sudo update-alternatives --auto java
-sudo update-alternatives --auto javac
\ No newline at end of file
diff --git a/build/install-neo.sh b/build/install-neo.sh
deleted file mode 100755
index 6cb81d5..0000000
--- a/build/install-neo.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-export JAVA_HOME=/usr/lib/jvm/java-8-oracle
-export JRE_HOME=/usr/lib/jvm/java-8-oracle
-
-wget http://dist.neo4j.org/neo4j-enterprise-3.0.2-unix.tar.gz > null
-mkdir neo
-tar xzf neo4j-enterprise-3.0.2-unix.tar.gz -C neo --strip-components=1 > null
-sed -i.bak '/\(dbms\.security\.auth_enabled=\).*/s/^#//g' ./neo/conf/neo4j.conf
-neo/bin/neo4j start > null &
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 9948c16..31d34ba 100644
--- a/composer.json
+++ b/composer.json
@@ -10,14 +10,12 @@
}
],
"require": {
- "php": "^7.0",
- "graphaware/neo4j-php-client": "^4.0",
- "symfony/event-dispatcher": "^2.7 || ^3.0 || ^4.0",
- "psr/log": "^1.0",
- "symfony/stopwatch": "^2.7 || ^3.0 || ^4.0"
+ "php": "^8.0",
+ "laudis/neo4j-php-client": "^2.7",
+ "symfony/stopwatch": "^4.4 || ^5.4 || ^6.0"
},
"require-dev": {
- "phpunit/phpunit": "^5.1"
+ "phpunit/phpunit": "^9.5"
},
"autoload": {
"psr-4": {
diff --git a/example.php b/example.php
index 4cda787..a4af12d 100644
--- a/example.php
+++ b/example.php
@@ -2,28 +2,30 @@
require_once __DIR__.'/vendor/autoload.php';
+use GraphAware\Reco4PHP\Context\SimpleContext;
use GraphAware\Reco4PHP\Demo\Github\RecommendationEngine;
use GraphAware\Reco4PHP\RecommenderService;
+use Symfony\Component\Stopwatch\Stopwatch;
-$rs = RecommenderService::create("http://localhost:7474");
+$rs = RecommenderService::create('bolt://localhost:7687');
$rs->registerRecommendationEngine(new RecommendationEngine());
-$stopwatch = new \Symfony\Component\Stopwatch\Stopwatch();
+$stopwatch = new Stopwatch();
$input = $rs->findInputBy('User', 'login', 'jakzal');
$engine = $rs->getRecommender("github_who_to_follow");
$stopwatch->start('reco');
-$recommendations = $engine->recommend($input);
+$recommendations = $engine->recommend($input, new SimpleContext());
$e = $stopwatch->stop('reco');
//echo $recommendations->size() . ' found in ' . $e->getDuration() . 'ms' .PHP_EOL;
foreach ($recommendations->getItems(10) as $reco) {
- echo $reco->item()->get('login') . PHP_EOL;
+ echo $reco->item()->getProperty('login') . PHP_EOL;
echo $reco->totalScore() . PHP_EOL;
foreach ($reco->getScores() as $name => $score) {
- echo "\t" . $name . ':' . $score->score() . PHP_EOL;
+ echo "\t" . $name . ':' . $score->getScore() . PHP_EOL;
}
-}
\ No newline at end of file
+}
diff --git a/phpunit.xml b/phpunit.xml
index 65a1fb8..dc67867 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -5,11 +5,11 @@
./tests
-
-
+
+
tests
vendor
bin
-
-
+
+
\ No newline at end of file
diff --git a/src/Algorithms/Model/KNNModelBuilder.php b/src/Algorithms/Model/KNNModelBuilder.php
index f64b790..42b32a1 100644
--- a/src/Algorithms/Model/KNNModelBuilder.php
+++ b/src/Algorithms/Model/KNNModelBuilder.php
@@ -16,27 +16,21 @@
class KNNModelBuilder
{
- protected $model;
+ protected Similarity $similarityFunction;
- protected $similarityFunction;
-
- protected $dataset;
-
- public function __construct($model = null, Similarity $similarityFunction = null, $dataset = null)
+ public function __construct(Similarity $similarityFunction)
{
- $this->model = $model;
$this->similarityFunction = $similarityFunction;
- $this->dataset = $dataset;
}
- public function computeSimilarity(ObjectSet $tfSource, ObjectSet $tfDestination)
+ public function computeSimilarity(ObjectSet $tfSource, ObjectSet $tfDestination): float
{
$vectors = $this->createVectors($tfSource, $tfDestination);
return $this->similarityFunction->getSimilarity($vectors[0], $vectors[1]);
}
- public function createVectors(ObjectSet $tfSource, ObjectSet $tfDestination)
+ public function createVectors(ObjectSet $tfSource, ObjectSet $tfDestination): array
{
$ratings = [];
foreach ($tfSource->getAll() as $source) {
@@ -58,6 +52,6 @@ public function createVectors(ObjectSet $tfSource, ObjectSet $tfDestination)
$yVector[] = array_key_exists(1, $ratings[$k]) ? $ratings[$k][1] : 0;
}
- return array($xVector, $yVector);
+ return [$xVector, $yVector];
}
}
diff --git a/src/Algorithms/Model/Rating.php b/src/Algorithms/Model/Rating.php
index 7f271bc..b536230 100644
--- a/src/Algorithms/Model/Rating.php
+++ b/src/Algorithms/Model/Rating.php
@@ -13,34 +13,22 @@
class Rating
{
- /**
- * @var float
- */
- protected $rating;
+ protected float $rating;
- /**
- * @var int
- */
- protected $userNodeId;
+ protected int $userNodeId;
- public function __construct($rating, $userNodeId)
+ public function __construct(float $rating, int $userNodeId)
{
$this->rating = (float) $rating;
$this->userNodeId = (int) $userNodeId;
}
- /**
- * @return float
- */
- public function getRating()
+ public function getRating(): float
{
return $this->rating;
}
- /**
- * @return int
- */
- public function getId()
+ public function getId(): int
{
return $this->userNodeId;
}
diff --git a/src/Algorithms/Similarity/CosineSimilarity.php b/src/Algorithms/Similarity/CosineSimilarity.php
index 0efb82e..1ee5603 100644
--- a/src/Algorithms/Similarity/CosineSimilarity.php
+++ b/src/Algorithms/Similarity/CosineSimilarity.php
@@ -13,7 +13,7 @@
class CosineSimilarity implements Similarity
{
- public function getSimilarity(array $xVector, array $yVector)
+ public function getSimilarity(array $xVector, array $yVector): float
{
$a = $this->getDotProduct($xVector, $yVector);
$b = $this->getNorm($xVector) * $this->getNorm($yVector);
@@ -25,7 +25,7 @@ public function getSimilarity(array $xVector, array $yVector)
return 0;
}
- private function getDotProduct(array $xVector, array $yVector)
+ private function getDotProduct(array $xVector, array $yVector): float
{
$sum = 0.0;
foreach ($xVector as $k => $v) {
@@ -35,7 +35,7 @@ private function getDotProduct(array $xVector, array $yVector)
return $sum;
}
- private function getNorm(array $vector)
+ private function getNorm(array $vector): float
{
$sum = 0.0;
foreach ($vector as $k => $v) {
diff --git a/src/Algorithms/Similarity/Similarity.php b/src/Algorithms/Similarity/Similarity.php
index 1439e60..a40c7a4 100644
--- a/src/Algorithms/Similarity/Similarity.php
+++ b/src/Algorithms/Similarity/Similarity.php
@@ -13,5 +13,5 @@
interface Similarity
{
- public function getSimilarity(array $xVector, array $yVector);
+ public function getSimilarity(array $xVector, array $yVector): float;
}
diff --git a/src/Common/NodeSet.php b/src/Common/NodeSet.php
deleted file mode 100644
index 20f4c32..0000000
--- a/src/Common/NodeSet.php
+++ /dev/null
@@ -1,63 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace GraphAware\Reco4PHP\Common;
-
-use GraphAware\Common\Type\Node;
-
-class NodeSet extends ObjectSet
-{
- /**
- * @param \GraphAware\Common\Type\Node $node
- */
- public function add(Node $node)
- {
- if (parent::valid($node) && !$this->contains($node)) {
- $this->elements[$node->identity()] = $node;
- }
- }
-
- /**
- * @param $key
- *
- * @return \GraphAware\Common\Type\Node
- */
- public function get($key)
- {
- return array_values($this->elements)[$key];
- }
-
- /**
- * @return \GraphAware\Common\Type\Node[]
- */
- public function all()
- {
- return $this->elements;
- }
-
- /**
- * @return int
- */
- public function size()
- {
- return count($this->elements);
- }
-
- /**
- * @param \GraphAware\Common\Type\Node $node
- *
- * @return bool
- */
- public function contains(Node $node)
- {
- return array_key_exists($node->identity(), $this->elements);
- }
-}
diff --git a/src/Common/ObjectSet.php b/src/Common/ObjectSet.php
index 5b42304..01e2ea3 100644
--- a/src/Common/ObjectSet.php
+++ b/src/Common/ObjectSet.php
@@ -13,17 +13,11 @@
class ObjectSet implements Set
{
- /**
- * @var string
- */
- protected $className;
+ protected string $className;
- /**
- * @var array
- */
- protected $elements = [];
+ protected array $elements = [];
- final public function __construct($className)
+ final public function __construct(string $className)
{
if (!class_exists($className)) {
throw new \InvalidArgumentException(sprintf('The classname %s does not exist', $className));
@@ -32,7 +26,7 @@ final public function __construct($className)
$this->className = $className;
}
- public function add($element)
+ public function add(object $element): void
{
if ($this->valid($element) && !in_array($element, $this->elements)) {
$this->elements[] = $element;
@@ -42,27 +36,25 @@ public function add($element)
/**
* @return object[]
*/
- public function getAll()
+ public function getAll(): array
{
return $this->elements;
}
- public function size()
+ public function size(): int
{
return count($this->elements);
}
- public function get($key)
+ public function get($key): ?object
{
return $this->elements[$key];
}
/**
- * @param object $element
- *
* @return bool
*/
- protected function valid($element)
+ protected function valid(object $element)
{
return $element instanceof $this->className;
}
diff --git a/src/Config/Config.php b/src/Config/Config.php
index f8ca3ce..91f29bf 100644
--- a/src/Config/Config.php
+++ b/src/Config/Config.php
@@ -13,16 +13,16 @@
interface Config
{
- const UNLIMITED = PHP_INT_MAX;
+ public const UNLIMITED = PHP_INT_MAX;
/**
* @return int maximum number of items to recommend
*/
- public function limit();
+ public function limit(): int;
/**
* @return int maximum number of ms the recommendation-computing process should take. Note that it is
* for information only, it is the responsibility of the engines to honour this configuration or not.
*/
- public function maxTime();
+ public function maxTime(): int;
}
diff --git a/src/Config/KeyValueConfig.php b/src/Config/KeyValueConfig.php
index 26e0633..6b8d48c 100644
--- a/src/Config/KeyValueConfig.php
+++ b/src/Config/KeyValueConfig.php
@@ -13,46 +13,24 @@
abstract class KeyValueConfig implements Config
{
- /**
- * @var array
- */
- protected $values = [];
+ protected array $values = [];
- /**
- * @param $key
- *
- * @return mixed
- */
- public function get($key)
+ public function get(string $key): mixed
{
return array_key_exists($key, $this->values) ? $this->values[$key] : null;
}
- /**
- * @param string $key
- * @param mixed $value
- */
- public function add($key, $value)
+ public function add(string $key, mixed $value): void
{
$this->values[$key] = $value;
}
- /**
- * @param string $key
- *
- * @return bool
- */
- public function containsKey($key)
+ public function containsKey(string $key): bool
{
return array_key_exists($key, $this->values);
}
- /**
- * @param mixed $o
- *
- * @return bool
- */
- public function contains($o)
+ public function contains(mixed $o): bool
{
return in_array($o, $this->values);
}
diff --git a/src/Config/SimpleConfig.php b/src/Config/SimpleConfig.php
index 0d9c0a1..aeb953c 100644
--- a/src/Config/SimpleConfig.php
+++ b/src/Config/SimpleConfig.php
@@ -13,30 +13,20 @@
class SimpleConfig extends KeyValueConfig
{
- /**
- * @var int
- */
- protected $limit;
+ protected int $limit;
- /**
- * @var int
- */
- protected $maxTime;
+ protected int $maxTime;
- /**
- * @param int|null $limit
- * @param int|null $maxTime
- */
- public function __construct($limit = null, $maxTime = null)
+ public function __construct(?int $limit = null, ?int $maxTime = null)
{
- $this->limit = null !== $limit ? $limit : self::UNLIMITED;
- $this->maxTime = null !== $maxTime ? $maxTime : self::UNLIMITED;
+ $this->limit = $limit ?? self::UNLIMITED;
+ $this->maxTime = $maxTime ?? self::UNLIMITED;
}
/**
* {@inheritdoc}
*/
- public function limit()
+ public function limit(): int
{
return $this->limit;
}
@@ -44,7 +34,7 @@ public function limit()
/**
* {@inheritdoc}
*/
- public function maxTime()
+ public function maxTime(): int
{
return $this->maxTime;
}
diff --git a/src/Context/Context.php b/src/Context/Context.php
index 082990e..6c68261 100644
--- a/src/Context/Context.php
+++ b/src/Context/Context.php
@@ -15,18 +15,12 @@
interface Context
{
- /**
- * @return \GraphAware\Reco4PHP\Config\Config
- */
- public function config() : Config;
+ public function config(): Config;
- /**
- * @return bool
- */
- public function timeLeft() : bool;
+ public function timeLeft(): bool;
/**
* @return \GraphAware\Reco4PHP\Context\Statistics
*/
- public function getStatistics() : Statistics;
+ public function getStatistics(): Statistics;
}
diff --git a/src/Context/SimpleContext.php b/src/Context/SimpleContext.php
index 47c07bb..557256f 100644
--- a/src/Context/SimpleContext.php
+++ b/src/Context/SimpleContext.php
@@ -11,35 +11,25 @@
namespace GraphAware\Reco4PHP\Context;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Config\Config;
use GraphAware\Reco4PHP\Config\SimpleConfig;
class SimpleContext implements Context
{
- /**
- * @var \GraphAware\Reco4PHP\Config\Config
- */
- protected $config;
+ protected Config $config;
- /**
- * @var \GraphAware\Reco4PHP\Context\Statistics
- */
- protected $statistics;
+ protected Statistics $statistics;
- /**
- * @param \GraphAware\Reco4PHP\Config\Config $config
- */
- public function __construct(Config $config = null)
+ public function __construct(?Config $config = null)
{
- $this->config = null !== $config ? $config : new SimpleConfig();
+ $this->config = $config ?? new SimpleConfig();
$this->statistics = new Statistics();
}
/**
* {@inheritdoc}
*/
- public function config() : Config
+ public function config(): Config
{
return $this->config;
}
@@ -47,12 +37,15 @@ public function config() : Config
/**
* {@inheritdoc}
*/
- public function timeLeft() : bool
+ public function timeLeft(): bool
{
return $this->statistics->getCurrentTimeSpent() < $this->config()->limit();
}
- public function getStatistics() : Statistics
+ /**
+ * {@inheritdoc}
+ */
+ public function getStatistics(): Statistics
{
return $this->statistics;
}
diff --git a/src/Context/Statistics.php b/src/Context/Statistics.php
index 84c3001..55396b0 100644
--- a/src/Context/Statistics.php
+++ b/src/Context/Statistics.php
@@ -16,71 +16,56 @@
class Statistics
{
private static $DISCOVERY_KEY = 'discovery';
-
private static $POST_PROCESS_KEY = 'post_process';
+ private static $TOTAL_KEY = 'total';
- /**
- * @var \Symfony\Component\Stopwatch\Stopwatch
- */
- protected $stopwatch;
+ protected Stopwatch $stopwatch;
- /**
- * @var float
- */
- protected $discoveryTime;
+ protected float $discoveryTime;
- /**
- * @var float
- */
- protected $postProcessingTime;
+ protected float $postProcessingTime;
public function __construct()
{
$this->stopwatch = new Stopwatch();
}
- public function startDiscovery()
+ public function startDiscovery(): void
{
$this->stopwatch->start(self::$DISCOVERY_KEY);
}
- public function stopDiscovery()
+ public function stopDiscovery(): void
{
$e = $this->stopwatch->stop(self::$DISCOVERY_KEY);
$this->discoveryTime = $e->getDuration();
}
- public function startPostProcess()
+ public function startPostProcess(): void
{
$this->stopwatch->start(self::$POST_PROCESS_KEY);
}
- public function stopPostProcess()
+ public function stopPostProcess(): void
{
$e = $this->stopwatch->stop(self::$POST_PROCESS_KEY);
$this->postProcessingTime = $e->getDuration();
}
- /**
- * @return array
- */
- public function getTimes()
+ public function getTimes(): array
{
- return array(
+ return [
self::$DISCOVERY_KEY => $this->discoveryTime,
self::$POST_PROCESS_KEY => $this->postProcessingTime,
- 'total' => $this->discoveryTime + $this->postProcessingTime
- );
+ self::$TOTAL_KEY => $this->discoveryTime + $this->postProcessingTime,
+ ];
}
- /**
- * @return float
- */
- public function getCurrentTimeSpent()
+ public function getCurrentTimeSpent(): float
{
$discovery = null !== $this->discoveryTime ? $this->discoveryTime : 0.0;
$postProcess = null !== $this->postProcessingTime ? $this->postProcessingTime : 0.0;
return $discovery + $postProcess;
}
-}
\ No newline at end of file
+}
diff --git a/src/Cypher/Query.php b/src/Cypher/Query.php
deleted file mode 100644
index 3080e49..0000000
--- a/src/Cypher/Query.php
+++ /dev/null
@@ -1,9 +0,0 @@
-discoveryEngines(), function (DiscoveryEngine $discoveryEngine) {
return true;
@@ -86,9 +71,9 @@ final public function getDiscoveryEngines() : array
}
/**
- * @return \GraphAware\Reco4PHP\Filter\BlackListBuilder[]
+ * {@inheritdoc}
*/
- final public function getBlacklistBuilders() : array
+ final public function getBlacklistBuilders(): array
{
return array_filter($this->blacklistBuilders(), function (BlackListBuilder $blackListBuilder) {
return true;
@@ -96,9 +81,9 @@ final public function getBlacklistBuilders() : array
}
/**
- * @return \GraphAware\Reco4PHP\Filter\Filter[]
+ * {@inheritdoc}
*/
- final public function getFilters() : array
+ final public function getFilters(): array
{
return array_filter($this->filters(), function (Filter $filter) {
return true;
@@ -106,9 +91,9 @@ final public function getFilters() : array
}
/**
- * @return \GraphAware\Reco4PHP\Post\PostProcessor[]
+ * {@inheritdoc}
*/
- final public function getPostProcessors() : array
+ final public function getPostProcessors(): array
{
return array_filter($this->postProcessors(), function (PostProcessor $postProcessor) {
return true;
@@ -116,28 +101,16 @@ final public function getPostProcessors() : array
}
/**
- * @return array|\Psr\Log\LoggerInterface[]
+ * {@inheritdoc}
*/
- final public function getLoggers() : array
+ final public function recommend(Node $input, Context $context): Recommendations
{
- return array_filter($this->loggers(), function (LoggerInterface $logger) {
- return true;
- });
+ return $this->recommendationExecutor->processRecommendation($input, $this, $context);
}
/**
- * @param Node $input
- * @param Context $context
- *
- * @return \GraphAware\Reco4PHP\Result\Recommendations
+ * {@inheritdoc}
*/
- final public function recommend(Node $input, Context $context) : Recommendations
- {
- $recommendations = $this->recommendationExecutor->processRecommendation($input, $this, $context);
-
- return $recommendations;
- }
-
final public function setDatabaseService(DatabaseService $databaseService)
{
$this->databaseService = $databaseService;
diff --git a/src/Engine/DiscoveryEngine.php b/src/Engine/DiscoveryEngine.php
index 991ab9c..7b95d63 100644
--- a/src/Engine/DiscoveryEngine.php
+++ b/src/Engine/DiscoveryEngine.php
@@ -11,58 +11,42 @@
namespace GraphAware\Reco4PHP\Engine;
-use GraphAware\Common\Cypher\StatementInterface;
-use GraphAware\Common\Result\Record;
-use GraphAware\Common\Type\Node;
-use GraphAware\Common\Result\ResultCollection;
use GraphAware\Reco4PHP\Context\Context;
use GraphAware\Reco4PHP\Result\Recommendations;
+use GraphAware\Reco4PHP\Result\ResultCollection;
use GraphAware\Reco4PHP\Result\SingleScore;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\CypherMap;
+use Laudis\Neo4j\Types\Node;
interface DiscoveryEngine
{
/**
* @return string The name of the discovery engine
*/
- public function name() : string;
+ public function name(): string;
/**
* The statement to be executed for finding items to be recommended.
- *
- * @param Node $input
- * @param Context $context
- *
- * @return \GraphAware\Common\Cypher\Statement
*/
- public function discoveryQuery(Node $input, Context $context) : StatementInterface;
+ public function discoveryQuery(Node $input, Context $context): Statement;
/**
* Returns the score produced by the recommended item.
*
- * @param Node $input
- * @param Node $item
- * @param Record $record
- * @param Context $context
- *
- * @return \GraphAware\Reco4PHP\Result\SingleScore A single score produced for the recommended item
+ * @return SingleScore A single score produced for the recommended item
*/
- public function buildScore(Node $input, Node $item, Record $record, Context $context) : SingleScore;
+ public function buildScore(Node $input, Node $item, CypherMap $result, Context $context): SingleScore;
/**
* Returns a collection of Recommendation object produced by this discovery engine.
- *
- * @param Node $input
- * @param ResultCollection $resultCollection
- * @param Context $context
- *
- * @return Recommendations
*/
- public function produceRecommendations(Node $input, ResultCollection $resultCollection, Context $context) : Recommendations;
+ public function produceRecommendations(Node $input, ResultCollection $resultCollection, Context $context): Recommendations;
/**
* @return string The column identifier of the row result representing the recommended item (node)
*/
- public function recoResultName() : string;
+ public function recoResultName(): string;
/**
* @return string The column identifier of the row result representing the score to be used, note that this
@@ -70,10 +54,10 @@ public function recoResultName() : string;
* defaultScore()
or the score logic if the concrete class override the buildScore
* method.
*/
- public function scoreResultName() : string;
+ public function scoreResultName(): string;
/**
* @return float The default score to be given to the discovered recommended item
*/
- public function defaultScore() : float;
+ public function defaultScore(): float;
}
diff --git a/src/Engine/RecommendationEngine.php b/src/Engine/RecommendationEngine.php
index da6df8f..2893cd0 100644
--- a/src/Engine/RecommendationEngine.php
+++ b/src/Engine/RecommendationEngine.php
@@ -11,77 +11,59 @@
namespace GraphAware\Reco4PHP\Engine;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Context\Context;
+use GraphAware\Reco4PHP\Filter\BlackListBuilder;
+use GraphAware\Reco4PHP\Filter\Filter;
use GraphAware\Reco4PHP\Persistence\DatabaseService;
+use GraphAware\Reco4PHP\Post\PostProcessor;
+use GraphAware\Reco4PHP\Result\Recommendations;
+use Laudis\Neo4j\Types\Node;
interface RecommendationEngine
{
- /**
- * @return string
- */
- public function name() : string;
-
- /**
- * @return \GraphAware\Reco4PHP\Engine\DiscoveryEngine[]
- */
- public function discoveryEngines() : array;
-
- /**
- * @return \GraphAware\Reco4PHP\Filter\BlackListBuilder[]
- */
- public function blacklistBuilders() : array;
+ public function name(): string;
/**
- * @return \GraphAware\Reco4PHP\Post\PostProcessor[]
+ * @return DiscoveryEngine[]
*/
- public function postProcessors() : array;
+ public function discoveryEngines(): array;
/**
- * @return \GraphAware\Reco4PHP\Filter\Filter[]
+ * @return BlackListBuilder[]
*/
- public function filters() : array;
+ public function blacklistBuilders(): array;
/**
- * @return \Psr\Log\LoggerInterface[]
+ * @return PostProcessor[]
*/
- public function loggers() : array;
+ public function postProcessors(): array;
/**
- * @return \GraphAware\Reco4PHP\Engine\DiscoveryEngine[]
+ * @return Filter[]
*/
- public function getDiscoveryEngines() : array;
+ public function filters(): array;
/**
- * @return \GraphAware\Reco4PHP\Filter\BlackListBuilder[]
+ * @return DiscoveryEngine[]
*/
- public function getBlacklistBuilders() : array;
+ public function getDiscoveryEngines(): array;
/**
- * @return \GraphAware\Reco4PHP\Filter\Filter[]
+ * @return BlackListBuilder[]
*/
- public function getFilters() : array;
+ public function getBlacklistBuilders(): array;
/**
- * @return \GraphAware\Reco4PHP\Post\PostProcessor[]
+ * @return Filter[]
*/
- public function getPostProcessors() : array;
+ public function getFilters(): array;
/**
- * @return \Psr\Log\LoggerInterface[]
+ * @return PostProcessor[]
*/
- public function getLoggers() : array;
+ public function getPostProcessors(): array;
- /**
- * @param Node $input
- * @param Context $context
- *
- * @return \GraphAware\Reco4PHP\Result\Recommendations
- */
- public function recommend(Node $input, Context $context);
+ public function recommend(Node $input, Context $context): Recommendations;
- /**
- * @param \GraphAware\Reco4PHP\Persistence\DatabaseService $databaseService
- */
public function setDatabaseService(DatabaseService $databaseService);
}
diff --git a/src/Engine/SingleDiscoveryEngine.php b/src/Engine/SingleDiscoveryEngine.php
index 7d74d6c..0a002f0 100644
--- a/src/Engine/SingleDiscoveryEngine.php
+++ b/src/Engine/SingleDiscoveryEngine.php
@@ -1,6 +1,6 @@
hasValue($this->scoreResultName()) ? $record->value($this->scoreResultName()) : $this->defaultScore();
- $reason = $record->hasValue($this->reasonResultName()) ? $record->value($this->reasonResultName()) : null;
+ $score = $result->hasKey($this->scoreResultName()) ? $result->get($this->scoreResultName()) : $this->defaultScore();
+ $reason = $result->hasKey($this->reasonResultName()) ? $result->get($this->reasonResultName()) : null;
return new SingleScore($score, $reason);
}
/**
* {@inheritdoc}
- *
- * @param Node $input
- * @param ResultCollection $resultCollection
- * @param Context $context
- *
- * @return \GraphAware\Reco4PHP\Result\Recommendations
*/
- final public function produceRecommendations(Node $input, ResultCollection $resultCollection, Context $context) : Recommendations
+ final public function produceRecommendations(Node $input, ResultCollection $resultCollection, Context $context): Recommendations
{
- $result = $resultCollection->get($this->name());
+ $results = $resultCollection->get($this->name());
$recommendations = new Recommendations($context);
- foreach ($result->records() as $record) {
- if ($record->hasValue($this->recoResultName())) {
- $recommendations->add($record->get($this->recoResultName()), $this->name(), $this->buildScore($input, $record->get($this->recoResultName()), $record, $context));
+ /** @var CypherMap $result */
+ foreach ($results as $result) {
+ if ($result->hasKey($this->recoResultName())) {
+ /** @var Node $node */
+ $node = $result->get($this->recoResultName());
+ $recommendations->add($node, $this->name(), $this->buildScore($input, $node, $result, $context));
}
}
@@ -70,7 +60,7 @@ final public function produceRecommendations(Node $input, ResultCollection $resu
/**
* {@inheritdoc}
*/
- public function recoResultName() : string
+ public function recoResultName(): string
{
return self::$DEFAULT_RECO_NAME;
}
@@ -78,7 +68,7 @@ public function recoResultName() : string
/**
* {@inheritdoc}
*/
- public function scoreResultName() : string
+ public function scoreResultName(): string
{
return self::$DEFAULT_SCORE_NAME;
}
@@ -86,7 +76,7 @@ public function scoreResultName() : string
/**
* {@inheritdoc}
*/
- public function reasonResultName() : string
+ public function reasonResultName(): string
{
return self::$DEFAULT_REASON_NAME;
}
@@ -94,7 +84,7 @@ public function reasonResultName() : string
/**
* {@inheritdoc}
*/
- public function defaultScore() : float
+ public function defaultScore(): float
{
return 1.0;
}
diff --git a/src/Executor/DiscoveryPhaseExecutor.php b/src/Executor/DiscoveryPhaseExecutor.php
index 625f2b8..8ecb82d 100644
--- a/src/Executor/DiscoveryPhaseExecutor.php
+++ b/src/Executor/DiscoveryPhaseExecutor.php
@@ -11,21 +11,19 @@
namespace GraphAware\Reco4PHP\Executor;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Context\Context;
+use GraphAware\Reco4PHP\Engine\DiscoveryEngine;
+use GraphAware\Reco4PHP\Filter\BlackListBuilder;
use GraphAware\Reco4PHP\Persistence\DatabaseService;
+use GraphAware\Reco4PHP\Result\ResultCollection;
+use Laudis\Neo4j\Types\Node;
class DiscoveryPhaseExecutor
{
- /**
- * @var \GraphAware\Reco4PHP\Persistence\DatabaseService
- */
- private $databaseService;
+ private DatabaseService $databaseService;
/**
* DiscoveryPhaseExecutor constructor.
- *
- * @param \GraphAware\Reco4PHP\Persistence\DatabaseService $databaseService
*/
public function __construct(DatabaseService $databaseService)
{
@@ -33,28 +31,28 @@ public function __construct(DatabaseService $databaseService)
}
/**
- * @param Node $input
- * @param \GraphAware\Reco4PHP\Engine\DiscoveryEngine[] $engines
- * @param \GraphAware\Reco4PHP\Filter\BlackListBuilder[] $blacklists
- * @param Context $context
- *
- * @return \GraphAware\Common\Result\ResultCollection
+ * @param DiscoveryEngine[] $engines
+ * @param BlackListBuilder[] $blacklists
*/
- public function processDiscovery(Node $input, array $engines, array $blacklists, Context $context)
+ public function processDiscovery(Node $input, array $engines, array $blacklists, Context $context): ResultCollection
{
- $stack = $this->databaseService->getDriver()->stack();
- foreach ($engines as $engine) {
- $statement = $engine->discoveryQuery($input, $context);
- $stack->push($statement->text(), $statement->parameters(), $engine->name());
+ $statements = [];
+ $tags = [];
+ foreach (array_values($engines) as $engine) {
+ $statements[] = $engine->discoveryQuery($input, $context);
+ $tags[] = $engine->name();
}
- foreach ($blacklists as $blacklist) {
- $statement = $blacklist->blacklistQuery($input);
- $stack->push($statement->text(), $statement->parameters(), $blacklist->name());
+ foreach (array_values($blacklists) as $blacklist) {
+ $statements[] = $blacklist->blacklistQuery($input);
+ $tags[] = $blacklist->name();
}
try {
- $resultCollection = $this->databaseService->getDriver()->runStack($stack);
+ $resultCollection = new ResultCollection();
+ foreach ($this->databaseService->getDriver()->runStatements($statements) as $key => $value) {
+ $resultCollection->add($value, $tags[$key]);
+ }
return $resultCollection;
} catch (\Exception $e) {
diff --git a/src/Executor/PostProcessPhaseExecutor.php b/src/Executor/PostProcessPhaseExecutor.php
index 95b7d3d..3f37df5 100644
--- a/src/Executor/PostProcessPhaseExecutor.php
+++ b/src/Executor/PostProcessPhaseExecutor.php
@@ -11,58 +11,50 @@
namespace GraphAware\Reco4PHP\Executor;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Engine\RecommendationEngine;
use GraphAware\Reco4PHP\Persistence\DatabaseService;
use GraphAware\Reco4PHP\Post\CypherAwarePostProcessor;
use GraphAware\Reco4PHP\Post\RecommendationSetPostProcessor;
use GraphAware\Reco4PHP\Result\Recommendations;
+use GraphAware\Reco4PHP\Result\ResultCollection;
+use Laudis\Neo4j\Types\Node;
class PostProcessPhaseExecutor
{
- /**
- * @var \GraphAware\Reco4PHP\Persistence\DatabaseService
- */
- protected $databaseService;
+ protected DatabaseService $databaseService;
/**
* PostProcessPhaseExecutor constructor.
- *
- * @param \GraphAware\Reco4PHP\Persistence\DatabaseService $databaseService
*/
public function __construct(DatabaseService $databaseService)
{
$this->databaseService = $databaseService;
}
- /**
- * @param \GraphAware\Common\Type\Node $input
- * @param \GraphAware\Reco4PHP\Result\Recommendations $recommendations
- * @param \GraphAware\Reco4PHP\Engine\RecommendationEngine $recommendationEngine
- *
- * @return \GraphAware\Common\Result\ResultCollection
- */
- public function execute(Node $input, Recommendations $recommendations, RecommendationEngine $recommendationEngine)
+ public function execute(Node $input, Recommendations $recommendations, RecommendationEngine $recommendationEngine): ResultCollection
{
- $stack = $this->databaseService->getDriver()->stack('post_process_'.$recommendationEngine->name());
+ $statements = [];
+ $tags = [];
foreach ($recommendationEngine->getPostProcessors() as $postProcessor) {
if ($postProcessor instanceof CypherAwarePostProcessor) {
foreach ($recommendations->getItems() as $recommendation) {
- $tag = sprintf('post_process_%s_%d', $postProcessor->name(), $recommendation->item()->identity());
- $statement = $postProcessor->buildQuery($input, $recommendation);
- $stack->push($statement->text(), $statement->parameters(), $tag);
+ $tags[] = sprintf('post_process_%s_%d', $postProcessor->name(), $recommendation->item()->identity());
+ $statements[] = $postProcessor->buildQuery($input, $recommendation);
}
} elseif ($postProcessor instanceof RecommendationSetPostProcessor) {
- $statement = $postProcessor->buildQuery($input, $recommendations);
- $stack->push($statement->text(), $statement->parameters(), $postProcessor->name());
+ $statements[] = $postProcessor->buildQuery($input, $recommendations);
+ $tags[] = $postProcessor->name();
}
}
try {
- $results = $this->databaseService->getDriver()->runStack($stack);
+ $resultCollection = new ResultCollection();
+ foreach ($this->databaseService->getDriver()->runStatements($statements) as $key => $value) {
+ $resultCollection->add($value, $tags[$key]);
+ }
- return $results;
+ return $resultCollection;
} catch (\Exception $e) {
throw new \RuntimeException('PostProcess Query Exception - '.$e->getMessage());
}
diff --git a/src/Executor/RecommendationExecutor.php b/src/Executor/RecommendationExecutor.php
index c90dbd2..0abdcbb 100644
--- a/src/Executor/RecommendationExecutor.php
+++ b/src/Executor/RecommendationExecutor.php
@@ -11,34 +11,28 @@
namespace GraphAware\Reco4PHP\Executor;
-use GraphAware\Common\Result\ResultCollection;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Context\Context;
+use GraphAware\Reco4PHP\Engine\RecommendationEngine;
use GraphAware\Reco4PHP\Persistence\DatabaseService;
+use GraphAware\Reco4PHP\Post\RecommendationSetPostProcessor;
use GraphAware\Reco4PHP\Result\Recommendations;
-use GraphAware\Reco4PHP\Engine\RecommendationEngine;
-use Symfony\Component\Stopwatch\Stopwatch;
+use GraphAware\Reco4PHP\Result\ResultCollection;
+use Laudis\Neo4j\Types\CypherMap;
+use Laudis\Neo4j\Types\Node;
class RecommendationExecutor
{
- /**
- * @var \GraphAware\Reco4PHP\Executor\DiscoveryPhaseExecutor
- */
- protected $discoveryExecutor;
+ protected DiscoveryPhaseExecutor $discoveryExecutor;
- /**
- * @var \GraphAware\Reco4PHP\Executor\PostProcessPhaseExecutor
- */
- protected $postProcessExecutor;
+ protected PostProcessPhaseExecutor $postProcessExecutor;
public function __construct(DatabaseService $databaseService)
{
$this->discoveryExecutor = new DiscoveryPhaseExecutor($databaseService);
$this->postProcessExecutor = new PostProcessPhaseExecutor($databaseService);
- $this->stopwatch = new Stopwatch();
}
- public function processRecommendation(Node $input, RecommendationEngine $engine, Context $context)
+ public function processRecommendation(Node $input, RecommendationEngine $engine, Context $context): Recommendations
{
$recommendations = $this->doDiscovery($input, $engine, $context);
$this->doPostProcess($input, $recommendations, $engine);
@@ -47,7 +41,7 @@ public function processRecommendation(Node $input, RecommendationEngine $engine,
return $recommendations;
}
- private function doDiscovery(Node $input, RecommendationEngine $engine, Context $context)
+ private function doDiscovery(Node $input, RecommendationEngine $engine, Context $context): Recommendations
{
$recommendations = new Recommendations($context);
$context->getStatistics()->startDiscovery();
@@ -69,22 +63,24 @@ private function doDiscovery(Node $input, RecommendationEngine $engine, Context
return $recommendations;
}
- private function doPostProcess(Node $input, Recommendations $recommendations, RecommendationEngine $engine)
+ private function doPostProcess(Node $input, Recommendations $recommendations, RecommendationEngine $engine): void
{
$recommendations->getContext()->getStatistics()->startPostProcess();
$postProcessResult = $this->postProcessExecutor->execute($input, $recommendations, $engine);
foreach ($engine->getPostProcessors() as $postProcessor) {
$tag = $postProcessor->name();
- $result = $postProcessResult->get($tag);
- $postProcessor->handleResultSet($input, $result, $recommendations);
+ $results = $postProcessResult->get($tag);
+ if ($postProcessor instanceof RecommendationSetPostProcessor) {
+ $postProcessor->handleResultSet($input, $results, $recommendations);
+ }
}
$recommendations->getContext()->getStatistics()->stopPostProcess();
}
- private function removeIrrelevant(Node $input, RecommendationEngine $engine, Recommendations $recommendations, array $blacklist)
+ private function removeIrrelevant(Node $input, RecommendationEngine $engine, Recommendations $recommendations, array $blacklist): void
{
foreach ($recommendations->getItems() as $recommendation) {
- if (array_key_exists($recommendation->item()->identity(), $blacklist)) {
+ if (array_key_exists($recommendation->item()->getId(), $blacklist)) {
$recommendations->remove($recommendation);
} else {
foreach ($engine->filters() as $filter) {
@@ -97,16 +93,17 @@ private function removeIrrelevant(Node $input, RecommendationEngine $engine, Rec
}
}
- private function buildBlacklistedNodes(ResultCollection $result, RecommendationEngine $engine)
+ private function buildBlacklistedNodes(ResultCollection $resultCollection, RecommendationEngine $engine): array
{
$set = [];
foreach ($engine->getBlacklistBuilders() as $blacklist) {
- $res = $result->get($blacklist->name());
- foreach ($res->records() as $record) {
- if ($record->hasValue($blacklist->itemResultName())) {
- $node = $record->get($blacklist->itemResultName());
+ $results = $resultCollection->get($blacklist->name());
+ /** @var CypherMap $result */
+ foreach ($results as $result) {
+ if ($result->hasKey($blacklist->itemResultName())) {
+ $node = $result->get($blacklist->itemResultName());
if ($node instanceof Node) {
- $set[$node->identity()] = $node;
+ $set[$node->getId()] = $node;
}
}
}
diff --git a/src/Filter/BaseBlacklistBuilder.php b/src/Filter/BaseBlacklistBuilder.php
index d1d1fab..f7cd4dd 100644
--- a/src/Filter/BaseBlacklistBuilder.php
+++ b/src/Filter/BaseBlacklistBuilder.php
@@ -11,29 +11,29 @@
namespace GraphAware\Reco4PHP\Filter;
-use GraphAware\Common\Result\Result;
-use GraphAware\Common\Type\Node;
+use Laudis\Neo4j\Types\CypherList;
+use Laudis\Neo4j\Types\CypherMap;
+use Laudis\Neo4j\Types\Node;
abstract class BaseBlacklistBuilder implements BlackListBuilder
{
/**
- * @param \GraphAware\Common\Result\Result $result
- *
- * @return \GraphAware\Common\Type\Node[]
+ * @return Node[]
*/
- public function buildBlackList(Result $result)
+ public function buildBlackList(CypherList $results): array
{
$nodes = [];
- foreach ($result->records() as $record) {
- if ($record->hasValue($this->itemResultName()) && $record->value($this->itemResultName()) instanceof Node) {
- $nodes[] = $record->get($this->itemResultName());
+ /** @var CypherMap $result */
+ foreach ($results as $result) {
+ if ($result->hasKey($this->itemResultName()) && $result->get($this->itemResultName()) instanceof Node) {
+ $nodes[] = $result->get($this->itemResultName());
}
}
return $nodes;
}
- public function itemResultName()
+ public function itemResultName(): string
{
return 'item';
}
diff --git a/src/Filter/BlackListBuilder.php b/src/Filter/BlackListBuilder.php
index 3ed9403..7bb44f3 100644
--- a/src/Filter/BlackListBuilder.php
+++ b/src/Filter/BlackListBuilder.php
@@ -11,32 +11,22 @@
namespace GraphAware\Reco4PHP\Filter;
-use GraphAware\Common\Result\Result;
-use GraphAware\Common\Type\Node;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\CypherList;
+use Laudis\Neo4j\Types\Node;
interface BlackListBuilder
{
- /**
- * @param \GraphAware\Common\Type\Node $input
- *
- * @return \GraphAware\Common\Cypher\Statement
- */
- public function blacklistQuery(Node $input);
+ public function blacklistQuery(Node $input): Statement;
/**
- * @param \GraphAware\Common\Result\Result
+ * @param CypherList
*
- * @return \GraphAware\Common\Type\Node[]
+ * @return Node[]
*/
- public function buildBlackList(Result $result);
+ public function buildBlackList(CypherList $results): array;
- /**
- * @return string
- */
- public function itemResultName();
+ public function itemResultName(): string;
- /**
- * @return string
- */
- public function name();
+ public function name(): string;
}
diff --git a/src/Filter/ExcludeSelf.php b/src/Filter/ExcludeSelf.php
index 13b73bc..00beeb9 100644
--- a/src/Filter/ExcludeSelf.php
+++ b/src/Filter/ExcludeSelf.php
@@ -11,12 +11,12 @@
namespace GraphAware\Reco4PHP\Filter;
-use GraphAware\Common\Type\Node;
+use Laudis\Neo4j\Types\Node;
class ExcludeSelf implements Filter
{
- public function doInclude(Node $input, Node $item)
+ public function doInclude(Node $input, Node $item): bool
{
- return $input->identity() !== $item->identity();
+ return $input->getId() !== $item->getId();
}
}
diff --git a/src/Filter/Filter.php b/src/Filter/Filter.php
index 7863e48..cc368c4 100644
--- a/src/Filter/Filter.php
+++ b/src/Filter/Filter.php
@@ -11,17 +11,12 @@
namespace GraphAware\Reco4PHP\Filter;
-use GraphAware\Common\Type\Node;
+use Laudis\Neo4j\Types\Node;
interface Filter
{
/**
* Returns whether or not the recommended node should be included in the recommendation.
- *
- * @param \GraphAware\Common\Type\Node $input
- * @param \GraphAware\Common\Type\Node $item
- *
- * @return bool
*/
- public function doInclude(Node $input, Node $item);
+ public function doInclude(Node $input, Node $item): bool;
}
diff --git a/src/Graph/Direction.php b/src/Graph/Direction.php
index 214fefd..5493c06 100644
--- a/src/Graph/Direction.php
+++ b/src/Graph/Direction.php
@@ -13,9 +13,9 @@
final class Direction
{
- const BOTH = 'BOTH';
+ public const BOTH = 'BOTH';
- const INCOMING = 'INCOMING';
+ public const INCOMING = 'INCOMING';
- const OUTGOING = 'OUTGOING';
+ public const OUTGOING = 'OUTGOING';
}
diff --git a/src/Persistence/DatabaseService.php b/src/Persistence/DatabaseService.php
index 92c6804..b730da7 100644
--- a/src/Persistence/DatabaseService.php
+++ b/src/Persistence/DatabaseService.php
@@ -11,34 +11,29 @@
namespace GraphAware\Reco4PHP\Persistence;
-use GraphAware\Neo4j\Client\ClientBuilder;
-use GraphAware\Neo4j\Client\ClientInterface;
+use Laudis\Neo4j\ClientBuilder;
+use Laudis\Neo4j\Contracts\ClientInterface;
class DatabaseService
{
- private $driver;
+ private ClientInterface $driver;
public function __construct($uri = null)
{
- if ($uri !== null) {
+ if (null !== $uri) {
$this->driver = ClientBuilder::create()
- ->addConnection('default', $uri)
+ ->withDriver('default', $uri)
+ ->withDefaultDriver('default')
->build();
}
}
- /**
- * @return ClientInterface
- */
- public function getDriver()
+ public function getDriver(): ClientInterface
{
return $this->driver;
}
- /**
- * @param \GraphAware\Neo4j\Client\ClientInterface $driver
- */
- public function setDriver(ClientInterface $driver)
+ public function setDriver(ClientInterface $driver): void
{
$this->driver = $driver;
}
diff --git a/src/Post/BasePostProcessor.php b/src/Post/BasePostProcessor.php
deleted file mode 100644
index aac7179..0000000
--- a/src/Post/BasePostProcessor.php
+++ /dev/null
@@ -1,16 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace GraphAware\Reco4PHP\Post;
-
-abstract class BasePostProcessor implements PostProcessor
-{
-}
diff --git a/src/Post/CypherAwarePostProcessor.php b/src/Post/CypherAwarePostProcessor.php
index f8d99e5..47b54ad 100644
--- a/src/Post/CypherAwarePostProcessor.php
+++ b/src/Post/CypherAwarePostProcessor.php
@@ -11,16 +11,14 @@
namespace GraphAware\Reco4PHP\Post;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Result\Recommendation;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\Node;
interface CypherAwarePostProcessor extends PostProcessor
{
/**
- * @param \GraphAware\Common\Type\Node $input
- * @param \GraphAware\Reco4PHP\Result\Recommendation $recommendation
- *
- * @return \GraphAware\Common\Cypher\Statement the statement to be executed
+ * @return Statement the statement to be executed
*/
- public function buildQuery(Node $input, Recommendation $recommendation);
+ public function buildQuery(Node $input, Recommendation $recommendation): Statement;
}
diff --git a/src/Post/PostProcessor.php b/src/Post/PostProcessor.php
index 1a10f91..7dfdf41 100644
--- a/src/Post/PostProcessor.php
+++ b/src/Post/PostProcessor.php
@@ -13,5 +13,5 @@
interface PostProcessor
{
- public function name();
+ public function name(): string;
}
diff --git a/src/Post/RecommendationSetPostProcessor.php b/src/Post/RecommendationSetPostProcessor.php
index be54632..10f4d97 100644
--- a/src/Post/RecommendationSetPostProcessor.php
+++ b/src/Post/RecommendationSetPostProcessor.php
@@ -11,46 +11,38 @@
namespace GraphAware\Reco4PHP\Post;
-use GraphAware\Common\Result\Record;
-use GraphAware\Common\Result\Result;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Result\Recommendation;
use GraphAware\Reco4PHP\Result\Recommendations;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\CypherList;
+use Laudis\Neo4j\Types\CypherMap;
+use Laudis\Neo4j\Types\Node;
abstract class RecommendationSetPostProcessor implements PostProcessor
{
- /**
- * @param \GraphAware\Common\Type\Node $input
- * @param \GraphAware\Reco4PHP\Result\Recommendations $recommendations
- *
- * @return \GraphAware\Common\Cypher\Statement
- */
- abstract public function buildQuery(Node $input, Recommendations $recommendations);
+ abstract public function buildQuery(Node $input, Recommendations $recommendations): Statement;
- abstract public function postProcess(Node $input, Recommendation $recommendation, Record $record);
+ abstract public function postProcess(Node $input, Recommendation $recommendation, CypherMap $result): void;
- final public function handleResultSet(Node $input, Result $result, Recommendations $recommendations)
+ final public function handleResultSet(Node $input, CypherList $results, Recommendations $recommendations): void
{
- $recordsMap = [];
- foreach ($result->records() as $i => $record) {
- if (!$record->hasValue($this->idResultName())) {
- throw new \InvalidArgumentException(sprintf(
- 'The record does not contain a value with key "%s" in "%s"',
- $this->idResultName(),
- $this->name()
- ));
+ $resultMap = [];
+ /** @var CypherMap $result */
+ foreach ($results as $result) {
+ if (!$result->hasKey($this->idResultName())) {
+ throw new \InvalidArgumentException(sprintf('The record does not contain a value with key "%s" in "%s"', $this->idResultName(), $this->name()));
}
- $recordsMap[$record->get($this->idResultName())] = $record;
+ $resultMap[$result->get($this->idResultName())] = $result;
}
foreach ($recommendations->getItems() as $recommendation) {
- if (array_key_exists($recommendation->item()->identity(), $recordsMap)) {
- $this->postProcess($input, $recommendation, $recordsMap[$recommendation->item()->identity()]);
+ if (array_key_exists($recommendation->item()->getId(), $resultMap)) {
+ $this->postProcess($input, $recommendation, $resultMap[$recommendation->item()->getId()]);
}
}
}
- public function idResultName()
+ public function idResultName(): string
{
return 'id';
}
diff --git a/src/Post/RewardSomethingShared.php b/src/Post/RewardSomethingShared.php
index 2a7cd5c..afc048d 100644
--- a/src/Post/RewardSomethingShared.php
+++ b/src/Post/RewardSomethingShared.php
@@ -11,48 +11,48 @@
namespace GraphAware\Reco4PHP\Post;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Result\RecordCursorInterface;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Graph\Direction;
use GraphAware\Reco4PHP\Result\Recommendation;
use GraphAware\Reco4PHP\Result\SingleScore;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\CypherList;
+use Laudis\Neo4j\Types\Node;
abstract class RewardSomethingShared implements CypherAwarePostProcessor
{
abstract public function relationshipType();
- public function relationshipDirection()
+ public function relationshipDirection(): string
{
return Direction::BOTH;
}
- final public function buildQuery(Node $input, Recommendation $recommendation)
+ final public function buildQuery(Node $input, Recommendation $recommendation): Statement
{
$relationshipPatterns = [
- Direction::BOTH => array('-[:%s]-', '-[:%s]-'),
- Direction::INCOMING => array('<-[:%s]-', '-[:%s]->'),
- Direction::OUTGOING => array('-[:%s]->', '<-[:%s]-'),
+ Direction::BOTH => ['-[:%s]-', '-[:%s]-'],
+ Direction::INCOMING => ['<-[:%s]-', '-[:%s]->'],
+ Direction::OUTGOING => ['-[:%s]->', '<-[:%s]-'],
];
$relPattern = sprintf($relationshipPatterns[$this->relationshipDirection()][0], $this->relationshipType());
$inversedRelPattern = sprintf($relationshipPatterns[$this->relationshipDirection()][1], $this->relationshipType());
- $query = 'MATCH (input) WHERE id(input) = {inputId}, (item) WHERE id(item) = {itemId}
+ $query = 'MATCH (input) WHERE id(input) = $inputId, (item) WHERE id(item) = $itemId
MATCH (input)'.$relPattern.'(shared)'.$inversedRelPattern.'(item)
RETURN shared as sharedThing';
- return Statement::create($query, ['inputId' => $input->identity(), 'itemId' => $recommendation->item()->identity()]);
+ return Statement::create($query, ['inputId' => $input->getId(), 'itemId' => $recommendation->item()->getId()]);
}
- public function postProcess(Node $input, Recommendation $recommendation, RecordCursorInterface $result = null)
+ public function postProcess(Node $input, Recommendation $recommendation, ?CypherList $results = null): void
{
- if (null === $result) {
+ if (null === $results) {
throw new \RuntimeException(sprintf('Expected a non-null result in %s::postProcess()', get_class($this)));
}
- if (count($result->records()) > 0) {
- foreach ($result->records() as $record) {
+ if (count($results) > 0) {
+ foreach ($results as $result) {
$recommendation->addScore($this->name(), new SingleScore(1));
}
}
diff --git a/src/RecommenderService.php b/src/RecommenderService.php
index c97423a..b0c4436 100644
--- a/src/RecommenderService.php
+++ b/src/RecommenderService.php
@@ -11,55 +11,34 @@
namespace GraphAware\Reco4PHP;
-use GraphAware\Common\Type\Node;
-use GraphAware\Neo4j\Client\ClientInterface;
-use GraphAware\Reco4PHP\Persistence\DatabaseService;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-use Symfony\Component\EventDispatcher\EventDispatcher;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use GraphAware\Reco4PHP\Engine\RecommendationEngine;
+use GraphAware\Reco4PHP\Persistence\DatabaseService;
+use InvalidArgumentException;
+use Laudis\Neo4j\Contracts\ClientInterface;
+use Laudis\Neo4j\Types\CypherList;
+use Laudis\Neo4j\Types\Node;
class RecommenderService
{
/**
- * @var \GraphAware\Reco4PHP\Engine\RecommendationEngine[]
+ * @var RecommendationEngine[]
*/
- private $engines = [];
+ private array $engines = [];
- /**
- * @var \GraphAware\Reco4PHP\Persistence\DatabaseService
- */
- private $databaseService;
-
- /**
- * @var null|\Symfony\Component\EventDispatcher\EventDispatcher|\Symfony\Component\EventDispatcher\EventDispatcherInterface
- */
- private $eventDispatcher;
-
- /**
- * @var null|\Psr\Log\LoggerInterface|\Psr\Log\NullLogger
- */
- private $logger;
+ private DatabaseService $databaseService;
/**
* RecommenderService constructor.
- *
- * @param \GraphAware\Reco4PHP\Persistence\DatabaseService $databaseService
- * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface|null $eventDispatcher
- * @param \Psr\Log\LoggerInterface|null $logger
*/
- public function __construct(DatabaseService $databaseService, EventDispatcherInterface $eventDispatcher = null, LoggerInterface $logger = null)
+ public function __construct(DatabaseService $databaseService)
{
$this->databaseService = $databaseService;
- $this->eventDispatcher = null !== $eventDispatcher ? $eventDispatcher : new EventDispatcher();
- $this->logger = null !== $logger ? $logger : new NullLogger();
}
/**
* @param string $uri
*
- * @return \GraphAware\Reco4PHP\RecommenderService
+ * @return RecommenderService
*/
public static function create($uri)
{
@@ -67,9 +46,7 @@ public static function create($uri)
}
/**
- * @param ClientInterface $client
- *
- * @return \GraphAware\Reco4PHP\RecommenderService
+ * @return RecommenderService
*/
public static function createWithClient(ClientInterface $client)
{
@@ -79,66 +56,46 @@ public static function createWithClient(ClientInterface $client)
return new self($databaseService);
}
- /**
- * @param $id
- *
- * @return \GraphAware\Bolt\Result\Type\Node|\GraphAware\Bolt\Result\Type\Path|\GraphAware\Bolt\Result\Type\Relationship|mixed
- */
- public function findInputById($id)
+ public function findInputById(int $id): Node
{
- $id = (int) $id;
- $result = $this->databaseService->getDriver()->run('MATCH (n) WHERE id(n) = {id} RETURN n as input', ['id' => $id]);
+ $result = $this->databaseService->getDriver()->run('MATCH (n) WHERE id(n) = $id RETURN n as input', ['id' => $id]);
return $this->validateInput($result);
}
- /**
- * @param string $label
- * @param string $key
- * @param mixed $value
- *
- * @return \GraphAware\Common\Type\Node
- */
- public function findInputBy($label, $key, $value)
+ public function findInputBy(string $label, string $key, mixed $value): Node
{
- $query = sprintf('MATCH (n:%s {%s: {value} }) RETURN n as input', $label, $key);
+ $query = sprintf('MATCH (n:%s {%s: $value }) RETURN n as input', $label, $key);
$result = $this->databaseService->getDriver()->run($query, ['value' => $value]);
return $this->validateInput($result);
}
/**
- * @param \GraphAware\Common\Result\Result $result
- *
- * @return \GraphAware\Common\Type\Node
+ * @throws InvalidArgumentException
*/
- public function validateInput($result)
+ public function validateInput(CypherList $results): Node
{
- if (count($result->records()) < 1 || !$result->getRecord()->value('input') instanceof Node) {
- throw new \InvalidArgumentException(sprintf('Node not found'));
+ if (count($results) < 1 || !$results->first()->get('input') instanceof Node) {
+ throw new InvalidArgumentException(sprintf('Node not found'));
}
- return $result->getRecord()->value('input');
+ return $results->first()->get('input');
}
/**
- * @param $name
- *
- * @return \GraphAware\Reco4PHP\Engine\RecommendationEngine
+ * @throws InvalidArgumentException
*/
- public function getRecommender($name)
+ public function getRecommender(string $name): RecommendationEngine
{
if (!array_key_exists($name, $this->engines)) {
- throw new \InvalidArgumentException(sprintf('The Recommendation engine "%s" is not registered in the Recommender Service', $name));
+ throw new InvalidArgumentException(sprintf('The Recommendation engine "%s" is not registered in the Recommender Service', $name));
}
return $this->engines[$name];
}
- /**
- * @param \GraphAware\Reco4PHP\Engine\RecommendationEngine $recommendationEngine
- */
- public function registerRecommendationEngine(RecommendationEngine $recommendationEngine)
+ public function registerRecommendationEngine(RecommendationEngine $recommendationEngine): void
{
$recommendationEngine->setDatabaseService($this->databaseService);
$this->engines[$recommendationEngine->name()] = $recommendationEngine;
diff --git a/src/Result/PartialScore.php b/src/Result/PartialScore.php
deleted file mode 100644
index 452ddbe..0000000
--- a/src/Result/PartialScore.php
+++ /dev/null
@@ -1,83 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace GraphAware\Reco4PHP\Result;
-
-class PartialScore
-{
- /**
- * @var float
- */
- protected $value;
-
- /**
- * @var \GraphAware\Reco4PHP\Result\Reason[]
- */
- protected $reasons = [];
-
- /**
- * @param float $value
- * @param mixed $reason
- */
- public function __construct($value = 0, $reason = null)
- {
- $this->value = (float) $value;
- $this->addReason($value, $reason);
- }
-
- /**
- * @param float $value
- * @param array $details
- */
- public function add($value, $reaon = null)
- {
- $this->value += (float) $value;
- $this->addReason($value, $reaon);
- }
-
- /**
- * @return float
- */
- public function getValue()
- {
- return $this->value;
- }
-
- /**
- * @param float $value
- * @param array $details
- */
- public function setNewValue($value, array $details = array())
- {
- $this->add(-($this->value - $value), $details);
- }
-
- /**
- * @return \GraphAware\Reco4PHP\Result\Reason[]
- */
- public function getReasons()
- {
- return $this->reasons;
- }
-
- /**
- * @param $value
- * @param array $details
- */
- private function addReason($value, $detail = null)
- {
- if (!$detail) {
- return;
- }
-
- $this->reasons[] = new Reason($value, $detail);
- }
-}
diff --git a/src/Result/Reason.php b/src/Result/Reason.php
deleted file mode 100644
index 6131f60..0000000
--- a/src/Result/Reason.php
+++ /dev/null
@@ -1,35 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace GraphAware\Reco4PHP\Result;
-
-class Reason
-{
- protected $value;
-
- protected $detail;
-
- public function __construct($value, $detail)
- {
- $this->value = (float) $value;
- $this->detail = $detail;
- }
-
- public function getValue()
- {
- return $this->value;
- }
-
- public function getDetail()
- {
- return $this->detail;
- }
-}
diff --git a/src/Result/Recommendation.php b/src/Result/Recommendation.php
index 58b8bee..ad5afed 100644
--- a/src/Result/Recommendation.php
+++ b/src/Result/Recommendation.php
@@ -11,29 +11,21 @@
namespace GraphAware\Reco4PHP\Result;
-use GraphAware\Common\Type\Node;
+use Laudis\Neo4j\Types\Node;
class Recommendation
{
- /**
- * @var \GraphAware\Common\Type\Node
- */
- protected $item;
+ protected Node $item;
/**
- * @var \GraphAware\Reco4PHP\Result\Score[]
+ * @var Score[]
*/
- protected $scores = [];
+ protected array $scores = [];
- /**
- * @var float
- */
- protected $totalScore = 0.0;
+ protected float $totalScore = 0.0;
/**
* Recommendation constructor.
- *
- * @param \GraphAware\Common\Type\Node $item
*/
public function __construct(Node $item)
{
@@ -41,19 +33,18 @@ public function __construct(Node $item)
}
/**
- * @param string $name
- * @param \GraphAware\Reco4PHP\Result\SingleScore $score
+ * @param string $name
*/
- public function addScore($name, SingleScore $score)
+ public function addScore($name, SingleScore $score): void
{
$this->getScoreOrCreate($name)->add($score);
$this->totalScore += $score->getScore();
}
/**
- * @param \GraphAware\Reco4PHP\Result\SingleScore[]
+ * @param SingleScore[]
*/
- public function addScores(array $scores)
+ public function addScores(array $scores): void
{
foreach ($scores as $name => $singleScores) {
foreach ($singleScores->getScores() as $score) {
@@ -63,19 +54,14 @@ public function addScores(array $scores)
}
/**
- * @return \GraphAware\Reco4PHP\Result\SingleScore[]
+ * @return SingleScore[]
*/
- public function getScores()
+ public function getScores(): array
{
return $this->scores;
}
- /**
- * @param string $key
- *
- * @return \GraphAware\Reco4PHP\Result\Score
- */
- public function getScore($key)
+ public function getScore(string $key): Score
{
if (!array_key_exists($key, $this->scores)) {
throw new \InvalidArgumentException(sprintf('The recommendation does not contains a score named "%s"', $key));
@@ -84,7 +70,7 @@ public function getScore($key)
return $this->scores[$key];
}
- private function getScoreOrCreate($name)
+ private function getScoreOrCreate(string $name): Score
{
if (!array_key_exists($name, $this->scores)) {
$this->scores[$name] = new Score($name);
@@ -93,18 +79,12 @@ private function getScoreOrCreate($name)
return $this->scores[$name];
}
- /**
- * @return float
- */
- public function totalScore()
+ public function totalScore(): float
{
return $this->totalScore;
}
- /**
- * @return \GraphAware\Common\Type\Node
- */
- public function item()
+ public function item(): Node
{
return $this->item;
}
diff --git a/src/Result/Recommendations.php b/src/Result/Recommendations.php
index 61b1dab..323b03d 100644
--- a/src/Result/Recommendations.php
+++ b/src/Result/Recommendations.php
@@ -11,78 +11,59 @@
namespace GraphAware\Reco4PHP\Result;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Context\Context;
+use Laudis\Neo4j\Types\Node;
class Recommendations
{
- /**
- * @var \GraphAware\Reco4PHP\Context\Context
- */
- protected $context;
+ protected Context $context;
/**
- * @var \GraphAware\Reco4PHP\Result\Recommendation[]
+ * @var Recommendation[]
*/
- protected $recommendations = [];
+ protected array $recommendations = [];
- /**
- * @param \GraphAware\Reco4PHP\Context\Context $context
- */
public function __construct(Context $context)
{
$this->context = $context;
}
- /**
- * @param \GraphAware\Common\Type\Node $item
- *
- * @return \GraphAware\Reco4PHP\Result\Recommendation
- */
- public function getOrCreate(Node $item)
+ public function getOrCreate(Node $item): Recommendation
{
- if (array_key_exists($item->identity(), $this->recommendations)) {
- return $this->recommendations[$item->identity()];
+ if (array_key_exists($item->getId(), $this->recommendations)) {
+ return $this->recommendations[$item->getId()];
}
$recommendation = new Recommendation($item);
- $this->recommendations[$item->identity()] = $recommendation;
+ $this->recommendations[$item->getId()] = $recommendation;
return $recommendation;
}
- /**
- * @param \GraphAware\Common\Type\Node $item
- * @param string $name
- * @param \GraphAware\Reco4PHP\Result\SingleScore $singleScore
- */
- public function add(Node $item, $name, SingleScore $singleScore)
+ public function add(Node $item, string $name, SingleScore $singleScore): void
{
$this->getOrCreate($item)->addScore($name, $singleScore);
}
- /**
- * @param \GraphAware\Reco4PHP\Result\Recommendations $recommendations
- */
- public function merge(Recommendations $recommendations)
+ public function merge(Recommendations $recommendations): void
{
foreach ($recommendations->getItems() as $recommendation) {
$this->getOrCreate($recommendation->item())->addScores($recommendation->getScores());
}
}
- public function remove(Recommendation $recommendation)
+ public function remove(Recommendation $recommendation): void
{
- if (!array_key_exists($recommendation->item()->identity(), $this->recommendations)) {
+ if (!array_key_exists($recommendation->item()->getId(), $this->recommendations)) {
return;
}
- unset($this->recommendations[$recommendation->item()->identity()]);
+ unset($this->recommendations[$recommendation->item()->getId()]);
}
/**
- * @return \GraphAware\Reco4PHP\Result\Recommendation[]
+ * @return Recommendation[]
*/
- public function getItems($size = null) : array
+ public function getItems(?int $size = null): array
{
if (is_int($size) && $size > 0) {
return array_slice($this->recommendations, 0, $size);
@@ -91,33 +72,20 @@ public function getItems($size = null) : array
return array_values($this->recommendations);
}
- /**
- * @param $position
- *
- * @return \GraphAware\Reco4PHP\Result\Recommendation
- */
- public function get($position) : Recommendation
+ public function get(int $position): Recommendation
{
return array_values($this->recommendations)[$position];
}
- /**
- * @return int
- */
- public function size() : int
+ public function size(): int
{
return count($this->recommendations);
}
- /**
- * @param string $key
- * @param mixed $value
- * @return \GraphAware\Reco4PHP\Result\Recommendation|null
- */
- public function getItemBy($key, $value)
+ public function getItemBy(string $key, mixed $value): ?Recommendation
{
foreach ($this->getItems() as $recommendation) {
- if ($recommendation->item()->hasValue($key) && $recommendation->item()->value($key) === $value) {
+ if ($recommendation->item()->getProperties()->hasKey($key) && $recommendation->item()->getProperty($key) === $value) {
return $recommendation;
}
}
@@ -125,14 +93,10 @@ public function getItemBy($key, $value)
return null;
}
- /**
- * @param int $id
- * @return \GraphAware\Reco4PHP\Result\Recommendation|null
- */
- public function getItemById($id)
+ public function getItemById(int $id): ?Recommendation
{
foreach ($this->getItems() as $item) {
- if ($item->item()->identity() === $id) {
+ if ($item->item()->getId() === $id) {
return $item;
}
}
@@ -140,17 +104,14 @@ public function getItemById($id)
return null;
}
- public function sort()
+ public function sort(): void
{
usort($this->recommendations, function (Recommendation $recommendationA, Recommendation $recommendationB) {
return $recommendationA->totalScore() <= $recommendationB->totalScore();
});
}
- /**
- * @return \GraphAware\Reco4PHP\Context\Context
- */
- public function getContext() : Context
+ public function getContext(): Context
{
return $this->context;
}
diff --git a/src/Result/ResultCollection.php b/src/Result/ResultCollection.php
new file mode 100644
index 0000000..408c48d
--- /dev/null
+++ b/src/Result/ResultCollection.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace GraphAware\Reco4PHP\Result;
+
+use InvalidArgumentException;
+use Laudis\Neo4j\Types\CypherList;
+
+class ResultCollection
+{
+ /**
+ * @var CypherList[]
+ */
+ protected $resultsMap = [];
+
+ public function add(CypherList $results, string $tag)
+ {
+ $this->resultsMap[$tag] = $results;
+ }
+
+ /**
+ * @param mixed $default
+ *
+ * @throws InvalidArgumentException
+ */
+ public function get(string $tag, mixed $default = null): CypherList
+ {
+ if (array_key_exists($tag, $this->resultsMap)) {
+ return $this->resultsMap[$tag];
+ }
+
+ if (2 === func_num_args()) {
+ return $default;
+ }
+
+ throw new InvalidArgumentException(sprintf('This result collection does not contains a results for tag "%s"', $tag));
+ }
+}
diff --git a/src/Result/Score.php b/src/Result/Score.php
index 9c5feba..89a792d 100644
--- a/src/Result/Score.php
+++ b/src/Result/Score.php
@@ -13,37 +13,28 @@
class Score
{
- /**
- * @var float
- */
- protected $score = 0.0;
+ protected float $score = 0.0;
/**
- * @var \GraphAware\Reco4PHP\Result\SingleScore[]
+ * @var SingleScore[]
*/
- protected $scores = [];
+ protected array $scores = [];
- /**
- * @param \GraphAware\Reco4PHP\Result\SingleScore $score
- */
- public function add(SingleScore $score)
+ public function add(SingleScore $score): void
{
$this->scores[] = $score;
- $this->score += (float) $score->getScore();
+ $this->score += $score->getScore();
}
- /**
- * @return float
- */
- public function score()
+ public function score(): float
{
return $this->score;
}
/**
- * @return \GraphAware\Reco4PHP\Result\SingleScore[]
+ * @return SingleScore[]
*/
- public function getScores()
+ public function getScores(): array
{
return $this->scores;
}
diff --git a/src/Result/SingleScore.php b/src/Result/SingleScore.php
index 8a9878b..275dc2d 100644
--- a/src/Result/SingleScore.php
+++ b/src/Result/SingleScore.php
@@ -13,47 +13,32 @@
class SingleScore
{
- /**
- * @var float
- */
- private $score;
+ private float $score;
- /**
- * @var null|string
- */
- private $reason;
+ private ?string $reason;
/**
* SingleScore constructor.
- *
- * @param float|$score
- * @param null|string $reason
*/
- public function __construct($score, $reason = null)
+ public function __construct(float $score, ?string $reason = null)
{
- $this->score = (float) $score;
+ $this->score = $score;
$this->addReason($reason);
}
- public function addReason($reason = null)
+ public function addReason(?string $reason = null): void
{
if (null !== $reason) {
- $this->reason = (string) $reason;
+ $this->reason = $reason;
}
}
- /**
- * @return float
- */
- public function getScore()
+ public function getScore(): float
{
return $this->score;
}
- /**
- * @return null|string
- */
- public function getReason()
+ public function getReason(): ?string
{
return $this->reason;
}
diff --git a/src/Transactional/BaseCypherAware.php b/src/Transactional/BaseCypherAware.php
deleted file mode 100644
index 186dcf0..0000000
--- a/src/Transactional/BaseCypherAware.php
+++ /dev/null
@@ -1,37 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace GraphAware\Reco4PHP\Transactional;
-
-abstract class BaseCypherAware implements CypherAware
-{
- private $parameters = [];
-
- final protected function addParameter($key, $value)
- {
- if (!is_scalar($value) && !is_array($value)) {
- throw new \InvalidArgumentException(sprintf("Expected a scalar or array value for '%s'", $key));
- }
- $this->parameters[$key] = $value;
- }
-
- final protected function addParameters(array $parameters)
- {
- foreach ($parameters as $key => $parameter) {
- $this->addParameter($key, $parameter);
- }
- }
-
- final public function parameters()
- {
- return $this->parameters;
- }
-}
diff --git a/src/Transactional/CypherAware.php b/src/Transactional/CypherAware.php
deleted file mode 100644
index cc01660..0000000
--- a/src/Transactional/CypherAware.php
+++ /dev/null
@@ -1,19 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace GraphAware\Reco4PHP\Transactional;
-
-interface CypherAware
-{
- public function query();
-
- public function parameters();
-}
diff --git a/src/Util/NodeProxy.php b/src/Util/NodeProxy.php
deleted file mode 100644
index fdd2f4c..0000000
--- a/src/Util/NodeProxy.php
+++ /dev/null
@@ -1,92 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace GraphAware\Reco4PHP\Util;
-
-use GraphAware\Common\Type\Node;
-
-class NodeProxy implements Node
-{
- protected $id;
-
- protected $properties = [];
-
- protected $labels = [];
-
- public function __construct($id = null, array $properties = array(), array $labels = array())
- {
- $this->id = $id;
- $this->properties = $properties;
- $this->labels = $labels;
- }
-
- public function identity()
- {
- return $this->id;
- }
-
- public function keys()
- {
- return array_keys($this->properties);
- }
-
- public function containsKey($key)
- {
- return array_key_exists($key, $this->properties);
- }
-
- public function get($key)
- {
- if (!$this->containsKey($key)) {
- throw new \InvalidArgumentException(sprintf('This node doesn\'t contain the "%s" property'), $key);
- }
-
- return $this->properties[$key];
- }
-
- public function hasValue($key)
- {
- return $this->containsKey($key);
- }
-
- public function value($key, $default = null)
- {
- if (!$this->containsKey($key) && 1 === func_num_args()) {
- throw new \InvalidArgumentException(sprintf('This node doesn\'t contain the "%s" property'), $key);
- }
-
- return $this->containsKey($key) ? $this->properties[$key] : $default;
- }
-
- public function values()
- {
- return $this->properties;
- }
-
- public function asArray()
- {
- return [
- 'id' => $this->id,
- 'labels' => $this->labels,
- 'properties' => $this->properties,
- ];
- }
-
- public function labels()
- {
- return $this->labels;
- }
-
- public function hasLabel($label)
- {
- return in_array($label, $this->labels);
- }
-}
diff --git a/tests/Algorithms/Model/KNNModelBuilderTest.php b/tests/Algorithms/Model/KNNModelBuilderTest.php
index 697fbbf..f1b8c31 100644
--- a/tests/Algorithms/Model/KNNModelBuilderTest.php
+++ b/tests/Algorithms/Model/KNNModelBuilderTest.php
@@ -2,82 +2,83 @@
namespace GraphAware\Reco4PHP\Tests\Algorithms\Model;
+use GraphAware\Reco4PHP\Algorithms\Model\KNNModelBuilder;
+use GraphAware\Reco4PHP\Algorithms\Model\Rating;
use GraphAware\Reco4PHP\Algorithms\Similarity\CosineSimilarity;
use GraphAware\Reco4PHP\Common\ObjectSet;
-use GraphAware\Reco4PHP\Algorithms\Model\Rating;
use GraphAware\Reco4PHP\Tests\Helper\FakeNode;
-use GraphAware\Reco4PHP\Algorithms\Model\KNNModelBuilder;
+use PHPUnit\Framework\TestCase;
/**
* @group algorithms
* @group knn
*/
-class KNNModelBuilderTest extends \PHPUnit_Framework_TestCase
+class KNNModelBuilderTest extends TestCase
{
- public function testCreateVectors()
+ public function testCreateVectors(): void
{
- $instance = new KNNModelBuilder();
+ $instance = new KNNModelBuilder(new CosineSimilarity());
$source = new ObjectSet(Rating::class);
$destination = new ObjectSet(Rating::class);
- $node1 = new FakeNode(1);
- $node2 = new FakeNode(2);
- $node3 = new FakeNode(3);
- $node4 = new FakeNode(4);
+ $node1 = FakeNode::createDummy(1);
+ $node2 = FakeNode::createDummy(2);
+ $node3 = FakeNode::createDummy(3);
+ $node4 = FakeNode::createDummy(4);
- $source->add(new Rating(1, $node1->identity()));
- $source->add(new Rating(1, $node3->identity()));
+ $source->add(new Rating(1, $node1->getId()));
+ $source->add(new Rating(1, $node3->getId()));
- $destination->add(new Rating(1, $node2->identity()));
- $destination->add(new Rating(1, $node4->identity()));
+ $destination->add(new Rating(1, $node2->getId()));
+ $destination->add(new Rating(1, $node4->getId()));
$vectors = $instance->createVectors($source, $destination);
$xVector = $vectors[0];
$yVector = $vectors[1];
- $this->assertEquals(array(1,0,1,0), $xVector);
- $this->assertEquals(array(0,1,0,1), $yVector);
+ $this->assertEquals([1, 0, 1, 0], $xVector);
+ $this->assertEquals([0, 1, 0, 1], $yVector);
}
- public function testComputeSimilarity()
+ public function testComputeSimilarity(): void
{
- $instance = new KNNModelBuilder(null, new CosineSimilarity());
+ $instance = new KNNModelBuilder(new CosineSimilarity());
$source = new ObjectSet(Rating::class);
$destination = new ObjectSet(Rating::class);
- $node1 = new FakeNode(1);
- $node2 = new FakeNode(2);
- $node3 = new FakeNode(3);
- $node4 = new FakeNode(4);
+ $node1 = FakeNode::createDummy(1);
+ $node2 = FakeNode::createDummy(2);
+ $node3 = FakeNode::createDummy(3);
+ $node4 = FakeNode::createDummy(4);
- $source->add(new Rating(1, $node1->identity()));
- $source->add(new Rating(1, $node3->identity()));
+ $source->add(new Rating(1, $node1->getId()));
+ $source->add(new Rating(1, $node3->getId()));
- $destination->add(new Rating(1, $node2->identity()));
- $destination->add(new Rating(1, $node4->identity()));
+ $destination->add(new Rating(1, $node2->getId()));
+ $destination->add(new Rating(1, $node4->getId()));
$similarity = $instance->computeSimilarity($source, $destination);
$this->assertEquals(0.0, $similarity);
}
- public function testComputeSimilarity2()
+ public function testComputeSimilarity2(): void
{
- $instance = new KNNModelBuilder(null, new CosineSimilarity());
+ $instance = new KNNModelBuilder(new CosineSimilarity());
$source = new ObjectSet(Rating::class);
$destination = new ObjectSet(Rating::class);
- $node1 = new FakeNode(1);
- $node2 = new FakeNode(2);
- $node3 = new FakeNode(3);
- $node4 = new FakeNode(4);
- $node5 = new FakeNode(5);
+ $node1 = FakeNode::createDummy(1);
+ $node2 = FakeNode::createDummy(2);
+ $node3 = FakeNode::createDummy(3);
+ $node4 = FakeNode::createDummy(4);
+ $node5 = FakeNode::createDummy(5);
- $source->add(new Rating(1, $node1->identity()));
- $source->add(new Rating(3, $node4->identity()));
+ $source->add(new Rating(1, $node1->getId()));
+ $source->add(new Rating(3, $node4->getId()));
- $destination->add(new Rating(1, $node2->identity()));
- $destination->add(new Rating(2, $node4->identity()));
- $destination->add(new Rating(5, $node5->identity()));
+ $destination->add(new Rating(1, $node2->getId()));
+ $destination->add(new Rating(2, $node4->getId()));
+ $destination->add(new Rating(5, $node5->getId()));
$similarity = $instance->computeSimilarity($source, $destination);
$this->assertTrue($similarity >= 0.34641016 && $similarity <= 0.346410161514);
}
-}
\ No newline at end of file
+}
diff --git a/tests/Config/SimpleConfigUnitTest.php b/tests/Config/SimpleConfigUnitTest.php
index 2ec2f9f..a376a00 100644
--- a/tests/Config/SimpleConfigUnitTest.php
+++ b/tests/Config/SimpleConfigUnitTest.php
@@ -4,9 +4,10 @@
use GraphAware\Reco4PHP\Config\SimpleConfig;
use GraphAware\Reco4PHP\Tests\Helper\FakeNode;
-use GraphAware\Common\Type\Node;
+use Laudis\Neo4j\Types\Node;
+use PHPUnit\Framework\TestCase;
-class SimpleConfigUnitTest extends \PHPUnit_Framework_TestCase
+class SimpleConfigUnitTest extends TestCase
{
public function testDefault()
{
@@ -31,4 +32,4 @@ public function testConfigExtendsKeyValue()
$this->assertEquals('june', $config->get('month'));
$this->assertInstanceOf(Node::class, $config->get('obj'));
}
-}
\ No newline at end of file
+}
diff --git a/tests/Context/ContextUnitTest.php b/tests/Context/ContextUnitTest.php
index b316bb6..2e943ad 100644
--- a/tests/Context/ContextUnitTest.php
+++ b/tests/Context/ContextUnitTest.php
@@ -2,11 +2,12 @@
namespace GraphAware\Reco4PHP\Tests\Context;
+use GraphAware\Reco4PHP\Config\Config;
use GraphAware\Reco4PHP\Context\SimpleContext;
use GraphAware\Reco4PHP\Tests\Helper\FakeNode;
-use GraphAware\Reco4PHP\Config\Config;
+use PHPUnit\Framework\TestCase;
-class ContextUnitTest extends \PHPUnit_Framework_TestCase
+class ContextUnitTest extends TestCase
{
public function testDefault()
{
@@ -15,4 +16,3 @@ public function testDefault()
$this->assertInstanceOf(Config::class, $context->config());
}
}
-
diff --git a/tests/Engine/OverrideDiscoveryEngine.php b/tests/Engine/OverrideDiscoveryEngine.php
index 0ef6876..cd00288 100644
--- a/tests/Engine/OverrideDiscoveryEngine.php
+++ b/tests/Engine/OverrideDiscoveryEngine.php
@@ -11,46 +11,37 @@
namespace GraphAware\Reco4PHP\Tests\Engine;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Cypher\StatementInterface;
-use GraphAware\Common\Result\Record;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Context\Context;
-use GraphAware\Reco4PHP\Result\SingleScore;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\Node;
class OverrideDiscoveryEngine extends TestDiscoveryEngine
{
- public function discoveryQuery(Node $input, Context $context) : StatementInterface
+ public function discoveryQuery(Node $input, Context $context): Statement
{
- $query = "MATCH (n) WHERE id(n) <> {input}
- RETURN n LIMIT {limit}";
+ $query = 'MATCH (n) WHERE id(n) <> $input
+ RETURN n LIMIT $limit';
- return Statement::create($query, ['input' => $input->identity(), 'limit' => 300]);
+ return Statement::create($query, ['input' => $input->getId(), 'limit' => 300]);
}
- public function buildScore(Node $input, Node $item, Record $record, Context $context) : SingleScore
+ public function idParamName(): string
{
- return parent::buildScore($input, $item, $record, $context);
+ return 'source';
}
- public function idParamName() : string
+ public function recoResultName(): string
{
- return "source";
+ return 'recommendation';
}
- public function recoResultName() : string
+ public function scoreResultName(): string
{
- return "recommendation";
+ return 'rate';
}
- public function scoreResultName() : string
- {
- return "rate";
- }
-
- public function defaultScore() : float
+ public function defaultScore(): float
{
return 10;
}
-
-}
\ No newline at end of file
+}
diff --git a/tests/Engine/RecommendationEngineTest.php b/tests/Engine/RecommendationEngineTest.php
index b54ae21..28f07b9 100644
--- a/tests/Engine/RecommendationEngineTest.php
+++ b/tests/Engine/RecommendationEngineTest.php
@@ -3,11 +3,12 @@
namespace GraphAware\Reco4PHP\Tests\Engine;
use GraphAware\Reco4PHP\Engine\BaseRecommendationEngine;
+use PHPUnit\Framework\TestCase;
-class RecommendationEngineTest extends \PHPUnit_Framework_TestCase
+class RecommendationEngineTest extends TestCase
{
- public function testWiring()
+ public function testWiring(): void
{
$stub = $this->getMockForAbstractClass(BaseRecommendationEngine::class);
}
-}
\ No newline at end of file
+}
diff --git a/tests/Engine/SingleDiscoveryEngineTest.php b/tests/Engine/SingleDiscoveryEngineTest.php
index 95045ff..16fab0a 100644
--- a/tests/Engine/SingleDiscoveryEngineTest.php
+++ b/tests/Engine/SingleDiscoveryEngineTest.php
@@ -11,48 +11,49 @@
namespace GraphAware\Reco4PHP\Tests\Engine;
-use GraphAware\Common\Cypher\Statement;
use GraphAware\Reco4PHP\Config\SimpleConfig;
use GraphAware\Reco4PHP\Context\SimpleContext;
use GraphAware\Reco4PHP\Engine\SingleDiscoveryEngine;
use GraphAware\Reco4PHP\Tests\Helper\FakeNode;
+use Laudis\Neo4j\Databags\Statement;
+use PHPUnit\Framework\TestCase;
/**
* @group engine
*/
-class SingleDiscoveryEngineTest extends \PHPUnit_Framework_TestCase
+class SingleDiscoveryEngineTest extends TestCase
{
- public function testInit()
+ public function testInit(): void
{
$engine = new TestDiscoveryEngine();
$this->assertInstanceOf(SingleDiscoveryEngine::class, $engine);
$input = FakeNode::createDummy();
$this->assertInstanceOf(Statement::class, $engine->discoveryQuery($input, new SimpleContext()));
- $this->assertEquals("MATCH (n) WHERE id(n) <> {inputId} RETURN n", $engine->discoveryQuery($input, new SimpleContext())->text());
- $this->assertEquals("score", $engine->scoreResultName());
- $this->assertEquals("reco", $engine->recoResultName());
+ $this->assertEquals('MATCH (n) WHERE id(n) <> $inputId RETURN n', $engine->discoveryQuery($input, new SimpleContext())->getText());
+ $this->assertEquals('score', $engine->scoreResultName());
+ $this->assertEquals('reco', $engine->recoResultName());
$this->assertEquals(1, $engine->defaultScore());
- $this->assertEquals("test_discovery", $engine->name());
+ $this->assertEquals('test_discovery', $engine->name());
}
- public function testParametersBuilding()
+ public function testParametersBuilding(): void
{
$engine = new TestDiscoveryEngine();
$input = FakeNode::createDummy();
- $this->assertEquals($input->identity(), $engine->discoveryQuery($input, new SimpleContext())->parameters()['inputId']);
- $this->assertCount(1, $engine->discoveryQuery($input, new SimpleContext())->parameters());
+ $this->assertEquals($input->getId(), $engine->discoveryQuery($input, new SimpleContext())->getParameters()['inputId']);
+ $this->assertCount(1, $engine->discoveryQuery($input, new SimpleContext())->getParameters());
}
- public function testOverride()
+ public function testOverride(): void
{
$engine = new OverrideDiscoveryEngine();
$input = FakeNode::createDummy();
$context = new SimpleContext(new SimpleConfig());
- $this->assertCount(2, $engine->discoveryQuery($input, $context)->parameters());
- $this->assertEquals($input->identity(), $engine->discoveryQuery($input, $context)->parameters()['input']);
- $this->assertEquals("recommendation", $engine->recoResultName());
- $this->assertEquals("rate", $engine->scoreResultName());
- $this->assertEquals("source", $engine->idParamName());
+ $this->assertCount(2, $engine->discoveryQuery($input, $context)->getParameters());
+ $this->assertEquals($input->getId(), $engine->discoveryQuery($input, $context)->getParameters()['input']);
+ $this->assertEquals('recommendation', $engine->recoResultName());
+ $this->assertEquals('rate', $engine->scoreResultName());
+ $this->assertEquals('source', $engine->idParamName());
$this->assertEquals(10, $engine->defaultScore());
}
-}
\ No newline at end of file
+}
diff --git a/tests/Engine/TestDiscoveryEngine.php b/tests/Engine/TestDiscoveryEngine.php
index 652368f..b74739d 100644
--- a/tests/Engine/TestDiscoveryEngine.php
+++ b/tests/Engine/TestDiscoveryEngine.php
@@ -11,24 +11,22 @@
namespace GraphAware\Reco4PHP\Tests\Engine;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Cypher\StatementInterface;
use GraphAware\Reco4PHP\Context\Context;
use GraphAware\Reco4PHP\Engine\SingleDiscoveryEngine;
-use GraphAware\Common\Type\Node;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\Node;
class TestDiscoveryEngine extends SingleDiscoveryEngine
{
- public function discoveryQuery(Node $input, Context $context) : StatementInterface
+ public function discoveryQuery(Node $input, Context $context): Statement
{
- $query = "MATCH (n) WHERE id(n) <> {inputId} RETURN n";
+ $query = 'MATCH (n) WHERE id(n) <> $inputId RETURN n';
- return Statement::create($query, ['inputId' => $input->identity()]);
+ return Statement::create($query, ['inputId' => $input->getId()]);
}
- public function name() : string
+ public function name(): string
{
- return "test_discovery";
+ return 'test_discovery';
}
-
-}
\ No newline at end of file
+}
diff --git a/tests/Example/Discovery/FromSameGenreILike.php b/tests/Example/Discovery/FromSameGenreILike.php
index 9cf7dbb..7d13b37 100644
--- a/tests/Example/Discovery/FromSameGenreILike.php
+++ b/tests/Example/Discovery/FromSameGenreILike.php
@@ -2,21 +2,21 @@
namespace GraphAware\Reco4PHP\Tests\Example\Discovery;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Context\Context;
use GraphAware\Reco4PHP\Engine\SingleDiscoveryEngine;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\Node;
class FromSameGenreILike extends SingleDiscoveryEngine
{
- public function name()
+ public function name(): string
{
return 'from_genre_i_like';
}
- public function discoveryQuery(Node $input, Context $context)
+ public function discoveryQuery(Node $input, Context $context): Statement
{
- $query = 'MATCH (input) WHERE id(input) = {id}
+ $query = 'MATCH (input) WHERE id(input) = $id
MATCH (input)-[r:RATED]->(movie)-[:HAS_GENRE]->(genre)
WITH distinct genre, sum(r.rating) as score
ORDER BY score DESC
@@ -25,7 +25,6 @@ public function discoveryQuery(Node $input, Context $context)
RETURN reco
LIMIT 200';
- return Statement::create($query, ['id' => $input->identity()]);
+ return Statement::create($query, ['id' => $input->getId()]);
}
-
-}
\ No newline at end of file
+}
diff --git a/tests/Example/Discovery/RatedByOthers.php b/tests/Example/Discovery/RatedByOthers.php
index 963907b..18c0ec2 100644
--- a/tests/Example/Discovery/RatedByOthers.php
+++ b/tests/Example/Discovery/RatedByOthers.php
@@ -2,28 +2,26 @@
namespace GraphAware\Reco4PHP\Tests\Example\Discovery;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Context\Context;
use GraphAware\Reco4PHP\Engine\SingleDiscoveryEngine;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\Node;
class RatedByOthers extends SingleDiscoveryEngine
{
- public function discoveryQuery(Node $input, Context $context)
+ public function discoveryQuery(Node $input, Context $context): Statement
{
- $query = 'MATCH (input:User) WHERE id(input) = {id}
+ $query = 'MATCH (input:User) WHERE id(input) = $id
MATCH (input)-[:RATED]->(m)<-[:RATED]-(o)
WITH distinct o
MATCH (o)-[:RATED]->(reco)
RETURN distinct reco LIMIT 500';
- return Statement::create($query, ['id' => $input->identity()]);
+ return Statement::create($query, ['id' => $input->getId()]);
}
-
- public function name()
+ public function name(): string
{
- return "rated_by_others";
+ return 'rated_by_others';
}
-
-}
\ No newline at end of file
+}
diff --git a/tests/Example/ExampleRecommendationEngine.php b/tests/Example/ExampleRecommendationEngine.php
index 7b78326..fc94944 100644
--- a/tests/Example/ExampleRecommendationEngine.php
+++ b/tests/Example/ExampleRecommendationEngine.php
@@ -4,44 +4,44 @@
use GraphAware\Reco4PHP\Engine\BaseRecommendationEngine;
use GraphAware\Reco4PHP\Tests\Example\Discovery\FromSameGenreILike;
+use GraphAware\Reco4PHP\Tests\Example\Discovery\RatedByOthers;
use GraphAware\Reco4PHP\Tests\Example\Filter\AlreadyRatedBlackList;
use GraphAware\Reco4PHP\Tests\Example\Filter\ExcludeOldMovies;
use GraphAware\Reco4PHP\Tests\Example\PostProcessing\RewardWellRated;
-use GraphAware\Reco4PHP\Tests\Example\Discovery\RatedByOthers;
class ExampleRecommendationEngine extends BaseRecommendationEngine
{
- public function name()
+ public function name(): string
{
- return "example";
+ return 'user_movie_reco';
}
- public function discoveryEngines()
+ public function discoveryEngines(): array
{
- return array(
+ return [
new RatedByOthers(),
- new FromSameGenreILike()
- );
+ new FromSameGenreILike(),
+ ];
}
- public function blacklistBuilders()
+ public function blacklistBuilders(): array
{
- return array(
- new AlreadyRatedBlackList()
- );
+ return [
+ new AlreadyRatedBlackList(),
+ ];
}
- public function postProcessors()
+ public function postProcessors(): array
{
- return array(
- new RewardWellRated()
- );
+ return [
+ new RewardWellRated(),
+ ];
}
- public function filters()
+ public function filters(): array
{
- return array(
- new ExcludeOldMovies()
- );
+ return [
+ new ExcludeOldMovies(),
+ ];
}
-}
\ No newline at end of file
+}
diff --git a/tests/Example/ExampleRecommenderService.php b/tests/Example/ExampleRecommenderService.php
index f2e78ec..b7250d9 100644
--- a/tests/Example/ExampleRecommenderService.php
+++ b/tests/Example/ExampleRecommenderService.php
@@ -4,33 +4,26 @@
use GraphAware\Reco4PHP\Context\SimpleContext;
use GraphAware\Reco4PHP\RecommenderService;
+use GraphAware\Reco4PHP\Result\Recommendations;
class ExampleRecommenderService
{
- /**
- * @var \GraphAware\Reco4PHP\RecommenderService
- */
- protected $service;
+ protected RecommenderService $service;
/**
* ExampleRecommenderService constructor.
- * @param string $databaseUri
*/
- public function __construct($databaseUri)
+ public function __construct(string $databaseUri)
{
$this->service = RecommenderService::create($databaseUri);
$this->service->registerRecommendationEngine(new ExampleRecommendationEngine());
}
- /**
- * @param int $id
- * @return \GraphAware\Reco4PHP\Result\Recommendations
- */
- public function recommendMovieForUserWithId($id)
+ public function recommendMovieForUserWithId(int $id): Recommendations
{
$input = $this->service->findInputBy('User', 'id', $id);
- $recommendationEngine = $this->service->getRecommender("user_movie_reco");
+ $recommendationEngine = $this->service->getRecommender('user_movie_reco');
return $recommendationEngine->recommend($input, new SimpleContext());
}
-}
\ No newline at end of file
+}
diff --git a/tests/Example/Filter/AlreadyRatedBlacklist.php b/tests/Example/Filter/AlreadyRatedBlackList.php
similarity index 50%
rename from tests/Example/Filter/AlreadyRatedBlacklist.php
rename to tests/Example/Filter/AlreadyRatedBlackList.php
index 47c28e9..d1217bb 100644
--- a/tests/Example/Filter/AlreadyRatedBlacklist.php
+++ b/tests/Example/Filter/AlreadyRatedBlackList.php
@@ -2,23 +2,23 @@
namespace GraphAware\Reco4PHP\Tests\Example\Filter;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Filter\BaseBlacklistBuilder;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\Node;
class AlreadyRatedBlackList extends BaseBlacklistBuilder
{
- public function blacklistQuery(Node $input)
+ public function blacklistQuery(Node $input): Statement
{
- $query = 'MATCH (input) WHERE id(input) = {inputId}
+ $query = 'MATCH (input) WHERE id(input) = $inputId
MATCH (input)-[:RATED]->(movie)
RETURN movie as item';
- return Statement::create($query, ['inputId' => $input->identity()]);
+ return Statement::create($query, ['inputId' => $input->getId()]);
}
- public function name()
+ public function name(): string
{
return 'already_rated';
}
-}
\ No newline at end of file
+}
diff --git a/tests/Example/Filter/ExcludeOldMovies.php b/tests/Example/Filter/ExcludeOldMovies.php
index 6f3b0d0..04ca250 100644
--- a/tests/Example/Filter/ExcludeOldMovies.php
+++ b/tests/Example/Filter/ExcludeOldMovies.php
@@ -2,19 +2,19 @@
namespace GraphAware\Reco4PHP\Tests\Example\Filter;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Filter\Filter;
+use Laudis\Neo4j\Types\Node;
class ExcludeOldMovies implements Filter
{
- public function doInclude(Node $input, Node $item)
+ public function doInclude(Node $input, Node $item): bool
{
- $title = $item->value("title");
+ $title = (string) $item->getProperty('title');
preg_match('/(?:\()\d+(?:\))/', $title, $matches);
if (isset($matches[0])) {
- $y = str_replace('(','',$matches[0]);
- $y = str_replace(')','', $y);
+ $y = str_replace('(', '', $matches[0]);
+ $y = str_replace(')', '', $y);
$year = (int) $y;
if ($year < 1999) {
return false;
@@ -25,5 +25,4 @@ public function doInclude(Node $input, Node $item)
return false;
}
-
-}
\ No newline at end of file
+}
diff --git a/tests/Example/PostProcessing/RewardWellRated.php b/tests/Example/PostProcessing/RewardWellRated.php
index b009ba3..6e1589e 100644
--- a/tests/Example/PostProcessing/RewardWellRated.php
+++ b/tests/Example/PostProcessing/RewardWellRated.php
@@ -2,39 +2,38 @@
namespace GraphAware\Reco4PHP\Tests\Example\PostProcessing;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Result\Record;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Post\RecommendationSetPostProcessor;
use GraphAware\Reco4PHP\Result\Recommendation;
use GraphAware\Reco4PHP\Result\Recommendations;
use GraphAware\Reco4PHP\Result\SingleScore;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\CypherMap;
+use Laudis\Neo4j\Types\Node;
class RewardWellRated extends RecommendationSetPostProcessor
{
- public function buildQuery(Node $input, Recommendations $recommendations)
+ public function buildQuery(Node $input, Recommendations $recommendations): Statement
{
- $query = 'UNWIND {ids} as id
+ $query = 'UNWIND $ids as id
MATCH (n) WHERE id(n) = id
MATCH (n)<-[r:RATED]-(u)
RETURN id(n) as id, sum(r.rating) as score';
$ids = [];
foreach ($recommendations->getItems() as $item) {
- $ids[] = $item->item()->identity();
+ $ids[] = $item->item()->getId();
}
return Statement::create($query, ['ids' => $ids]);
}
- public function postProcess(Node $input, Recommendation $recommendation, Record $record)
+ public function postProcess(Node $input, Recommendation $recommendation, CypherMap $result): void
{
- $recommendation->addScore($this->name(), new SingleScore($record->get('score'), 'total_ratings_relationships'));
+ $recommendation->addScore($this->name(), new SingleScore((float) $result->get('score'), 'total_ratings_relationships'));
}
- public function name()
+ public function name(): string
{
- return "reward_well_rated";
+ return 'reward_well_rated';
}
-
-}
\ No newline at end of file
+}
diff --git a/tests/Helper/FakeNode.php b/tests/Helper/FakeNode.php
index db4c2c2..dfdff20 100644
--- a/tests/Helper/FakeNode.php
+++ b/tests/Helper/FakeNode.php
@@ -2,81 +2,16 @@
namespace GraphAware\Reco4PHP\Tests\Helper;
-use GraphAware\Common\Type\Node;
+use Laudis\Neo4j\Types\CypherList;
+use Laudis\Neo4j\Types\CypherMap;
+use Laudis\Neo4j\Types\Node;
-class FakeNode implements Node
+class FakeNode
{
- protected $identity;
-
- protected $labels = [];
-
- function __get($name)
- {
- return null;
- }
-
-
- public function __construct($identity, array $labels = array())
- {
- $this->identity = $identity;
- $this->labels = $labels;
- }
-
- public static function createDummy($id = null)
- {
- $identity = null !== $id ? $id : rand(0,1000);
- return new self($identity, array("Dummy"));
- }
-
- function identity()
- {
- return $this->identity;
- }
-
- function labels()
- {
- return $this->labels;
- }
-
- function hasLabel($label)
- {
- return in_array($label, $this->labels);
- }
-
- public function keys()
- {
- // TODO: Implement keys() method.
- }
-
- public function containsKey($key)
- {
- // TODO: Implement containsKey() method.
- }
-
- public function get($key)
- {
- // TODO: Implement get() method.
- }
-
- public function values()
+ public static function createDummy(?int $id = null)
{
- // TODO: Implement values() method.
- }
-
- public function asArray()
- {
- // TODO: Implement asArray() method.
- }
-
- public function hasValue($key)
- {
- // TODO: Implement hasValue() method.
- }
+ $id = $id ?? rand(0, 1000);
- public function value($key, $default = null)
- {
- // TODO: Implement value() method.
+ return new Node($id, new CypherList(['Dummy']), new CypherMap([]));
}
-
-
-}
\ No newline at end of file
+}
diff --git a/tests/Integration/Model/FriendsEngine.php b/tests/Integration/Model/FriendsEngine.php
index 01d145f..0a79b1d 100644
--- a/tests/Integration/Model/FriendsEngine.php
+++ b/tests/Integration/Model/FriendsEngine.php
@@ -2,27 +2,25 @@
namespace GraphAware\Reco4PHP\Tests\Integration\Model;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Context\Context;
use GraphAware\Reco4PHP\Engine\SingleDiscoveryEngine;
-use GraphAware\Common\Cypher\StatementInterface;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\Node;
class FriendsEngine extends SingleDiscoveryEngine
{
- public function name() : string
+ public function name(): string
{
return 'friends_discovery';
}
- public function discoveryQuery(Node $input, Context $context) : StatementInterface
+ public function discoveryQuery(Node $input, Context $context): Statement
{
- $query = 'MATCH (n) WHERE id(n) = {id}
+ $query = 'MATCH (n) WHERE id(n) = $id
MATCH (n)-[:FRIEND]->(friend)-[:FRIEND]->(reco)
WHERE NOT (n)-[:FRIEND]->(reco)
RETURN reco, count(*) as score';
- return Statement::prepare($query, ['id' => $input->identity()]);
+ return Statement::create($query, ['id' => $input->getId()]);
}
-
-}
\ No newline at end of file
+}
diff --git a/tests/Integration/Model/RecoEngine.php b/tests/Integration/Model/RecoEngine.php
index dcb2058..ea2e82b 100644
--- a/tests/Integration/Model/RecoEngine.php
+++ b/tests/Integration/Model/RecoEngine.php
@@ -3,28 +3,25 @@
namespace GraphAware\Reco4PHP\Tests\Integration\Model;
use GraphAware\Reco4PHP\Engine\BaseRecommendationEngine;
-use GraphAware\Reco4PHP\Filter\ExcludeSelf;
class RecoEngine extends BaseRecommendationEngine
{
- public function name() : string
+ public function name(): string
{
return 'find_friends';
}
- public function discoveryEngines() : array
+ public function discoveryEngines(): array
{
- return array(
- new FriendsEngine()
- );
+ return [
+ new FriendsEngine(),
+ ];
}
- public function blacklistBuilders() : array
+ public function blacklistBuilders(): array
{
- return array(
- new SimpleBlacklist()
- );
+ return [
+ new SimpleBlacklist(),
+ ];
}
-
-
-}
\ No newline at end of file
+}
diff --git a/tests/Integration/Model/SimpleBlacklist.php b/tests/Integration/Model/SimpleBlacklist.php
index 8b6c778..474abd1 100644
--- a/tests/Integration/Model/SimpleBlacklist.php
+++ b/tests/Integration/Model/SimpleBlacklist.php
@@ -2,22 +2,21 @@
namespace GraphAware\Reco4PHP\Tests\Integration\Model;
-use GraphAware\Common\Cypher\Statement;
-use GraphAware\Common\Type\Node;
use GraphAware\Reco4PHP\Filter\BaseBlacklistBuilder;
+use Laudis\Neo4j\Databags\Statement;
+use Laudis\Neo4j\Types\Node;
class SimpleBlacklist extends BaseBlacklistBuilder
{
- public function blacklistQuery(Node $input)
+ public function blacklistQuery(Node $input): Statement
{
$query = 'MATCH (n) WHERE n.name = "Zoe" RETURN n as item';
- return Statement::prepare($query, ['id' => $input->identity()]);
+ return Statement::create($query);
}
- public function name()
+ public function name(): string
{
return 'simple_blacklist';
}
-
-}
\ No newline at end of file
+}
diff --git a/tests/Integration/SimpleFriendsRecoEngineTest.php b/tests/Integration/SimpleFriendsRecoEngineTest.php
index 057ead7..03c2f9d 100644
--- a/tests/Integration/SimpleFriendsRecoEngineTest.php
+++ b/tests/Integration/SimpleFriendsRecoEngineTest.php
@@ -2,44 +2,36 @@
namespace GraphAware\Reco4PHP\Tests\Integration;
-use GraphAware\Neo4j\Client\ClientBuilder;
use GraphAware\Reco4PHP\Context\SimpleContext;
-use GraphAware\Reco4PHP\Tests\Integration\Model\RecoEngine;
+use GraphAware\Reco4PHP\Persistence\DatabaseService;
use GraphAware\Reco4PHP\RecommenderService;
-use GraphAware\Neo4j\Client\Client;
+use GraphAware\Reco4PHP\Tests\Integration\Model\RecoEngine;
+use Laudis\Neo4j\Types\Node;
+use PHPUnit\Framework\TestCase;
/**
- * Class SimpleFriendsRecoEngineTest
- * @package GraphAware\Reco4PHP\Tests\Integration
+ * Class SimpleFriendsRecoEngineTest.
*
* @group integration
*/
-class SimpleFriendsRecoEngineTest extends \PHPUnit_Framework_TestCase
+class SimpleFriendsRecoEngineTest extends TestCase
{
- /**
- * @var RecommenderService
- */
- protected $recoService;
+ protected RecommenderService $recoService;
- /**
- * @var Client
- */
- protected $client;
+ protected DatabaseService $databaseService;
/**
* @setUp()
*/
- public function setUp()
+ public function setUp(): void
{
- $this->recoService = RecommenderService::create('http://localhost:7474');
+ $this->databaseService = new DatabaseService('bolt://localhost:7687');
+ $this->recoService = new RecommenderService($this->databaseService);
$this->recoService->registerRecommendationEngine(new RecoEngine());
- $this->client = ClientBuilder::create()
- ->addConnection('default', 'http://localhost:7474')
- ->build();
$this->createGraph();
}
- public function testRecoForJohn()
+ public function testRecoForJohn(): void
{
$engine = $this->recoService->getRecommender('find_friends');
$john = $this->getUserNode('John');
@@ -47,23 +39,23 @@ public function testRecoForJohn()
$recommendations->sort();
$this->assertEquals(2, $recommendations->size());
$this->assertNull($recommendations->getItemBy('name', 'John'));
- $recoForMarc = $recommendations->getItemBy('name','marc');
+ $recoForMarc = $recommendations->getItemBy('name', 'marc');
$this->assertEquals(1, $recoForMarc->totalScore());
$recoForBill = $recommendations->getItemBy('name', 'Bill');
$this->assertEquals(2, $recoForBill->totalScore());
}
- private function getUserNode($name)
+ private function getUserNode(string $name): Node
{
- $q = 'MATCH (n:User) WHERE n.name = {name} RETURN n';
- $result = $this->client->run($q, ['name' => $name]);
+ $q = 'MATCH (n:User) WHERE n.name = $name RETURN n';
+ $results = $this->databaseService->getDriver()->run($q, ['name' => $name]);
- return $result->firstRecord()->get('n');
+ return $results->first()->get('n');
}
- private function createGraph()
+ private function createGraph(): void
{
- $this->client->run('MATCH (n) DETACH DELETE n');
+ $this->databaseService->getDriver()->run('MATCH (n) DETACH DELETE n');
$query = 'CREATE (john:User {name:"John"})-[:FRIEND]->(judith:User {name:"Judith"}),
(john)-[:FRIEND]->(paul:User {name:"paul"}),
(paul)-[:FRIEND]->(marc:User {name:"marc"}),
@@ -72,6 +64,6 @@ private function createGraph()
(judith)-[:FRIEND]->(sofia),
(john)-[:FRIEND]->(sofia),
(sofia)-[:FRIEND]->(:User {name:"Zoe"})';
- $this->client->run($query);
+ $this->databaseService->getDriver()->run($query);
}
-}
\ No newline at end of file
+}
diff --git a/tests/Result/RecommendationsListTest.php b/tests/Result/RecommendationsListTest.php
index ec2b6a3..56444a4 100644
--- a/tests/Result/RecommendationsListTest.php
+++ b/tests/Result/RecommendationsListTest.php
@@ -4,19 +4,18 @@
use GraphAware\Reco4PHP\Context\SimpleContext;
use GraphAware\Reco4PHP\Result\Recommendations;
-use GraphAware\Reco4PHP\Result\Score;
use GraphAware\Reco4PHP\Result\SingleScore;
use GraphAware\Reco4PHP\Tests\Helper\FakeNode;
+use PHPUnit\Framework\TestCase;
/**
- * Class RecommendationsListTest
- * @package GraphAware\Reco4PHP\Tests\Result
+ * Class RecommendationsListTest.
*
* @group result
*/
-class RecommendationsListTest extends \PHPUnit_Framework_TestCase
+class RecommendationsListTest extends TestCase
{
- public function testResultGetTwoScoresIfDiscoveredTwice()
+ public function testResultGetTwoScoresIfDiscoveredTwice(): void
{
$node = FakeNode::createDummy();
$list = new Recommendations(new SimpleContext());
@@ -31,7 +30,7 @@ public function testResultGetTwoScoresIfDiscoveredTwice()
$this->assertArrayHasKey('e2', $list->get(0)->getScores());
}
- public function testTotalScoreIsIncremented()
+ public function testTotalScoreIsIncremented(): void
{
$node = FakeNode::createDummy();
$list = new Recommendations(new SimpleContext());
@@ -43,7 +42,7 @@ public function testTotalScoreIsIncremented()
$this->assertEquals(2, $reco->totalScore());
}
- public function testReasons()
+ public function testReasons(): void
{
$node = FakeNode::createDummy();
$list = new Recommendations(new SimpleContext());
@@ -59,4 +58,4 @@ public function testReasons()
$this->assertArrayHasKey('disc3', $reco->getScores());
$this->assertCount(10, $reco->getScore('disc3')->getScores());
}
-}
\ No newline at end of file
+}