{"action":"created","comment":{"url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/comments/22888005","id":22888005,"diff_hunk":"@@ -0,0 +1,147 @@\n+---\n+layout: with-comments\n+title: \"Matryoshka: A Configurable Caching Library for PHP\"\n+author: Marc Zych\n+author_url: https://github.com/marczych\n+summary: We recently open-sourced Matryoshka, a configurable caching library\n+         for PHP which makes common operations easier and allows for on-the-fly\n+         configuration.\n+---\n+\n+Like most websites, we make heavy use of caching to reduce load on our servers and decrease page response times.\n+Our caching daemon of choice is [memcached].\n+The PHP extensions are certainly usable and provide all of the core functionality that you could need.\n+However, we use a lot of patterns to make our day to day caching much easier that aren't provided by the extensions.\n+\n+In comes [Matryoshka], an open source caching library for PHP which makes common operations easier and allows for on-the-fly configuration.\n+\n+# Configurable Behavior\n+\n+Matryoshka is designed to be very configurable.\n+You can add functionality on the fly simply by wrapping an existing `Backend` with a new one.\n+We use this extensively to prefix keys, modify expiration times, disable gets, gather metrics, etc.\n+\n+Start off with a `Memcache` instance:\n+\n+{% highlight php startinline=true %}\n+// From the native extension.\n+$memcache = new Memcache();\n+$memcache->pconnect('localhost', 11211);\n+$cache = Matryoshka\\Memcache::create($memcache);\n+{% endhighlight %}\n+\n+Then prefix all the keys:\n+\n+{% highlight php startinline=true %}\n+$prefixedCache = new Matryoshka\\Prefix($cache, 'prefix-');\n+// The key ends up being \"prefix-key\".\n+$prefixedCache->set('key', 'value');\n+$value = $prefixedCache->get('key');\n+{% endhighlight %}\n+\n+Finally double all expiration times for the prefixed backend:\n+\n+{% highlight php startinline=true %}\n+$doubleExpiration = function($expiration) {\n+   return $expiration * 2;\n+};\n+$cache = new Matryoshka\\ExpirationChange($prefixedCache,\n+ $doubleExpiration);\n+// Results in an expiration time of 20 for \"prefix-key\".\n+$cache->set('key', 'value', 10);\n+{% endhighlight %}\n+\n+By using composition, caching configurations can be assembled on the fly quite easily.\n+The core API is identical for all backends so the caller doesn't need to be aware of the exact configuration.\n+Common configurations and base backends (like memcached connections) can be made into singletons or provided using dependency injection in your application.\n+Additionally, this architecture results in very maintainable and testable code because each class has exactly one job.\n+\n+# Scopes\n+\n+Cache invalidation is hard.\n+To make it easier, Matryoshka provides \"cache scopes\" to invalidate a group of keys at once.\n+[This works][Scope.php] by prefixing all keys with a unique value that is stored in the backend using the scope name.\n+\n+{% highlight php startinline=true %}\n+$cache = new Matryoshka\\Scope($memcachedBackend, 'name');\n+\n+// This results in a get request to memcached for 'scope-name'\n+// which results in something like '0fb4ae36'. This `set` call\n+// then results in a key of '0fb4ae36-key'.\n+$cache->set('key', 'value');\n+$cache->set('key2', 'value2');\n+$value = $cache->get('key'); // '0fb4ae36-key' => 'value'\n+\n+// Deleting the scope results in a new scope value e.g. 'e093f71e'.\n+$cache->deleteScope();\n+\n+// Both of these result in a miss because the scope has a new\n+// value so the keys are now prefixed with 'e093f71e-'.\n+$value = $cache->get('key'); // 'e093f71e-key' => false\n+$value2 = $cache->get('key2'); // 'e093f71e-key2' => false\n+{% endhighlight %}\n+\n+We have found this to be particularly useful for scoping keys to code deploys.\n+We simply put any caches that should be invalidated under the `'deploy'` scope which is deleted anytime we deploy code.\n+You can also have dynamic scopes such as `\"post-{$postid}\"` which can be cleared anytime a specific post is modified.\n+This is an implementation of [generational caching] which we make [heavy use of][caching thesis] throughout our application.\n+\n+Cache scopes help with cache invalidation but unfortunately don't make [naming things] any easier.\n+\n+# Helper Functions\n+\n+Matryoshka adds a few helper functions to make common operations easier.\n+`getAndSet` makes populating the cache dead simple:\n+\n+{% highlight php startinline=true %}\n+$cache = new Matryoshka\\Ephemeral();\n+// Calls the provided callback if the key is not found and sets\n+// it in the cache before returning the value to the caller.\n+$value = $cache->getAndSet('key', function() {\n+   return 'value';\n+});\n+{% endhighlight %}\n+\n+Similarly, `getAndSetMultiple` makes doing multi-gets significantly easier:\n+\n+{% highlight php startinline=true %}\n+$cache = new Matryoshka\\Ephemeral();\n+// Array of key => id. The id's can be anything used to identify\n+// the resource that the key represents.\n+$keys = [\n+   'key1' => ['id1a', 'id1b'],\n+   'key2' => ['id2a', 'id2b']\n+];","path":"_posts/2015-01-13-matryoshka-configurable-caching-library-for-php.md","position":114,"original_position":114,"commit_id":"945907d78e752b4b6cdced4dcf1cc5a39349cc51","original_commit_id":"945907d78e752b4b6cdced4dcf1cc5a39349cc51","user":{"login":"davidrans","id":806152,"avatar_url":"https://avatars.githubusercontent.com/u/806152?v=3","gravatar_id":"","url":"https://api.github.com/users/davidrans","html_url":"https://github.com/davidrans","followers_url":"https://api.github.com/users/davidrans/followers","following_url":"https://api.github.com/users/davidrans/following{/other_user}","gists_url":"https://api.github.com/users/davidrans/gists{/gist_id}","starred_url":"https://api.github.com/users/davidrans/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/davidrans/subscriptions","organizations_url":"https://api.github.com/users/davidrans/orgs","repos_url":"https://api.github.com/users/davidrans/repos","events_url":"https://api.github.com/users/davidrans/events{/privacy}","received_events_url":"https://api.github.com/users/davidrans/received_events","type":"User","site_admin":false},"body":"What do you think of using a simpler example here. `id2a` almost looks like a random hash at first glance.\r\nPerhaps:\r\n```php\r\n$keys = [\r\n   'key1' => 1,\r\n   'key2' => 2\r\n];","created_at":"2015-01-13T19:37:37Z","updated_at":"2015-01-13T19:37:37Z","html_url":"https://github.com/iFixit/itbrokeand.ifixit.com/pull/29#discussion_r22888005","pull_request_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/29","_links":{"self":{"href":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/comments/22888005"},"html":{"href":"https://github.com/iFixit/itbrokeand.ifixit.com/pull/29#discussion_r22888005"},"pull_request":{"href":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/29"}}},"pull_request":{"url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/29","id":27292551,"html_url":"https://github.com/iFixit/itbrokeand.ifixit.com/pull/29","diff_url":"https://github.com/iFixit/itbrokeand.ifixit.com/pull/29.diff","patch_url":"https://github.com/iFixit/itbrokeand.ifixit.com/pull/29.patch","issue_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/issues/29","number":29,"state":"open","locked":false,"title":"Post: Matryoshka","user":{"login":"marczych","id":1094445,"avatar_url":"https://avatars.githubusercontent.com/u/1094445?v=3","gravatar_id":"","url":"https://api.github.com/users/marczych","html_url":"https://github.com/marczych","followers_url":"https://api.github.com/users/marczych/followers","following_url":"https://api.github.com/users/marczych/following{/other_user}","gists_url":"https://api.github.com/users/marczych/gists{/gist_id}","starred_url":"https://api.github.com/users/marczych/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marczych/subscriptions","organizations_url":"https://api.github.com/users/marczych/orgs","repos_url":"https://api.github.com/users/marczych/repos","events_url":"https://api.github.com/users/marczych/events{/privacy}","received_events_url":"https://api.github.com/users/marczych/received_events","type":"User","site_admin":false},"body":"I figured it's about time I wrote a blog post about Matryoshka\r\nexplaining why we wrote it and how we use it.\r\n\r\nAny feedback is appreciated!\r\n\r\n@davidrans @danielbeardsley ","created_at":"2015-01-13T18:23:00Z","updated_at":"2015-01-13T19:37:37Z","closed_at":null,"merged_at":null,"merge_commit_sha":"203a9a77d9baf5dfd557bbc64638a7ababba07cd","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/29/commits","review_comments_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/29/comments","review_comment_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/comments/{number}","comments_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/issues/29/comments","statuses_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/statuses/945907d78e752b4b6cdced4dcf1cc5a39349cc51","head":{"label":"iFixit:post--matryoshka","ref":"post--matryoshka","sha":"945907d78e752b4b6cdced4dcf1cc5a39349cc51","user":{"login":"iFixit","id":318148,"avatar_url":"https://avatars.githubusercontent.com/u/318148?v=3","gravatar_id":"","url":"https://api.github.com/users/iFixit","html_url":"https://github.com/iFixit","followers_url":"https://api.github.com/users/iFixit/followers","following_url":"https://api.github.com/users/iFixit/following{/other_user}","gists_url":"https://api.github.com/users/iFixit/gists{/gist_id}","starred_url":"https://api.github.com/users/iFixit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iFixit/subscriptions","organizations_url":"https://api.github.com/users/iFixit/orgs","repos_url":"https://api.github.com/users/iFixit/repos","events_url":"https://api.github.com/users/iFixit/events{/privacy}","received_events_url":"https://api.github.com/users/iFixit/received_events","type":"Organization","site_admin":false},"repo":{"id":5125959,"name":"itbrokeand.ifixit.com","full_name":"iFixit/itbrokeand.ifixit.com","owner":{"login":"iFixit","id":318148,"avatar_url":"https://avatars.githubusercontent.com/u/318148?v=3","gravatar_id":"","url":"https://api.github.com/users/iFixit","html_url":"https://github.com/iFixit","followers_url":"https://api.github.com/users/iFixit/followers","following_url":"https://api.github.com/users/iFixit/following{/other_user}","gists_url":"https://api.github.com/users/iFixit/gists{/gist_id}","starred_url":"https://api.github.com/users/iFixit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iFixit/subscriptions","organizations_url":"https://api.github.com/users/iFixit/orgs","repos_url":"https://api.github.com/users/iFixit/repos","events_url":"https://api.github.com/users/iFixit/events{/privacy}","received_events_url":"https://api.github.com/users/iFixit/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/iFixit/itbrokeand.ifixit.com","description":"iFixit Developer Blog: Sometimes things break, and then we've got to fix them.","fork":false,"url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com","forks_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/forks","keys_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/keys{/key_id}","collaborators_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/teams","hooks_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/hooks","issue_events_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/issues/events{/number}","events_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/events","assignees_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/assignees{/user}","branches_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/branches{/branch}","tags_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/tags","blobs_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/git/refs{/sha}","trees_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/git/trees{/sha}","statuses_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/statuses/{sha}","languages_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/languages","stargazers_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/stargazers","contributors_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/contributors","subscribers_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/subscribers","subscription_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/subscription","commits_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/commits{/sha}","git_commits_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/git/commits{/sha}","comments_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/comments{/number}","issue_comment_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/issues/comments/{number}","contents_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/contents/{+path}","compare_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/compare/{base}...{head}","merges_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/merges","archive_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/downloads","issues_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/issues{/number}","pulls_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls{/number}","milestones_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/milestones{/number}","notifications_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/labels{/name}","releases_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/releases{/id}","created_at":"2012-07-20T17:09:34Z","updated_at":"2014-12-23T03:39:28Z","pushed_at":"2015-01-13T19:23:11Z","git_url":"git://github.com/iFixit/itbrokeand.ifixit.com.git","ssh_url":"git@github.com:iFixit/itbrokeand.ifixit.com.git","clone_url":"https://github.com/iFixit/itbrokeand.ifixit.com.git","svn_url":"https://github.com/iFixit/itbrokeand.ifixit.com","homepage":"http://itbrokeand.ifixit.com/","size":822,"stargazers_count":3,"watchers_count":3,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":3,"mirror_url":null,"open_issues_count":3,"forks":3,"open_issues":3,"watchers":3,"default_branch":"gh-pages"}},"base":{"label":"iFixit:gh-pages","ref":"gh-pages","sha":"2fc0cc7724a78474abf46a92f16f9dfa620f4e17","user":{"login":"iFixit","id":318148,"avatar_url":"https://avatars.githubusercontent.com/u/318148?v=3","gravatar_id":"","url":"https://api.github.com/users/iFixit","html_url":"https://github.com/iFixit","followers_url":"https://api.github.com/users/iFixit/followers","following_url":"https://api.github.com/users/iFixit/following{/other_user}","gists_url":"https://api.github.com/users/iFixit/gists{/gist_id}","starred_url":"https://api.github.com/users/iFixit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iFixit/subscriptions","organizations_url":"https://api.github.com/users/iFixit/orgs","repos_url":"https://api.github.com/users/iFixit/repos","events_url":"https://api.github.com/users/iFixit/events{/privacy}","received_events_url":"https://api.github.com/users/iFixit/received_events","type":"Organization","site_admin":false},"repo":{"id":5125959,"name":"itbrokeand.ifixit.com","full_name":"iFixit/itbrokeand.ifixit.com","owner":{"login":"iFixit","id":318148,"avatar_url":"https://avatars.githubusercontent.com/u/318148?v=3","gravatar_id":"","url":"https://api.github.com/users/iFixit","html_url":"https://github.com/iFixit","followers_url":"https://api.github.com/users/iFixit/followers","following_url":"https://api.github.com/users/iFixit/following{/other_user}","gists_url":"https://api.github.com/users/iFixit/gists{/gist_id}","starred_url":"https://api.github.com/users/iFixit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iFixit/subscriptions","organizations_url":"https://api.github.com/users/iFixit/orgs","repos_url":"https://api.github.com/users/iFixit/repos","events_url":"https://api.github.com/users/iFixit/events{/privacy}","received_events_url":"https://api.github.com/users/iFixit/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/iFixit/itbrokeand.ifixit.com","description":"iFixit Developer Blog: Sometimes things break, and then we've got to fix them.","fork":false,"url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com","forks_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/forks","keys_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/keys{/key_id}","collaborators_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/teams","hooks_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/hooks","issue_events_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/issues/events{/number}","events_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/events","assignees_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/assignees{/user}","branches_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/branches{/branch}","tags_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/tags","blobs_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/git/refs{/sha}","trees_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/git/trees{/sha}","statuses_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/statuses/{sha}","languages_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/languages","stargazers_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/stargazers","contributors_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/contributors","subscribers_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/subscribers","subscription_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/subscription","commits_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/commits{/sha}","git_commits_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/git/commits{/sha}","comments_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/comments{/number}","issue_comment_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/issues/comments/{number}","contents_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/contents/{+path}","compare_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/compare/{base}...{head}","merges_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/merges","archive_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/downloads","issues_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/issues{/number}","pulls_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls{/number}","milestones_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/milestones{/number}","notifications_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/labels{/name}","releases_url":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/releases{/id}","created_at":"2012-07-20T17:09:34Z","updated_at":"2014-12-23T03:39:28Z","pushed_at":"2015-01-13T19:23:11Z","git_url":"git://github.com/iFixit/itbrokeand.ifixit.com.git","ssh_url":"git@github.com:iFixit/itbrokeand.ifixit.com.git","clone_url":"https://github.com/iFixit/itbrokeand.ifixit.com.git","svn_url":"https://github.com/iFixit/itbrokeand.ifixit.com","homepage":"http://itbrokeand.ifixit.com/","size":822,"stargazers_count":3,"watchers_count":3,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":3,"mirror_url":null,"open_issues_count":3,"forks":3,"open_issues":3,"watchers":3,"default_branch":"gh-pages"}},"_links":{"self":{"href":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/29"},"html":{"href":"https://github.com/iFixit/itbrokeand.ifixit.com/pull/29"},"issue":{"href":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/issues/29"},"comments":{"href":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/issues/29/comments"},"review_comments":{"href":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/29/comments"},"review_comment":{"href":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/pulls/29/commits"},"statuses":{"href":"https://api.github.com/repos/iFixit/itbrokeand.ifixit.com/statuses/945907d78e752b4b6cdced4dcf1cc5a39349cc51"}}}}