{"id":21570804,"url":"https://github.com/snowlaboratory/laravel-mirror","last_synced_at":"2025-04-10T14:20:24.743Z","repository":{"id":186833042,"uuid":"675327720","full_name":"SnowLaboratory/Laravel-Mirror","owner":"SnowLaboratory","description":"Laravel recommendation engine - take your blog to the next level!","archived":false,"fork":false,"pushed_at":"2023-09-17T02:02:04.000Z","size":187,"stargazers_count":14,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-24T12:56:23.145Z","etag":null,"topics":["content-suggest","embeddings","laravel-package","recommendation-engine","recommendation-system"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SnowLaboratory.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-08-06T15:01:07.000Z","updated_at":"2024-08-26T03:03:04.000Z","dependencies_parsed_at":"2023-08-07T23:23:09.926Z","dependency_job_id":null,"html_url":"https://github.com/SnowLaboratory/Laravel-Mirror","commit_stats":null,"previous_names":["snowlaboratory/laravel-mirror","snowlaboratory/mirror"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SnowLaboratory%2FLaravel-Mirror","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SnowLaboratory%2FLaravel-Mirror/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SnowLaboratory%2FLaravel-Mirror/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SnowLaboratory%2FLaravel-Mirror/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SnowLaboratory","download_url":"https://codeload.github.com/SnowLaboratory/Laravel-Mirror/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248233935,"owners_count":21069493,"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":["content-suggest","embeddings","laravel-package","recommendation-engine","recommendation-system"],"created_at":"2024-11-24T11:13:48.320Z","updated_at":"2025-04-10T14:20:24.721Z","avatar_url":"https://github.com/SnowLaboratory.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg width=\"472\" src=\"/art/laravel-mirror-banner.png\" alt=\"Laravel Mirror Package Logo\"\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://packagist.org/packages/snowbuilds/laravel-mirror\"\u003e\n        \u003cimg src=\"https://img.shields.io/packagist/v/snowbuilds/laravel-mirror.svg?style=flat-square\" alt=\"Latest Version on Packagist\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://packagist.org/packages/snowbuilds/laravel-mirror\"\u003e\n        \u003cimg src=\"https://img.shields.io/packagist/dt/snowbuilds/laravel-mirror.svg?style=flat-square\" alt=\"Total Downloads\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"#\"\u003e\n        \u003cimg src=\"https://github.com/SnowLaboratory/Laravel-Mirror/actions/workflows/main.yml/badge.svg\" alt=\"GitHub Actions\" /\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\n- [Introduction](#introduction)\n- [Installation](#installation)\n- [Usage](#usage)\n    - [Weighted Averages](#weighted-averages)\n    - [Compare Different Properties](different-properties-in-the-same-calculation)\n    - [Custom Scoring Algorithms](#custom-scoring-algorithms)\n    - [Combining Weights \u0026 Custom Algorithms](#combining-weights-with-custom-algorithms)\n    - [Organizing Code](#managing-multiple-algorithms-and-weights)\n    - [Macros](#macros-extracting-algorithms)\n- [Relationships](#relationships)\n- [Generate Recommendations](#generate)\n- [Roadmap](#roadmap)\n- [Testing](#testing)\n- [Changelog](#changelog)\n- [Contributing](#contributing)\n- [Security Vulnerabilities](#security)\n- [Code of Conduct](#code-of-conduct)\n- [License](#license)\n\n\u003ca name=\"introduction\"\u003e\u003c/a\u003e\n## Introduction\nBring your user experience to the next level! Laravel Mirror lets you suggest content to your users intelligently!  Easily recommend blog posts, products, recipes, books, etc., with pure PHP! Start by registering a recommendation strategy and routinely updating recommendations in a CRON job! \n\n\u003ca name=\"installation\"\u003e\u003c/a\u003e\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require \"snowbuilds/laravel-mirror:^0.0.3-alpha\"\n```\n\n```bash\nphp artisan vendor:publish --provider=\"SnowBuilds\\Mirror\\MirrorServiceProvider\"\n```\n\n\u003ca name=\"usage\"\u003e\u003c/a\u003e\n## Usage\nRegistering a strategy is as simple as comparing two values! We added some utilities for convenience. For example, recommending blog posts with similar titles:\n\n```php\nuse SnowBuilds\\Mirror\\Concerns\\Recommendations;\nuse SnowBuilds\\Mirror\\Mirror;\n\nclass Post extends Model\n{\n    use Recommendations;\n\n    public function registerRecommendations(): void\n    {\n        $this-\u003eregisterStrategy(Post::class)\n            -\u003elevenshtein('title');\n    }\n}\n```\n\n\u003ca name=\"weighted-averages\"\u003e\u003c/a\u003e\n### Weighted Averages\nIt is possible to combine algorithms! For example, suggesting posts with similar titles and tags. Adding weights will give fields precedence. Larger numbers have higher precedence. We made the title field score higher in a recommendation engine than the tags:\n\n```php\npublic function registerRecommendations(): void\n{\n    $this-\u003eregisterStrategy(Post::class)\n        -\u003elevenshtein('title', 2)\n        -\u003eeuclidean('tags', 1);\n}\n```\n\n\u003ca name=\"different-properties-in-the-same-calculation\"\u003e\u003c/a\u003e\n### Different Properties in the Same Calculation\nYou can add a second parameter to the utility method when comparing properties with different names. For example, users should see posts based on their biography and followed communities:\n\n```php\nclass User extends Model\n{\n    use Recommendations;\n\n    public function registerRecommendations(): void\n    {\n        $this-\u003eregisterStrategy(Post::class)\n            -\u003elevenshtein('biography', 'title', 1) // compare biography to post title\n            -\u003eeuclidean('communities', 'tags', 3); // compare communities to post tags\n    }\n}\n```\n\n\u003ca name=\"custom-scoring-algorithms\"\u003e\u003c/a\u003e\n### Custom Scoring algorithms\nWhen the helper utilities are insufficient, you can invoke custom algorithms using the `using` method. The first value, `$a`, is the model that has recommendations, and the second value, `$b`, is the model being suggested:\n\n```php\nclass User extends Model\n{\n    public function registerRecommendations(): void\n    {\n        $this-\u003eregisterStrategy(Post::class)\n            -\u003eusing(function (User $a, Post $b) {\n                return Algorithm::levenshtein($a-\u003ename, $b-\u003ename);\n            });\n    }\n}\n```\n\n\u003ca name=\"combining-weights-with-custom-algorithms\"\u003e\u003c/a\u003e\n### Combining Weights with Custom Algorithms\nWeights can also be applied to custom algorithms. The weights are applied in the order that the algorithm was registered.  Our custom title comparator will take precedence over our tag comparator:\n\n```php\npublic function registerRecommendations(): void\n{\n    $this-\u003eregisterStrategy(Post::class)\n        -\u003eusing(function ($a, $b) {\n            return Algorithm::levenshtein($a-\u003etitle, $b-\u003etitle);\n        })\n        -\u003eusing(function ($a, $b) {\n            return Algorithm::euclidean($a-\u003etags, $b-\u003etags);\n        })\n        -\u003eweights([2,1]);\n}\n\n```\n\n\u003ca name=\"managing-multiple-algorithms-and-weights\"\u003e\u003c/a\u003e\n### Managing Multiple Algorithms and Weights\nThe code becomes hard to read when using multiple custom algorithms and weights. If you use an associative array, you can keep your algorithms and weights organized:\n\n```php\npublic function registerRecommendations(): void\n{\n    $this-\u003eregisterStrategy(Post::class)\n        -\u003eusing([\n            'titles' =\u003e fn ($a, $b) =\u003e Algorithm::levenshtein($a-\u003etitle, $b-\u003etitle),\n            'tags' =\u003e fn ($a, $b) =\u003e Algorithm::levenshtein($a-\u003etags, $b-\u003etags),\n        ])\n        -\u003eweights([\n            'titles' =\u003e 2,\n            'tags' =\u003e 1,\n        ]);\n}\n```\n\n\u003ca name=\"macros-extracting-algorithms\"\u003e\u003c/a\u003e\n### Macros - Extracting Algorithms\nWhen your custom algorithm is too cumbersome, you can extract it into a macro. We use an internal utility for registering algorithms, which you are free to use in your macros.  This will create a clean utility API `-\u003ehuggingFace` for our user model:\n\n```php\n// ServiceProvider.php\nScoringStrategy::macro('huggingFace', function (...$args) {\n  return $this-\u003eregisterAlgorithm(\n    fn($a, $b) =\u003e HuggingFace::invokeEmbedding($a, $b),\n    ...$args\n  );\n});\n\n// Model.php\nclass User extends Model \n{\n    public function registerRecommendations(): void\n    {\n        $this-\u003eregisterStrategy(User::class)\n            -\u003eeuclidean('follewers')\n            -\u003ehuggingFace('activity')\n            -\u003elevenshtein('bio');\n    }\n}\n```\n\n\u003ca name=\"relationships\"\u003e\u003c/a\u003e\n## Relationships\nYou can define a relationship between the model and the suggested content using the `morphsRecommendation` method. The content is ordered by the most suggested content:\n\n```php\nclass User extends Authenticatable\n{\n    use Recommendations;\n\n    public function recommendedRecipes() {\n        return $this-\u003emorphRecommendation(Recipe::class);\n    }\n}\n```\n\n\u003ca name=\"generate\"\u003e\u003c/a\u003e\n## Generating Recommendation Matrix\nCalculating recommendations is resource-intensive. Laravel Mirror provides a command for syncing recommendations. After syncing, the recommendations are stored in the database and you will be able to fetch related suggestions:\n\n```bash\nphp artisan mirror:sync\n```\n\nIn production, this should be a CRON job or registered in the Laravel kernel.\n```php\nclass Kernel extends ConsoleKernel\n{\n    protected function schedule(Schedule $schedule): void\n    {\n        $schedule-\u003ecommand('mirror:sync')-\u003edaily();\n    }\n}\n```\n\n\u003ca name=\"roadmap\"\u003e\u003c/a\u003e\n## Roadmap\n- [x] Blazingly Fast!\n- [x] Polymorphic recommendations\n- [x] Recommendation collections\n- [x] Common comparison algorithms\n- [x] Sync command\n- [ ] Testing\n- [ ] Programmatically invoke syncing actions\n- [x] Simplified API for weights and faceted algorithms\n- [ ] Queueing\n- [ ] More algorithms\n- [ ] More settings\n\n\u003ca name=\"testing\"\u003e\u003c/a\u003e\n### Testing\n\n```bash\ncomposer test\n```\n\n\u003ca name=\"changelog\"\u003e\u003c/a\u003e\n### Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n\u003ca name=\"contributing\"\u003e\u003c/a\u003e\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n\u003ca name=\"security\"\u003e\u003c/a\u003e\n### Security\n\nIf you discover any security-related issues, please email dev@snowlaboratory.com instead of using the issue tracker.\n\n## Code of Conduct\n\u003ca name=\"code-of-conduct\"\u003e\u003c/a\u003e\n\nIn order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).\n\n\u003ca name=\"credits\"\u003e\u003c/a\u003e\n## Credits\n\n-   [Snow Labs](https://github.com/snowbuilds)\n-   [Inspiration for Laravel Mirror](https://oliverlundquist.com/2019/03/11/recommender-system-with-ml-in-laravel.html)\n-   [All Contributors](../../contributors)\n\n\u003ca name=\"license\"\u003e\u003c/a\u003e\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnowlaboratory%2Flaravel-mirror","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsnowlaboratory%2Flaravel-mirror","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnowlaboratory%2Flaravel-mirror/lists"}