{"id":18750696,"url":"https://github.com/webiny/hrc","last_synced_at":"2025-10-08T03:57:38.338Z","repository":{"id":34043591,"uuid":"37816769","full_name":"webiny/Hrc","owner":"webiny","description":"Cache management layer where cache rules are based on incoming HTTP request.","archived":false,"fork":false,"pushed_at":"2017-09-29T07:33:57.000Z","size":53,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-05-20T11:09:12.321Z","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/webiny.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}},"created_at":"2015-06-21T16:47:25.000Z","updated_at":"2023-05-21T17:38:35.000Z","dependencies_parsed_at":"2022-07-13T11:10:25.953Z","dependency_job_id":null,"html_url":"https://github.com/webiny/Hrc","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/webiny/Hrc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webiny%2FHrc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webiny%2FHrc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webiny%2FHrc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webiny%2FHrc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webiny","download_url":"https://codeload.github.com/webiny/Hrc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webiny%2FHrc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278886410,"owners_count":26062975,"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","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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-11-07T17:12:49.939Z","updated_at":"2025-10-08T03:57:38.308Z","avatar_url":"https://github.com/webiny.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"HRC - Http Request Cache\n=====\n\nThis is a cache management system that provides control mechanisms around your cache. Caches are tied to the incoming HTTP request.\nIn practice that means that you can create caches based on a combination of a request path, cookies, sent headers, query strings and some custom callbacks.\n\n### ... but I use memcache (or redis)\n\nSure, but that's not a cache management system, that's just a storage ... managing cache requires a lot of knowledge and \n it's not an easy thing to do (especially with memcache).\n \nA much smarter person than me, once stated: \n\n\u003e There are only two hard things in Computer Science: cache invalidation and naming things.\n\n\u003e -- Phil Karlton\n\nYou can still use memcache, redis or any other cache storage system you want, just create a driver for it and hook it into Hrc. \n(see the guide down below)\n\n## Installation\n\n```\ncomposer require webiny/hrc\n```\n\nRequires PHP 7.0 or later.\n\n## How it works\n\n```php\n// define cache rules\n$cacheRules = [\n    'FooBar' =\u003e [\n        'Ttl'   =\u003e 60,\n        'Tags'  =\u003e ['cacheAll'],\n        'Match' =\u003e [\n            'Url' =\u003e '*'\n        ]\n    ]\n];\n\n// where and how to store cache files\n$cacheStorage = new Webiny\\Hrc\\CacheStorage\\FileSystem('/path/to/cache/folder/');\n// where and how to keep a record of all stored cache files\n$indexStorage = new Webiny\\Hrc\\IndexStorage\\FileSystem('/path/to/index/folder');\n// create Hrc instance\n$hrc = new Hrc($cacheRules, $cacheStorage, $indexStorage);\n// store something into cache\n$hrc-\u003esave('entryName', 'some content');\n// retrieve something from cache\n$data = $hrc-\u003eread('entryName'); // some content\n```\n\n### 1. Define a set of cache rules\n\nCache rules define how and if something can be cached. \nFor example, a simple cache rule looks something like this:\n\n```php\n$cacheRules = [\n    'SimpleRule'    =\u003e [\n        'Ttl'   =\u003e 60,\n        'Tags'  =\u003e ['simple', 'rule'],\n        'Match' =\u003e [\n            'Url' =\u003e '/some/*'\n        ]\n    ],\n];\n```\n\n`Ttl` (Time-to-live) defines for how long that entry should be kept in cache.\n\n`Tags` are used to tag the content, so you can invalidate it easier.\n\n`Match` is a set of match criterias that the rule needs to satisfy in order for you to be able to store content. They \nalso define what is used to create a cache key.\n\n#### Match criteria\n\nThere are several things you can use in your match criteria:\n* Url path\n* Query string\n* Request headers\n* Cookies\n* Custom callback\n\n\nMatch options:\n- `true`: the parameter needs to be present, but only the parameter name, and not its value, will be used in the cache key.\n- `false`: the parameter must not be preset for the rule to match.\n- `*`: the parameter needs to exists and needs to have some value, and it's value will be used in the cache key.\n- `?`: the parameter is optional, if it exists it's value is used in the cache key.\n- any PHP regex `preg_match` standard\n- any fixed string for exact match\n\n\nHere are some match examples:\n\n```php\n$mockRules = [\n    'AdvancedMatch' =\u003e [\n        'Ttl'   =\u003e 100,\n        'Tags'  =\u003e ['advanced', 'one'],\n        'Match' =\u003e [\n            'Url'      =\u003e '/simple/url/([\\w]+)/page/([\\d]+)/(yes|no)/',\n            'Cookie'   =\u003e [\n                'X-Cart'          =\u003e 'cart value (\\d+) currency ([\\w]{2})',\n                'X-CacheByCookie' =\u003e 'yes'\n            ],\n            'Header'   =\u003e [\n                'X-Cache-Me'     =\u003e 'foo (\\w+)',\n                'X-Cache-Header' =\u003e '*'\n            ],\n            'Query'    =\u003e [\n                'Cache'   =\u003e true,\n                'foo'     =\u003e ?\n            ],\n            'Callback' =\u003e [\n                'Webiny\\Hrc\\UnitTests\\CacheRules\\MockCallbacks::returnValue'\n            ]\n        ]\n    ]\n];\n```\n\nThe `Callback` section is used to invoke a custom callback which is basically just an extension to the match rules. \n The callback method should return a value, that value will be used to build the cache key. If the callback returns boolean `false`, \n the rule will not match the request.\n\nA callback method takes two parameters, `Webiny\\Hrc\\Request` and `Webiny\\Hrc\\CacheRules\\CacheRule\\CacheRule`:\n\n```php\nclass MockCallbacks\n{\n    public static function returnTrue(Webiny\\Hrc\\Request $r, Webiny\\Hrc\\CacheRules\\CacheRule\\CacheRule $cr)\n    {\n        // do you thing here\n    }\n}\n```\n\nAlthough not recommended, instead of strictly specifying match options for `Cookie`, `Header` and `Query`, you can put a `*`, to simply take all received parameters (eg. all query parameters), and build a cache key for any variation in a request.\n\n```php\n$mockRules = [\n    'GetAllParameters' =\u003e [\n        'Ttl'   =\u003e 86400,\n        'Tags'  =\u003e ['global', 'all'],\n        'Match' =\u003e [\n            'Url'      =\u003e '/match/all',\n            'Cookie'   =\u003e '*',\n            'Header'   =\u003e '*',\n            'Query'   =\u003e '*'\n        ]\n    ]\n];\n```\n \n### 2. Cache storage\n\nHrc is built in a way that you can store cache using any storage you want, from memcache to mongodb. By default\nwe provide a filesystem storage and MongoDb. If you write a driver for any other storage mechanism, send over a pull request, and we will gladly merge it.\n\nCreating a storage drive is rather simple, just create a class and implement `Webiny\\Hrc\\CacheStorage\\CacheStorageInterface`.\nYou have 3 simple methods to implement, and you're done.\n\n### 3. Index storage\n\nIndex storage is used to store additional cache information, you can look at it as a combination of cache metadata\nand taxonomy. The index is mainly used to achieve more possibilities around cache invalidation and faster cache invalidation times.\n\nBy default we provide a filesystem cache index  and a MongoDb cache index, to create a custom-one, just implement `Webiny\\Hrc\\IndexStorage\\IndexStorageInterface`.\n\n#### Mongo storage and index\n\nIf you plan to use the `Mongo` cache storage and cache index, make sure you run the `installCollections` method prior to using the driver,\notherwise the required indexes won't be created and the performance will be slow.\n\n```php\n$this-\u003ecacheStorage-\u003einstallCollections();\n$this-\u003eindexStorage-\u003einstallCollections();\n```\n\nYou need to run this only once. Alternative approach is to create the two collections and indexes manually:\n - `HrcCacheStorage` collection should have the following indexes\n    - `key` =\u003e unique index on the `key` field, sparse should be false\n    - `ttl` =\u003e index on ttl field with expireAfterSeconds set to 0 seconds\n - `HrcIndexStorage` collection should have the following indexes:\n    - `key` =\u003e unique index on the `key` field, sparse should be false\n    - `tags` =\u003e index on the `tags` field\n    - `ttl` =\u003e index on ttl field with expireAfterSeconds set to 0 seconds\n\n### 4. Matching a cache rule\n\nWhen you call the `read` or `save` method, if you don't provide the name of the cache rule, the class will run through all\nof defined cache rules, and will select the **first rule that matched the request**.\nHowever if you provide the cache rule name, the cache rule match patterns still must match, but the check will only be done on that particular rule.\nBy providing a cache rule name, you can match multiple cache rules inside the same HTTP request.\n\n```php\n// use the first matched cache rule\n$hrc-\u003esave('entryName', 'some content');\n$data = $hrc-\u003eread('entryName');\n\n// use fooBar cache rule\n$hrc-\u003esave('block 122', 'some content', 'fooBar');\n$data = $hrc-\u003eread('block 122');\n```\n\n### 5. Callbacks\n\nThere are two main callback events supported: \n- `beforeSave(SavePayload)` \n- `afterSave(SavePayload)`\n- `beforeRead(ReadPayload)`\n- `afterRead(ReadPayload)`\n\nTo register a callback for those events create a class that implements `\\Webiny\\Hrc\\EventCallbackInterface`. You will have to implement all the callback methods. Both save methods receive 1 parameter, which is `SavePayload` instance. This instance contains all the relevant data about the current cache entry that is about to be created, or has been created. Similar is for the read methods, they receive the `ReadPayload` instance, which gives you access to the current cache entry as well as the option to set the purge flag, so that the content is actually purged and not retrieved from the database. \n\nThe callback methods don't need to return anything, but since the `SavePayload` instance is an object, on `beforeSave` you can use it to manipulate your cache entry, by changing the cache content, adding or removing tags and similar. On `afterSave` callback you will get back the same object, but this is just a confirmation that the object was successfully saved.\n\n ```php\n // your Hrc instance\n $hrc = new Hrc($cacheRules, $cacheStorage, $indexStorage);\n \n // my callback handler -\u003e must implement \\Webiny\\Hrc\\EventCallbackInterface\n $handler = new MyCallbackHandler();\n \n // register callbacks\n $hrc-\u003eregisterCallback($handler); \n ```\n\n## Cache purge\n\nThere are couple of ways you can purge cache:\n\n### Purge by cache key\n\nWhen you save a cache entry, the save method will return a cache key, using that key, you can purge that particular entry:\n\n```php\n// save the cache and get back the cache key\n$key = $hrc-\u003esave('entryName', 'some content');\n// purge that cache entry\n$hrc-\u003epurgeByCacheKey($key);\n```\n\n### Purge by tag\n\nEvery cache rule has one or more tags associated. Using the same tags, you can purge multiple cache entries.\n**Note:** when providing multiple tags, only entries that match ALL tags will be purged, or to put it in different words, \nwe use logical AND condition between the tags.\n\n```php\n$hrc-\u003epurgeByTag(['tag1', 'tag2']);\n```\n\n### Purge by request\n\nThere is a flag that you can set, so that every matched cache entry, inside the current request, will automatically be purged.\n\n```php\n// first set the purge flag to true\n$hrc-\u003esetPurgeFlag(true);\n\n// once the flag is set to true, every `read` call, that has a cache hit, will actually do a purge\n$hrc-\u003eread('entryName');\n```\n\nAnother way of doing this is by sending a special request header inside your request. That header is `X-HRC-Purge` and it just \nneeds to be defined, there is no need to set any value for it. Sending that header has the same effect as setting the purge flag to true, \nbut this way you don't need to set the flag, and it's actually only valid for that particular request. \n\n#### Security\n\nBased on the previous section, you might think that there is a big risk in having that header, because everybody can purge the cache and hit your\ndatabase/backend on every request...and that's true, but there's a built-in mechanism to prevent that. \nYou can set a `control key`, so only requests that have a valid key can actually purge via the header.\n\n```php\n// set the control header\n$hrc-\u003esetControlKey('someSecretString');\n```\n\nOnce the control header is set, you now need to send a `X-HRC-Control-Key` request header, containing the same value. Only if both values\nmatch, the purge request will be executed.\n\n\n## Bugs and improvements\n\nJust report them under issues, or even better, send a pull request :)\n\n## License\n\nMIT\n\n## Resources\n\nTo run unit tests, you need to use the following command:\n```\n$ cd path/to/Webiny/Hrc/\n$ composer install\n$ phpunit\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebiny%2Fhrc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebiny%2Fhrc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebiny%2Fhrc/lists"}