diff --git a/README.md b/README.md index 48f15a8f..f8b1c03e 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ $storage->setItem('foo', 'bar'); We provide scripts for benchmarking laminas-cache using the [PHPBench](https://github.com/phpbench/phpbench) framework; these can be -found in the `benchmark/` directory. +found in the `benchmark/` directory of each storage adapter. To execute the benchmarks you can run the following command: diff --git a/docs/book/v4/application-integration/usage-in-a-laminas-mvc-application.md b/docs/book/v4/application-integration/usage-in-a-laminas-mvc-application.md new file mode 100644 index 00000000..68712387 --- /dev/null +++ b/docs/book/v4/application-integration/usage-in-a-laminas-mvc-application.md @@ -0,0 +1,181 @@ +# Usage in a laminas-mvc Application + +The following example shows _one_ potential use case of laminas-cache within a laminas-mvc based application. +The example uses a module, a controller and shows the resolving of dependencies of the controller by configuration. + +## Preparation + +Before starting, make sure laminas-cache is [installed and configured](../installation.md). + +> MISSING: **Installation Requirements** +> laminas-cache is shipped without a specific cache adapter to allow free choice of storage backends and their dependencies. +> So make sure that the required adapters are installed. +> +> The following example used the [filesystem adapter of laminas-cache](../storage/adapter.md#filesystem-adapter): +> +> ```bash +> $ composer require laminas/laminas-cache-storage-adapter-filesystem +> ``` + +## Configure Cache + +To configure the cache in a laminas-mvc based application, use either application or module configuration (such as `config/autoload/*.global.php` or `module/Application/config/module.config.php`, respectively), and define the configuration key `caches`. + +In this example, the global configuration is used and a separate file is created for the cache configuration. +Create a configuration file with name like `config/autoload/cache.global.php` and it will [automatically be included](https://docs.laminas.dev/tutorials/advanced-config/#environment-specific-application-configuration): + +```php +return [ + 'caches' => [ + 'default-cache' => [ + 'adapter' => Laminas\Cache\Storage\Adapter\Filesystem::class, + 'options' => [ + 'cache_dir' => __DIR__ . '/../../data/cache', + ], + ], + ], +]; +``` + +The factory `Laminas\Cache\Service\StorageCacheAbstractServiceFactory` uses the configuration, searches for the configuration key `caches` and creates the storage adapters using the discovered configuration. + +## Create Controller + +[Create a controller class](https://docs.laminas.dev/laminas-mvc/quick-start/#create-a-controller) and inject the cache with the interface for all cache storage adapters via the constructor, e.g. `module/Application/Controller/IndexController.php`: + +```php +namespace Application\Controller; + +use Laminas\Cache\Storage\StorageInterface; +use Laminas\Mvc\Controller\AbstractActionController; + +final class IndexController extends AbstractActionController +{ + public function __construct( + private readonly StorageInterface $cache + ) {} + + public function indexAction(): array + { + if (! $this->cache->hasItem('example')) { + $this->cache->addItem('example', 'value'); + } + + echo $this->cache->getItem('example') // value; + + // … + + return []; + } +} +``` + +## Register Controller + +To [register the controller](https://docs.laminas.dev/laminas-mvc/quick-start/#create-a-route) for the application, extend the configuration of the module. +Add the following lines to the module configuration file, e.g. `module/Application/config/module.config.php`: + +

+namespace Application;
+
+use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
+
+return [
+    'controllers' => [
+        'factories' => [
+            Controller\IndexController::class => ConfigAbstractFactory::class,
+        ],
+    ],
+    // …
+];
+
+ +The example uses the [config factory from laminas-servicemanager](https://docs.laminas.dev/laminas-servicemanager/config-abstract-factory/) which allows any string to be used to fetch a service from the application service container, like the name of the configured cache: `default-cache`. + +This means that the factory [searches for an appropriate configuration](https://docs.laminas.dev/laminas-servicemanager/config-abstract-factory/#configuration) to create the controller and to resolve the constructor dependencies for the controller class. + +### Add Factory Configuration For Controller + +Extend the module configuration file to add the configuration for the controller. +Use the name of the cache (`default-cache`), which was previously defined in the configuration of the caches, to retrieve the related cache storage instance: + +

+namespace Application;
+
+use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
+
+return [
+    'controllers' => [
+        'factories' => [
+            Controller\IndexController::class => ConfigAbstractFactory::class,
+        ],
+    ],
+    ConfigAbstractFactory::class => [
+        Controller\IndexController::class => [
+            'default-cache',
+        ],
+    ],
+    // …
+];
+
+ +## Using Multiple Caches + +The use more than one cache backend, the factory `Laminas\Cache\Service\StorageCacheAbstractServiceFactory` allows to define multiple cache storages. + +Extend the cache configuration in `config/autoload/cache.global.php` and add more cache adapters: + +

+return [
+    'caches' => [
+        'default-cache' => [
+            'adapter' => Laminas\Cache\Storage\Adapter\Filesystem::class,
+            'options' => [
+                'cache_dir' => __DIR__ . '/../../data/cache',
+            ],
+        ],
+        'secondary-cache' => [
+            'adapter' => Laminas\Cache\Storage\Adapter\Memory::class,
+        ],
+        'dummy-cache' => [
+            'adapter' => Laminas\Cache\Storage\Adapter\BlackHole::class,
+        ],
+    ],
+];
+
+ +MISSING: **Installation Requirements** +Make sure that the [used storage adapters are installed](#preparation): +```bash +$ composer require laminas/laminas-cache-storage-adapter-memory laminas/laminas-cache-storage-adapter-blackhole +``` + +### Change Used Adapter for Controller + +To use a different cache adapter for the controller, change the related module configuration and use one of the previously defined names: + +

+namespace Application;
+
+use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
+
+return [
+    'controllers' => [
+        'factories' => [
+            Controller\IndexController::class => ConfigAbstractFactory::class,
+        ],
+    ],
+    ConfigAbstractFactory::class => [
+        Controller\IndexController::class => [
+            'dummy-cache',
+        ],
+    ],
+    // …
+];
+
+ +## Learn More + +- [Storage Adapters](../storage/adapter.md) +- [Environment-Specific Application Configuration](https://docs.laminas.dev/tutorials/advanced-config/#environment-specific-application-configuration) +- [Configuration-based Abstract Factory](https://docs.laminas.dev/laminas-servicemanager/config-abstract-factory/) diff --git a/docs/book/v4/installation.md b/docs/book/v4/installation.md new file mode 100644 index 00000000..be1198c9 --- /dev/null +++ b/docs/book/v4/installation.md @@ -0,0 +1,5 @@ +# This Is Only a Placeholder + +The content of this page can be found under: + +https://github.com/laminas/documentation-theme/blob/master/theme/pages/installation.html diff --git a/docs/book/v4/migration/to-version-4.md b/docs/book/v4/migration/to-version-4.md new file mode 100644 index 00000000..ad0646f4 --- /dev/null +++ b/docs/book/v4/migration/to-version-4.md @@ -0,0 +1,45 @@ +# Migration to Version 4.0 + +Finally, native types **everywhere**. With v4.0, `laminas-cache` depends on `laminas-servicemanager` v4 which already introduced full native types and thus, cache now has native types as well. +Along with these changes, we also decided to remove and/or enhance some features to make the usage of this component more user-friendly. +So instead of working with metadata arrays, a new `MetadataCapableInterface` was introduced which provides a generic interface for storage adapters to tell both IDEs and static analysers to understand what metadata instances are returned for which storage adapter. +This allows per-storage Metadata which can differ depending on the storage being used. + +## Checklist + +1. Ensure you are on latest `laminas/laminas-cache` v3 +2. Ensure you are on latest `laminas/laminas-cache-storage-adapter-*` version (might differ) +3. Verify that you are **not** using one of the following methods + 1. `StorageInterface#incrementItem` (no replacement available, should be implemented in userland code) + 2. `StorageInterface#incrementItems` (no replacement available, should be implemented in userland code) + 3. `StorageInterface#decrementItem` (no replacement available, should be implemented in userland code) + 4. `StorageInterface#decrementItems` (no replacement available, should be implemented in userland code) +4. Verify that you are **not** using `supportedMetadata` capability (use `MetadataCapableInterface#getMetadata` instead) +5. Verify that you are **not** using `KeyListIterator` with mode `CURRENT_AS_METADATA` (use the returned `key` instead and pass it to the `MetadataCapable` storage adapter (**NOTE: not all adapters do implement `MetadataCapableInterface`**) +6. If you use the `Serializer` plugin + 1. Verify that if you pass a `string` as `serializer` option, you do not directly depend on the return value of `PluginOptions#getSerializer` (method will return `string` instead of instantiating a new `SerializerInterface` instance). The plugin itself can still handle `string` and an instance of `SerializerInterface` as in previous versions +7. If you provide own plugins, storage adapters, pattern, you have to upgrade to v4 and update all method/argument/property (return-) types according to the updated versions. Check out [rector](https://github.com/rectorphp/rector) which can help with this kind of migration +8. If you are handling `Laminas\Cache\Exception\MissingKeyException`, you can remove that code as the exception does not exist anymore +9. Check if you use `ObjectCache` pattern, that your code does not expect an instance of `CallbackCache` to be passed + +## New Features + +- Every adapter which supports `metadata` now implements `MetadataCapableInterface` and provides a dedicated object containing all the metadata values it supports +- Adds support for `psr/cache` and `psr/simple-cache` v2 & v3 + +## Removed Classes + +- `Laminas\Cache\Exception\MissingKeyException` + +## Breaking Changes + +- `AbstractAdapter` and `StorageInterface` are not aware of the methods `getMetadata` anymore. These were moved to the new `MetadataCapableInterface` +- `Capabilities` do not provide `supportedMetadata` anymore. The supported metadata is tied to the used storage adapter and thus, was already requiring projects to explicitly know the exact implementation of the cache backend in case of using these metadatas anyway +- `KeyListIterator` and the corresponding `IteratorInterface` does not provide the `mode` `CURRENT_AS_METADATA` anymore +- `PluginOptions#getSerializer` does not create a serializer anymore if a `string` option was passed, instead, the `string` is returned +- Increment and decrement feature was removed from `StorageInterface`, so there is no more `StorageInterface#incrementItem`, `StorageInterface#decrementItem`, `StorageInterface#decrementItems` and `StorageInterface#incrementItems` + - this also removes `incrementItem`, `incrementItems`, `decrementItem`, `derementItems` events (`pre`, `post` and `exception`) +- Every method now has native return types +- Every property now has native types +- Every method argument now has native types +- `ObjectCache` does not inherit the `CallbackCache` pattern anymore diff --git a/docs/book/v4/pattern/callback-cache.md b/docs/book/v4/pattern/callback-cache.md new file mode 100644 index 00000000..07bd1901 --- /dev/null +++ b/docs/book/v4/pattern/callback-cache.md @@ -0,0 +1,87 @@ +# CallbackCache + +The callback cache pattern caches the results of arbitrary PHP callables. + +## Quick Start + +```php +use Laminas\Cache\Pattern\CallbackCache; +use Laminas\Cache\Pattern\PatternOptions; + +// Or the equivalent manual instantiation: +$callbackCache = new CallbackCache( + $storage, + new PatternOptions([ + 'cache_output' => true, + ]) +); +``` + +> ### Storage Adapter +> +> The `$storage` adapter can be any adapter which implements the `StorageInterface`. Check out the [Pattern Quick Start](./intro.md#quick-start)-Section for a standard adapter which can be used here. + +## Configuration Options + +| Option | Data Type | Default Value | Description | +|----------------|---------------------------------------------------------|---------------|------------------------------------------------------------------| +| `storage` | `string\|array\|Laminas\Cache\Storage\StorageInterface` | none | **deprecated** Adapter used for reading and writing cached data. | +| `cache_output` | `bool` | `true` | Whether or not to cache callback output. | + +## Examples + +### Instantiating the Callback Cache Pattern + +```php +use Laminas\Cache\Pattern\CallbackCache; + +$callbackCache = new CallbackCache($storage); +``` + +## Available Methods + +In addition to the methods defined in the `PatternInterface` and the `StorageCapableInterface`, this +implementation provides the following methods. + +```php +namespace Laminas\Cache\Pattern; + +use Laminas\Cache\Exception; + +class CallbackCache extends AbstractStorageCapablePattern +{ + /** + * Call the specified callback or get the result from cache + * + * @param callable $callback A valid callback + * @param array $args Callback arguments + * @return mixed Result + * @throws Exception\RuntimeException if invalid cached data + * @throws \Exception + */ + public function call(callable $callback, array $args = []): mixed; + + /** + * Intercept method overloading; proxies to call() + * + * @param callable-string $function Function name to call + * @param array $args Function arguments + * @return mixed + * @throws Exception\RuntimeException + * @throws \Exception + */ + public function __call(string $function, array $args): mixed; + + /** + * Generate a unique key in base of a key representing the callback part + * and a key representing the arguments part. + * + * @param callable $callback A valid callback + * @param array $args Callback arguments + * @return non-empty-string + * @throws Exception\RuntimeException + * @throws Exception\InvalidArgumentException + */ + public function generateKey(callable $callback, array $args = []): string; +} +``` diff --git a/docs/book/v4/pattern/capture-cache.md b/docs/book/v4/pattern/capture-cache.md new file mode 100644 index 00000000..b67d5b3a --- /dev/null +++ b/docs/book/v4/pattern/capture-cache.md @@ -0,0 +1,138 @@ +# CaptureCache + +The `CaptureCache` pattern is useful for generating static resources to return +via HTTP request. When used in such a fashion, the web server needs to be +configured to run a PHP script generating the requested resource so that +subsequent requests for the same resource can be shipped without calling PHP +again. + +This pattern comes with basic logic for managing generated resources. + +## Quick Start + +For use with an Apache 404 handler extend the Apache configuration, e.g. +`.htdocs`: + +```apacheconf +ErrorDocument 404 /index.php +``` + +And add the cache to the related application script, e.g. `index.php`: + +```php +use Laminas\Cache\Pattern\CaptureCache; +use Laminas\Cache\Pattern\PatternOptions; + +$capture = new CaptureCache( + new PatternOptions([ + 'public_dir' => __DIR__, + ]) +); + +// Start capturing all output, excluding headers, and write to the public +// directory: +$capture->start(); + +// Don't forget to change the HTTP response code +header('Status: 200', true, 200); + +// do stuff to dynamically generate output +``` + +## Configuration Options + +| Option | Data Type | Default Value | Description | +|-------------------|--------------|-----------------------------|---------------------------------------------------------------------| +| `public_dir` | `string` | none | Location of the public web root directory in which to write output. | +| `index_filename` | `string` | "index.html" | The name of the index file if only a directory was requested. | +| `file_locking` | `bool` | `true` | Whether or not to lock output files when writing. | +| `file_permission` | `int\|false` | `0600` (`false` on Windows) | Default permissions for generated output files. | +| `dir_permission` | `int\|false` | `0700` (`false` on Windows) | Default permissions for generated output directories. | +| `umask` | `int\|false` | `false` | Whether or not to umask generated output files / directories. | + +## Examples + +### Scaling Images in the Web Root + +Using the following Apache 404 configuration: + +```apacheconf +# .htdocs +ErrorDocument 404 /index.php +``` + +Use the following script: + +```php +// index.php +use Laminas\Cache\Pattern\CaptureCache; +use Laminas\Cache\Pattern\PatternOptions; + +$capture = new CaptureCache( + new PatternOptions([ + 'public_dir' => __DIR__, + ]) +); +``` + +## Available Methods + +In addition to the methods exposed in `PatternInterface`, this implementation +exposes the following methods. + +```php +namespace Laminas\Cache\Pattern; + +use Laminas\Cache\Exception; + +class CaptureCache extends AbstractPattern +{ + /** + * Starts capturing. + */ + public function start(string|null $pageId = null): void; + + /** + * Write a page to the requested path. + * + * @throws Exception\LogicException + */ + public function set(string $content, string|null $pageId = null): void; + + /** + * Retrieve a generated page from the cache. + * + * @throws Exception\LogicException + * @throws Exception\RuntimeException + */ + public function get(string|null $pageId = null): string|null; + + /** + * Check if a cache exists for the given page. + * + * @throws Exception\LogicException + * @return bool + */ + public function has(string|null $pageId = null): bool; + + /** + * Remove a page from the cache. + * + * @throws Exception\LogicException + * @throws Exception\RuntimeException + */ + public function remove(string|null $pageId = null): bool; + + /** + * Clear cached pages that match the specified glob pattern. + * + * @throws Exception\LogicException + */ + public function clearByGlob(string $pattern = '**'): void; + + /** + * Returns the generated file name. + */ + public function getFilename(string|null $pageId = null): string; +} +``` diff --git a/docs/book/v4/pattern/intro.md b/docs/book/v4/pattern/intro.md new file mode 100644 index 00000000..bcb22975 --- /dev/null +++ b/docs/book/v4/pattern/intro.md @@ -0,0 +1,77 @@ +# Introduction + +Cache patterns are configurable objects that solve known performance +bottlenecks. Each should be used only in the specific situations they are +designed to address. For example, you can use the `CallbackCache` or +`ObjectCache` patterns to cache method and function calls; to +cache output generation, the `OutputCache` pattern could assist. + +All cache patterns implement `Laminas\Cache\Pattern\PatternInterface`, and most +extend the abstract class `Laminas\Cache\Pattern\AbstractPattern`, which provides +common logic. + +Configuration is provided via the `Laminas\Cache\Pattern\PatternOptions` class, +which can be instantiated with an associative array of options passed to the +constructor. To configure a pattern object, you can provide a +`Laminas\Cache\Pattern\PatternOptions` instance to the `setOptions()` method, or +provide your options (either as an associative array or `PatternOptions` +instance) to the second argument of the factory. + +It's also possible to use a single instance of +`Laminas\Cache\Pattern\PatternOptions` and pass it to multiple pattern objects. + +## Quick Start + +Pattern objects can be created +by instantiating one of the `Laminas\Cache\Pattern\*Cache` classes. + +> ### Standard Storage Adapter for Documentation +> +> A cache adapter needs a storage adapter. To be able to follow the examples in the documentation, the [adapter for the filesystem](https://docs.laminas.dev/laminas-cache/storage/adapter/#filesystem-adapter) or the [BlackHole adapter](https://docs.laminas.dev/laminas-cache/storage/adapter/#blackhole-adapter) can be used, for example. +> +> ```php +> $storage = new Laminas\Cache\Storage\Adapter\Filesystem(); +> // or +> $storage = new Laminas\Cache\Storage\Adapter\BlackHole(); +> ``` + +```php +use Laminas\Cache\Pattern\CallbackCache; +use Laminas\Cache\Pattern\PatternOptions; + +$callbackCache = new CallbackCache( + $storage, + new PatternOptions() +); +``` + +## Available Methods + +The following methods are implemented by every cache pattern. +Please read documentation of specific patterns to get more information. + +```php +namespace Laminas\Cache\Pattern; + +interface PatternInterface +{ + + /** + * Get all pattern options + */ + public function getOptions(): PatternOptions; +} +``` + +There are cache patterns which depend on a storage. In this case, these adapters implement the `StorageCapableInterface`: + +```php +namespace Laminas\Cache\Pattern; + +use Laminas\Cache\Storage\StorageInterface; + +interface StorageCapableInterface extends PatternInterface +{ + public function getStorage(): ?StorageInterface; +} +``` diff --git a/docs/book/v4/pattern/object-cache.md b/docs/book/v4/pattern/object-cache.md new file mode 100644 index 00000000..691825d0 --- /dev/null +++ b/docs/book/v4/pattern/object-cache.md @@ -0,0 +1,176 @@ +# ObjectCache + +The `ObjectCache` pattern is an extension to the `CallbackCache` pattern. It has +the same methods, but instead caches output from any instance method calls or +public properties. + +## Quick Start + +```php +use Laminas\Cache\Pattern\ObjectCache; +use Laminas\Cache\Pattern\PatternOptions; +use stdClass; + +$object = new stdClass(); +$objectCache = new ObjectCache( + $storage, + new PatternOptions([ + 'object' => $object, + ]) +); +``` + +> ### Storage Adapter +> +> The `$storage` adapter can be any adapter which implements the `StorageInterface`. Check out the [Pattern Quick Start](./intro.md#quick-start)-Section for a standard adapter which can be used here. + +## Configuration Options + +| Option | Data Type | Default Value | Description | +|---------------------------------|---------------------------------------------------------|----------------------|-------------------------------------------------------------------| +| `storage` | `string\|array\|Laminas\Cache\Storage\StorageInterface` | none | **deprecated** Adapter used for reading and writing cached data. | +| `object` | `object` | none | The object for which to cache method calls. | +| `object_key` | `null\|string` | Class name of object | Hopefully unique! | +| `cache_output` | `bool` | `true` | Whether or not to cache method output. | +| `cache_by_default` | `bool` | `true` | Cache all method calls by default. | +| `object_cache_methods` | `array` | `[]` | List of methods to cache (if `cache_by_default` is disabled). | +| `object_non_cache_methods` | `array` | `[]` | List of methods to blacklist (if `cache_by_default` is enabled). | +| `object_cache_magic_properties` | `bool` | `false` | Whether or not to cache properties exposed by method overloading. | + +## Examples + +### Caching a Filter + +```php +use Laminas\Cache\Pattern\ObjectCache; +use Laminas\Cache\Pattern\PatternOptions; + +$filter = new \Laminas\Filter\RealPath(); +$cachedFilter = new ObjectCache( + $storage, + new PatternOptions([ + 'object' => $filter, + 'object_key' => 'RealpathFilter', + + // The realpath filter doesn't output anything + // so the output don't need to be caught and cached + 'cache_output' => false, + ]) +); + +$path = $cachedFilter->call("filter", ['/www/var/path/../../mypath']); + +// OR +$path = $cachedFilter->filter('/www/var/path/../../mypath'); +``` + +## Available Methods + +In addition to the methods defined in `PatternInterface` and the `StorageCapableInterface`, this implementation +defines the following methods. + +```php +namespace Laminas\Cache\Pattern; + +use Laminas\Cache\Exception; + +class ObjectCache extends CallbackCache +{ + /** + * Call and cache a class method + * + * @param non-empty-string $method Method name to call + * @param array $args Method arguments + * @return mixed + * @throws Exception\RuntimeException + * @throws \Exception + */ + public function call(string $method, array $args = []): mixed; + + /** + * Method overloading: proxies to call(). + * + * @param non-empty-string $method Method name to call + * @param array $args Method arguments + * @return mixed + * @throws Exception\RuntimeException + * @throws \Exception + */ + public function __call(string $method, array $args): mixed; + + /** + * Generate a unique key in base of a key representing the callback part + * and a key representing the arguments part. + * + * @param non-empty-string $method The method + * @param array $args Callback arguments + * @return non-empty-string + * @throws Exception\RuntimeException + */ + public function generateKey(string $methodOrProperty, array $args = []): string; + + /** + * Property overloading: write data to a named property. + * + * NOTE: + * Magic properties will be cached too if the option cacheMagicProperties + * is enabled and the property doesn't exist in real. If so it calls __set + * and removes cached data of previous __get and __isset calls. + * + * @param non-empty-string $name + * @see http://php.net/manual/language.oop5.overloading.php#language.oop5.overloading.members + */ + public function __set(string $name, mixed $value): void; + + /** + * Property overloading: read data from a named property. + * + * NOTE: + * Magic properties will be cached too if the option cacheMagicProperties + * is enabled and the property doesn't exist in real. If so it calls __get. + * + * @param non-empty-string $name + * @see http://php.net/manual/language.oop5.overloading.php#language.oop5.overloading.members + */ + public function __get($name): mixed; + + /** + * Property overloading: check if a named property exists. + * + * NOTE: + * Magic properties will be cached too if the option cacheMagicProperties + * is enabled and the property doesn't exist in real. If so it calls __get. + * + * @param non-empty-string $name + * @see http://php.net/manual/language.oop5.overloading.php#language.oop5.overloading.members + */ + public function __isset($name): bool; + + /** + * Property overloading: unset a named property. + * + * NOTE: + * Magic properties will be cached too if the option cacheMagicProperties + * is enabled and the property doesn't exist in real. If so it removes + * previous cached __isset and __get calls. + * + * @param non-empty-string $name + * @see http://php.net/manual/language.oop5.overloading.php#language.oop5.overloading.members + */ + public function __unset(string $name): void; + + /** + * Handle casting to string + * + * @see http://php.net/manual/language.oop5.magic.php#language.oop5.magic.tostring + */ + public function __toString(): string; + + /** + * Intercept and cache invokable usage. + * + * @see http://php.net/manual/language.oop5.magic.php#language.oop5.magic.invoke + */ + public function __invoke(): mixed; +} +``` diff --git a/docs/book/v4/pattern/output-cache.md b/docs/book/v4/pattern/output-cache.md new file mode 100644 index 00000000..aa6f35ba --- /dev/null +++ b/docs/book/v4/pattern/output-cache.md @@ -0,0 +1,76 @@ +# OutputCache + +The `OutputCache` pattern caches output between calls to `start()` and `end()`. + +## Quick Start + +```php +use Laminas\Cache\Pattern\OutputCache; +use Laminas\Cache\Pattern\PatternOptions; + +$outputCache = new OutputCache( + $storage, + new PatternOptions() +); +``` + +> ### Storage Adapter +> +> The `$storage` adapter can be any adapter which implements the `StorageInterface`. Check out the [Pattern Quick Start](./intro.md#quick-start)-Section for a standard adapter which can be used here. + +## Configuration Options + +| Option | Data Type | Default Value | Description | +|-----------|---------------------------------------------------------|---------------|------------------------------------------------------------------| +| `storage` | `string\|array\|Laminas\Cache\Storage\StorageInterface` | none | **deprecated** Adapter used for reading and writing cached data. | + +## Examples + +### Caching a View Script + +```php +use Laminas\Cache\Pattern\OutputCache; +use Laminas\Cache\Pattern\PatternOptions; + +$outputCache = new OutputCache( + $storage, + new PatternOptions() +); + +$outputCache->start('mySimpleViewScript'); +include '/path/to/view/script.phtml'; +$outputCache->end(); +``` + +## Available Methods + +In addition to the methods defined in `PatternInterface` and `StorageCapableInterface`, this implementation +defines the following methods. + +```php +namespace Laminas\Cache\Pattern; + +use Laminas\Cache\Exception; + +class OutputCache extends AbstractStorageCapablePattern +{ + /** + * If there is a cached item with the given key, display its data, and + * return true. Otherwise, start buffering output until end() is called, or + * the script ends. + * + * @param non-empty-string $key + * @throws Exception\MissingKeyException if key is missing + */ + public function start(string $key): bool; + + /** + * Stop buffering output, write buffered data to the cache using the key + * provided to start(), and display the buffer. + * + * @throws Exception\RuntimeException if output cache not started or buffering not active + * @return bool TRUE on success, FALSE on failure writing to cache + */ + public function end(): bool; +} +``` diff --git a/docs/book/v4/psr16.md b/docs/book/v4/psr16.md new file mode 100644 index 00000000..78caf178 --- /dev/null +++ b/docs/book/v4/psr16.md @@ -0,0 +1,124 @@ +# PSR-16 + +[PSR-16](https://www.php-fig.org/psr/psr-16/) provides a simplified approach to +cache access that does not involve cache pools, tags, deferment, etc.; it +can be thought of as a key/value storage approach to caching. + +laminas-cache provides PSR-16 support via the class +`Laminas\Cache\Psr\SimpleCache\SimpleCacheDecorator`. This class implements PSR-16's +`Psr\SimpleCache\CacheInterface`, and composes a +`Laminas\Cache\Storage\StorageInterface` instance to which it proxies all +operations. + +Instantiation is as follows: + +```php +use Laminas\Cache\Psr\SimpleCache\SimpleCacheDecorator; +use Laminas\Cache\Service\StorageAdapterFactoryInterface; +use Psr\Container\ContainerInterface; + +/** @var ContainerInterface $container */ +$container = null; // can be any configured PSR-11 container + +$storageFactory = $container->get(StorageAdapterFactoryInterface::class); +$storage = $storageFactory->create('apc'); + +$cache = new SimpleCacheDecorator($storage); +``` + +Once you have a `SimpleCacheDecorator` instance, you can perform operations per +that specification: + +```php +// Use has() to determine whether to fetch the value or calculate it: +$value = $cache->has('someKey') ? $cache->get('someKey') : calculateValue(); +if (! $cache->has('someKey')) { + $cache->set('someKey', $value); +} + +// Or use a default value: +$value = $cache->get('someKey', $defaultValue); +``` + +When setting values, whether single values or multiple, you can also optionally +provide a Time To Live (TTL) value. This proxies to the underlying storage +instance's options, temporarily resetting its TTL value for the duration of the +operation. TTL values may be expressed as integers (in which case they represent +seconds) or `DateInterval` instances. As examples: + +```php +$cache->set('someKey', $value, 30); // set TTL to 30s +$cache->set('someKey', $value, new DateInterval('P1D')); // set TTL to 1 day + +$cache->setMultiple([ + 'key1' => $value1, + 'key2' => $value2, +], 3600); // set TTL to 1 hour +$cache->setMultiple([ + 'key1' => $value1, + 'key2' => $value2, +], new DateInterval('P6H')); // set TTL to 6 hours +``` + +For more details on what methods are exposed, consult the [CacheInterface +specification](https://www.php-fig.org/psr/psr-16/#21-cacheinterface). + +## Serialization + +PSR-16 has strict requirements around serialization of values. This is done to +ensure that if you swap one PSR-16 adapter for another, the new one will be able +to return the same values that the original adapter saved to the cache. + +Not all cache backends support the same data types, however. laminas-cache provides +a plugin, `Laminas\Cache\Storage\Plugin\Serializer`, that you can attach to +adapters in order to ensure data is serialized to a string when saving to the +cache, and deserialized to native PHP types on retrieval. The following adapters +require this plugin in order to work with the `SimpleCacheDecorator`: + +- Dba +- Filesystem +- Memcache +- MongoDB +- Redis +- XCache + +We provide a number of examples of [attaching plugins to storage adapters in the +plugins chapter](storage/plugin.md). Generally, it will be one of: + +```php +use Laminas\Cache\Service\StorageAdapterFactoryInterface; +use Laminas\Cache\Storage\Plugin\Serializer; +use Psr\Container\ContainerInterface; + +// Manual attachment after you have an instance: +$cache->addPlugin(new Serializer()); + +// Via configuration: +/** @var ContainerInterface $container */ +$container = null; // can be any configured PSR-11 container + +$storageFactory = $container->get(StorageAdapterFactoryInterface::class); +$cache = $storageFactory->create( + 'filesystem', + [], + [ + ['name' => 'serializer'], + ] +); +``` + +## Deleting Items and Exceptions + +PSR-16 states that the `delete()` and `deleteMultiple()` methods should return +`false` if an _error_ occured when deleting the key(s) provided, but `true` +otherwise. + +Generally, laminas-cache storage adapters comply with this. However, it is possible +to configure your adapter such that you may get a false positive result from +these methods. + +When an exception is raised and caught during key removal by an adapter, the adapter triggers an event with a `Laminas\Cache\Storage\ExceptionEvent`. Plugins can react to these, and even manipulate the event instance. One such plugin, `Laminas\Cache\Storage\Plugin\ExceptionHandler`, has a configuration option, `throw_exceptions` that, when boolean `false`, will prevent raising the exception. In such cases, adapters will typically return a boolean `false` anyways, but custom, third-party adapters may not. + +Additionally, if you add a custom plugin that listens to removal event exceptions and modifies the return value and/or disables throwing the exception, a false positive return value could occur. + +As such, we recommend that if you wish to use laminas-cache to provide a PSR-16 adapter, you audit the plugins you use with your adapter to ensure that you will get consistent, correct behavior for `delete()` and `deleteMultiple()` operations. diff --git a/docs/book/v4/psr6.md b/docs/book/v4/psr6.md new file mode 100644 index 00000000..ba45afe5 --- /dev/null +++ b/docs/book/v4/psr6.md @@ -0,0 +1,161 @@ +# PSR-6 + +## Overview + +The `Laminas\Cache\Psr\CacheItemPool\CacheItemPoolDecorator` provides a [PSR-6](https://www.php-fig.org/psr/psr-6/) +compliant wrapper for supported storage adapters. + +PSR-6 specifies a common interface to cache storage, enabling developers to switch between implementations without +having to worry about any behind-the-scenes differences between them. + +## Quick Start + +To use the pool, instantiate your storage as normal, then pass it to the +`CacheItemPoolDecorator`. + +```php +use Laminas\Cache\Psr\CacheItemPool\CacheItemPoolDecorator;use Laminas\Cache\Service\StorageAdapterFactoryInterface;use Psr\Container\ContainerInterface; + +/** @var ContainerInterface $container */ +$container = null; // can be any configured PSR-11 container + +$storageFactory = $container->get(StorageAdapterFactoryInterface::class); + +$storage = $storageFactory->create('apc'); + +$pool = new CacheItemPoolDecorator($storage); + +// attempt to get an item from cache +$item = $pool->getItem('foo'); + +// check whether item was found +if (! $item->isHit()) { + // ... + // perform expensive operation to calculate $value for 'foo' + // ... + + $item->set($value); + $pool->save($item); +} + +// use the value of the item +echo $item->get(); +``` + +Note that you will always get back a `CacheItem` object, whether it was found in cache or not: this is so `false`-y +values like an empty string, `null`, or `false` can be stored. Always check `isHit()` to determine if the item was +found. + +## Supported Adapters + +The PSR-6 specification requires that the underlying storage support time-to-live (TTL), which is set when the +item is saved. For this reason the following adapters cannot be used: `Dba`, `Filesystem`, `Memory` and `Session`. The +`XCache` adapter calculates TTLs based on the request time, not the time the item is actually persisted, which means +that it also cannot be used. + +In addition adapters must support the `Laminas\Cache\FlushableInterface`. All the current `Laminas\Cache\Storage\Adapter`s +fulfil this requirement. + +Attempting to use an unsupported adapter will throw an exception implementing `Psr\Cache\CacheException`. + +The `Laminas\Cache\Psr\CacheItemPool\CacheItemPoolDecorator` adapter doesn't support driver deferred saves, so cache items are saved +on destruct or on explicit `commit()` call. + +### Quirks + +#### APC + +You cannot set the [`apc.use_request_time`](http://php.net/manual/en/apc.configuration.php#ini.apc.use-request-time) +ini setting with the APC adapter: the specification requires that all TTL values are calculated from when the item is +actually saved to storage. If this is set when you instantiate the pool it will throw an exception implementing +`Psr\Cache\CacheException`. Changing the setting after you have instantiated the pool will result in non-standard +behaviour. + +## Logging Errors + +The specification [states](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-6-cache.md#error-handling): + +> While caching is often an important part of application performance, it should never be a critical part of application +> functionality. Thus, an error in a cache system SHOULD NOT result in application failure. + +Once you've got your pool instance, almost all exceptions thrown by the storage will be caught and ignored. The only +storage exceptions that bubble up implement `Psr\Cache\InvalidArgumentException` and are typically caused by invalid +key errors. To be PSR-6 compliant, cache keys must not contain the following characters: `{}()/\@:`. However different +storage adapters may have further restrictions. Check the documentation for your particular adapter to be sure. + +We strongly recommend tracking exceptions caught from storage, either by logging them or recording them in some other +way. Doing so is as simple as adding an [`ExceptionHandler` plugin](storage/plugin.md#the-exceptionhandler-plugin). Say you have a +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) compliant logger +called `$logger`: + +```php +use Laminas\Cache\Psr\CacheItemPool\CacheItemPoolDecorator; +use Laminas\Cache\Service\StorageAdapterFactoryInterface; +use Psr\Container\ContainerInterface; + +$cacheLogger = function (\Exception $e) use ($logger) { + $message = sprintf( + '[CACHE] %s:%s %s "%s"', + $exception->getFile(), + $exception->getLine(), + $exception->getCode(), + $exception->getMessage() + ); + $logger->error($message); +}; + +/** @var ContainerInterface $container */ +$container = null; // can be any configured PSR-11 container + +$storageFactory = $container->get(StorageAdapterFactoryInterface::class); + +$storage = $storageFactory->create( + 'apc', + [], + [ + [ + 'name' => 'exceptionhandler', + 'options' => [ + 'exception_callback' => $cacheLogger, + 'throw_exceptions' => true, + ], + ], + ] +); + +$pool = new CacheItemPoolDecorator($storage); +``` + +Note that `throw_exceptions` should always be `true` (the default) or you will not get the correct return values from +calls on the pool such as `save()`. + +## Supported Data Types + +As per [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-6-cache.md#data), the +following data types can be stored in cache: `string`, `integer`, `float`, `boolean`, `null`, `array`, `object` and be +returned as a value with exactly the same type. + +Not all adapters can natively store all these types. For instance, Redis stores booleans and integers as a string. Where +this is the case *all* values will be automatically run through `serialize()` on save and `unserialize()` on get: you +do not need to use a `Laminas\Cache\Storage\Plugin\Serializer` plugin. + +## Time-Related Operations + +By default, the PSR-6 cache decorator uses the systems time to determine cache expirations. This can be changed by +passing a [`stella-maris/clock`](https://packagist.org/packages/stella-maris/clock) `Clock` implementation to the `CacheItemPoolDecorator#__construct`. +By doing so, the method `CacheItemInterface#expiresAfter` uses that `Clock` to calculate the cache items TTL when passing +a `DateInterval` instance. + +```php +use Lcobucci\Clock\SystemClock; +$clock = new SystemClock('Antarctica/Troll'); +$pool = new CacheItemPoolDecorator($storage, $clock); + +$item = $pool->getItem('non-existent-key'); +$item + ->set('foo') + ->expiresAfter(DateInterval::createFromDateString('24 hours')); + +// Saves the item which expires in 24h +$pool->save($item); +``` diff --git a/docs/book/v4/storage/adapter.md b/docs/book/v4/storage/adapter.md new file mode 100644 index 00000000..8ec7de20 --- /dev/null +++ b/docs/book/v4/storage/adapter.md @@ -0,0 +1,977 @@ +# Adapters + +Storage adapters are wrappers for real storage resources such as memory or the filesystem, using +the well known *adapter* pattern. + +They come with tons of methods to read, write, and modify stored items, and to get information about +stored items and the storage. + +All adapters implement `Laminas\Cache\Storage\StorageInterface`, and most extend +`Laminas\Cache\Storage\Adapter\AbstractAdapter`, which provides a foundation of +common logic. + +Configuration is handled by either `Laminas\Cache\Storage\Adapter\AdapterOptions`, +or an adapter-specific options class if it exists. You may pass the options +instance to the class at instantiation, via the `setOptions()` method, or, +alternately, pass an associative array of options in either place (internally, +these are then passed to an options class instance). Alternately, you can pass associative array to the +`Laminas\Cache\Service\StorageAdapterFactoryInterface::create` method. + +## Quick Start + +Caching adapters can either be created from the provided +`Laminas\Cache\Service\StorageAdapterFactoryInterface`, or by instantiating one of the +`Laminas\Cache\Storage\Adapter\*` classes. To make life easier, the +`Laminas\Cache\Service\StorageAdapterFactoryInterface` comes with a `create()` method to create an adapter +and all requested plugins at once. + +```php +use Laminas\Cache\Service\StorageAdapterFactoryInterface; +use Laminas\Cache\Service\StoragePluginFactoryInterface; +use Psr\Container\ContainerInterface; + +/** @var ContainerInterface $container */ +$container = null; // can be any configured PSR-11 container + +/** @var StorageAdapterFactoryInterface $storageFactory */ +$storageFactory = $container->get(StorageAdapterFactoryInterface::class); + +// Via factory: +$cache = $storageFactory->create( + 'apcu', + ['ttl' => 3600], + [ + [ + 'name' => 'exception_handler', + 'options' => [ + 'throw_exceptions' => false, + ], + ], + ] +); + +// Via array configuration: +$cache = $storageFactory->createFromArrayConfiguration([ + 'adapter' => 'apcu', + 'options' => ['ttl' => 3600], + 'plugins' => [ + [ + 'name' => 'exception_handler', + 'options' => [ + 'throw_exceptions' => false, + ], + ], + ], +]); + +// Alternately, create the adapter and plugin separately: +$cache = $storageFactory->create('apcu', ['ttl' => 3600]); +$pluginFactory = $container->get(StoragePluginFactoryInterface::class); +$plugin = $pluginFactory->create('exception_handler', [ + 'throw_exceptions' => false, +]); +$cache->addPlugin($plugin); + +// Or do it completely manually: +$cache = new Laminas\Cache\Storage\Adapter\Apcu(); +$cache->getOptions()->setTtl(3600); + +$plugin = new Laminas\Cache\Storage\Plugin\ExceptionHandler(); +$plugin->getOptions()->setThrowExceptions(false); +$cache->addPlugin($plugin); +``` + +> ### Many Methods throw Exceptions +> +> Because many caching operations throw an exception on error, you need to catch +> them. You can do so manually, or you can use the plugin +> `Laminas\Cache\Storage\Plugin\ExceptionHandler` with `throw_exceptions` set to +> `false` to automatically catch them. You can also define an +> `exception_callback` to log exceptions. + +## Basic Configuration Options + +The following configuration options are defined by `Laminas\Cache\Storage\Adapter\AdapterOptions` and +are available for every supported adapter. Adapter-specific configuration options are described on +adapter level below. + + Option | Data Type | Default Value | Description +---------------|-----------|----------------|------------------------------------------------ + `ttl` | `integer` | `0` | Time to live + `namespace` | `string` | “laminascache” | The “namespace” in which cache items will live + `key_pattern` | `null | string` | `null` | Pattern against which to validate cache keys + `readable` | `boolean` | `true` | Enable/Disable reading data from cache + `writable` | `boolean` | `true` | Enable/Disable writing data to cache + +## StorageInterface + +`Laminas\Cache\Storage\StorageInterface` is the basic interface implemented by all +storage adapters. + +```php +namespace Laminas\Cache\Storage; + +use Traversable; + +interface StorageInterface +{ + /** + * Set options. + * + * @param array|Traversable|Adapter\AdapterOptions $options + * @return StorageInterface Fluent interface + */ + public function setOptions($options); + + /** + * Get options + * + * @return Adapter\AdapterOptions + */ + public function getOptions(); + + /* reading */ + + /** + * Get an item. + * + * @param string $key + * @param bool $success + * @param mixed $casToken + * @return mixed Data on success, null on failure + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function getItem($key, & $success = null, & $casToken = null); + + /** + * Get multiple items. + * + * @param array $keys + * @return array Associative array of keys and values + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function getItems(array $keys); + + /** + * Test if an item exists. + * + * @param string $key + * @return bool + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function hasItem($key); + + /** + * Test multiple items. + * + * @param array $keys + * @return array Array of found keys + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function hasItems(array $keys); + + /* writing */ + + /** + * Store an item. + * + * @param string $key + * @param mixed $value + * @return bool + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function setItem($key, $value); + + /** + * Store multiple items. + * + * @param array $keyValuePairs + * @return array Array of not stored keys + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function setItems(array $keyValuePairs); + + /** + * Add an item. + * + * @param string $key + * @param mixed $value + * @return bool + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function addItem($key, $value); + + /** + * Add multiple items. + * + * @param array $keyValuePairs + * @return array Array of not stored keys + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function addItems(array $keyValuePairs); + + /** + * Replace an existing item. + * + * @param string $key + * @param mixed $value + * @return bool + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function replaceItem($key, $value); + + /** + * Replace multiple existing items. + * + * @param array $keyValuePairs + * @return array Array of not stored keys + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function replaceItems(array $keyValuePairs); + + /** + * Set an item only if token matches + * + * It uses the token received from getItem() to check if the item has + * changed before overwriting it. + * + * @param mixed $token + * @param string $key + * @param mixed $value + * @return bool + * @throws \Laminas\Cache\Exception\ExceptionInterface + * @see getItem() + * @see setItem() + */ + public function checkAndSetItem($token, $key, $value); + + /** + * Reset lifetime of an item + * + * @param string $key + * @return bool + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function touchItem($key); + + /** + * Reset lifetime of multiple items. + * + * @param array $keys + * @return array Array of not updated keys + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function touchItems(array $keys); + + /** + * Remove an item. + * + * @param string $key + * @return bool + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function removeItem($key); + + /** + * Remove multiple items. + * + * @param array $keys + * @return array Array of not removed keys + * @throws \Laminas\Cache\Exception\ExceptionInterface + */ + public function removeItems(array $keys); + + /* status */ + + /** + * Capabilities of this storage + * + * @return Capabilities + */ + public function getCapabilities(); +} +``` + +## AvailableSpaceCapableInterface + +`Laminas\Cache\Storage\AvailableSpaceCapableInterface` implements a method to allow +retrieving the current available space remaining in storage. + +```php +namespace Laminas\Cache\Storage; + +interface AvailableSpaceCapableInterface +{ + /** + * Get available space in bytes + * + * @return int|float + */ + public function getAvailableSpace(); +} +``` + +## TotalSpaceCapableInterface + +`Laminas\Cache\Storage\TotalSpaceCapableInterface` implements a method to allow +retrieving the total storage space. + +```php +namespace Laminas\Cache\Storage; + +interface TotalSpaceCapableInterface +{ + /** + * Get total space in bytes + * + * @return int|float + */ + public function getTotalSpace(); +} +``` + +## ClearByNamespaceInterface + +`Laminas\Cache\Storage\ClearByNamespaceInterface` implements a method to allow +clearing all cached items within a given namespace. + +```php +namespace Laminas\Cache\Storage; + +interface ClearByNamespaceInterface +{ + /** + * Remove items of given namespace + * + * @param string $namespace + * @return bool + */ + public function clearByNamespace($namespace); +} +``` + +## ClearByPrefixInterface + +`Laminas\Cache\Storage\ClearByPrefixInterface` implements a method to allow +clearing all cached items that have a given prefix (within the currently +configured namespace). + +```php +namespace Laminas\Cache\Storage; + +interface ClearByPrefixInterface +{ + /** + * Remove items matching given prefix + * + * @param string $prefix + * @return bool + */ + public function clearByPrefix($prefix); +} +``` + +## ClearExpiredInterface + +`Laminas\Cache\Storage\ClearExpiredInterface` implements a method to allow clearing +all expired items (within the current configured namespace). + +```php +namespace Laminas\Cache\Storage; + +interface ClearExpiredInterface +{ + /** + * Remove expired items + * + * @return bool + */ + public function clearExpired(); +} +``` + +## FlushableInterface + +`Laminas\Cache\Storage\FlushableInterface` implements a method for flushing the +entire cache storage. + +```php +namespace Laminas\Cache\Storage; + +interface FlushableInterface +{ + /** + * Flush the whole storage + * + * @return bool + */ + public function flush(); +} +``` + +## IterableInterface + +`Laminas\Cache\Storage\IterableInterface` implements a method for retrieving an +iterator of all items in storage. It extends `IteratorAggregate`, so it's +possible to directly iterate over the storage implementations that implement +this interface using `foreach`. + +```php +namespace Laminas\Cache\Storage; + +use IteratorAggregate; + +/** + * + * @method IteratorInterface getIterator() Get the storage iterator + */ +interface IterableInterface extends IteratorAggregate +{ + /** + * @return \Traversable + */ + public function getIterator(); +} +``` + +## OptimizableInterface + +`Laminas\Cache\Storage\OptimizableInterface` implements a method for running +optimization processes on the storage adapter. + +```php +namespace Laminas\Cache\Storage; + +interface OptimizableInterface +{ + /** + * Optimize the storage + * + * @return bool + */ + public function optimize(); +} +``` + +## TaggableInterface + +`Laminas\Cache\Storage\TaggableInterface` implements methods for tagging items, and +cleaning (expiring) items matching tags. + +```php +namespace Laminas\Cache\Storage; + +interface TaggableInterface +{ + /** + * Set tags to an item by given key. + * An empty array will remove all tags. + * + * @param string $key + * @param string[] $tags + * @return bool + */ + public function setTags($key, array $tags); + + /** + * Get tags of an item by given key + * + * @param string $key + * @return string[]|FALSE + */ + public function getTags($key); + + /** + * Remove items matching given tags. + * + * If $disjunction only one of the given tags must match + * else all given tags must match. + * + * @param string[] $tags + * @param bool $disjunction + * @return bool + */ + public function clearByTags(array $tags, $disjunction = false); +} +``` + +## APCu Adapter + +`Laminas\Cache\Storage\Adapter\Apcu` stores cache items in shared memory through the +PHP extension [APCu](http://pecl.php.net/package/APCu) (Alternative PHP Cache). + +This adapter implements the following interfaces: + +- `Laminas\Cache\Storage\StorageInterface` +- `Laminas\Cache\Storage\AvailableSpaceCapableInterface` +- `Laminas\Cache\Storage\ClearByNamespaceInterface` +- `Laminas\Cache\Storage\ClearByPrefixInterface` +- `Laminas\Cache\Storage\FlushableInterface` +- `Laminas\Cache\Storage\IterableInterface` +- `Laminas\Cache\Storage\TotalSpaceCapableInterface` + +### Capabilities + +| Capability | Value | +|----------------------|---------------------------------------------------------------------------------------| +| `supportedDatatypes` | `null`, `bool`, `int`, `float`, `string`, `array` (serialized), `object` (serialized) | +| `minTtl` | 1 | +| `maxTtl` | 0 | +| `staticTtl` | `true` | +| `ttlPrecision` | 1 | +| `useRequestTime` | value of `apc.use_request_time` from `php.ini` | +| `lockOnExpire` | 0 | +| `maxKeyLength` | 5182 | +| `namespaceIsPrefix` | `true` | +| `namespaceSeparator` | Option value of `namespace_separator` | + +### Metadata + +The APCu adapter does provide a couple of metadatas, which can be fetched by by using either `MetadataCapableInterface#getMetadata` or `MetadataCapableInterface#getMetadatas`. + +It will return an object with the following properties (or null): + +| Metadata | Type | Description | +|--------------------|----------|:-------------------------------------------------------------------------| +| `internalKey` | `string` | The internal key used to store the cache item | +| `lastAccessTime` | `int` | The time the cache item was last accessed | +| `creationTime` | `int` | The time the cache item was created | +| `lastModifiedTime` | `int` | The time the cache item was last modified | +| `size` | `int` | The size the cache item is consuming within the cache | +| `hits` | `int` | The amount of times the item was requested and returned from the backend | +| `timeToLive` | `int` | The overall time to live (in seconds) the cache item was persisted for | + + +### Adapter Specific Options + +| Name | Data Type | Default Value | Description | +|-----------------------|-----------|---------------|-------------------------------------------| +| `namespace_separator` | `string` | ":" | A separator for the namespace and prefix. | + +## BlackHole Adapter + +`Laminas\Cache\Storage\Adapter\BlackHole` **does not** store any cache items. This adapter is useful to bypass caching behavior. This might be the case in development mode or unit testing. + +This adapter implements the following interfaces: + +- `Laminas\Cache\Storage\StorageInterface` +- `Laminas\Cache\Storage\AvailableSpaceCapableInterface` +- `Laminas\Cache\Storage\ClearByNamespaceInterface` +- `Laminas\Cache\Storage\ClearByPrefixInterface` +- `Laminas\Cache\Storage\ClearExpiredInterface` +- `Laminas\Cache\Storage\FlushableInterface` +- `Laminas\Cache\Storage\IterableInterface` +- `Laminas\Cache\Storage\OptimizableInterface` +- `Laminas\Cache\Storage\TaggableInterface` +- `Laminas\Cache\Storage\TotalSpaceCapableInterface` + +### Capabilities + +| Capability | Value | +|----------------------|-------------------------------------------------------------| +| `supportedDatatypes` | `null`, `bool`, `int`, `float`, `string`, `array`, `object` | +| `minTtl` | 1 | +| `maxTtl` | 0 | +| `staticTtl` | `false` or `true`, depending on `psr` option | +| `ttlPrecision` | 1 | +| `useRequestTime` | false | +| `lockOnExpire` | 0 | +| `maxKeyLength` | -1 | +| `namespaceIsPrefix` | `true` | +| `namespaceSeparator` | none | + +## Filesystem Adapter + +`Laminas\Cache\Storage\Adapter\Filesystem` stores cache items on the filesystem. + +This adapter implements the following interfaces: + +- `Laminas\Cache\Storage\StorageInterface` +- `Laminas\Cache\Storage\AvailableSpaceCapableInterface` +- `Laminas\Cache\Storage\ClearByNamespaceInterface` +- `Laminas\Cache\Storage\ClearByPrefixInterface` +- `Laminas\Cache\Storage\ClearExpiredInterface` +- `Laminas\Cache\Storage\FlushableInterface` +- `Laminas\Cache\Storage\IterableInterface` +- `Laminas\Cache\Storage\OptimizableInterface` +- `Laminas\Cache\Storage\TaggableInterface` +- `Laminas\Cache\Storage\TotalSpaceCapableInterface` + +### Capabilities + +| Capability | Value | +|----------------------|--------------------------------------------------------------------------------------------------| +| `supportedDatatypes` | `string`, `null` => `string`, `boolean` => `string`, `integer` => `string`, `double` => `string` | +| `minTtl` | 1 | +| `maxTtl` | 0 | +| `staticTtl` | `false` | +| `ttlPrecision` | 1 | +| `useRequestTime` | `false` | +| `lockOnExpire` | 0 | +| `maxKeyLength` | 251 | +| `namespaceIsPrefix` | `true` | +| `namespaceSeparator` | Option value of `namespace_separator` | + +### Metadata + +| Metadata | Type | Description | +|--------------------|-------------|:------------------------------------------------------------------| +| `lastAccessTime` | `int\|null` | The time the cache item was last accessed | +| `creationTime` | `int\|null` | The time the cache item was created | +| `lastModifiedTime` | `int\|null` | The time the cache item was last modified | +| `filesize` | `int\|null` | The amount of bytes the cache item is consuming in the filesystem | +| `filespec` | `string` | The absolute path to the cache file without suffix | + +### Adapter Specific Options + +| Name | Data Type | Default Value | Description | +|-----------------------|------------------|------------------------|-------------------------------------------------------------------------------------| +| `namespace_separator` | `string` | ":" | A separator for the namespace and prefix | +| `cache_dir` | `string` | "" | Directory to store cache files. | +| `clear_stat_cache` | `boolean` | `true` | Call `clearstatcache()` enabled? | +| `dir_level` | `integer` | `1` | Defines how much sub-directories should be created. | +| `dir_permission` | `integer\|false` | `0700` | Set explicit permission on creating new directories. | +| `file_locking` | `boolean` | `true` | Lock files on writing. | +| `file_permission` | `integer` | `false` | 0600 Set explicit permission on creating new files. | +| `key_pattern` | `string` | `/^[a-z0-9_\+\-]*$/Di` | Validate key against pattern. | +| `no_atime` | `boolean` | `true` | Don’t get `fileatime` as `atime` on metadata. | +| `no_ctime` | `boolean` | `true` | Don’t get `filectime` as `ctime` on metadata. | +| `umask` | `integer\|false` | `false` | Use [umask](http://wikipedia.org/wiki/Umask) to set file and directory permissions. | +| `suffix` | `string` | `dat` | Suffix for cache files | +| `tag_suffix` | `string` | `tag` | Suffix for tag files | + +Note: the `suffix` and `tag_suffix` options will be escaped in order to be safe +for glob operations. + +## Memcached Adapter + +`Laminas\Cache\Storage\Adapter\Memcached` stores cache items over the memcached +protocol, using the PHP extension [memcached](http://pecl.php.net/package/memcached), +based on [Libmemcached](http://libmemcached.org/). + +This adapter implements the following interfaces: + +- `Laminas\Cache\Storage\StorageInterface` +- `Laminas\Cache\Storage\AvailableSpaceCapableInterface` +- `Laminas\Cache\Storage\FlushableInterface` +- `Laminas\Cache\Storage\TotalSpaceCapableInterface` + +### Capabilities + +| Capability | Value | +|----------------------|-----------------------------------------------------------------------------------------------| +| `supportedDatatypes` | `null`, `boolean`, `integer`, `double`, `string`, `array` (serialized), `object` (serialized) | +| `minTtl` | 1 | +| `maxTtl` | 0 | +| `staticTtl` | `true` | +| `ttlPrecision` | 1 | +| `useRequestTime` | `false` | +| `lockOnExpire` | 0 | +| `maxKeyLength` | 255 | +| `namespaceIsPrefix` | `true` | +| `namespaceSeparator` | none | + +### Adapter Specific Options + +| Name | Data Type | Default Value | Description | +|---------------|-----------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `servers` | `array` | `[]` | List of servers in the format `[] = [string host, integer port]` | +| `lib_options` | `array` | `[]` | Associative array of Libmemcached options where the array key is the option name (without the prefix `OPT_`) or the constant value. The array value is the option value. Please read [the memcached setOption() page](http://php.net/manual/memcached.setoption.php) for more information | + +## Redis Adapter + +`Laminas\Cache\Storage\Adapter\Redis` stores cache items over the [Redis](https://redis.io) protocol +using the PHP extension [PhpRedis](https://github.com/phpredis/phpredis). + +This adapter implements the following interfaces: + +- `Laminas\Cache\Storage\ClearByNamespaceInterface` +- `Laminas\Cache\Storage\ClearByPrefixInterface` +- `Laminas\Cache\Storage\FlushableInterface` +- `Laminas\Cache\Storage\TotalSpaceCapableInterface` + +### Capabilities + +| Capability | Value | +|----------------------|-------------------------------------------------------| +| `supportedDatatypes` | `string`, `array` (serialized), `object` (serialized) | +| `minTtl` | 1 | +| `maxTtl` | 0 | +| `staticTtl` | `true` | +| `ttlPrecision` | 1 | +| `useRequestTime` | `false` | +| `lockOnExpire` | 0 | +| `maxKeyLength` | 512000000 (in Redis v3+, 255 otherwise) | +| `namespaceIsPrefix` | `true` | +| `namespaceSeparator` | none | + +### Metadata + +| Metadata | Type | Description | +|-----------------------|-------------|:--------------------------------------------------------------------------------------------------------------------------| +| `remainingTimeToLive` | `int\|null` | The amount of time (seconds) the cache item will remain in the cache. Will be `null` in case the cache item won't expire. | + +### Adapter Specific Options + +| Name | Data Type | Default Value | Description | +|-----------------------|-----------------|---------------|---------------------------------------------------------------------------------------------| +| `database` | `integer` | 0 | Set database identifier. | +| `lib_options` | `array` | `[]` | Associative array of Redis options where the array key is the option name. | +| `namespace_separator` | `string` | ":" | A separator for the namespace and prefix. | +| `password` | `string` | "" | Set password. | +| `persistent_id` | `string` | | Set persistent id (name of the connection, leave blank to not use a persistent connection). | +| `resource_manager` | `string` | "" | Set the Redis resource manager to use | +| `server` | `string\|array` | "" | See below. | + +`server` can be described as any of the following: + +- URI: `/path/to/sock.sock` +- Associative array: `['host' => [, 'port' => [, 'timeout' => ]]]` +- List: `[[, , [, ]]]` + +## RedisCluster Adapter + +`Laminas\Cache\Storage\Adapter\RedisCluster` stores cache items over the [Redis cluster](https://github.com/phpredis/phpredis#redis-cluster-support) protocol +using the PHP extension [PhpRedis](https://github.com/phpredis/phpredis). + +This adapter implements the following interfaces: + +- `Laminas\Cache\Storage\ClearByNamespaceInterface` +- `Laminas\Cache\Storage\ClearByPrefixInterface` +- `Laminas\Cache\Storage\FlushableInterface` + +### Capabilities + +| Capability | Value | +|----------------------|-------------------------------------------------------| +| `supportedDatatypes` | `string`, `array` (serialized), `object` (serialized) | +| `minTtl` | 1 | +| `maxTtl` | 0 | +| `staticTtl` | `true` | +| `ttlPrecision` | 1 | +| `useRequestTime` | `false` | +| `lockOnExpire` | 0 | +| `maxKeyLength` | 512000000 (in Redis v3+, 255 otherwise) | +| `namespaceIsPrefix` | `true` | +| `namespaceSeparator` | none | + +### Metadata + +| Metadata | Type | Description | +|-----------------------|-------------|:--------------------------------------------------------------------------------------------------------------------------| +| `remainingTimeToLive` | `int\|null` | The amount of time (seconds) the cache item will remain in the cache. Will be `null` in case the cache item won't expire. | + +### Adapter Specific Options + +| Name | Data Type | Default Value | Description | +|-----------------------|-----------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `lib_options` | `array` | `[]` | Associative array of Redis options where the array key is the options constant value (see `RedisCluster::OPT_*` [constants](https://github.com/JetBrains/phpstorm-stubs/blob/master/redis/RedisCluster.php) for details). | +| `namespace_separator` | `string` | ":" | A separator for the namespace and prefix. | +| `password` | `string` | "" | Password to authenticate with Redis server | +| `name` | `string` | "" | Name to determine configuration from [php.ini](https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#loading-a-cluster-configuration-by-name) (**MUST NOT** be combined with `seeds`) | +| `seeds` | `array` | `[]` | List of strings containing `:` (**MUST NOT** be combined with `name`) | +| `timeout` | `float` | `1.0` | Timeout for commands, see [PhpRedis](https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#timeouts) timeouts documentation for more background. | +| `read_timeout` | `float` | `2.0` | Read timeout for commands, see [PhpRedis](https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#timeouts) timeouts documentation for more background. | +| `persistent` | `bool` | `false` | Flag to specify whether to create a persistent connection or not | +| `version` | `string` | "" | The Redis server version. **MUST** be specified in a [Semantic Versioning 2.0.0](https://semver.org/#semantic-versioning-200) format. This information is used to determine some features/capabilities without opening a connection to the server. | + +## Memory Adapter + +The `Laminas\Cache\Storage\Adapter\Memory` stores items in-memory in the current +process only. + +This adapter implements the following interfaces: + +- `Laminas\Cache\Storage\StorageInterface` +- `Laminas\Cache\Storage\AvailableSpaceCapableInterface` +- `Laminas\Cache\Storage\ClearByPrefixInterface` +- `Laminas\Cache\Storage\ClearExpiredInterface` +- `Laminas\Cache\Storage\FlushableInterface` +- `Laminas\Cache\Storage\IterableInterface` +- `Laminas\Cache\Storage\TaggableInterface` +- `Laminas\Cache\Storage\TotalSpaceCapableInterface` + +### Capabilities + +| Capability | Value | +|----------------------|---------------------------------------------------------------------------------| +| `supportedDatatypes` | `string`, `null`, `boolean`, `integer`, `double`, `array`, `object`, `resource` | +| `minTtl` | 1 | +| `maxTtl` | Value of `PHP_INT_MAX` | +| `staticTtl` | `false` | +| `ttlPrecision` | 0.05 | +| `useRequestTime` | `false` | +| `lockOnExpire` | 0 | +| `maxKeyLength` | 0 | +| `namespaceIsPrefix` | `false` | + +### Metadata + +| Metadata | Type | Description | +|--------------------|---------|:------------------------------------------| +| `lastModifiedTime` | `float` | The time the cache item was last modified | + +### Adapter Specific Options + +| Name | Data Type | Default Value | Description | +|----------------|---------------|---------------------------------|-----------------------------------------------------------------| +| `memory_limit` | `string\|int` | 50% of `memory_limit` INI value | Limit of how much memory can PHP allocate to allow store items. | + +> #### Memory Limit +> +> The adapter has the following behavior with regards to the memory limit: +> +> - If the consumed memory exceeds the limit provided, an `OutOfSpaceException` + > is thrown. +> - A number less the or equal to zero disables the memory limit. +> - When a value is provided for the memory limit, the value is measured in + > bytes. Shorthand notation may also be provided. + +> ### Current process only +> +> All stored items will be lost on termination of the script. For web-facing +> requests, this typically means the cache is volatile. + +## ExtMongoDB Adapter + +`Laminas\Cache\Storage\Adapter\ExtMongoDB` stores cache items using the mongodb extension, and +requires that the MongoDB PHP Client library is also installed. You can install the client +library using the following: + +```bash +$ composer require mongodb/mongodb +``` + +This adapter implements the following interfaces: + +- `Laminas\Cache\Storage\FlushableInterface` + +### Capabilities + +| Capability | Value | +|----------------------|-----------------------------------------------------------| +| `supportedDatatypes` | `string`, `null`, `boolean`, `integer`, `double`, `array` | +| `minTtl` | 0 | +| `maxTtl` | 0 | +| `staticTtl` | `true` | +| `ttlPrecision` | 1 | +| `useRequestTime` | `false` | +| `lockOnExpire` | 0 | +| `maxKeyLength` | 255 | +| `namespaceIsPrefix` | `true` | +| `namespaceSeparator` | *Option value of `namespace_separator`* | + +### Metadata + +| Metadata | Type | Description | +|----------|----------|:---------------------------------------------| +| `id` | `string` | The primary key within the mongo collection. | + + +### Adapter Specific Options + +| Name | Data Type | Default Value | Description | +|-----------------------|-----------|---------------|----------------------------------------------------------------------| +| `lib_option` | `array` | | Associative array of options where the array key is the option name. | +| `namespace_separator` | `string` | ":" | A separator for the namespace and prefix. | + +Available keys for `lib_option` include: + +| Key | Default | Description | +|---------------------|-----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `server` | `mongodb://localhost:27017` | The MongoDB server connection string (see the [MongoDB\\Client docs](https://docs.mongodb.com/php-library/current/reference/method/MongoDBClient__construct/)). | +| `database` | `laminas` | Name of the database to use; MongoDB will create this database if it does not exist. | +| `collection` | `cache` | Name of the collection to use; MongoDB will create this collection if it does not exist. | +| `connectionOptions` | `['fsync' => false, 'journal' => true]` | Associative array of URI options (such as authentication credentials or query string parameters) to pass to `MongoDB\\Client` (see the [MongoDB\\Client docs](https://docs.mongodb.com/php-library/current/reference/method/MongoDBClient__construct/)). | +| `driverOptions` | `[]` | Associative array of driver options to pass to `MongoDB\\Client` (see the [MongoDB\\Client docs](https://docs.mongodb.com/php-library/current/reference/method/MongoDBClient__construct/)). | + +## Examples + +### Basic Usage + +```php +use Laminas\Cache\Service\StorageAdapterFactoryInterface; +use Psr\Container\ContainerInterface; + +/** @var ContainerInterface $container */ +$container = null; // can be any configured PSR-11 container + +/** @var StorageAdapterFactoryInterface $storageFactory */ +$storageFactory = $container->get(StorageAdapterFactoryInterface::class); + +$cache = $storageFactory->create( + 'filesystem', + [], + [ + // Don't throw exceptions on cache errors + [ + 'name' => 'exception_handler', + 'options' => [ + 'throw_exceptions' => false + ], + ], + ] +); + +$key = 'unique-cache-key'; +$result = $cache->getItem($key, $success); +if (! $success) { + $result = doExpensiveStuff(); + $cache->setItem($key, $result); +} +``` + +### Get multiple Rows from a Database + +```php +use Laminas\Cache\Service\StorageAdapterFactoryInterface; +use Psr\Container\ContainerInterface; + +/** @var ContainerInterface $container */ +$container = null; // can be any configured PSR-11 container + +/** @var StorageAdapterFactoryInterface $storageFactory */ +$storageFactory = $container->get(StorageAdapterFactoryInterface::class); + +// Instantiate the cache instance using a namespace for the same type of items +$cache = $storageFactory->create( + 'filesystem', + // With a namespace, we can indicate the same type of items, + // so we can simply use the database id as the cache key + [ + 'namespace' => 'dbtable', + ], + [ + // Don't throw exceptions on cache errors + [ + 'name' => 'exception_handler', + 'options' => [ + 'throw_exceptions' => false, + ], + ], + // We store database rows on filesystem so we need to serialize them + [ + 'name' => 'Serializer', + ], + ] +); + +// Load two rows from cache if possible +$ids = [1, 2]; +$results = $cache->getItems($ids); +if (count($results) < count($ids)) { + // Load rows from db if loading from cache failed + $missingIds = array_diff($ids, array_keys($results)); + $missingResults = []; + $query = 'SELECT * FROM dbtable WHERE id IN (' . implode(',', $missingIds) . ')'; + foreach ($pdo->query($query, PDO::FETCH_ASSOC) as $row) { + $missingResults[ $row['id'] ] = $row; + } + + // Update cache items of the loaded rows from db + $cache->setItems($missingResults); + + // merge results from cache and db + $results = array_merge($results, $missingResults); +} +``` diff --git a/docs/book/v4/storage/capabilities.md b/docs/book/v4/storage/capabilities.md new file mode 100644 index 00000000..6d1c8e92 --- /dev/null +++ b/docs/book/v4/storage/capabilities.md @@ -0,0 +1,228 @@ +# Storage Capabilities + +Storage capabilities describe how a storage adapter works, and which features it +supports. + +To get capabilities of a storage adapter, you can use the method +`getCapabilities()`, but only the storage adapter and its plugins have +permissions to change them. + +Because capabilities are mutable, you can subscribe to the "change" event to get +notifications; see the examples for details. + +If you are writing your own plugin or adapter, you can also change capabilities +because you have access to the marker object and can create your own marker to +instantiate a new instance of `Laminas\Cache\Storage\Capabilities`. + +## Available Methods + +```php +namespace Laminas\Cache\Storage; + +use ArrayObject; +use stdClass; +use Laminas\Cache\Exception; +use Laminas\EventManager\EventsCapableInterface; + +class Capabilities +{ + /** + * Constructor + * + */ + public function __construct( + StorageInterface $storage, + stdClass $marker, + array $capabilities = [], + Capabilities|null $baseCapabilities = null + ); + + /** + * Get the storage adapter + */ + public function getAdapter(): StorageInterface; + + /** + * Get supported datatypes + */ + public function getSupportedDatatypes(): array; + + /** + * Set supported datatypes + * + * @param stdClass $marker + * @param array $datatypes + * @throws Exception\InvalidArgumentException + * @return Capabilities Fluent interface + */ + public function setSupportedDatatypes(stdClass $marker, array $datatypes); + + /** + * Get minimum supported time-to-live + * + * @return int 0 means items never expire + */ + public function getMinTtl(): int; + + /** + * Set minimum supported time-to-live + * + * @param stdClass $marker + * @param int $minTtl + * @throws Exception\InvalidArgumentException + */ + public function setMinTtl(stdClass $marker, int $minTtl): self; + + /** + * Get maximum supported time-to-live + * + * @return int 0 means infinite + */ + public function getMaxTtl(): int; + + /** + * Set maximum supported time-to-live + * + * @throws Exception\InvalidArgumentException + */ + public function setMaxTtl(stdClass $marker, int $maxTtl): self; + + /** + * Is the time-to-live handled static (on write) + * or dynamic (on read) + */ + public function getStaticTtl(): bool; + + /** + * Set if the time-to-live handled static (on write) or dynamic (on read) + */ + public function setStaticTtl(stdClass $marker, bool $flag): self; + + /** + * Get time-to-live precision + */ + public function getTtlPrecision(): float; + + /** + * Set time-to-live precision + * + * @throws Exception\InvalidArgumentException + */ + public function setTtlPrecision(stdClass $marker, float $ttlPrecision): self; + + /** + * Get use request time + */ + public function getUseRequestTime(): bool; + + /** + * Set use request time + */ + public function setUseRequestTime(stdClass $marker, bool $flag): self; + + + /** + * Get "lock-on-expire" support in seconds. + * + * @return int 0 = Expired items will never be retrieved + * >0 = Time in seconds an expired item could be retrieved + * -1 = Expired items could be retrieved forever + */ + public function getLockOnExpire(): int + { + return $this->getCapability('lockOnExpire', 0); + } + + /** + * Set "lock-on-expire" support in seconds. + */ + public function setLockOnExpire(stdClass $marker, int $timeout): self + { + return $this->setCapability($marker, 'lockOnExpire', (int) $timeout); + } + + /** + * Get maximum key length + * + * @return int -1 means unknown, 0 means infinite + */ + public function getMaxKeyLength(): int; + + /** + * Set maximum key length + * + * @throws Exception\InvalidArgumentException + */ + public function setMaxKeyLength(stdClass $marker, int $maxKeyLength): self; + + /** + * Get if namespace support is implemented as prefix + */ + public function getNamespaceIsPrefix(): bool; + + /** + * Set if namespace support is implemented as prefix + */ + public function setNamespaceIsPrefix(stdClass $marker, bool $flag): self; + + /** + * Get namespace separator if namespace is implemented as prefix + */ + public function getNamespaceSeparator(): string; + + /** + * Set the namespace separator if namespace is implemented as prefix + */ + public function setNamespaceSeparator(stdClass $marker, string $separator): self; +} +``` + +## Examples + +### Get Storage Capabilities and do specific Stuff based on them + +```php +use Laminas\Cache\Service\StorageAdapterFactoryInterface; +use Psr\Container\ContainerInterface; + +/** @var ContainerInterface $container */ +$container = null; // can be any configured PSR-11 container + +/** @var StorageAdapterFactoryInterface $storageFactory */ +$storageFactory = $container->get(StorageAdapterFactoryInterface::class); + +$cache = $storageFactory->create('filesystem'); +$supportedDatatypes = $cache->getCapabilities()->getSupportedDatatypes(); + +// now you can run specific stuff in base of supported feature +if ($supportedDatatypes['object']) { + $cache->set($key, $object); +} else { + $cache->set($key, serialize($object)); +} +``` + +### Listen to the change Event + +```php +use Laminas\Cache\Service\StorageAdapterFactoryInterface; +use Psr\Container\ContainerInterface; + +/** @var ContainerInterface $container */ +$container = null; // can be any configured PSR-11 container + +/** @var StorageAdapterFactoryInterface $storageFactory */ +$storageFactory = $container->get(StorageAdapterFactoryInterface::class); + +$cache = $storageFactory->create('filesystem', [ + 'no_atime' => false, +]); + +// Catching capability changes +$cache->getEventManager()->attach('capability', function($event) { + echo count($event->getParams()) . ' capabilities changed'; +}); + +// change option which changes capabilities +$cache->getOptions()->setNoATime(true); +``` diff --git a/docs/book/v4/storage/plugin.md b/docs/book/v4/storage/plugin.md new file mode 100644 index 00000000..cf66fa89 --- /dev/null +++ b/docs/book/v4/storage/plugin.md @@ -0,0 +1,220 @@ +# Plugins + +Cache storage plugins are objects that provide additional functionality to or +influence behavior of a storage adapter. + +The plugins listen to events the adapter triggers, and can: + +- change the arguments provided to the method triggering the event (via `*.post` events) +- skip and directly return a result (by calling `stopPropagation`) +- change the result (by calling `setResult` on the provided `Laminas\Cache\Storage\PostEvent`) +- catch exceptions (by reacting to `Laminas\Cache\Storage\ExceptionEvent`) + +## Quick Start + +Storage plugins can either be created from +`Laminas\Cache\Service\StoragePluginFactoryInterface::create()`, or by instantiating one of the +`Laminas\Cache\Storage\Plugin\*` classes. + +To make life easier, `Laminas\Cache\Service\StoragePluginFactoryInterface::create()` can create both the +requested adapter and all specified plugins at once. + +```php +use Laminas\Cache\Service\StoragePluginFactoryInterface; +use Psr\Container\ContainerInterface; +use Laminas\Cache\Service\StorageAdapterFactoryInterface; + +/** @var ContainerInterface $container */ +$container = null; // can be any configured PSR-11 container + +$storageFactory = $container->get(StorageAdapterFactoryInterface::class); + +// All at once: +$cache = $storageFactory->create( + 'filesystem', + [], + [ + ['name' => 'serializer'], + ] +); + +// Alternately, via discrete factory methods: +$cache = $storageFactory->create('filesystem'); + +$pluginFactory = $container->get(StoragePluginFactoryInterface::class); +$plugin = $pluginFactory->create('serializer'); +$cache->addPlugin($plugin); + +// Or manually: +$cache = new Laminas\Cache\Storage\Adapter\Filesystem(); +$plugin = new Laminas\Cache\Storage\Plugin\Serializer(); +$cache->addPlugin($plugin); +``` + +## The ClearExpiredByFactor Plugin + +`Laminas\Cache\Storage\Plugin\ClearExpiredByFactor` calls the storage method +`clearExpired()` randomly (by factor) after every call of `setItem()`, +`setItems()`, `addItem()`, and `addItems()`. + +### Plugin specific Options + +| Name | Data Type | Default Value | Description | +|-------------------|-----------|---------------|--------------------------------| +| `clearing_factor` | `integer` | `0` | The automatic clearing factor. | + +> ### Adapter must implement ClearExpiredInterface +> +> The storage adapter must implement `Laminas\Cache\Storage\ClearExpiredInterface` +> to work with this plugin. + +## The ExceptionHandler Plugin + +`Laminas\Cache\Storage\Plugin\ExceptionHandler` catches all exceptions thrown on +reading from or writing to the cache, and sends the exception to a defined callback function. +You may also configure the plugin to re-throw exceptions. + +### Plugin specific Options + +| Name | Data Type | Default Value | Description | +|----------------------|-----------|---------------|-----------------------------| +| `exception_callback` | `callable | null` | null | Callback to invoke on exception; receives the exception as the sole argument. +| `throw_exceptions` | `boolean` | `true` | Re-throw caught exceptions. | + +## The IgnoreUserAbort Plugin + +`Laminas\Cache\Storage\Plugin\IgnoreUserAbort` ignores user-invoked script +termination when, allowing cache write operations to complete first. + +### Plugin specific Options + +| Name | Data Type | Default Value | Description | +|-----------------|-----------|---------------|-------------------------------------------| +| `exit_on_abort` | `boolean` | `true` | Terminate script execution on user abort. | + +## The OptimizeByFactor Plugin + +`Laminas\Cache\Storage\Plugin\OptimizeByFactor` calls the storage method `optimize()` +randomly (by factor) after removing items from the cache. + +### Plugin specific Options + +| Name | Data Type | Default Value | Description | +|---------------------|-----------|---------------|------------------------------------| +| `optimizing_factor` | `integer` | `0` | The automatic optimization factor. | + +> ### Adapter must implement OptimizableInterface +> +> The storage adapter must implement `Laminas\Cache\Storage\OptimizableInterface` +> to work with this plugin. + +## The Serializer Plugin + +`Laminas\Cache\Storage\Plugin\Serializer` will serialize data when writing to +cache, and deserialize when reading. This allows storing datatypes not supported +by the underlying storage adapter. + +### Plugin specific Options + +| Name | Data Type | Default Value | Description | +|----------------------|-------------------------------------------------------------|---------------|----------------------------------------------------------------------| +| `serializer` | `null\|string\|Laminas\Serializer\Adapter\AdapterInterface` | `null` | The serializer to use; see below. | +| `serializer_options` | `array` | `[]` | Array of options to use when instantiating the specified serializer. | + +The `serializer` value has two special cases: + +- When `null`, the default serializer is used (JSON). +- When a `string`, the value will be pulled via + `Laminas\Serializer\AdapterPluginManager`, with the provided + `serializer_options`. + +## Available Methods + +The following methods are available to all `Laminas\Cache\Storage\Plugin\PluginInterface` implementations: + +```php +namespace Laminas\Cache\Storage\Plugin; + +use Laminas\EventManager\EventManagerInterface; +use Laminas\EventManager\ListenerAggregateInterface; + +interface PluginInterface extends ListenerAggregateInterface +{ + /** + * Set options + */ + public function setOptions(PluginOptions $options): self; + + /** + * Get options + */ + public function getOptions(): PluginOptions; + + /** + * Attach listeners; inherited from ListenerAggregateInterface. + */ + public function attach(EventManagerInterface $events, int $priority = 1): void; + + /** + * Detach listeners; inherited from ListenerAggregateInterface. + */ + public function detach(EventManagerInterface $events): void; +} +``` + +## Examples + +### Basic Plugin Implementation + +```php +use Laminas\Cache\Storage\Event; +use Laminas\Cache\Storage\Plugin\AbstractPlugin; +use Laminas\EventManager\EventManagerInterface; + +class MyPlugin extends AbstractPlugin +{ + protected $handles = []; + + /** + * Attach to all events this plugin is interested in. + */ + public function attach(EventManagerInterface $events, int $priority = 1): void + { + $this->handles[] = $events->attach('getItem.pre', array($this, 'onGetItemPre'), $priority); + $this->handles[] = $events->attach('getItem.post', array($this, 'onGetItemPost'), $priority); + } + + /** + * Detach all handlers this plugin previously attached. + */ + public function detach(EventManagerInterface $events): void + { + foreach ($this->handles as $handle) { + $events->detach($handle); + } + $this->handles = []; + } + + public function onGetItemPre(Event $event): void + { + $params = $event->getParams(); + echo sprintf("Method 'getItem' with key '%s' started\n", $params['key']); + } + + public function onGetItemPost(Event $event): void + { + $params = $event->getParams(); + echo sprintf("Method 'getItem' with key '%s' finished\n", $params['key']); + } +} + +// After defining this plugin, we can instantiate and add it to an adapter +// instance: +$plugin = new MyPlugin(); +$cache->addPlugin($plugin); + +// Now when calling getItem(), our plugin should print the expected output: +$cache->getItem('cache-key'); +// Method 'getItem' with key 'cache-key' started +// Method 'getItem' with key 'cache-key' finished +``` diff --git a/mkdocs.yml b/mkdocs.yml index 79b5402c..5163ea8b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,6 +2,25 @@ docs_dir: docs/book site_dir: docs/html nav: - Home: index.md + - v4: + - Installation: v4/installation.md + - Storage: + - Adapters: v4/storage/adapter.md + - Capabilities: v4/storage/capabilities.md + - Plugins: v4/storage/plugin.md + - "Cache Patterns": + - Introduction: v4/pattern/intro.md + - CallbackCache: v4/pattern/callback-cache.md + - ObjectCache: v4/pattern/object-cache.md + - OutputCache: v4/pattern/output-cache.md + - CaptureCache: v4/pattern/capture-cache.md + - "Standards Support": + - PSR-6: v4/psr6.md + - PSR-16: v4/psr16.md + - "Application Integration": + - "Usage in a laminas-mvc Application": v4/application-integration/usage-in-a-laminas-mvc-application.md + - "Migration Guide": + - "Migration to Version 4.0": v4/migration/to-version-4.md - v3: - Installation: v3/installation.md - Storage: diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index ad4a4a50..762f7a89 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -25,9 +25,9 @@ class ConfigProvider /** * Return default configuration for laminas-cache. * - * @return array + * @return array{dependencies:ServiceManagerConfiguration,...} */ - public function __invoke() + public function __invoke(): array { return [ 'dependencies' => $this->getDependencyConfig(), @@ -40,7 +40,7 @@ public function __invoke() * * @return ServiceManagerConfiguration */ - public function getDependencyConfig() + public function getDependencyConfig(): array { $dependencies = [ 'abstract_factories' => [ diff --git a/src/Module.php b/src/Module.php index a5ee7944..81e71178 100644 --- a/src/Module.php +++ b/src/Module.php @@ -6,10 +6,8 @@ class Module { /** * Return default laminas-cache configuration for laminas-mvc context. - * - * @return array */ - public function getConfig() + public function getConfig(): array { $provider = new ConfigProvider(); return [ diff --git a/src/Pattern/CaptureCache.php b/src/Pattern/CaptureCache.php index e673456b..dcda1b8e 100644 --- a/src/Pattern/CaptureCache.php +++ b/src/Pattern/CaptureCache.php @@ -28,13 +28,7 @@ class CaptureCache extends AbstractPattern { - /** - * Start the cache - * - * @param string|null $pageId Page identifier - * @return void - */ - public function start(string|null $pageId = null) + public function start(string|null $pageId = null): void { if ($pageId === null) { $pageId = $this->detectPageId(); diff --git a/src/Pattern/ObjectCache.php b/src/Pattern/ObjectCache.php index e24c4311..14df28a2 100644 --- a/src/Pattern/ObjectCache.php +++ b/src/Pattern/ObjectCache.php @@ -211,7 +211,7 @@ public function __call(string $method, array $args): mixed * * @param non-empty-string $name */ - public function __set($name, mixed $value): void + public function __set(string $name, mixed $value): void { $this->call('__set', [$name, $value]); } diff --git a/src/Psr/SerializationTrait.php b/src/Psr/SerializationTrait.php index 52652eef..c6541bb8 100644 --- a/src/Psr/SerializationTrait.php +++ b/src/Psr/SerializationTrait.php @@ -16,10 +16,8 @@ trait SerializationTrait { /** * Determine if the given storage adapter requires serialization. - * - * @return bool */ - private function isSerializationRequired(StorageInterface $storage) + private function isSerializationRequired(StorageInterface $storage): bool { $capabilities = $storage->getCapabilities(); $requiredTypes = ['string', 'integer', 'double', 'boolean', 'NULL', 'array', 'object']; diff --git a/src/Storage/Plugin/OptimizeByFactor.php b/src/Storage/Plugin/OptimizeByFactor.php index 6c3a5dc1..7c4a369f 100644 --- a/src/Storage/Plugin/OptimizeByFactor.php +++ b/src/Storage/Plugin/OptimizeByFactor.php @@ -23,10 +23,9 @@ public function attach(EventManagerInterface $events, $priority = 1): void /** * Optimize by factor on a success _RESULT_ * - * @return void * @phpcs:disable Generic.NamingConventions.ConstructorName.OldStyle */ - public function optimizeByFactor(PostEvent $event) + public function optimizeByFactor(PostEvent $event): void { $storage = $event->getStorage(); if (! $storage instanceof OptimizableInterface) {