diff --git a/README.md b/README.md index f2fe095..2b33231 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2FItalyStrap%2Fstorage%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/ItalyStrap/storage/master) Storage API for WordPress the OOP way +This package provides API for WordPress Transients, Cache, Options, and ThemeMods. ## Table Of Contents @@ -32,11 +33,16 @@ This package adheres to the [SemVer](http://semver.org/) specification and will ### What is the purpose of this library? The first idea is to have a common API for all the storage system in WordPress, like [WordPress Cache API](https://developer.wordpress.org/reference/classes/wp_object_cache/), [WordPress Transients API](https://developer.wordpress.org/apis/transients/), [WordPress Options API](https://developer.wordpress.org/apis/options/), [WordPress Theme Mods API](https://codex.wordpress.org/Theme_Modification_API) and so on. +These libraries try to uniform some WordPress API that have similar behavior under the same umbrella. +This means that you could extend the API of this library to create your own storage system. +To name a few you can use this API for metadata, post meta, user meta, and so on. ### Why the Storage word for the library? In this case the `Storage` word is used to refer to some data stored in a DB table or in a file as well as in memory like `array` or `object`, but in those case you may need to create your own class. -Now in this library you can find API for `Options` (or similar) and API for `Cache` (or similar), you may ask why :-D, the first reason is that I already have a library called [Cache](https://github.com/ItalyStrap/cache) used to implements the PSR-6 and PSR-16, so I don't want to duplicate the name, and the second reason group all the storage system you could find in WordPress in a single library. + +Now in this library you can find API for `Options` (or similar) and API for `Cache` (or similar), you may ask why :-D, the first reason is that I already have a library called [Cache](https://github.com/ItalyStrap/cache) used to implements the PSR-6 and PSR-16, so I don't want to duplicate the name, and the second reason is to group all the similar storage system you could find in WordPress. + Right now the interfaces used in this library are placed in another library called [Common](https://github.com/ItalyStrap/common) because I want to use them in other libraries. ### Why the OOP way? @@ -45,23 +51,27 @@ With this library you can inject the storage system you need to use in your clas This API takes some concept from the [PSR-16](https://www.php-fig.org/psr/psr-16/), and also it could be applied to other kind of storage system in WordPress not only for the Transients, Options, Mods, etc., to naming a few you can use this API for metadata, post meta, user meta, and so on. -If you need a PSR-6 or PSR-16 implementation for WordPress transient and cache you can use the [ItalyStrap\Cache](https://github.com/ItalyStrap/cache) package and this library used as a driver. +If you need a PSR-6 or PSR-16 implementation for WordPress transient and cache you can use the [ItalyStrap\Cache](https://github.com/ItalyStrap/cache) package and this library used as a driver, remember that you can use only the driver that extend the Cache API and not the Store API because of the missing TTL in the Store API. Think of this like a wrapper around the WordPress Transients API, Options API, Mods API, etc. but with some differences. ### Differences with the WordPress Transients API, Options API, Mods API, etc. -The most important difference is the return value of the `::get()` method, in the WordPress Transients API, Cache API, Option API and so on the `\get_*()` functions return `false` if the result does not exist or has expired, in this API the `::get()` method return `null` if the result does not exist or has expired. +The most important difference is the return value of the `Class::get()` method, in the WordPress Transients API, Cache API, Option API and so on the `\get_*()` functions return `false` if the result does not exist or has expired, in this API the `Class::get()` method return `null` if the result does not exist or has expired. I'm not a fan of the `null` value but this adheres to the PSR-16 specification where no value means `null` and `false` could be a valid value. ### The expiration time +The `StoreInterface::class` has no expiration time so think of it like a forever storage system that never expire. + +If you need to store a value for a specific time you should use the `CacheInterface::class`. + The second difference is that if you provide a `0` value as the expiration time it means you will store the value per `0` second and not `forever` like WordPress does. Maybe you would ask why? -Because if you provide a `0` second to the expiration time you are telling to the library to store the value for `0` second and not forever (f**k), it does not make any sense to use the `0` value as `forever`. +Because if you provide a `0` second to the expiration time you are telling to the library to store the value for `0` second and not forever (f**k), it does not make any sense to use the `0` value as `forever` (if you have a very good reason to do that please open an issue, and we will discuss about it). If you want to store a value forever just use the `::set()` method without the expiration time at all or pass `null` as the expiration time, internally the library will set expiration time to `1 year`, and `1 year` should be enough for a `forever` value. ### The expiration time and the Cache API @@ -71,10 +81,10 @@ If you install some other plugin that provide to you another implementation of t ### Why the `::update()` method exists? -You may ask why the `::update()` method exists, in fact you could use the `::set()` method to update a value, there was only a `\wp_cache_replace()` function in the WordPress Cache API that return false if value does not exist and `\update_option()` function that instead create a value if does not exist, so I decided to create the `::update()` method to have the same behavior of the Options API. +You may ask why the `::update()` method exists, in fact you could use the `::set()` method to update a value, there was only a `\wp_cache_replace()` function in the WordPress Cache API that return false if value does not exist and `\update_option()` function that instead create a value if it does not exist, so I decided to create the `::update()` method to have the same behavior of the Options API to all other storage system. It is a bad decision? -I left to you to decide :-D +I leave the decision to you :-D I think this method is not necessary, but I decided to leave it there for the sake of completeness. ## Basic Usage @@ -169,6 +179,8 @@ From [WordPress Transients API docs](https://codex.wordpress.org/Transients_API) ### Timer constants +In the WordPress environment you can use the following constants to set the expiration time if you want. + ```php const MINUTE_IN_SECONDS = 60; // (seconds) const HOUR_IN_SECONDS = 60 * MINUTE_IN_SECONDS; @@ -190,6 +202,42 @@ if (false === ($special_data_to_save = \get_transient('special_data_to_save'))) } ``` +And this is the same example above but with this library. + +```php +declare(strict_types=1); + +namespace Your\Namespace; + +use ItalyStrap\Storage\Transient; +$transient = new Transient(); + +if (false === ($special_data_to_save = $transient->get('special_data_to_save'))) { + // It wasn't there, so regenerate the data and save the transient + $special_data_to_save = ['some-key' => 'come value']; + $transient->set('special_data_to_save', $special_data_to_save, 12 * HOUR_IN_SECONDS); +} +``` + +The same is with the `Cache::class` class. + + +```php +declare(strict_types=1); + +namespace Your\Namespace; + +use ItalyStrap\Storage\Cache; + +$cache = new Cache(); + +if (false === ($special_data_to_save = $cache->get('special_data_to_save'))) { + // It wasn't there, so regenerate the data and save the transient + $special_data_to_save = ['some-key' => 'come value']; + $cache->set('special_data_to_save', $special_data_to_save, 12 * HOUR_IN_SECONDS); +} +``` + ### Transient API ```php @@ -234,23 +282,6 @@ null === $transient->get('mod_1'); // true null === $transient->get('mod_2'); // true ``` - -```php -declare(strict_types=1); - -namespace Your\Namespace; - -use ItalyStrap\Storage\Transient; - -$transient = new Transient(); - -if (false === ($special_data_to_save = $transient->get('special_data_to_save'))) { - // It wasn't there, so regenerate the data and save the transient - $special_data_to_save = ['some-key' => 'come value']; - $transient->set('special_data_to_save', $special_data_to_save, 12 * HOUR_IN_SECONDS); -} -``` - ### Cache API ```php @@ -295,25 +326,49 @@ null === $cache->get('mod_1'); // true null === $cache->get('mod_2'); // true ``` +## Advanced Usage + +### How to crete keyword for the Cache API and Transients API + +It is good idea to prefix the keyword with some other string like your namespace, your class name, method name, and so on, you could also use constant, this will help you to avoid conflicts with other plugins or themes. + +A simple example: ```php declare(strict_types=1); namespace Your\Namespace; -use ItalyStrap\Storage\Cache; +use ItalyStrap\Storage\CacheInterface;use phpDocumentor\Reflection\Types\Mixed_; $cache = new Cache(); -if (false === ($special_data_to_save = $cache->get('special_data_to_save'))) { - // It wasn't there, so regenerate the data and save the transient - $special_data_to_save = ['some-key' => 'come value']; - $cache->set('special_data_to_save', $special_data_to_save, 12 * HOUR_IN_SECONDS); +$your_class = new class($cache) { + private $cache; + private $prefix = 'your_namespace'; + + public function __construct(CacheInterface $cache) + { + $this->cache = $cache; + } + + public function getSomething(): mixed + { + $keyword = $this->prefix . __CLASS__ . __METHOD__; + + if (false === ($data = $this->cache->get($keyword))) { + // It wasn't there, so regenerate the data and save the transient + $data = ['some-key' => 'come value']; + $this->cache->set($keyword, $data, 12 * HOUR_IN_SECONDS); + } + + return $data; + } +}; + } ``` -## Advanced Usage - ## Contributing All feedback / bug reports / pull requests are welcome. diff --git a/composer.json b/composer.json index 5dff564..2bb2eda 100644 --- a/composer.json +++ b/composer.json @@ -74,6 +74,9 @@ "psalm": [ "@php ./vendor/bin/psalm --no-cache" ], + "clean": [ + "@php ./vendor/bin/codecept clean" + ], "tests": [ "@unit", "@wpunit" diff --git a/src/Cache.php b/src/Cache.php index fbc099b..01f9ca7 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -17,10 +17,17 @@ class Cache implements CacheInterface, ClearableInterface, IncrDecrInterface { use NormalizeTtlTrait; + private string $group; + + public function __construct(string $group = 'default') + { + $this->group = $group; + } + public function set(string $key, $value, ?int $ttl = null): bool { $ttl = $this->parseTtl($ttl); - return \wp_cache_set($key, $value, 'default', $ttl); + return \wp_cache_set($key, $value, $this->group, $ttl); } public function get(string $key, $default = null) @@ -28,7 +35,7 @@ public function get(string $key, $default = null) /** * @var mixed $value */ - $value = \wp_cache_get($key, 'default'); + $value = \wp_cache_get($key, $this->group); if ($value === 0) { return $value; } @@ -43,7 +50,7 @@ public function update(string $key, $value, ?int $ttl = null): bool public function delete(string $key): bool { - return \wp_cache_delete($key, 'default'); + return \wp_cache_delete($key, $this->group); } /** @@ -55,7 +62,7 @@ public function delete(string $key): bool public function increment(string $key, int $offset = 1) { /** @psalm-suppress MixedReturnStatement */ - return \wp_cache_incr($key, $offset, 'default'); + return \wp_cache_incr($key, $offset, $this->group); } /** @@ -67,7 +74,7 @@ public function increment(string $key, int $offset = 1) public function decrement(string $key, int $offset = 1) { /** @psalm-suppress MixedReturnStatement */ - return \wp_cache_decr($key, $offset, 'default'); + return \wp_cache_decr($key, $offset, $this->group); } public function clear(): bool @@ -77,9 +84,9 @@ public function clear(): bool public function setMultiple(iterable $values, ?int $ttl = null): bool { - $newValues = $this->convertArray($values); + $newValues = $this->iteratorToArray($values); $ttl = $this->parseTtl($ttl); - foreach (\wp_cache_set_multiple($newValues, 'default', $ttl) as $value) { + foreach (\wp_cache_set_multiple($newValues, $this->group, $ttl) as $value) { if (!$value) { return false; } @@ -94,19 +101,19 @@ public function setMultiple(iterable $values, ?int $ttl = null): bool */ public function getMultiple(iterable $keys, $default = null): iterable { - $newValues = $this->convertArray($keys); + $newValues = $this->iteratorToArray($keys); /** * @var mixed $value */ - foreach (\wp_cache_get_multiple($newValues, 'default') as $key => $value) { + foreach (\wp_cache_get_multiple($newValues, $this->group) as $key => $value) { yield $key => $value ?: $default; } } public function deleteMultiple(iterable $keys): bool { - $newValues = $this->convertArray($keys); - $result = \wp_cache_delete_multiple($newValues, 'default'); + $newValues = $this->iteratorToArray($keys); + $result = \wp_cache_delete_multiple($newValues, $this->group); foreach ($result as $value) { if (!$value) { return false; @@ -120,7 +127,7 @@ public function deleteMultiple(iterable $keys): bool * @param iterable $values * @return array */ - private function convertArray(iterable $values): array + private function iteratorToArray(iterable $values): array { return $values instanceof \Traversable ? \iterator_to_array($values) : $values; }