├── roave-bc-check.yaml ├── CHANGELOG.md ├── infection.json.dist ├── CONTRIBUTING.md ├── psalm_autoload.php ├── src ├── CacheArgumentException.php ├── SupportTrait.php ├── Item.php ├── SimpleCache.php └── Pool.php ├── composer-unused.php ├── psalm.xml ├── .php-cs-fixer.dist.php ├── LICENSE ├── SECURITY.md ├── composer.json ├── README.md ├── deptrac.yaml └── rector.php /roave-bc-check.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - '#\[BC\] SKIPPED: .+ could not be found in the located source#' 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 6 | -------------------------------------------------------------------------------- /infection.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "directories": [ 4 | "src/" 5 | ], 6 | "excludes": [ 7 | "Config", 8 | "Database/Migrations", 9 | "Views" 10 | ] 11 | }, 12 | "logs": { 13 | "text": "build/infection.log" 14 | }, 15 | "mutators": { 16 | "@default": true 17 | }, 18 | "bootstrap": "vendor/codeigniter4/framework/system/Test/bootstrap.php" 19 | } 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CodeIgniter4 2 | 3 | CodeIgniter is a community driven project and accepts contributions of 4 | code and documentation from the community. 5 | 6 | If you'd like to contribute, please read [Contributing to CodeIgniter](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/README.md) 7 | in the [main repository](https://github.com/codeigniter4/CodeIgniter4). 8 | 9 | If you are going to contribute to this repository, please report bugs or send PRs 10 | to this repository instead of the main repository. 11 | -------------------------------------------------------------------------------- /psalm_autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace CodeIgniter\Psr\Cache; 13 | 14 | use InvalidArgumentException; 15 | use Psr\Cache\InvalidArgumentException as CacheException; 16 | use Psr\SimpleCache\InvalidArgumentException as SimpleCacheException; 17 | 18 | final class CacheArgumentException extends InvalidArgumentException implements CacheException, SimpleCacheException 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /composer-unused.php: -------------------------------------------------------------------------------- 1 | addNamedFilter(NamedFilter::fromString('symfony/config')) 13 | // ->addPatternFilter(PatternFilter::fromString('/symfony-.*/')) 14 | ->setAdditionalFilesFor('codeigniter4/framework', [ 15 | ...Glob::glob(__DIR__ . '/vendor/codeigniter4/framework/system/Helpers/*.php'), 16 | ]); 17 | }; 18 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | files() 9 | ->in([ 10 | __DIR__ . '/src/', 11 | __DIR__ . '/tests/', 12 | ]) 13 | ->exclude([ 14 | 'build', 15 | 'Views', 16 | ]) 17 | ->append([ 18 | __FILE__, 19 | __DIR__ . '/rector.php', 20 | ]); 21 | 22 | $overrides = [ 23 | // 'declare_strict_types' => true, 24 | // 'void_return' => true, 25 | ]; 26 | 27 | $options = [ 28 | 'finder' => $finder, 29 | 'cacheFile' => 'build/.php-cs-fixer.cache', 30 | ]; 31 | 32 | return Factory::create(new CodeIgniter4(), $overrides, $options)->forProjects(); 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 CodeIgniter Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | The development team and community take all security issues seriously. **Please do not make public any uncovered flaws.** 4 | 5 | ## Reporting a Vulnerability 6 | 7 | Thank you for improving the security of our code! Any assistance in removing security flaws will be acknowledged. 8 | 9 | **Please report security flaws by emailing the development team directly: security@codeigniter.com**. 10 | 11 | The lead maintainer will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating 12 | the next steps in handling your report. After the initial reply to your report, the security team will endeavor to keep you informed of the 13 | progress towards a fix and full announcement, and may ask for additional information or guidance. 14 | 15 | ## Disclosure Policy 16 | 17 | When the security team receives a security bug report, they will assign it to a primary handler. 18 | This person will coordinate the fix and release process, involving the following steps: 19 | 20 | - Confirm the problem and determine the affected versions. 21 | - Audit code to find any potential similar problems. 22 | - Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible. 23 | 24 | ## Comments on this Policy 25 | 26 | If you have suggestions on how this process could be improved please submit a Pull Request. 27 | -------------------------------------------------------------------------------- /src/SupportTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace CodeIgniter\Psr\Cache; 13 | 14 | use CodeIgniter\Cache\CacheInterface; 15 | use Config\Cache; 16 | 17 | /** 18 | * Cache Support Trait 19 | * 20 | * Provides methods common to both 21 | * PSR-6 and PSR-16 drivers. 22 | */ 23 | trait SupportTrait 24 | { 25 | /** 26 | * The adapter to use. 27 | * 28 | * @var CacheInterface 29 | */ 30 | private $adapter; 31 | 32 | /** 33 | * Initializes the underlying adapter 34 | * from an existing instance or from the 35 | * Cache Service (with optional config). 36 | * 37 | * @param object|null $object 38 | * 39 | * @throws CacheArgumentException 40 | */ 41 | public function __construct($object = null) 42 | { 43 | if (null === $object) { 44 | $this->adapter = service('cache'); 45 | } elseif ($object instanceof Cache) { 46 | $this->adapter = service('cache', $object, false); 47 | } elseif ($object instanceof CacheInterface) { 48 | $this->adapter = $object; 49 | } else { 50 | throw new CacheArgumentException(self::class . ' constructor only accepts an adapter or configuration'); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codeigniter4/cache", 3 | "description": "PSR-6 and PSR-16 Cache Adapters for CodeIgniter 4", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "codeigniter", 8 | "codeigniter4", 9 | "cache", 10 | "pool", 11 | "simplecache", 12 | "psr-6", 13 | "psr-16" 14 | ], 15 | "authors": [ 16 | { 17 | "name": "Matthew Gatner", 18 | "email": "mgatner@tattersoftware.com", 19 | "homepage": "https://tattersoftware.com", 20 | "role": "Developer" 21 | } 22 | ], 23 | "homepage": "https://github.com/codeigniter4/cache", 24 | "require": { 25 | "php": "^7.4 || ^8.0", 26 | "psr/cache": "^1.0", 27 | "psr/simple-cache": "^1.0" 28 | }, 29 | "require-dev": { 30 | "cache/integration-tests": "^0.17.0", 31 | "codeigniter4/devkit": "^1.0", 32 | "codeigniter4/framework": "^4.1", 33 | "phpunit/phpunit": "^9.6", 34 | "rector/rector": "1.2.8" 35 | }, 36 | "provide": { 37 | "psr/cache-implementation": "^1.0", 38 | "psr/simple-cache-implementation": "^1.0 || ^3.0" 39 | }, 40 | "minimum-stability": "dev", 41 | "prefer-stable": true, 42 | "autoload": { 43 | "psr-4": { 44 | "CodeIgniter\\Psr\\Cache\\": "src" 45 | }, 46 | "exclude-from-classmap": [ 47 | "**/Database/Migrations/**" 48 | ] 49 | }, 50 | "autoload-dev": { 51 | "psr-4": { 52 | "Tests\\Support\\": "tests/_support" 53 | } 54 | }, 55 | "config": { 56 | "allow-plugins": { 57 | "phpstan/extension-installer": true 58 | } 59 | }, 60 | "scripts": { 61 | "analyze": [ 62 | "Composer\\Config::disableProcessTimeout", 63 | "phpstan analyze", 64 | "psalm", 65 | "rector process --dry-run" 66 | ], 67 | "sa": "@analyze", 68 | "ci": [ 69 | "Composer\\Config::disableProcessTimeout", 70 | "@cs", 71 | "@deduplicate", 72 | "@inspect", 73 | "@analyze", 74 | "@test" 75 | ], 76 | "cs": "php-cs-fixer fix --ansi --verbose --dry-run --diff", 77 | "cs-fix": "php-cs-fixer fix --ansi --verbose --diff --using-cache=yes", 78 | "style": "@cs-fix", 79 | "deduplicate": "phpcpd src/ tests/", 80 | "inspect": "deptrac analyze --cache-file=build/deptrac.cache", 81 | "test": "phpunit" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeIgniter 4 PSR Cache 2 | 3 | PSR-6 and PSR-16 Cache Adapters for CodeIgniter 4 4 | 5 | [![](https://github.com/codeigniter4/cache/workflows/PHPUnit/badge.svg)](https://github.com/codeigniter4/cache/actions/workflows/phpunit.yml) 6 | [![](https://github.com/codeigniter4/cache/workflows/PHPStan/badge.svg)](https://github.com/codeigniter4/cache/actions/workflows/phpstan.yml) 7 | [![](https://github.com/codeigniter4/cache/workflows/Deptrac/badge.svg)](https://github.com/codeigniter4/cache/actions/workflows/deptrac.yml) 8 | [![Coverage Status](https://coveralls.io/repos/github/codeigniter4/cache/badge.svg?branch=develop)](https://coveralls.io/github/codeigniter4/cache?branch=develop) 9 | 10 | **Disclaimer: CodeIgniter 4 comes with a fully-functional cache component! This module 11 | is only for integrating third-party packages that rely on the PSR interface provisions.** 12 | 13 | ## Quick Start 14 | 15 | 1. Install with Composer: `> composer require codeigniter4/cache` 16 | 2. Integrate with your favorite packages: 17 | 18 | ```php 19 | use CodeIgniter\Psr\Cache\SimpleCache; 20 | use Kreait\Firebase\Factory; 21 | ... 22 | 23 | $factory = (new Factory)->withVerifierCache(new SimpleCache()); 24 | ``` 25 | 26 | ## Features 27 | 28 | A set of adapters fully-compliant with PSR-6 and PSR-16 to integrate with CodeIgniter 4's Caching Driver. 29 | 30 | ## Installation 31 | 32 | Install easily via Composer to take advantage of CodeIgniter 4's autoloading capabilities 33 | and always be up-to-date: 34 | 35 | * `> composer require codeigniter4/cache` 36 | 37 | Or, install manually by downloading the source files and adding the directory to 38 | `app/Config/Autoload.php`. 39 | 40 | ## Usage 41 | 42 | This module has adapters for CodeIgniter 4 to supply the following FIG PHP Standards Recommendations (PSR): 43 | * [Caching Interface](https://www.php-fig.org/psr/psr-6) 44 | * [Simple Cache](https://www.php-fig.org/psr/psr-16) 45 | 46 | If you just need a caching agent then you should use the framework's native [Caching Driver](https://codeigniter4.github.io/CodeIgniter4/libraries/caching.html). 47 | These adapters are intended to integrate with any library or project that requires either of the following: 48 | 49 | * [psr/cache-implementation](https://packagist.org/packages/psr/cache/dependents?order_by=downloads) 50 | * [psr/simple-cache-implementation](https://packagist.org/packages/psr/simple-cache/dependents?order_by=downloads) 51 | 52 | The interfaces are provided by the following classes: 53 | 54 | * `Psr\Cache\CacheItemInterface` provided by `CodeIgniter\Psr\Cache\Item` 55 | * `Psr\Cache\CacheItemPoolInterface` provided by `CodeIgniter\Psr\Cache\Pool` 56 | * `Psr\SimpleCache\CacheInterface` provided by `CodeIgniter\Psr\Cache\SimpleCache` 57 | 58 | By default the adapters (`Pool` and `SimpleCache`) will work with the Caching Driver as defined 59 | in you cache configuration (e.g. **app/Config/Cache.php**). You may create either driver explicitly 60 | with an alternative Cache Handler or Config: 61 | 62 | ```php 63 | $sharedCacheServicePool = new \CodeIgniter\Psr\Cache\Pool(); 64 | 65 | $fileHandler = new \CodeIgniter\Cache\Handlers\FileHandler(config('Cache')); 66 | $explicitFileHandlerSimpleCache = new \CodeIgniter\Psr\Cache\SimpleCache($fileHandler); 67 | 68 | $config = config('Cache'); 69 | $config->prefix = 'banana-'; 70 | $alternativeConfigPool = new \CodeIgniter\Psr\Cache\Pool($config); 71 | ``` 72 | 73 | ## Testing 74 | 75 | Testing of the underlying Caching Driver is handled in the [CodeIgniter 4 repo](https://github.com/codeigniter4/CodeIgniter4/tree/develop/tests/system/Cache). 76 | These adapters are tested with the [PHP Cache Integration Tests](https://github.com/php-cache/integration-tests), by Aaron Scherer. 77 | You may run the test suite by cloning this repo, installing all dependencies, and running: 78 | 79 | * `> composer test` 80 | -------------------------------------------------------------------------------- /deptrac.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | paths: 3 | - ./src/ 4 | - ./vendor/codeigniter4/framework/system/ 5 | exclude_files: 6 | - '#.*test.*#i' 7 | layers: 8 | - name: Model 9 | collectors: 10 | - type: bool 11 | must: 12 | - type: className 13 | regex: .*[A-Za-z]+Model$ 14 | must_not: 15 | - type: directory 16 | regex: vendor/.* 17 | - name: Vendor Model 18 | collectors: 19 | - type: bool 20 | must: 21 | - type: className 22 | regex: .*[A-Za-z]+Model$ 23 | - type: directory 24 | regex: vendor/.* 25 | - name: Controller 26 | collectors: 27 | - type: bool 28 | must: 29 | - type: className 30 | regex: .*\/Controllers\/.* 31 | must_not: 32 | - type: directory 33 | regex: vendor/.* 34 | - name: Vendor Controller 35 | collectors: 36 | - type: bool 37 | must: 38 | - type: className 39 | regex: .*\/Controllers\/.* 40 | - type: directory 41 | regex: vendor/.* 42 | - name: Config 43 | collectors: 44 | - type: bool 45 | must: 46 | - type: directory 47 | regex: src/Config/.* 48 | must_not: 49 | - type: className 50 | regex: .*Services 51 | - type: directory 52 | regex: vendor/.* 53 | - name: Vendor Config 54 | collectors: 55 | - type: bool 56 | must: 57 | - type: directory 58 | regex: vendor/.*/Config/.* 59 | must_not: 60 | - type: className 61 | regex: .*Services 62 | - name: Entity 63 | collectors: 64 | - type: bool 65 | must: 66 | - type: directory 67 | regex: src/Entities/.* 68 | must_not: 69 | - type: directory 70 | regex: vendor/.* 71 | - name: Vendor Entity 72 | collectors: 73 | - type: bool 74 | must: 75 | - type: directory 76 | regex: vendor/.*/Entities/.* 77 | - name: View 78 | collectors: 79 | - type: bool 80 | must: 81 | - type: directory 82 | regex: src/Views/.* 83 | must_not: 84 | - type: directory 85 | regex: vendor/.* 86 | - name: Vendor View 87 | collectors: 88 | - type: bool 89 | must: 90 | - type: directory 91 | regex: vendor/.*/Views/.* 92 | - name: Service 93 | collectors: 94 | - type: className 95 | regex: .*Services.* 96 | ruleset: 97 | Entity: 98 | - Config 99 | - Model 100 | - Service 101 | - Vendor Config 102 | - Vendor Entity 103 | - Vendor Model 104 | Config: 105 | - Service 106 | - Vendor Config 107 | Model: 108 | - Config 109 | - Entity 110 | - Service 111 | - Vendor Config 112 | - Vendor Entity 113 | - Vendor Model 114 | Service: 115 | - Config 116 | - Vendor Config 117 | 118 | # Ignore anything in the Vendor layers 119 | Vendor Model: 120 | - Config 121 | - Service 122 | - Vendor Config 123 | - Vendor Controller 124 | - Vendor Entity 125 | - Vendor Model 126 | - Vendor View 127 | Vendor Controller: 128 | - Service 129 | - Vendor Config 130 | - Vendor Controller 131 | - Vendor Entity 132 | - Vendor Model 133 | - Vendor View 134 | Vendor Config: 135 | - Config 136 | - Service 137 | - Vendor Config 138 | - Vendor Controller 139 | - Vendor Entity 140 | - Vendor Model 141 | - Vendor View 142 | Vendor Entity: 143 | - Service 144 | - Vendor Config 145 | - Vendor Controller 146 | - Vendor Entity 147 | - Vendor Model 148 | - Vendor View 149 | Vendor View: 150 | - Service 151 | - Vendor Config 152 | - Vendor Controller 153 | - Vendor Entity 154 | - Vendor Model 155 | - Vendor View 156 | skip_violations: 157 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | sets([SetList::DEAD_CODE, LevelSetList::UP_TO_PHP_74, PHPUnitSetList::PHPUNIT_80]); 34 | $rectorConfig->parallel(); 35 | // The paths to refactor (can also be supplied with CLI arguments) 36 | $rectorConfig->paths([ 37 | __DIR__ . '/src/', 38 | __DIR__ . '/tests/', 39 | ]); 40 | 41 | // Include Composer's autoload - required for global execution, remove if running locally 42 | $rectorConfig->autoloadPaths([ 43 | __DIR__ . '/vendor/autoload.php', 44 | ]); 45 | 46 | // Do you need to include constants, class aliases, or a custom autoloader? 47 | $rectorConfig->bootstrapFiles([ 48 | realpath(getcwd()) . '/vendor/codeigniter4/framework/system/Test/bootstrap.php', 49 | ]); 50 | 51 | if (is_file(__DIR__ . '/phpstan.neon.dist')) { 52 | $rectorConfig->phpstanConfig(__DIR__ . '/phpstan.neon.dist'); 53 | } 54 | 55 | // Set the target version for refactoring 56 | $rectorConfig->phpVersion(PhpVersion::PHP_74); 57 | 58 | // Auto-import fully qualified class names 59 | $rectorConfig->importNames(); 60 | 61 | // Are there files or rules you need to skip? 62 | $rectorConfig->skip([ 63 | __DIR__ . '/src/Views', 64 | 65 | StringifyStrNeedlesRector::class, 66 | 67 | // Note: requires php 8 68 | RemoveUnusedPromotedPropertyRector::class, 69 | 70 | // May load view files directly when detecting classes 71 | StringClassNameToClassConstantRector::class, 72 | ]); 73 | $rectorConfig->rule(SimplifyUselessVariableRector::class); 74 | $rectorConfig->rule(RemoveAlwaysElseRector::class); 75 | $rectorConfig->rule(CountArrayToEmptyArrayComparisonRector::class); 76 | $rectorConfig->rule(ChangeNestedForeachIfsToEarlyContinueRector::class); 77 | $rectorConfig->rule(ChangeIfElseValueAssignToEarlyReturnRector::class); 78 | $rectorConfig->rule(SimplifyStrposLowerRector::class); 79 | $rectorConfig->rule(CombineIfRector::class); 80 | $rectorConfig->rule(SimplifyIfReturnBoolRector::class); 81 | $rectorConfig->rule(InlineIfToExplicitIfRector::class); 82 | $rectorConfig->rule(PreparedValueToEarlyReturnRector::class); 83 | $rectorConfig->rule(ShortenElseIfRector::class); 84 | $rectorConfig->rule(SimplifyIfElseToTernaryRector::class); 85 | $rectorConfig->rule(UnusedForeachValueToArrayKeysRector::class); 86 | $rectorConfig->rule(ChangeArrayPushToArrayAssignRector::class); 87 | $rectorConfig->rule(UnnecessaryTernaryExpressionRector::class); 88 | $rectorConfig->rule(SimplifyRegexPatternRector::class); 89 | $rectorConfig->rule(FuncGetArgsToVariadicParamRector::class); 90 | $rectorConfig->rule(MakeInheritedMethodVisibilitySameAsParentRector::class); 91 | $rectorConfig->rule(SimplifyEmptyArrayCheckRector::class); 92 | $rectorConfig 93 | ->ruleWithConfiguration(TypedPropertyFromAssignsRector::class, [ 94 | // Set to false if you use in libraries, or it does create breaking changes. 95 | TypedPropertyFromAssignsRector::INLINE_PUBLIC => true, 96 | ]); 97 | }; 98 | -------------------------------------------------------------------------------- /src/Item.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace CodeIgniter\Psr\Cache; 13 | 14 | use CodeIgniter\Cache\Handlers\BaseHandler; 15 | use CodeIgniter\I18n\Time; 16 | use DateInterval; 17 | use DateTimeInterface; 18 | use InvalidArgumentException; 19 | use Psr\Cache\CacheItemInterface; 20 | 21 | final class Item implements CacheItemInterface 22 | { 23 | /** 24 | * Reserved characters that cannot be used in a key or tag. 25 | * 26 | * @see https://github.com/symfony/cache-contracts/blob/c0446463729b89dd4fa62e9aeecc80287323615d/ItemInterface.php#L43 27 | */ 28 | public const RESERVED_CHARACTERS = '{}()/\@:'; 29 | 30 | private string $key; 31 | 32 | /** 33 | * @var mixed 34 | */ 35 | private $value; 36 | 37 | /** 38 | * Whether this Item was the result 39 | * of a cache hit. 40 | */ 41 | private bool $hit; 42 | 43 | /** 44 | * The expiration time 45 | */ 46 | private ?Time $expiration = null; 47 | 48 | /** 49 | * Validates a cache key according to PSR-6. 50 | * 51 | * @param mixed $key The key to validate 52 | * 53 | * @throws CacheArgumentException When $key is not valid 54 | */ 55 | public static function validateKey($key) 56 | { 57 | // Use the framework's Cache key validation 58 | try { 59 | BaseHandler::validateKey($key); 60 | } catch (InvalidArgumentException $e) { 61 | throw new CacheArgumentException($e->getMessage(), $e->getCode(), $e); 62 | } 63 | } 64 | 65 | /** 66 | * Stores the Item's details. 67 | * 68 | * @param mixed $value 69 | */ 70 | public function __construct(string $key, $value, bool $hit) 71 | { 72 | $this->key = $key; 73 | $this->value = $value; 74 | $this->hit = $hit; 75 | } 76 | 77 | /** 78 | * Returns the key for the current cache item. 79 | * 80 | * The key is loaded by the Implementing Library, but should be available to 81 | * the higher level callers when needed. 82 | * 83 | * The key string for this cache item. 84 | */ 85 | public function getKey(): string 86 | { 87 | return $this->key; 88 | } 89 | 90 | /** 91 | * Retrieves the value of the item from the cache associated with this object's key. 92 | * 93 | * The value returned must be identical to the value originally stored by set(). 94 | * 95 | * If isHit() returns false, this method MUST return null. Note that null 96 | * is a legitimate cached value, so the isHit() method SHOULD be used to 97 | * differentiate between "null value was found" and "no value was found." 98 | * 99 | * @return mixed 100 | * The value corresponding to this cache item's key, or null if not found. 101 | */ 102 | public function get() 103 | { 104 | return $this->value; 105 | } 106 | 107 | /** 108 | * Confirms if the cache item lookup resulted in a cache hit. 109 | * 110 | * Note: This method MUST NOT have a race condition between calling isHit() 111 | * and calling get(). 112 | * 113 | * True if the request resulted in a cache hit. False otherwise. 114 | */ 115 | public function isHit(): bool 116 | { 117 | return $this->hit; 118 | } 119 | 120 | /** 121 | * Sets the value represented by this cache item. 122 | * 123 | * The $value argument may be any item that can be serialized by PHP, 124 | * although the method of serialization is left up to the Implementing 125 | * Library. 126 | * 127 | * @param mixed $value 128 | * The serializable value to be stored. 129 | * 130 | * The invoked object. 131 | */ 132 | public function set($value): self 133 | { 134 | $this->value = $value; 135 | 136 | return $this; 137 | } 138 | 139 | /** 140 | * Sets the expiration time for this cache item. 141 | * 142 | * @param DateTimeInterface|null $expiration 143 | * The point in time after which the item MUST be considered expired. 144 | * If null is passed explicitly, a default value MAY be used. If none is set, 145 | * the value should be stored permanently or for as long as the 146 | * implementation allows. 147 | * 148 | * The called object. 149 | */ 150 | public function expiresAt($expiration): self 151 | { 152 | if ($expiration === null) { 153 | $this->expiration = null; 154 | } elseif ($expiration instanceof DateTimeInterface) { 155 | $this->expiration = Time::createFromInstance($expiration); 156 | } else { 157 | throw new CacheArgumentException('Expiration date must be a DateTimeInterface or null'); 158 | } 159 | 160 | return $this; 161 | } 162 | 163 | /** 164 | * Sets the expiration time for this cache item. 165 | * 166 | * @param DateInterval|int|null $time 167 | * The period of time from the present after which the item MUST be considered 168 | * expired. An integer parameter is understood to be the time in seconds until 169 | * expiration. If null is passed explicitly, a default value MAY be used. 170 | * If none is set, the value should be stored permanently or for as long as the 171 | * implementation allows. 172 | * 173 | * The called object. 174 | */ 175 | public function expiresAfter($time): self 176 | { 177 | if ($time === null) { 178 | $this->expiration = null; 179 | } elseif ($time instanceof DateInterval) { 180 | $this->expiration = Time::now()->add($time); 181 | } elseif (is_int($time)) { 182 | $this->expiration = Time::now()->addSeconds($time); 183 | } else { 184 | throw new CacheArgumentException('Expiration date must be an integer, a DateInterval or null'); 185 | } 186 | 187 | return $this; 188 | } 189 | 190 | /** 191 | * Returns the expiration Time. 192 | * This method is not a requirement of PSR-6 but is necessary 193 | * to pass "testExpiration". 194 | * 195 | * @see https://groups.google.com/g/php-fig/c/Qr4OxCf7J5Y 196 | */ 197 | public function getExpiration(): ?Time 198 | { 199 | return $this->expiration; 200 | } 201 | 202 | /** 203 | * Returns whether or not this Item is expired. 204 | * This method is not a requirement of PSR-6 but is necessary 205 | * to pass "testSavedExpired". 206 | * 207 | * @see https://groups.google.com/g/php-fig/c/Qr4OxCf7J5Y 208 | * 209 | * @return bool True if this Item is expired. 210 | */ 211 | public function isExpired(): bool 212 | { 213 | if (isset($this->expiration)) { 214 | $now = Time::now(); 215 | 216 | return $this->expiration->isBefore($now) || $this->expiration->sameAs($now); 217 | } 218 | 219 | return false; 220 | } 221 | 222 | /** 223 | * Sets the hit value. 224 | * This method is not a requirement of PSR-6 but is necessary 225 | * to allow deferred items to count as hits. 226 | * 227 | * @return $this 228 | */ 229 | public function setHit(bool $hit): self 230 | { 231 | $this->hit = $hit; 232 | 233 | return $this; 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/SimpleCache.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace CodeIgniter\Psr\Cache; 13 | 14 | use DateInterval; 15 | use Psr\SimpleCache\CacheInterface; 16 | use Traversable; 17 | 18 | final class SimpleCache implements CacheInterface 19 | { 20 | use SupportTrait; 21 | 22 | /** 23 | * Fetches a value from the cache. 24 | * 25 | * @param string $key The unique key of this item in the cache. 26 | * @param mixed $default Default value to return if the key does not exist. 27 | * 28 | * @return mixed The value of the item from the cache, or $default in case of cache miss. 29 | * 30 | * @throws CacheArgumentException 31 | * MUST be thrown if the $key string is not a legal value. 32 | */ 33 | public function get($key, $default = null) 34 | { 35 | Item::validateKey($key); 36 | 37 | $meta = $this->adapter->getMetaData($key); 38 | 39 | // If the adapter does not return an array or if the item is expired then it is a miss 40 | if (! is_array($meta) || (is_int($meta['expire']) && $meta['expire'] < time())) { 41 | return $default; 42 | } 43 | 44 | return $this->adapter->get($key); 45 | } 46 | 47 | /** 48 | * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. 49 | * 50 | * @param string $key The key of the item to store. 51 | * @param mixed $value The value of the item to store. Must be serializable. 52 | * @param DateInterval|int|null $ttl Optional. The TTL value of this item. If no value is sent and 53 | * the driver supports TTL then the library may set a default value 54 | * for it or let the driver take care of that. 55 | * 56 | * @return bool True on success and false on failure. 57 | * 58 | * @throws CacheArgumentException 59 | * MUST be thrown if the $key string is not a legal value. 60 | */ 61 | public function set($key, $value, $ttl = null) 62 | { 63 | Item::validateKey($key); 64 | 65 | // Get TTL as an integer (seconds) 66 | if (null === $ttl) { 67 | $ttl = config('Cache')->ttl ?? 60; 68 | } elseif ($ttl instanceof DateInterval) { 69 | $ttl = $ttl->s; 70 | } elseif (! is_int($ttl)) { 71 | throw new CacheArgumentException('TTL value must be one of: null, integer, DateInterval.'); 72 | } 73 | 74 | // Do not save expired items 75 | if ($ttl <= 0) { 76 | $this->delete($key); 77 | 78 | return false; 79 | } 80 | 81 | return $this->adapter->save($key, $value, $ttl); 82 | } 83 | 84 | /** 85 | * Delete an item from the cache by its unique key. 86 | * 87 | * @param string $key The unique cache key of the item to delete. 88 | * 89 | * @return bool True if the item was successfully removed. False if there was an error. 90 | * 91 | * @throws CacheArgumentException 92 | * MUST be thrown if the $key string is not a legal value. 93 | */ 94 | public function delete($key) 95 | { 96 | Item::validateKey($key); 97 | 98 | // Nonexistant keys return true 99 | if (! is_array($this->adapter->getMetaData($key))) { 100 | return true; 101 | } 102 | 103 | return $this->adapter->delete($key); 104 | } 105 | 106 | /** 107 | * Wipes clean the entire cache's keys. 108 | * 109 | * @return bool True on success and false on failure. 110 | */ 111 | public function clear() 112 | { 113 | return $this->adapter->clean(); 114 | } 115 | 116 | /** 117 | * Obtains multiple cache items by their unique keys. 118 | * 119 | * @param iterable $keys A list of keys that can obtained in a single operation. 120 | * @param mixed $default Default value to return for keys that do not exist. 121 | * 122 | * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value. 123 | * 124 | * @throws CacheArgumentException 125 | * MUST be thrown if $keys is neither an array nor a Traversable, 126 | * or if any of the $keys are not a legal value. 127 | */ 128 | public function getMultiple($keys, $default = null) 129 | { 130 | if (! (is_iterable($keys))) { 131 | throw new CacheArgumentException('getMultiple only accepts traversable input.'); 132 | } 133 | 134 | // CacheInterface has no spec for multiple item retrieval 135 | // so we have to power through them individually. 136 | $items = []; 137 | 138 | foreach ($keys as $key) { 139 | $items[$key] = $this->get($key, $default); 140 | } 141 | 142 | return $items; 143 | } 144 | 145 | /** 146 | * Persists a set of key => value pairs in the cache, with an optional TTL. 147 | * 148 | * @param iterable $values A list of key => value pairs for a multiple-set operation. 149 | * @param DateInterval|int|null $ttl Optional. The TTL value of this item. If no value is sent and 150 | * the driver supports TTL then the library may set a default value 151 | * for it or let the driver take care of that. 152 | * 153 | * @return bool True on success and false on failure. 154 | * 155 | * @throws CacheArgumentException 156 | * MUST be thrown if $values is neither an array nor a Traversable, 157 | * or if any of the $values are not a legal value. 158 | */ 159 | public function setMultiple($values, $ttl = null) 160 | { 161 | if (! (is_iterable($values))) { 162 | throw new CacheArgumentException('setMultiple only accepts traversable input.'); 163 | } 164 | 165 | // CacheInterface has no spec for multiple item storage 166 | // so we have to power through them individually. 167 | $return = true; 168 | 169 | foreach ($values as $key => $value) { 170 | if (is_int($key)) { 171 | $key = (string) $key; 172 | } 173 | $result = $this->set($key, $value, $ttl); 174 | $return = $result && $return; 175 | } 176 | 177 | return $return; 178 | } 179 | 180 | /** 181 | * Deletes multiple cache items in a single operation. 182 | * 183 | * @param iterable $keys A list of string-based keys to be deleted. 184 | * 185 | * @return bool True if the items were successfully removed. False if there was an error. 186 | * 187 | * @throws CacheArgumentException 188 | * MUST be thrown if $keys is neither an array nor a Traversable, 189 | * or if any of the $keys are not a legal value. 190 | */ 191 | public function deleteMultiple($keys) 192 | { 193 | if (! (is_iterable($keys))) { 194 | throw new CacheArgumentException('deleteMultiple only accepts traversable input.'); 195 | } 196 | 197 | // CacheInterface has no spec for multiple item removal 198 | // so we have to power through them individually. 199 | $return = true; 200 | 201 | foreach ($keys as $key) { 202 | $result = $this->delete($key); 203 | $return = $result && $return; 204 | } 205 | 206 | return $return; 207 | } 208 | 209 | /** 210 | * Determines whether an item is present in the cache. 211 | * 212 | * NOTE: It is recommended that has() is only to be used for cache warming type purposes 213 | * and not to be used within your live applications operations for get/set, as this method 214 | * is subject to a race condition where your has() will return true and immediately after, 215 | * another script can remove it, making the state of your app out of date. 216 | * 217 | * @param string $key The cache item key. 218 | * 219 | * @return bool 220 | * 221 | * @throws CacheArgumentException 222 | * MUST be thrown if the $key string is not a legal value. 223 | */ 224 | public function has($key) 225 | { 226 | Item::validateKey($key); 227 | 228 | $meta = $this->adapter->getMetaData($key); 229 | 230 | // The adapter must return an array that is not expired 231 | return is_array($meta) && is_int($meta['expire']) && $meta['expire'] > time(); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/Pool.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace CodeIgniter\Psr\Cache; 13 | 14 | use CodeIgniter\I18n\Time; 15 | use Psr\Cache\CacheItemInterface; 16 | use Psr\Cache\CacheItemPoolInterface; 17 | 18 | final class Pool implements CacheItemPoolInterface 19 | { 20 | use SupportTrait; 21 | 22 | /** 23 | * Deferred Items to be saved. 24 | * 25 | * @var array 26 | */ 27 | private array $deferred = []; 28 | 29 | /** 30 | * Commits any deferred Items. 31 | */ 32 | public function __destruct() 33 | { 34 | $this->commit(); 35 | } 36 | 37 | /** 38 | * Returns a Cache Item representing the specified key. 39 | * 40 | * This method must always return a CacheItemInterface object, even in case of 41 | * a cache miss. It MUST NOT return null. 42 | * 43 | * @param string $key 44 | * The key for which to return the corresponding Cache Item. 45 | * 46 | * @throws CacheArgumentException 47 | * If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException 48 | * MUST be thrown. 49 | * 50 | * The corresponding Cache Item. 51 | */ 52 | public function getItem($key): CacheItemInterface 53 | { 54 | Item::validateKey($key); 55 | 56 | // First check for a deferred Item 57 | if (array_key_exists($key, $this->deferred) && ! $this->deferred[$key]->isExpired()) { 58 | return (clone $this->deferred[$key])->setHit(true); 59 | } 60 | 61 | $meta = $this->adapter->getMetaData($key); 62 | 63 | // If the adapter does not return an array or if the item is expired then it is a miss 64 | if (! is_array($meta) || (is_int($meta['expire']) && $meta['expire'] < time())) { 65 | return new Item($key, null, false); 66 | } 67 | 68 | // Create the Item with the actual value 69 | $item = new Item($key, $this->adapter->get($key), true); 70 | 71 | // Check for an expiration 72 | if ($meta['expire'] !== null) { 73 | $item->expiresAt(Time::createFromTimestamp($meta['expire'])); 74 | } 75 | 76 | return $item; 77 | } 78 | 79 | /** 80 | * Returns a traversable set of cache items. 81 | * 82 | * @param list $keys 83 | * An indexed array of keys of items to retrieve. 84 | * 85 | * @throws CacheArgumentException 86 | * If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException 87 | * MUST be thrown. 88 | * 89 | * A traversable collection of Cache Items keyed by the cache keys of 90 | * each item. A Cache item will be returned for each key, even if that 91 | * key is not found. However, if no keys are specified then an empty 92 | * traversable MUST be returned instead. 93 | */ 94 | public function getItems(array $keys = []): array 95 | { 96 | // CacheInterface has no spec for multiple item retrieval 97 | // so we have to power through them individually. 98 | $items = []; 99 | 100 | foreach ($keys as $key) { 101 | $items[$key] = $this->getItem($key); 102 | } 103 | 104 | return $items; 105 | } 106 | 107 | /** 108 | * Confirms if the cache contains specified cache item. 109 | * 110 | * Note: This method MAY avoid retrieving the cached value for performance reasons. 111 | * This could result in a race condition with CacheItemInterface::get(). To avoid 112 | * such situation use CacheItemInterface::isHit() instead. 113 | * 114 | * @param string $key 115 | * The key for which to check existence. 116 | * 117 | * @throws CacheArgumentException 118 | * If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException 119 | * MUST be thrown. 120 | * 121 | * True if item exists in the cache, false otherwise. 122 | */ 123 | public function hasItem($key): bool 124 | { 125 | Item::validateKey($key); 126 | 127 | // First check for a deferred Item 128 | if (array_key_exists($key, $this->deferred) && ! $this->deferred[$key]->isExpired()) { 129 | return true; 130 | } 131 | 132 | return is_array($this->adapter->getMetaData($key)); 133 | } 134 | 135 | /** 136 | * Deletes all items in the pool. 137 | * 138 | * @return bool 139 | * True if the pool was successfully cleared. False if there was an error. 140 | */ 141 | public function clear() 142 | { 143 | $this->deferred = []; 144 | 145 | return $this->adapter->clean(); 146 | } 147 | 148 | /** 149 | * Removes the item from the pool. 150 | * 151 | * @param string $key 152 | * The key to delete. 153 | * 154 | * @throws CacheArgumentException 155 | * If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException 156 | * MUST be thrown. 157 | * 158 | * True if the item was successfully removed. False if there was an error. 159 | */ 160 | public function deleteItem($key): bool 161 | { 162 | Item::validateKey($key); 163 | 164 | // First check for a deferred Item 165 | if (array_key_exists($key, $this->deferred)) { 166 | unset($this->deferred[$key]); 167 | 168 | return true; 169 | } 170 | 171 | if ($this->hasItem($key)) { 172 | return $this->adapter->delete($key); 173 | } 174 | 175 | return true; 176 | } 177 | 178 | /** 179 | * Removes multiple items from the pool. 180 | * 181 | * @param list $keys 182 | * An array of keys that should be removed from the pool. 183 | * 184 | * @throws CacheArgumentException 185 | * If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException 186 | * MUST be thrown. 187 | * 188 | * True if the items were successfully removed. False if there was an error. 189 | */ 190 | public function deleteItems(array $keys): bool 191 | { 192 | // CacheInterface has no spec for multiple item removal 193 | // so we have to power through them individually. 194 | $return = true; 195 | 196 | foreach ($keys as $key) { 197 | $result = $this->deleteItem($key); 198 | $return = $return && $result; 199 | } 200 | 201 | return $return; 202 | } 203 | 204 | /** 205 | * Persists a cache item immediately. 206 | * 207 | * The cache item to save. 208 | * 209 | * @return bool 210 | * True if the item was successfully persisted. False if there was an error. 211 | */ 212 | public function save(CacheItemInterface $item) 213 | { 214 | // Only deal in our Pool's Items 215 | if (! $item instanceof Item) { 216 | return false; // @codeCoverageIgnore 217 | } 218 | 219 | // Do not save expired Items 220 | if ($item->isExpired()) { 221 | $this->deleteItem($item->getKey()); 222 | 223 | return false; 224 | } 225 | 226 | // Deteremine TTL 227 | if ($expiration = $item->getExpiration()) { 228 | $ttl = Time::now()->difference($expiration)->getSeconds(); 229 | } else { 230 | $ttl = config('Cache')->ttl ?? 60; 231 | } 232 | 233 | return $this->adapter->save($item->getKey(), $item->get(), $ttl); 234 | } 235 | 236 | /** 237 | * Sets a cache item to be persisted later. 238 | * 239 | * The cache item to save. 240 | * False if the item could not be queued or if a commit was attempted and failed. True otherwise. 241 | */ 242 | public function saveDeferred(CacheItemInterface $item): bool 243 | { 244 | // Only deal in our Pool's Items 245 | if (! $item instanceof Item) { 246 | return false; // @codeCoverageIgnore 247 | } 248 | 249 | // Do not save expired Items 250 | if ($item->isExpired()) { 251 | return false; 252 | } 253 | 254 | $this->deferred[$item->getKey()] = clone $item; 255 | 256 | return true; 257 | } 258 | 259 | /** 260 | * Persists any deferred cache items. 261 | * 262 | * True if all not-yet-saved items were successfully saved or there were none. False otherwise. 263 | */ 264 | public function commit(): bool 265 | { 266 | if ($this->deferred === []) { 267 | return true; 268 | } 269 | 270 | $failed = []; 271 | 272 | foreach ($this->deferred as $item) { 273 | if (! $this->save($item)) { 274 | $failed[$item->getKey()] = $item; 275 | } 276 | } 277 | 278 | if ($failed === []) { 279 | return true; 280 | } 281 | 282 | $this->deferred = $failed; 283 | 284 | return false; 285 | } 286 | } 287 | --------------------------------------------------------------------------------