{"id":24962386,"url":"https://github.com/directorytree/activeredis","last_synced_at":"2025-04-05T10:08:50.205Z","repository":{"id":257791006,"uuid":"860189709","full_name":"DirectoryTree/ActiveRedis","owner":"DirectoryTree","description":"An Active Record implementation for Redis hashes in Laravel.","archived":false,"fork":false,"pushed_at":"2025-03-05T21:27:49.000Z","size":16999,"stargazers_count":57,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-29T09:12:17.466Z","etag":null,"topics":[],"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/DirectoryTree.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-20T01:42:36.000Z","updated_at":"2025-03-05T21:27:24.000Z","dependencies_parsed_at":"2024-11-19T21:25:39.081Z","dependency_job_id":"11156cc3-a2d1-4712-bd2e-f4e9558fac48","html_url":"https://github.com/DirectoryTree/ActiveRedis","commit_stats":null,"previous_names":["directorytree/activeredis"],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DirectoryTree%2FActiveRedis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DirectoryTree%2FActiveRedis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DirectoryTree%2FActiveRedis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DirectoryTree%2FActiveRedis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DirectoryTree","download_url":"https://codeload.github.com/DirectoryTree/ActiveRedis/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247318744,"owners_count":20919484,"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":"2025-02-03T09:00:02.355Z","updated_at":"2025-04-05T10:08:50.184Z","avatar_url":"https://github.com/DirectoryTree.png","language":"PHP","funding_links":["https://github.com/sponsors/taylorotwell"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://github.com/DirectoryTree/ActiveRedis/blob/master/art/logo.svg\" width=\"250\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\nAn Active Record implementation for Redis hashes in Laravel.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://github.com/directorytree/activeredis/actions\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/directorytree/activeredis/run-tests.yml?branch=master\u0026style=flat-square\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://packagist.org/packages/directorytree/activeredis\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/packagist/v/directorytree/activeredis.svg?style=flat-square\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://packagist.org/packages/directorytree/activeredis\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/packagist/dt/directorytree/activeredis.svg?style=flat-square\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://packagist.org/packages/directorytree/activeredis\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/packagist/l/directorytree/activeredis.svg?style=flat-square\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nActiveRedis provides you simple and efficient way to interact with Redis hashes using an Eloquent-like API.\n\n## Index\n\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Creating Models](#creating-models)\n    - [Model Identifiers](#model-identifiers)\n    - [Model Timestamps](#model-timestamps)\n    - [Model Casts](#model-casts)\n    - [Model Events](#model-events)\n    - [Model Connection](#model-connection)\n  - [Updating Models](#updating-models)\n  - [Deleting Models](#deleting-models)\n  - [Expiring Models](#expiring-models)\n  - [Querying Models](#querying-models)\n    - [Chunking](#chunking)\n    - [Searching](#searching)\n  - [Testing](#testing)\n- [Credits](#credits)\n\n## Requirements\n\n- PHP \u003e= 8.1\n- Redis \u003e= 3.0\n- Laravel \u003e= 9.0\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require directorytree/activeredis\n```\n\n## Usage\n\n### Creating Models\n\nTo get started, define a new ActiveRedis model:\n\n```php\nnamespace App\\Redis;\n\nuse DirectoryTree\\ActiveRedis\\Model;\n\nclass Visit extends Model {}\n```\n\nThen, create models with whatever data you'd like:\n\n\u003e [!important]\n\u003e Without [model casts](#model-casts) defined, all values you assign to model attributes\n\u003e will be cast to strings, as that is their true storage type in Redis.\n\n```php\nuse App\\Redis\\Visit;\n\n$visit = Visit::create([\n    'ip' =\u003e request()-\u003eip(),\n    'url' =\u003e request()-\u003eurl(),\n    'user_agent' =\u003e request()-\u003euserAgent(),\n]);\n```\n\nThis will create a new Redis hash in below format:\n\n```\n{plural_model_name}:{key_name}:{key_value}\n```\n\nFor example:\n\n```\nvisits:id:f195637b-7d48-43ab-abab-86e93dfc9410\n```\n\nAccess attributes as you would expect on the model instance:\n\n```php\n$visit-\u003eip; // xxx.xxx.xxx.xxx\n$visit-\u003eurl; // https://example.com\n$visit-\u003euser_agent; // Mozilla/5.0 ...\n```\n\n\u003e [!important]\n\u003e Before you begin using your model in production, consider possible [searchable attributes](#searching) that\n\u003e you may like to be part of your model schema, as these should not be modified once you have existing records.\n\n#### Model Identifiers\n\nWhen creating models, ActiveRedis will automatically generate a UUID for each model, assigned to the `id` attribute:\n\n```php\n$visit-\u003eid; // \"f195637b-7d48-43ab-abab-86e93dfc9410\"\n$visit-\u003egetKey(); // \"f195637b-7d48-43ab-abab-86e93dfc9410\"\n$visit-\u003egetHashKey(); // \"visits:id:f195637b-7d48-43ab-abab-86e93dfc9410\"\n\n$visit-\u003egetKeyName(); // \"id\"\n$visit-\u003egetBaseHash(); // \"visits:id\"\n$visit-\u003egetHashPrefix(): // \"visits\"\n```\n\nYou may provide your own ID if you'd prefer:\n\n\u003e [!important]\n\u003e Redis keys are **case-sensitive**. Be mindful of this, as it impacts queries, discussed below.\n\n```php\n$visit = Visit::create([\n    'id' =\u003e 'custom-id',\n    // ...\n]);\n\n$visit-\u003eid; // \"custom-id\"\n$visit-\u003egetHashKey(); // \"visits:id:custom-id\"\n```\n\nAttempting to create a model with an ID that already exists will result in a `DuplicateKeyException`:\n\n```php\nVisit::create(['id' =\u003e 'custom-id']);\n\n// DuplicateKeyException: A model with the key 'custom-id' already exists.\nVisit::create(['id' =\u003e 'custom-id']);\n```\n\nSimilarly, attempting to create a model with an empty ID will throw a `InvalidKeyException`:\n\n```php\n// InvalidKeyException: A key is required to create a model.\nVisit::create(['id' =\u003e '']);\n```\n\nIf you're running a high traffic application that may encounter a race condition, you may pass in `true`\nin the second argument to `create` to ignore the `DuplicateKeyException` and delete the existing record:\n\n```php\nVisit::create(['id' =\u003e 'custom-id']);\n\n// The existing model will be deleted.\nVisit::create(['id' =\u003e 'custom-id'], force: true);\n```\n\nSimilarly, you may pass in `true` into the `save` method to ignore the exception and delete the existing record:\n\n```php\n(new Visit(['id' =\u003e 'custom-id'])-\u003esave();\n\n(new Visit(['id' =\u003e 'custom-id']))-\u003esave(force: true);\n```\n\nTo change the name of the field in which the model key is stored, override the `key` property:\n\n```php\nnamespace App\\Redis;\n\nuse DirectoryTree\\ActiveRedis\\Model;\n\nclass Visit extends Model\n{\n    /**\n     * The key name for the model.\n     */\n    protected string $key = 'custom_key';\n}\n```\n\nActiveRedis will always generate a new UUID in the key's attribute if you do not provide one.\n\nTo change this behaviour or generate your own unique keys, you may override the `getNewKey()` method:\n\n\u003e [!important]\n\u003e **Do not** generate keys with colons (:) or asterisks (*). They are reserved characters in Redis.\n\u003e \n\u003e This also applies to values of [searchable](#searching) attributes.\n\n```php\nnamespace App\\Redis;\n\nuse Illuminate\\Support\\Str;\nuse DirectoryTree\\ActiveRedis\\Model;\n\nclass Visit extends Model\n{    \n    /**\n     * Generate a new key for the model.\n     */\n    protected function getNewKey(): string\n    {\n        return Str::uuid();\n    }\n}\n```\n\n#### Model Timestamps\n\nModels will also maintain `created_at` and `updated_at` attributes:\n\n\u003e [!important]\n\u003e Timestamp attributes will be returned as `Carbon` instances when accessed.\n\n```php\n$visit-\u003ecreated_at; // \\Carbon\\Carbon('2024-01-01 00:00:00')\n$visit-\u003eupdated_at; // \\Carbon\\Carbon('2024-01-01 00:00:00')\n```\n\nTo only update a models `updated_at` timestamp, you may call the `touch()` method:\n\n```php\n$visit-\u003etouch();\n```\n\nYou may provide a timestamp attribute to touch as well:\n\n```php\n$visit-\u003etouch('created_at');\n```\n\nTo disable timestamps, you may override the `timestamps` property and set it to `false`:\n\n```php\nclass Visit extends Model\n{\n    /**\n     * Indicates if the model should be timestamped.\n     */\n    public bool $timestamps = false;\n}\n```\n\nIf you need to customize the names of the columns used to store the timestamps, you may define `CREATED_AT` and `UPDATED_AT` constants on your model:\n\n```php\nclass Visit extends Model\n{\n    const CREATED_AT = 'creation_date';\n    const UPDATED_AT = 'updated_date';\n}\n```\n\n#### Model Casts\n\nTo cast model attributes to a specific type, you may define a `casts` property on the model:\n\n```php\nclass Visit extends Model\n{\n    /**\n     * The attributes that should be cast to native types.\n     */\n    protected array $casts = [\n        'user_id' =\u003e 'integer',\n        'authenticated' =\u003e 'boolean',\n    ];\n}\n```\n\nWhen you access the attribute, it will be cast to the specified type:\n\n```php\n$visit = new Visit([\n    'user_id' =\u003e '1',\n    'authenticated' =\u003e '1',\n    // ...\n]);\n\n$visit-\u003euser_id; // (int) 1\n$visit-\u003eauthenticated; // (bool) true\n\n$visit-\u003egetAttributes(); // ['user_id' =\u003e '1', 'authenticated' =\u003e '1'],\n```\n\nHere is a list of all supported casts:\n\n- `json`\n- `date`\n- `real`\n- `array`\n- `float`\n- `string`\n- `object`\n- `double`\n- `integer`\n- `boolean`\n- `datetime`\n- `timestamp`\n- `collection`\n- `immutable_date`\n- `immutable_datetime`\n- `decimal:\u003cprecision\u003e`\n\nEnum casts are also available:\n\n```php\nuse App\\Enums\\VisitType;\n\nclass Visit extends Model\n{\n    /**\n     * The attributes that should be cast to native types.\n     */\n    protected array $casts = [\n        'type' =\u003e VisitType::class,\n    ];\n}\n```\n\n```php\n$visit = Visit::create(['type' =\u003e VisitType::Unique]);\n// Or:\n$visit = Visit::create(['type' =\u003e 'unique']);\n\n$visit-\u003etype; // (enum) VisitType::Unique\n```\n\n#### Model Events\n\nActiveRedis models dispatch several events, allowing you to hook into the following moments in a model's lifecycle:\n`retrieved`, `creating`, `created`, `updating`, `updated`, `saving`, `saved`, `deleting`, and `deleted`.\n\nYou may register listeners for these methods inside your model's `booted` method:\n\n\u003e You may return `false` from an event listener on the `creating`, `updating`, or `deleting` events to cancel the operation.\n\n```php\nclass Visit extends Model\n{\n    /**\n     * The \"booted\" method of the model.\n     */\n    protected static function booted(): void\n    {\n        static::creating(function (Visit $visit) {\n            // ...\n        });\n        \n        // ...\n    }\n}\n```\n\nIf you prefer, you may also create a model observer:\n\n```php\nclass VisitObserver\n{\n    /**\n     * Handle the \"creating\" event.\n     */\n    public function creating(Visit $visit): void\n    {\n        // ...\n    }\n}\n```\n\nAnd register it using the `observe` method:\n\n```php\nuse App\\Redis\\Visit;\n\nclass AppServiceProvider extends ServiceProvider\n{\n    /**\n     * Bootstrap any application services.\n     */\n    public function boot(): void\n    {\n        Visit::observe(VisitObserver::class);\n    }\n}\n```\n\n#### Model Connection\n\nBy default, models will use the default Redis connection defined in your Laravel configuration.\n\nIt is recommended to define a separate connection for your ActiveRedis models in your `config/database.php` \nfile, as your default Redis connection will contain other data, such as jobs, cache, and sessions.\n\nThis will make queries more efficient, as it will only need to scan over the keys owned by your models:\n\n```php\nreturn [\n    // ...\n    \n    'redis' =\u003e [\n        'activeredis' =\u003e [\n            'url' =\u003e env('REDIS_URL'),\n            'host' =\u003e env('REDIS_HOST', '127.0.0.1'),\n            'password' =\u003e env('REDIS_PASSWORD', null),\n            'port' =\u003e env('REDIS_PORT', '6379'),\n            'database' =\u003e '10',\n        ],\n    ],\n];\n```\n\nTo use this connection, you may override the `connection` property on the model:\n\n```php\nclass Visit extends Model\n{\n    /**\n     * The Redis connection to use.\n     */\n    protected ?string $connection = 'activeredis';\n}\n```\n\n### Updating Models\n\nYou may update models using the `update()` method:\n\n```php\n$visit-\u003eupdate([\n    'ip' =\u003e 'xxx.xxx.xxx.xxx',\n    'url' =\u003e 'https://example.com',\n    'user_agent' =\u003e 'Mozilla/5.0 ...',\n]);\n```\n\nOr by setting model attributes and calling the `save()` method:\n\n```php\n$visit-\u003eip = 'xxx.xxx.xxx.xxx';\n\n$visit-\u003esave();\n```\n\n### Deleting Models\n\nTo delete models, use the `delete()` method on the model instance:\n\n```php\n$visit-\u003edelete();\n```\n\nOr, you may also delete models by their ID:\n\n```php\n$deleted = Visit::destroy('f195637b-7d48-43ab-abab-86e93dfc9410');\n\necho $deleted; // 1\n```\n\nYou may delete multiple models by providing an array of IDs:\n\n```php\n$deleted = Visit::destroy(['f195637b...', 'a195637b...']);\n\necho $deleted; // 2\n```\n\n### Expiring Models\n\nTo expire models after a certain amount of time, use the `setExpiry()` method:\n\n```php\n$visit-\u003esetExpiry(now()-\u003eaddMinutes(5));\n```\n\nAfter 5 minutes have elapsed, the model will be automatically deleted from Redis.\n\nTo retrieve a model's expiry time, use the `getExpiry()` method:\n\n\u003e [!important]\n\u003e The value returned will be a `Carbon` instance, or `null` if the model does not expire.\n\n```php\n$visit-\u003egetExpiry(); // \\Carbon\\Carbon|null\n```\n\n### Querying Models\n\nQuerying models uses Redis' `SCAN` command to iterate over all keys in the model's hash set.\n\nFor example, when the Visit model is queried, the pattern `visits:id:*` is used:\n\n```\nSCAN {cursor} MATCH visits:id:* COUNT {count}\n```\n\nTo begin querying models, you may call the `query()` method on the model:\n\n```php\n$visits = Visit::query()-\u003eget();\n```\n\nThis will iterate over all keys in the model's hash set and return a collection of models matching the pattern.\n\nMissing model methods will be forwarded to the query builder, so you may call query methods dynamically on the model if you prefer.\n\n```php\n$visits = Visit::get();\n```\n\n#### Finding\n\nTo retrieve specific models, you may use the `find` method:\n\n```php\n$visit = Visit::find('f195637b-7d48-43ab-abab-86e93dfc9410');\n```\n\nIf you would like to throw an exception when the model is not found, you may use the `findOrFail` method:\n\n```php\nVisit::findOrFail('missing'); // ModelNotFoundException\n```\n\n#### Chunking\n\nYou may chunk query results using the `chunk()` method:\n\n\u003e [!important]\n\u003e Redis does not guarantee the exact number of records returned in each SCAN iteration.\n\u003e See https://redis.io/docs/latest/commands/scan/#the-count-option for more information.\n\n```php\nuse App\\Redis\\Visit;\nuse Illuminate\\Support\\Collection;\n\nVisit::chunk(100, function (Collection $visits) {\n    $visits-\u003eeach(function ($visit) {\n        // ...\n    });\n});\n```\n\nOr call the `each` method:\n\n```php\nuse App\\Redis\\Visit;\n\nVisit::each(function (Visit $visit) {\n    // ...\n}, 100);\n```\n\nYou may return `false` in the callback to stop the chunking query:\n\n```php\nuse App\\Redis\\Visit;\n\nVisit::each(function (Visit $visit) {\n    if ($visit-\u003eip === 'xxx.xxx.xxx.xxx') {\n        return false;\n    }\n});\n```\n\n#### Searching\n\nBefore attempting to search models, you must define which attributes you would like to be searchable on the model:\n\n```php\nnamespace App\\Redis;\n\nclass Visit extends Model\n{\n    /**\n     * The attributes that are searchable.\n     */\n    protected array $searchable = ['ip'];\n}\n```\n\n\u003e [!important]\n\u003e Consider searchable attributes to be part of your model schema. They should be defined before \n\u003e you begin using your model. **Do not change these while you have existing records**. Doing \n\u003e so will lead to models that cannot be retrieved without interacting with Redis manually.\n\nWhen you define these attributes, they will be stored as a part of the hash key in the below format:\n\n```\nvisits:id:{id}:ip:{ip}\n```\n\nFor example:\n\n```\nvisits:id:f195637b-7d48-43ab-abab-86e93dfc9410:ip:127.0.0.1\n```\n\nIf you do not provide a value for a searchable attribute, literal string `null` will be used as the value in the key:\n\n```\nvisits:id:f195637b-7d48-43ab-abab-86e93dfc9410:ip:null\n```\n\nWhen multiple searchable attributes are defined, they will be stored in alphabetical order from left to right in the hash key:\n\n```php\nclass Visit extends Model\n{\n    /**\n     * The attributes that are searchable.\n     */\n    protected array $searchable = ['user_id', 'ip'];\n}\n```\n\nFor example:\n\n```\nvisits:id:{id}:ip:{ip}:user_id:{user_id}\n```\n\n\u003e [!tip]\n\u003e Because searchable attributes should not be modified while you have existing records, you may find it \n\u003e useful name your models in a way that references the searchable attributes. For example `UserVisit`.\n\n```php\n$visit = UserVisit::create([\n    'user_id' =\u003e 1,\n    'ip' =\u003e request()-\u003eip(),\n]);\n\n$visit-\u003egetHashKey(); // \"user_visits:id:f195637b-7d48-43ab-abab-86e93dfc9410:ip:127.0.0.1:user_id:1\"\n```\n\nOnce the searchable attributes have been defined, you may begin querying for them using the `where()` method:\n\n```php\n// SCAN ... MATCH visits:id:*:ip:127.0.0.1:user_id:1\n$visits = Visit::query()\n    -\u003ewhere('user_id', 1)\n    -\u003ewhere('ip', '127.0.0.1')\n    -\u003eget();\n```\n\nYou may omit searchable attributes from the query and an asterisk will be inserted automatically:\n\n```php\n// SCAN ... MATCH visits:id:*:ip:127.0.0.1:user_id:*\n$visit = Visit::where('ip', '127.0.0.1')-\u003efirst();\n```\n\nYou may also use asterisks in your where clauses to perform wildcard searches:\n\n```php\n// SCAN ... MATCH visits:id:*:ip:127.0.*:user_id:*\n$visit = Visit::where('ip', '127.0.*')-\u003efirst();\n```\n\nYou may also use a string literal `'null'` as a search value to query for models where the attribute is `null`:\n\n```php\n// SCAN ... MATCH visits:id:*:ip:null:user_id:*\n$visit = Visit::where('ip', 'null')-\u003efirst();\n```\n\nSearchable attribute values may be updated at any time on models. If they have been changed, \nthe existing model instance is deleted in Redis, and a new one is saved automatically:\n\n```php\n$visit = Visit::create(['user_id' =\u003e 1]);\n\n// HDEL visits:id:f195637b-7d48-43ab-abab-86e93dfc9410:ip:127.0.0.1:user_id:1\n// HSET visits:id:f195637b-7d48-43ab-abab-86e93dfc9410:ip:127.0.0.1:user_id:2\n$visit-\u003eupdate(['user_id' =\u003e 2]);\n```\n\n## Testing\n\nTo run your application tests without a real Redis server, you may swap the underlying repository to an array.\n\nThis will allow you to test with your models without needing to interact with Redis:\n\n```php\nuse DirectoryTree\\ActiveRedis\\Model;\n\n// Pest\nbeforeAll(function () {\n    Model::setRepository('array');\n});\n\n// PHPUnit\nprotected function setUp(): void\n{\n    parent::setUp();\n\n    Model::setRepository('array');\n}\n```\n\nOtherwise, you will need to run your tests with a Redis server running, and flush your Redis database after each test:\n\n```php\nuse Illuminate\\Support\\Facades\\Redis;\n\n// Pest\nbeforeEach(function () {\n    Redis::flushdb();\n});\n\n// PHPUnit\nprotected function setUp(): void\n{\n    parent::setUp();\n\n    Redis::flushdb();\n}\n```\n\n## Credits\n\nThis package is directly inspired from \u003ca href=\"https://laravel.com/docs/eloquent\"\u003eLaravel's Eloquent\u003c/a\u003e, and most features are direct ports to a Redis equivalent.\n\nI am forever grateful for the work \u003ca href=\"https://github.com/taylorotwell\"\u003eTaylor Otwell\u003c/a\u003e has produced.\n\nIf you can, support his work by purchasing a \u003ca href=\"https://github.com/sponsors/taylorotwell\"\u003esponsorship\u003c/a\u003e, or one of his many Laravel based services.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdirectorytree%2Factiveredis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdirectorytree%2Factiveredis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdirectorytree%2Factiveredis/lists"}