├── .phpunit-watcher.yml ├── .styleci.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── infection.json.dist ├── psalm-8.0.xml ├── psalm.xml └── src ├── ApcuCache.php └── InvalidArgumentException.php /.phpunit-watcher.yml: -------------------------------------------------------------------------------- 1 | watch: 2 | directories: 3 | - src 4 | - tests 5 | fileMask: '*.php' 6 | notifications: 7 | passingTests: false 8 | failingTests: false 9 | phpunit: 10 | binaryPath: vendor/bin/phpunit 11 | timeout: 180 12 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr12 2 | risky: true 3 | 4 | version: 8.1 5 | 6 | finder: 7 | exclude: 8 | - docs 9 | - vendor 10 | 11 | enabled: 12 | - alpha_ordered_traits 13 | - array_indentation 14 | - array_push 15 | - combine_consecutive_issets 16 | - combine_consecutive_unsets 17 | - combine_nested_dirname 18 | - declare_strict_types 19 | - dir_constant 20 | - fully_qualified_strict_types 21 | - function_to_constant 22 | - hash_to_slash_comment 23 | - is_null 24 | - logical_operators 25 | - magic_constant_casing 26 | - magic_method_casing 27 | - method_separation 28 | - modernize_types_casting 29 | - native_function_casing 30 | - native_function_type_declaration_casing 31 | - no_alias_functions 32 | - no_empty_comment 33 | - no_empty_phpdoc 34 | - no_empty_statement 35 | - no_extra_block_blank_lines 36 | - no_short_bool_cast 37 | - no_superfluous_elseif 38 | - no_unneeded_control_parentheses 39 | - no_unneeded_curly_braces 40 | - no_unneeded_final_method 41 | - no_unset_cast 42 | - no_unused_imports 43 | - no_unused_lambda_imports 44 | - no_useless_else 45 | - no_useless_return 46 | - normalize_index_brace 47 | - php_unit_dedicate_assert 48 | - php_unit_dedicate_assert_internal_type 49 | - php_unit_expectation 50 | - php_unit_mock 51 | - php_unit_mock_short_will_return 52 | - php_unit_namespaced 53 | - php_unit_no_expectation_annotation 54 | - phpdoc_no_empty_return 55 | - phpdoc_no_useless_inheritdoc 56 | - phpdoc_order 57 | - phpdoc_property 58 | - phpdoc_scalar 59 | - phpdoc_singular_inheritdoc 60 | - phpdoc_trim 61 | - phpdoc_trim_consecutive_blank_line_separation 62 | - phpdoc_type_to_var 63 | - phpdoc_types 64 | - phpdoc_types_order 65 | - print_to_echo 66 | - regular_callable_call 67 | - return_assignment 68 | - self_accessor 69 | - self_static_accessor 70 | - set_type_to_cast 71 | - short_array_syntax 72 | - short_list_syntax 73 | - simplified_if_return 74 | - single_quote 75 | - standardize_not_equals 76 | - ternary_to_null_coalescing 77 | - trailing_comma_in_multiline_array 78 | - unalign_double_arrow 79 | - unalign_equals 80 | - empty_loop_body_braces 81 | - integer_literal_case 82 | - union_type_without_spaces 83 | 84 | disabled: 85 | - function_declaration 86 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Yii Cache Library - APCu Handler Change Log 2 | 3 | ## 1.1.1 under development 4 | 5 | - no changes in this release. 6 | 7 | ## 1.1.0 April 24, 2024 8 | 9 | - Chg #28: Raise the minimum `psr/simple-cache` version to `^2.0|^3.0` and the minimum PHP version to `^8.0` (@dehbka) 10 | 11 | ## 1.0.0 February 02, 2021 12 | 13 | - Initial release. 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2008 by Yii Software () 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions, and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Yii Software nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Yii 4 | 5 |

Yii Cache Library - APCu Handler

6 |
7 |

