├── .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 |
4 |
5 |
Yii Cache Library - APCu Handler
6 |
7 |
8 |
9 | [](https://packagist.org/packages/yiisoft/cache-apcu)
10 | [](https://packagist.org/packages/yiisoft/cache-apcu)
11 | [](https://github.com/yiisoft/cache-apcu/actions?query=workflow%3Abuild)
12 | [](https://codecov.io/gh/yiisoft/cache-apcu)
13 | [](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/cache-apcu/master)
14 | [](https://github.com/yiisoft/cache-apcu/actions?query=workflow%3A%22static+analysis%22)
15 | [](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 | [](https://opencollective.com/yiisoft)
93 |
94 | ## Follow updates
95 |
96 | [](https://www.yiiframework.com/)
97 | [](https://twitter.com/yiiframework)
98 | [](https://t.me/yii3en)
99 | [](https://www.facebook.com/groups/yiitalk)
100 | [](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 |