{"id":13529983,"url":"https://github.com/graphaware/reco4php","last_synced_at":"2025-04-05T09:08:56.930Z","repository":{"id":60774538,"uuid":"49773470","full_name":"graphaware/reco4php","owner":"graphaware","description":"Neo4j based Recommendation Engine Framework for PHP","archived":false,"fork":false,"pushed_at":"2022-10-23T16:51:55.000Z","size":2470,"stargazers_count":131,"open_issues_count":3,"forks_count":22,"subscribers_count":31,"default_branch":"master","last_synced_at":"2024-04-25T05:41:11.274Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/graphaware.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-01-16T13:01:01.000Z","updated_at":"2024-01-26T17:41:46.000Z","dependencies_parsed_at":"2022-10-04T15:37:21.044Z","dependency_job_id":null,"html_url":"https://github.com/graphaware/reco4php","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphaware%2Freco4php","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphaware%2Freco4php/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphaware%2Freco4php/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphaware%2Freco4php/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/graphaware","download_url":"https://codeload.github.com/graphaware/reco4php/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247312081,"owners_count":20918344,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T07:00:41.718Z","updated_at":"2025-04-05T09:08:56.280Z","avatar_url":"https://github.com/graphaware.png","language":"PHP","readme":"# GraphAware Reco4PHP\n\n## Neo4j based Recommendation Engine Framework for PHP\n\nGraphAware Reco4PHP is a library for building complex recommendation engines atop Neo4j.\n\n[![Build Status](https://travis-ci.org/graphaware/neo4j-php-client.svg)](https://travis-ci.org/graphaware/reco4php)\n\nFeatures:\n\n* Clean and flexible design\n* Built-in algorithms and functions\n* Ability to measure recommendation quality\n* Built-in Cypher transaction management\n\nRequirements:\n\n* PHP7.0+\n* Neo4j 2.2.6+ (Neo4j 3.0+ recommended)\n\nThe library imposes a specific recommendation engine architecture, which has emerged from our experience building recommendation\nengines and solves the architectural challenge to run recommendation engines remotely via Cypher.\nIn return it handles all the plumbing so that you only write the recommendation business logic specific to your use case.\n\n### Recommendation Engine Architecture\n\n#### Discovery Engines and Recommendations\n\nThe purpose of a recommendation engine is to `recommend` something, should be users you should follow, products you should buy,\narticles you should read.\n\nThe first part in the recommendation process is to find items to recommend, it is called the `discovery` process.\n\nIn Reco4PHP, a `DiscoveryEngine` is responsible for discovering items to recommend in one possible way.\n\nGenerally, recommender systems will contains multiple discovery engines, if you would write the `who you should follow on github` recommendation engine,\nyou might end up with the non-exhaustive list of `Discovery Engines` :\n\n* Find people that contributed on the same repositories than me\n* Find people that `FOLLOWS` the same people I follow\n* Find people that `WATCH` the same repositories I'm watching\n* ...\n\nEach `Discovery Engine` will produce a set of `Recommendations` which contains the discovered `Item` as well as the score for this item (more below).\n\n#### Filters and BlackLists\n\nThe purpose of `Filters` is to compare the original `input` to the `discovered` item and decide whether or not this item should be recommended to the user.\nA very straightforward filter could be `ExcludeSelf` which would exclude the item if it is the same node as the input, which can relatively happen in a densely connected graph.\n\n`BlackLists` on the other hand are a set of predefined nodes that should not be recommended to the user. An example could be to create a `BlackList` with the already purchased items\nby the user if you would recommend him products he should buy.\n\n#### PostProcessors\n\n`PostProcessors` are providing the ability to post process the recommendation after it has passed the filters and blacklisting process.\n\nFor example, if you would reward a recommended person if he/she lives in the same city than you, it wouldn't make sense to load all people from the database that live\nin this city in the discovery phase (this could be millions if you take London as an example).\n\nYou would then create a `RewardSameCity` post processor that would adapt the score of the produced recommendation if the input node and the recommended item are living in the same city.\n\n#### Summary\n\nTo summarize, a typical recommendation engine will be a set of :\n\n* one or more `Discovery Engines`\n* zero or more `Fitlers` and `BlackLists`\n* zero or more `PostProcessors`\n\nLet's start it !\n\n\n#### Usage by example\n\nWe will use the small dataset available from MovieLens containing movies, users and ratings as well as genres.\n\nThe dataset is publicly available here : http://grouplens.org/datasets/movielens/. The data set to download is in the **MovieLens Latest Datasets** section and is named `ml-latest-small.zip`.\n\nOnce 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 :\n\n```\nCREATE CONSTRAINT ON (m:Movie) ASSERT m.id IS UNIQUE;\nCREATE CONSTRAINT ON (g:Genre) ASSERT g.name IS UNIQUE;\nCREATE CONSTRAINT ON (u:User) ASSERT u.id IS UNIQUE;\n```\n\n```\nLOAD CSV WITH HEADERS FROM \"file:///Users/ikwattro/dev/movielens/movies.csv\" AS row\nWITH row\nMERGE (movie:Movie {id: toInt(row.movieId)})\nON CREATE SET movie.title = row.title\nWITH movie, row\nUNWIND split(row.genres, '|') as genre\nMERGE (g:Genre {name: genre})\nMERGE (movie)-[:HAS_GENRE]-\u003e(g)\n```\n\n\n```\nUSING PERIODIC COMMIT 500\nLOAD CSV WITH HEADERS FROM \"file:///Users/ikwattro/dev/movielens/ratings.csv\" AS row\nWITH row\nMATCH (movie:Movie {id: toInt(row.movieId)})\nMERGE (user:User {id: toInt(row.userId)})\nMERGE (user)-[r:RATED]-\u003e(movie)\nON CREATE SET r.rating = toInt(row.rating), r.timestamp = toInt(row.timestamp)\n```\n\nFor the purpose of the example, we will assume we are recommending movies for the User with ID 460.\n\n\n### Installation\n\nRequire the dependency with `composer` :\n\n```bash\ncomposer require graphaware/reco4php\n```\n\n### Usage\n\n#### Discovery\n\nIn order to recommend movies people should watch, you have decided that we should find potential recommendations in the following way :\n\n* Find movies rated by people who rated the same movies than me, but that I didn't rated yet\n\nAs 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\nthe methods of the upper interfaces, here is how you would create your first discovery engine :\n\n```php\n\u003c?php\n\nnamespace GraphAware\\Reco4PHP\\Tests\\Example\\Discovery;\n\nuse GraphAware\\Common\\Cypher\\Statement;\nuse GraphAware\\Common\\Type\\Node;\nuse GraphAware\\Reco4PHP\\Context\\Context;\nuse GraphAware\\Reco4PHP\\Engine\\SingleDiscoveryEngine;\n\nclass RatedByOthers extends SingleDiscoveryEngine\n{\n    public function discoveryQuery(Node $input, Context $context)\n    {\n        $query = 'MATCH (input:User) WHERE id(input) = {id}\n        MATCH (input)-[:RATED]-\u003e(m)\u003c-[:RATED]-(o)\n        WITH distinct o\n        MATCH (o)-[:RATED]-\u003e(reco)\n        RETURN distinct reco LIMIT 500';\n\n        return Statement::create($query, ['id' =\u003e $input-\u003eidentity()]);\n    }\n\n    public function name()\n    {\n        return \"rated_by_others\";\n    }\n}\n```\n\nThe `discoveryMethod` method should return a `Statement` object containing the query for finding recommendations,\nthe `name` method should return a string describing the name of your engine (this is mostly for logging purposes).\n\nThe 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\nof the ratings and returning the most rated ones, limit the results to 500 potential recommendations.\n\n\nThe base class assumes that the recommended node will have the identifier `reco` and the score of the produced recommendation the identifier `score`. The score is not mandatory, and it will be given a default score of `1`.\n\nAll these defaults are customizable by overriding the methods from the base class (see the Customization section).\n\nThis discovery engine will then produce a set of 500 scored `Recommendation` objects that you can use in your filters or post processors.\n\n#### Filtering\n\nAs an example of a filter, we will filter the movies that were produced before the year 1999. The year is written in the movie title, so we will use a regex for extracting the year in the filter.\n\n```php\n\u003c?php\n\nnamespace GraphAware\\Reco4PHP\\Tests\\Example\\Filter;\n\nuse GraphAware\\Common\\Type\\Node;\nuse GraphAware\\Reco4PHP\\Filter\\Filter;\n\nclass ExcludeOldMovies implements Filter\n{\n    public function doInclude(Node $input, Node $item)\n    {\n        $title = $item-\u003evalue(\"title\");\n        preg_match('/(?:\\()\\d+(?:\\))/', $title, $matches);\n\n        if (isset($matches[0])) {\n            $y = str_replace('(','',$matches[0]);\n            $y = str_replace(')','', $y);\n            $year = (int) $y;\n            if ($year \u003c 1999) {\n                return false;\n            }\n\n            return true;\n        }\n\n        return false;\n    }\n}\n```\n\nThe `Filter` interfaces forces you to implement the `doInclude` method which should return a boolean. You have access to the recommended node as well as the input in the method arguments.\n\n#### Blacklist\n\nOf course we do not want to recommend movies that the current user has already rated, for this we will create a Blacklist building a set of these already rated movie nodes.\n\n```php\n\u003c?php\n\nnamespace GraphAware\\Reco4PHP\\Tests\\Example\\Filter;\n\nuse GraphAware\\Common\\Cypher\\Statement;\nuse GraphAware\\Common\\Type\\Node;\nuse GraphAware\\Reco4PHP\\Filter\\BaseBlacklistBuilder;\n\nclass AlreadyRatedBlackList extends BaseBlacklistBuilder\n{\n    public function blacklistQuery(Node $input)\n    {\n        $query = 'MATCH (input) WHERE id(input) = {inputId}\n        MATCH (input)-[:RATED]-\u003e(movie)\n        RETURN movie as item';\n\n        return Statement::create($query, ['inputId' =\u003e $input-\u003eidentity()]);\n    }\n\n    public function name()\n    {\n        return 'already_rated';\n    }\n}\n```\n\nYou really just need to add the logic for matching the nodes that should be blacklisted, the framework takes care for filtering the recommended\nnodes against the blacklists provided.\n\n#### Post Processors\n\n`Post Processors` are meant to add additional scoring to the recommended items. In our example, we could reward a produced recommendation if it has more than 10 ratings :\n\n```php\n\u003c?php\n\nnamespace GraphAware\\Reco4PHP\\Tests\\Example\\PostProcessing;\n\nuse GraphAware\\Common\\Cypher\\Statement;\nuse GraphAware\\Common\\Result\\Record;\nuse GraphAware\\Common\\Type\\Node;\nuse GraphAware\\Reco4PHP\\Post\\RecommendationSetPostProcessor;\nuse GraphAware\\Reco4PHP\\Result\\Recommendation;\nuse GraphAware\\Reco4PHP\\Result\\Recommendations;\nuse GraphAware\\Reco4PHP\\Result\\SingleScore;\n\nclass RewardWellRated extends RecommendationSetPostProcessor\n{\n    public function buildQuery(Node $input, Recommendations $recommendations)\n    {\n        $query = 'UNWIND {ids} as id\n        MATCH (n) WHERE id(n) = id\n        MATCH (n)\u003c-[r:RATED]-(u)\n        RETURN id(n) as id, sum(r.rating) as score';\n\n        $ids = [];\n        foreach ($recommendations-\u003egetItems() as $item) {\n            $ids[] = $item-\u003eitem()-\u003eidentity();\n        }\n\n        return Statement::create($query, ['ids' =\u003e $ids]);\n    }\n\n    public function postProcess(Node $input, Recommendation $recommendation, Record $record)\n    {\n        $recommendation-\u003eaddScore($this-\u003ename(), new SingleScore($record-\u003eget('score'), 'total_ratings_relationships'));\n    }\n\n    public function name()\n    {\n        return \"reward_well_rated\";\n    }\n}\n```\n\n#### Wiring all together\n\nNow that our components are created, we need to build effectively our recommendation engine :\n\n```php\n\u003c?php\n\nnamespace GraphAware\\Reco4PHP\\Tests\\Example;\n\nuse GraphAware\\Reco4PHP\\Engine\\BaseRecommendationEngine;\nuse GraphAware\\Reco4PHP\\Tests\\Example\\Filter\\AlreadyRatedBlackList;\nuse GraphAware\\Reco4PHP\\Tests\\Example\\Filter\\ExcludeOldMovies;\nuse GraphAware\\Reco4PHP\\Tests\\Example\\PostProcessing\\RewardWellRated;\nuse GraphAware\\Reco4PHP\\Tests\\Example\\Discovery\\RatedByOthers;\n\nclass ExampleRecommendationEngine extends BaseRecommendationEngine\n{\n    public function name()\n    {\n        return \"example\";\n    }\n\n    public function discoveryEngines()\n    {\n        return array(\n            new RatedByOthers()\n        );\n    }\n\n    public function blacklistBuilders()\n    {\n        return array(\n            new AlreadyRatedBlackList()\n        );\n    }\n\n    public function postProcessors()\n    {\n        return array(\n            new RewardWellRated()\n        );\n    }\n\n    public function filters()\n    {\n        return array(\n            new ExcludeOldMovies()\n        );\n    }\n}\n```\n\nAs in your recommender service, you might have multiple recommendation engines serving different recommendations, the last step is to create this service and register each `RecommendationEngine` you have created.\nYou'll need to provide also a connection to your Neo4j database, in your application this could look like this :\n\n```php\n\u003c?php\n\nnamespace GraphAware\\Reco4PHP\\Tests\\Example;\n\nuse GraphAware\\Reco4PHP\\Context\\SimpleContext;\nuse GraphAware\\Reco4PHP\\RecommenderService;\n\nclass ExampleRecommenderService\n{\n    /**\n     * @var \\GraphAware\\Reco4PHP\\RecommenderService\n     */\n    protected $service;\n\n    /**\n     * ExampleRecommenderService constructor.\n     * @param string $databaseUri\n     */\n    public function __construct($databaseUri)\n    {\n        $this-\u003eservice = RecommenderService::create($databaseUri);\n        $this-\u003eservice-\u003eregisterRecommendationEngine(new ExampleRecommendationEngine());\n    }\n\n    /**\n     * @param int $id\n     * @return \\GraphAware\\Reco4PHP\\Result\\Recommendations\n     */\n    public function recommendMovieForUserWithId($id)\n    {\n        $input = $this-\u003eservice-\u003efindInputBy('User', 'id', $id);\n        $recommendationEngine = $this-\u003eservice-\u003egetRecommender(\"user_movie_reco\");\n\n        return $recommendationEngine-\u003erecommend($input, new SimpleContext());\n    }\n}\n```\n\n#### Inspecting recommendations\n\nThe `recommend()` method on a recommendation engine will returns you a `Recommendations` object which contains a set of `Recommendation` that holds the recommended item and their score.\n\nEach score is inserted so you can easily inspect why such recommendation has been produced, example :\n\n```php\n\n$recommender = new ExampleRecommendationService(\"http://localhost:7474\");\n$recommendation = $recommender-\u003erecommendMovieForUserWithId(460);\n\nprint_r($recommendations-\u003egetItems(1));\n\nArray\n(\n    [0] =\u003e GraphAware\\Reco4PHP\\Result\\Recommendation Object\n        (\n            [item:protected] =\u003e GraphAware\\Bolt\\Result\\Type\\Node Object\n                (\n                    [identity:protected] =\u003e 13248\n                    [labels:protected] =\u003e Array\n                        (\n                            [0] =\u003e Movie\n                        )\n\n                    [properties:protected] =\u003e Array\n                        (\n                            [id] =\u003e 2571\n                            [title] =\u003e Matrix, The (1999)\n                        )\n\n                )\n\n            [scores:protected] =\u003e Array\n                (\n                    [rated_by_others] =\u003e GraphAware\\Reco4PHP\\Result\\Score Object\n                        (\n                            [score:protected] =\u003e 1067\n                            [scores:protected] =\u003e Array\n                                (\n                                    [0] =\u003e GraphAware\\Reco4PHP\\Result\\SingleScore Object\n                                        (\n                                            [score:GraphAware\\Reco4PHP\\Result\\SingleScore:private] =\u003e 1067\n                                            [reason:GraphAware\\Reco4PHP\\Result\\SingleScore:private] =\u003e\n                                        )\n\n                                )\n\n                        )\n\n                    [reward_well_rated] =\u003e GraphAware\\Reco4PHP\\Result\\Score Object\n                        (\n                            [score:protected] =\u003e 261\n                            [scores:protected] =\u003e Array\n                                (\n                                    [0] =\u003e GraphAware\\Reco4PHP\\Result\\SingleScore Object\n                                        (\n                                            [score:GraphAware\\Reco4PHP\\Result\\SingleScore:private] =\u003e 261\n                                            [reason:GraphAware\\Reco4PHP\\Result\\SingleScore:private] =\u003e\n                                        )\n\n                                )\n\n                        )\n\n                )\n\n            [totalScore:protected] =\u003e 261\n        )\n)\n```\n### License\n\nThis library is released under the Apache v2 License, please read the attached `LICENSE` file.\n\nCommercial support or custom development/extension available upon request to info@graphaware.com.\n","funding_links":[],"categories":["Development","REST API"],"sub_categories":["REST API","Other"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgraphaware%2Freco4php","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgraphaware%2Freco4php","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgraphaware%2Freco4php/lists"}