8 | 9 | [![Latest Stable Version](https://poser.pugx.org/yiisoft/cache-apcu/v/stable.png)](https://packagist.org/packages/yiisoft/cache-apcu) 10 | [![Total Downloads](https://poser.pugx.org/yiisoft/cache-apcu/downloads.png)](https://packagist.org/packages/yiisoft/cache-apcu) 11 | [![Build status](https://github.com/yiisoft/cache-apcu/workflows/build/badge.svg)](https://github.com/yiisoft/cache-apcu/actions?query=workflow%3Abuild) 12 | [![Code Coverage](https://codecov.io/gh/yiisoft/cache-apcu/branch/master/graph/badge.svg)](https://codecov.io/gh/yiisoft/cache-apcu) 13 | [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fyiisoft%2Fcache-apcu%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/cache-apcu/master) 14 | [![static analysis](https://github.com/yiisoft/cache-apcu/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/cache-apcu/actions?query=workflow%3A%22static+analysis%22) 15 | [![type-coverage](https://shepherd.dev/github/yiisoft/cache-apcu/coverage.svg)](https://shepherd.dev/github/yiisoft/cache-apcu) 16 | 17 | This package uses the PHP [APCu](https://www.php.net/manual/book.apcu.php) 18 | extension and implements [PSR-16](https://www.php-fig.org/psr/psr-16/) cache. 19 | 20 | This option can be considered as the fastest one when dealing with a cache for a 21 | centralized thick application (e.g. one server, no dedicated load balancers, etc.). 22 | 23 | ## Requirements 24 | 25 | - PHP 8.0 or higher. 26 | - `APCu` PHP extension. 27 | 28 | ## Installation 29 | 30 | The package could be installed with [Composer](https://getcomposer.org): 31 | 32 | ```shell 33 | composer require yiisoft/cache-apcu 34 | ``` 35 | 36 | ## General usage 37 | 38 | The package does not contain any additional functionality for interacting with the cache, 39 | except those defined in the [PSR-16](https://www.php-fig.org/psr/psr-16/) interface. 40 | 41 | ```php 42 | $cache = new \Yiisoft\Cache\Apcu\ApcuCache(); 43 | $parameters = ['user_id' => 42]; 44 | $key = 'demo'; 45 | 46 | // try retrieving $data from cache 47 | $data = $cache->get($key); 48 | 49 | if ($data === null) { 50 | // $data is not found in cache, calculate it from scratch 51 | $data = calculateData($parameters); 52 | 53 | // store $data in cache for an hour so that it can be retrieved next time 54 | $cache->set($key, $data, 3600); 55 | } 56 | 57 | // $data is available here 58 | ``` 59 | 60 | In order to delete value you can use: 61 | 62 | ```php 63 | $cache->delete($key); 64 | // Or all cache 65 | $cache->clear(); 66 | ``` 67 | 68 | To work with values in a more efficient manner, batch operations should be used: 69 | 70 | - `getMultiple()` 71 | - `setMultiple()` 72 | - `deleteMultiple()` 73 | 74 | This package can be used as a cache handler for the [Yii Caching Library](https://github.com/yiisoft/cache). 75 | 76 | ## Documentation 77 | 78 | - [Internals](docs/internals.md) 79 | 80 | If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for 81 | that. You may also check out other [Yii Community Resources](https://www.yiiframework.com/community). 82 | 83 | ## License 84 | 85 | The Yii Cache Library - APCu Handler is free software. It is released under the terms of the BSD License. 86 | Please see [`LICENSE`](./LICENSE.md) for more information. 87 | 88 | Maintained by [Yii Software](https://www.yiiframework.com/). 89 | 90 | ## Support the project 91 | 92 | [![Open Collective](https://img.shields.io/badge/Open%20Collective-sponsor-7eadf1?logo=open%20collective&logoColor=7eadf1&labelColor=555555)](https://opencollective.com/yiisoft) 93 | 94 | ## Follow updates 95 | 96 | [![Official website](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](https://www.yiiframework.com/) 97 | [![Twitter](https://img.shields.io/badge/twitter-follow-1DA1F2?logo=twitter&logoColor=1DA1F2&labelColor=555555?style=flat)](https://twitter.com/yiiframework) 98 | [![Telegram](https://img.shields.io/badge/telegram-join-1DA1F2?style=flat&logo=telegram)](https://t.me/yii3en) 99 | [![Facebook](https://img.shields.io/badge/facebook-join-1DA1F2?style=flat&logo=facebook&logoColor=ffffff)](https://www.facebook.com/groups/yiitalk) 100 | [![Slack](https://img.shields.io/badge/slack-join-1DA1F2?style=flat&logo=slack)](https://yiiframework.com/go/slack) 101 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiisoft/cache-apcu", 3 | "type": "library", 4 | "description": "Yii Caching Library - APCu Handler", 5 | "keywords": [ 6 | "yii", 7 | "framework", 8 | "cache", 9 | "apcu", 10 | "psr-16" 11 | ], 12 | "homepage": "https://www.yiiframework.com/", 13 | "license": "BSD-3-Clause", 14 | "support": { 15 | "issues": "https://github.com/yiisoft/cache-apcu/issues?state=open", 16 | "source": "https://github.com/yiisoft/cache-apcu", 17 | "forum": "https://www.yiiframework.com/forum/", 18 | "wiki": "https://www.yiiframework.com/wiki/", 19 | "irc": "ircs://irc.libera.chat:6697/yii", 20 | "chat": "https://t.me/yii3en" 21 | }, 22 | "funding": [ 23 | { 24 | "type": "opencollective", 25 | "url": "https://opencollective.com/yiisoft" 26 | }, 27 | { 28 | "type": "github", 29 | "url": "https://github.com/sponsors/yiisoft" 30 | } 31 | ], 32 | "require": { 33 | "php": "^8.0", 34 | "ext-apcu": "*", 35 | "psr/simple-cache": "^2.0|^3.0" 36 | }, 37 | "require-dev": { 38 | "phpunit/phpunit": "^9.5", 39 | "roave/infection-static-analysis-plugin": "^1.16", 40 | "spatie/phpunit-watcher": "^1.23", 41 | "vimeo/psalm": "^4.30|^5.25" 42 | }, 43 | "provide": { 44 | "psr/simple-cache-implementation": "1.0.0" 45 | }, 46 | "autoload": { 47 | "psr-4": { 48 | "Yiisoft\\Cache\\Apcu\\": "src" 49 | } 50 | }, 51 | "autoload-dev": { 52 | "psr-4": { 53 | "Yiisoft\\Cache\\Apcu\\Tests\\": "tests" 54 | } 55 | }, 56 | "config": { 57 | "sort-packages": true, 58 | "allow-plugins": { 59 | "infection/extension-installer": true, 60 | "composer/package-versions-deprecated": true 61 | } 62 | }, 63 | "scripts": { 64 | "test": "phpunit --testdox --no-interaction", 65 | "test-watch": "phpunit-watcher watch" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /infection.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "directories": [ 4 | "src" 5 | ] 6 | }, 7 | "logs": { 8 | "text": "php:\/\/stderr", 9 | "stryker": { 10 | "report": "master" 11 | } 12 | }, 13 | "mutators": { 14 | "@default": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /psalm-8.0.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/ApcuCache.php: -------------------------------------------------------------------------------- 1 | validateKey($key); 40 | $value = apcu_fetch($key, $success); 41 | return $success ? $value : $default; 42 | } 43 | 44 | public function set(string $key, mixed $value, null|int|DateInterval $ttl = null): bool 45 | { 46 | $this->validateKey($key); 47 | $ttl = $this->normalizeTtl($ttl); 48 | 49 | if ($ttl <= self::TTL_EXPIRED) { 50 | return $this->delete($key); 51 | } 52 | 53 | return apcu_store($key, $value, $ttl); 54 | } 55 | 56 | public function delete(string $key): bool 57 | { 58 | $this->validateKey($key); 59 | return apcu_delete($key); 60 | } 61 | 62 | public function clear(): bool 63 | { 64 | return apcu_clear_cache(); 65 | } 66 | 67 | public function getMultiple(iterable $keys, mixed $default = null): iterable 68 | { 69 | $keys = $this->iterableToArray($keys); 70 | $this->validateKeys($keys); 71 | $values = array_fill_keys($keys, $default); 72 | 73 | if (($valuesFromCache = apcu_fetch($keys)) === false) { 74 | /** @var array $values */ 75 | return $values; 76 | } 77 | 78 | $valuesFromCache = $this->normalizeAPCuOutput($valuesFromCache); 79 | 80 | foreach ($values as $key => $value) { 81 | $values[$key] = $valuesFromCache[(string) $key] ?? $value; 82 | } 83 | 84 | /** @var array $values */ 85 | return $values; 86 | } 87 | 88 | public function setMultiple(iterable $values, null|int|DateInterval $ttl = null): bool 89 | { 90 | $ttl = $this->normalizeTtl($ttl); 91 | $values = $this->iterableToArray($values); 92 | $this->validateKeysOfValues($values); 93 | [$valuesWithStringKeys, $valuesWithIntegerKeys] = $this->splitValuesByKeyType($values); 94 | 95 | /** @psalm-suppress RedundantCondition */ 96 | if (apcu_store($valuesWithStringKeys, null, $ttl) !== []) { 97 | return false; 98 | } 99 | 100 | foreach ($valuesWithIntegerKeys as $key => $value) { 101 | if (!apcu_store((string) $key, $value, $ttl)) { 102 | return false; 103 | } 104 | } 105 | 106 | return true; 107 | } 108 | 109 | public function deleteMultiple(iterable $keys): bool 110 | { 111 | $keys = $this->iterableToArray($keys); 112 | $this->validateKeys($keys); 113 | return apcu_delete($keys) === []; 114 | } 115 | 116 | public function has(string $key): bool 117 | { 118 | $this->validateKey($key); 119 | return apcu_exists($key); 120 | } 121 | 122 | /** 123 | * Normalizes cache TTL handling `null` value, strings and {@see DateInterval} objects. 124 | * 125 | * @param DateInterval|int|string|null $ttl The raw TTL. 126 | * 127 | * @return int TTL value as UNIX timestamp. 128 | */ 129 | private function normalizeTtl(null|int|string|DateInterval $ttl = null): int 130 | { 131 | if ($ttl === null) { 132 | return self::TTL_INFINITY; 133 | } 134 | 135 | if ($ttl instanceof DateInterval) { 136 | return (new DateTime('@0'))->add($ttl)->getTimestamp(); 137 | } 138 | 139 | $ttl = (int) $ttl; 140 | return $ttl > 0 ? $ttl : self::TTL_EXPIRED; 141 | } 142 | 143 | /** 144 | * Converts iterable to array. If provided value is not iterable it throws an InvalidArgumentException. 145 | * 146 | * @param iterable $iterable 147 | * 148 | * @return array 149 | */ 150 | private function iterableToArray(iterable $iterable): array 151 | { 152 | /** @psalm-suppress RedundantCast */ 153 | return $iterable instanceof Traversable ? iterator_to_array($iterable) : (array) $iterable; 154 | } 155 | 156 | private function validateKey(string $key): void 157 | { 158 | if ($key === '' || strpbrk($key, '{}()/\@:')) { 159 | throw new InvalidArgumentException('Invalid key value.'); 160 | } 161 | } 162 | 163 | /** 164 | * @param array $keys 165 | */ 166 | private function validateKeys(array $keys): void 167 | { 168 | foreach ($keys as $key) { 169 | $this->validateKey($key); 170 | } 171 | } 172 | 173 | /** 174 | * @param array $values 175 | */ 176 | private function validateKeysOfValues(array $values): void 177 | { 178 | $keys = array_map('\strval', array_keys($values)); 179 | $this->validateKeys($keys); 180 | } 181 | 182 | /** 183 | * Normalizes keys returned from apcu_fetch in multiple mode. If one of the keys is an integer (123) or a string 184 | * representation of an integer ('123') the returned key from the cache doesn't equal neither to an integer nor a 185 | * string ($key !== 123 and $key !== '123'). Coping element from the returned array one by one to the new array 186 | * fixes this issue. 187 | * 188 | * @param array $values 189 | * 190 | * @return array 191 | */ 192 | private function normalizeAPCuOutput(array $values): array 193 | { 194 | $normalizedValues = []; 195 | 196 | foreach ($values as $key => $value) { 197 | $normalizedValues[$key] = $value; 198 | } 199 | 200 | return $normalizedValues; 201 | } 202 | 203 | /** 204 | * Splits the array of values into two arrays, one with int keys and one with string keys. 205 | * 206 | * @param array $values 207 | * 208 | * @return array 209 | */ 210 | private function splitValuesByKeyType(array $values): array 211 | { 212 | $withIntKeys = []; 213 | 214 | foreach ($values as $key => $value) { 215 | if (is_int($key)) { 216 | $withIntKeys[$key] = $value; 217 | unset($values[$key]); 218 | } 219 | } 220 | 221 | return [$values, $withIntKeys]; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 |