Repository: doctrine/cache
Branch: 2.2.x
Commit: e0a9919443c1
Files: 38
Total size: 82.2 KB
Directory structure:
gitextract_4su6f833/
├── .doctrine-project.json
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── coding-standards.yml
│ ├── continuous-integration.yml
│ └── release-on-milestone-closed.yml
├── .gitignore
├── LICENSE
├── README.md
├── UPGRADE-1.11.md
├── UPGRADE-1.4.md
├── build.properties
├── build.xml
├── composer.json
├── docs/
│ └── en/
│ └── index.rst
├── lib/
│ └── Doctrine/
│ └── Common/
│ └── Cache/
│ ├── Cache.php
│ ├── CacheProvider.php
│ ├── ClearableCache.php
│ ├── FlushableCache.php
│ ├── MultiDeleteCache.php
│ ├── MultiGetCache.php
│ ├── MultiOperationCache.php
│ ├── MultiPutCache.php
│ └── Psr6/
│ ├── CacheAdapter.php
│ ├── CacheItem.php
│ ├── DoctrineProvider.php
│ ├── InvalidArgument.php
│ └── TypedCacheItem.php
├── phpcs.xml.dist
├── phpunit.xml.dist
└── tests/
├── Doctrine/
│ └── Tests/
│ ├── Common/
│ │ └── Cache/
│ │ ├── ArrayCache.php
│ │ ├── ArrayCacheTest.php
│ │ ├── CacheProviderTest.php
│ │ ├── CacheTest.php
│ │ └── Psr6/
│ │ ├── CacheAdapterTest.php
│ │ └── DoctrineProviderTest.php
│ └── DoctrineTestCase.php
└── travis/
└── php.ini
================================================
FILE CONTENTS
================================================
================================================
FILE: .doctrine-project.json
================================================
{
"active": false,
"name": "Cache",
"slug": "cache",
"docsSlug": "doctrine-cache",
"versions": [
{
"name": "2.2",
"branchName": "2.2.x",
"slug": "2.2",
"current": true
},
{
"name": "2.1",
"branchName": "2.1.x",
"slug": "2.1",
"maintained": false
},
{
"name": "2.0",
"branchName": "2.0.x",
"slug": "2.0",
"maintained": false
},
{
"name": "1.13",
"branchName": "1.13.x",
"slug": "1.13",
"maintained": true
},
{
"name": "1.12",
"branchName": "1.12.x",
"slug": "1.12",
"maintained": false
},
{
"name": "1.11",
"branchName": "1.11.x",
"slug": "1.11",
"maintained": false
},
{
"name": "1.10",
"branchName": "1.10.x",
"slug": "1.10",
"maintained": false
},
{
"name": "1.9",
"branchName": "1.9.x",
"slug": "1.9",
"maintained": false
},
{
"name": "1.8",
"branchName": "1.8.x",
"slug": "1.8",
"maintained": false
}
]
}
================================================
FILE: .gitattributes
================================================
/docs export-ignore
/.github export-ignore
/tests export-ignore
.doctrine-project.json export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.scrutinizer.yml export-ignore
build.properties export-ignore
build.xml export-ignore
phpcs.xml.dist export-ignore
phpunit.xml.dist export-ignore
================================================
FILE: .github/FUNDING.yml
================================================
patreon: phpdoctrine
tidelift: packagist/doctrine%2Fcache
custom: https://www.doctrine-project.org/sponsorship.html
================================================
FILE: .github/workflows/coding-standards.yml
================================================
name: "Coding Standards"
on:
pull_request:
branches:
- "*.x"
push:
branches:
- "*.x"
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@5.0.1"
================================================
FILE: .github/workflows/continuous-integration.yml
================================================
name: "Continuous Integration"
on:
pull_request:
branches:
- "*.x"
push:
branches:
- "*.x"
env:
fail-fast: true
jobs:
phpunit:
name: "PHPUnit"
runs-on: "ubuntu-22.04"
strategy:
fail-fast: false
matrix:
php-version:
- "7.1"
- "7.2"
- "7.3"
- "7.4"
- "8.0"
- "8.1"
- "8.2"
- "8.3"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Install PHP with XDebug"
uses: "shivammathur/setup-php@v2"
if: "${{ matrix.php-version == '7.1' }}"
with:
php-version: "${{ matrix.php-version }}"
coverage: "xdebug"
ini-values: "zend.assertions=1"
- name: "Install PHP with PCOV"
uses: "shivammathur/setup-php@v2"
if: "${{ matrix.php-version != '7.1' }}"
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: "${{ matrix.dependencies }}"
composer-options: "--prefer-dist --no-suggest"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
with:
name: "phpunit-${{ matrix.php-version }}.coverage"
path: "coverage.xml"
upload_coverage:
name: "Upload coverage to Codecov"
runs-on: "ubuntu-22.04"
needs:
- "phpunit"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v4"
with:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v4"
with:
directory: reports
================================================
FILE: .github/workflows/release-on-milestone-closed.yml
================================================
name: "Automatic Releases"
on:
milestone:
types:
- "closed"
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@5.0.1"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
ORGANIZATION_ADMIN_TOKEN: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }}
================================================
FILE: .gitignore
================================================
vendor/
/composer.lock
build/
phpunit.xml
/.phpunit.result.cache
clover.xml
.phpcs-cache
phpcs.xml
================================================
FILE: LICENSE
================================================
Copyright (c) 2006-2015 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Doctrine Cache
[](https://github.com/doctrine/cache/actions)
[](https://codecov.io/gh/doctrine/cache/branch/1.10.x)
[](https://packagist.org/packages/doctrine/cache)
[](https://packagist.org/packages/doctrine/cache)
Cache component extracted from the Doctrine Common project. [Documentation](https://www.doctrine-project.org/projects/doctrine-cache/en/current/index.html)
This library is deprecated and will no longer receive bug fixes from the
Doctrine Project. Please use a different cache library, preferably PSR-6 or
PSR-16 instead.
================================================
FILE: UPGRADE-1.11.md
================================================
# Upgrade to 1.11
doctrine/cache will no longer be maintained and all cache implementations have
been marked as deprecated. These implementations will be removed in 2.0, which
will only contain interfaces to provide a lightweight package for backward
compatibility.
There are two new classes to use in the `Doctrine\Common\Cache\Psr6` namespace:
* The `CacheAdapter` class allows using any Doctrine Cache as PSR-6 cache. This
is useful to provide a forward compatibility layer in libraries that accept
Doctrine cache implementations and switch to PSR-6.
* The `DoctrineProvider` class allows using any PSR-6 cache as Doctrine cache.
This implementation is designed for libraries that leak the cache and want to
switch to allowing PSR-6 implementations. This class is design to be used
during the transition phase of sunsetting doctrine/cache support.
A full example to setup a filesystem based PSR-6 cache with symfony/cache
using the `DoctrineProvider` to convert back to Doctrine's `Cache` interface:
```php
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
$cachePool = new FilesystemAdapter();
$cache = DoctrineProvider::wrap($cachePool);
// $cache instanceof \Doctrine\Common\Cache\Cache
```
================================================
FILE: UPGRADE-1.4.md
================================================
# Upgrade to 1.4
## Minor BC Break: `Doctrine\Common\Cache\FileCache#$extension` is now `private`.
If you need to override the value of `Doctrine\Common\Cache\FileCache#$extension`, then use the
second parameter of `Doctrine\Common\Cache\FileCache#__construct()` instead of overriding
the property in your own implementation.
## Minor BC Break: file based caches paths changed
`Doctrine\Common\Cache\FileCache`, `Doctrine\Common\Cache\PhpFileCache` and
`Doctrine\Common\Cache\FilesystemCache` are using a different cache paths structure.
If you rely on warmed up caches for deployments, consider that caches generated
with `doctrine/cache` `<1.4` are not compatible with the new directory structure,
and will be ignored.
================================================
FILE: build.properties
================================================
# Version class and file
project.version_class = Doctrine\\Common\\Cache\\Version
project.version_file = lib/Doctrine/Common/Cache/Version.php
================================================
FILE: build.xml
================================================
================================================
FILE: composer.json
================================================
{
"name": "doctrine/cache",
"abandoned": true,
"type": "library",
"description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.",
"keywords": [
"php",
"cache",
"caching",
"abstraction",
"redis",
"memcached",
"couchdb",
"xcache",
"apcu"
],
"homepage": "https://www.doctrine-project.org/projects/cache.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": "~7.1 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"doctrine/coding-standard": "^9",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"cache/integration-tests": "dev-master",
"symfony/cache": "^4.4 || ^5.4 || ^6",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6"
},
"conflict": {
"doctrine/common": ">2.2,<2.4"
},
"autoload": {
"psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" }
},
"autoload-dev": {
"psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests" }
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
================================================
FILE: docs/en/index.rst
================================================
Deprecation Notice
==================
Please note that doctrine/cache is deprecated and no longer maintained. The last
version to include cache drivers is 1.11. The 2.x major release series only
provides the interfaces for libraries that need to maintain backward
compatibility. For all cache uses, we suggest relying on PSR-6 or PSR-16 instead
and using a cache library that supports those interfaces.
Introduction
============
Doctrine Cache is a library that provides an interface for caching data.
Here is what the ``Cache`` interface looks like.
.. code-block:: php
namespace Doctrine\Common\Cache;
interface Cache
{
public function fetch($id);
public function contains($id);
public function save($id, $data, $lifeTime = 0);
public function delete($id);
public function getStats();
}
Use with PSR-6
==============
If you are using the ``Cache`` interface in your application, then you need to
upgrade your application to use a PSR-6 cache library and wrap the PSR-6
``CacheItemPoolInterface`` into the
``Doctrine\Common\Cache\Psr6\DoctrineProvider`` wrapper:
.. code-block:: php
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
$cache = DoctrineProvider::wrap($psr6CachePool);
An implementation of the PSR-6 cache is provided by `"symfony/cache" library
`_ for example, you can install it
via Composer with:
::
composer require symfony/cache
A full example to setup a filesystem based cache with symfony/cache then looks
like this:
.. code-block:: php
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
$cachePool = new FilesystemAdapter();
$cache = DoctrineProvider::wrap($cachePool);
// $cache instanceof \Doctrine\Common\Cache\Cache
================================================
FILE: lib/Doctrine/Common/Cache/Cache.php
================================================
hits
* Number of keys that have been requested and found present.
*
* - misses
* Number of items that have been requested and not found.
*
* - uptime
* Time that the server is running.
*
* - memory_usage
* Memory used by this server to store items.
*
* - memory_available
* Memory allowed to use for storage.
*
* @return mixed[]|null An associative array with server's statistics if available, NULL otherwise.
*/
public function getStats();
}
================================================
FILE: lib/Doctrine/Common/Cache/CacheProvider.php
================================================
namespace = (string) $namespace;
$this->namespaceVersion = null;
}
/**
* Retrieves the namespace that prefixes all cache ids.
*
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* {@inheritdoc}
*/
public function fetch($id)
{
return $this->doFetch($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function fetchMultiple(array $keys)
{
if (empty($keys)) {
return [];
}
// note: the array_combine() is in place to keep an association between our $keys and the $namespacedKeys
$namespacedKeys = array_combine($keys, array_map([$this, 'getNamespacedId'], $keys));
$items = $this->doFetchMultiple($namespacedKeys);
$foundItems = [];
// no internal array function supports this sort of mapping: needs to be iterative
// this filters and combines keys in one pass
foreach ($namespacedKeys as $requestedKey => $namespacedKey) {
if (! isset($items[$namespacedKey]) && ! array_key_exists($namespacedKey, $items)) {
continue;
}
$foundItems[$requestedKey] = $items[$namespacedKey];
}
return $foundItems;
}
/**
* {@inheritdoc}
*/
public function saveMultiple(array $keysAndValues, $lifetime = 0)
{
$namespacedKeysAndValues = [];
foreach ($keysAndValues as $key => $value) {
$namespacedKeysAndValues[$this->getNamespacedId($key)] = $value;
}
return $this->doSaveMultiple($namespacedKeysAndValues, $lifetime);
}
/**
* {@inheritdoc}
*/
public function contains($id)
{
return $this->doContains($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function save($id, $data, $lifeTime = 0)
{
return $this->doSave($this->getNamespacedId($id), $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
public function deleteMultiple(array $keys)
{
return $this->doDeleteMultiple(array_map([$this, 'getNamespacedId'], $keys));
}
/**
* {@inheritdoc}
*/
public function delete($id)
{
return $this->doDelete($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function getStats()
{
return $this->doGetStats();
}
/**
* {@inheritDoc}
*/
public function flushAll()
{
return $this->doFlush();
}
/**
* {@inheritDoc}
*/
public function deleteAll()
{
$namespaceCacheKey = $this->getNamespaceCacheKey();
$namespaceVersion = $this->getNamespaceVersion() + 1;
if ($this->doSave($namespaceCacheKey, $namespaceVersion)) {
$this->namespaceVersion = $namespaceVersion;
return true;
}
return false;
}
/**
* Prefixes the passed id with the configured namespace value.
*
* @param string $id The id to namespace.
*
* @return string The namespaced id.
*/
private function getNamespacedId(string $id): string
{
$namespaceVersion = $this->getNamespaceVersion();
return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion);
}
/**
* Returns the namespace cache key.
*/
private function getNamespaceCacheKey(): string
{
return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace);
}
/**
* Returns the namespace version.
*/
private function getNamespaceVersion(): int
{
if ($this->namespaceVersion !== null) {
return $this->namespaceVersion;
}
$namespaceCacheKey = $this->getNamespaceCacheKey();
$this->namespaceVersion = (int) $this->doFetch($namespaceCacheKey) ?: 1;
return $this->namespaceVersion;
}
/**
* Default implementation of doFetchMultiple. Each driver that supports multi-get should owerwrite it.
*
* @param string[] $keys Array of keys to retrieve from cache
*
* @return mixed[] Array of values retrieved for the given keys.
*/
protected function doFetchMultiple(array $keys)
{
$returnValues = [];
foreach ($keys as $key) {
$item = $this->doFetch($key);
if ($item === false && ! $this->doContains($key)) {
continue;
}
$returnValues[$key] = $item;
}
return $returnValues;
}
/**
* Fetches an entry from the cache.
*
* @param string $id The id of the cache entry to fetch.
*
* @return mixed|false The cached data or FALSE, if no cache entry exists for the given id.
*/
abstract protected function doFetch($id);
/**
* Tests if an entry exists in the cache.
*
* @param string $id The cache id of the entry to check for.
*
* @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise.
*/
abstract protected function doContains($id);
/**
* Default implementation of doSaveMultiple. Each driver that supports multi-put should override it.
*
* @param mixed[] $keysAndValues Array of keys and values to save in cache
* @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these
* cache entries (0 => infinite lifeTime).
*
* @return bool TRUE if the operation was successful, FALSE if it wasn't.
*/
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
$success = true;
foreach ($keysAndValues as $key => $value) {
if ($this->doSave($key, $value, $lifetime)) {
continue;
}
$success = false;
}
return $success;
}
/**
* Puts data into the cache.
*
* @param string $id The cache id.
* @param string $data The cache entry/data.
* @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this
* cache entry (0 => infinite lifeTime).
*
* @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise.
*/
abstract protected function doSave($id, $data, $lifeTime = 0);
/**
* Default implementation of doDeleteMultiple. Each driver that supports multi-delete should override it.
*
* @param string[] $keys Array of keys to delete from cache
*
* @return bool TRUE if the operation was successful, FALSE if it wasn't
*/
protected function doDeleteMultiple(array $keys)
{
$success = true;
foreach ($keys as $key) {
if ($this->doDelete($key)) {
continue;
}
$success = false;
}
return $success;
}
/**
* Deletes a cache entry.
*
* @param string $id The cache id.
*
* @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise.
*/
abstract protected function doDelete($id);
/**
* Flushes all cache entries.
*
* @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise.
*/
abstract protected function doFlush();
/**
* Retrieves cached information from the data store.
*
* @return mixed[]|null An associative array with server's statistics if available, NULL otherwise.
*/
abstract protected function doGetStats();
}
================================================
FILE: lib/Doctrine/Common/Cache/ClearableCache.php
================================================
infinite lifeTime).
*
* @return bool TRUE if the operation was successful, FALSE if it wasn't.
*/
public function saveMultiple(array $keysAndValues, $lifetime = 0);
}
================================================
FILE: lib/Doctrine/Common/Cache/Psr6/CacheAdapter.php
================================================
*/
private $deferredItems = [];
public static function wrap(Cache $cache): CacheItemPoolInterface
{
if ($cache instanceof DoctrineProvider && ! $cache->getNamespace()) {
return $cache->getPool();
}
if ($cache instanceof SymfonyDoctrineProvider && ! $cache->getNamespace()) {
$getPool = function () {
// phpcs:ignore Squiz.Scope.StaticThisUsage.Found
return $this->pool;
};
return $getPool->bindTo($cache, SymfonyDoctrineProvider::class)();
}
return new self($cache);
}
private function __construct(Cache $cache)
{
$this->cache = $cache;
}
/** @internal */
public function getCache(): Cache
{
return $this->cache;
}
/**
* {@inheritDoc}
*/
public function getItem($key): CacheItemInterface
{
assert(self::validKey($key));
if (isset($this->deferredItems[$key])) {
$this->commit();
}
$value = $this->cache->fetch($key);
if (PHP_VERSION_ID >= 80000) {
if ($value !== false) {
return new TypedCacheItem($key, $value, true);
}
return new TypedCacheItem($key, null, false);
}
if ($value !== false) {
return new CacheItem($key, $value, true);
}
return new CacheItem($key, null, false);
}
/**
* {@inheritDoc}
*/
public function getItems(array $keys = []): array
{
if ($this->deferredItems) {
$this->commit();
}
assert(self::validKeys($keys));
$values = $this->doFetchMultiple($keys);
$items = [];
if (PHP_VERSION_ID >= 80000) {
foreach ($keys as $key) {
if (array_key_exists($key, $values)) {
$items[$key] = new TypedCacheItem($key, $values[$key], true);
} else {
$items[$key] = new TypedCacheItem($key, null, false);
}
}
return $items;
}
foreach ($keys as $key) {
if (array_key_exists($key, $values)) {
$items[$key] = new CacheItem($key, $values[$key], true);
} else {
$items[$key] = new CacheItem($key, null, false);
}
}
return $items;
}
/**
* {@inheritDoc}
*/
public function hasItem($key): bool
{
assert(self::validKey($key));
if (isset($this->deferredItems[$key])) {
$this->commit();
}
return $this->cache->contains($key);
}
public function clear(): bool
{
$this->deferredItems = [];
if (! $this->cache instanceof ClearableCache) {
return false;
}
return $this->cache->deleteAll();
}
/**
* {@inheritDoc}
*/
public function deleteItem($key): bool
{
assert(self::validKey($key));
unset($this->deferredItems[$key]);
return $this->cache->delete($key);
}
/**
* {@inheritDoc}
*/
public function deleteItems(array $keys): bool
{
foreach ($keys as $key) {
assert(self::validKey($key));
unset($this->deferredItems[$key]);
}
return $this->doDeleteMultiple($keys);
}
public function save(CacheItemInterface $item): bool
{
return $this->saveDeferred($item) && $this->commit();
}
public function saveDeferred(CacheItemInterface $item): bool
{
if (! $item instanceof CacheItem && ! $item instanceof TypedCacheItem) {
return false;
}
$this->deferredItems[$item->getKey()] = $item;
return true;
}
public function commit(): bool
{
if (! $this->deferredItems) {
return true;
}
$now = microtime(true);
$itemsCount = 0;
$byLifetime = [];
$expiredKeys = [];
foreach ($this->deferredItems as $key => $item) {
$lifetime = ($item->getExpiry() ?? $now) - $now;
if ($lifetime < 0) {
$expiredKeys[] = $key;
continue;
}
++$itemsCount;
$byLifetime[(int) $lifetime][$key] = $item->get();
}
$this->deferredItems = [];
switch (count($expiredKeys)) {
case 0:
break;
case 1:
$this->cache->delete(current($expiredKeys));
break;
default:
$this->doDeleteMultiple($expiredKeys);
break;
}
if ($itemsCount === 1) {
return $this->cache->save($key, $item->get(), (int) $lifetime);
}
$success = true;
foreach ($byLifetime as $lifetime => $values) {
$success = $this->doSaveMultiple($values, $lifetime) && $success;
}
return $success;
}
public function __destruct()
{
$this->commit();
}
/**
* @param mixed $key
*/
private static function validKey($key): bool
{
if (! is_string($key)) {
throw new InvalidArgument(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key)));
}
if ($key === '') {
throw new InvalidArgument('Cache key length must be greater than zero.');
}
if (strpbrk($key, self::RESERVED_CHARACTERS) !== false) {
throw new InvalidArgument(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS));
}
return true;
}
/**
* @param mixed[] $keys
*/
private static function validKeys(array $keys): bool
{
foreach ($keys as $key) {
self::validKey($key);
}
return true;
}
/**
* @param mixed[] $keys
*/
private function doDeleteMultiple(array $keys): bool
{
if ($this->cache instanceof MultiDeleteCache) {
return $this->cache->deleteMultiple($keys);
}
$success = true;
foreach ($keys as $key) {
$success = $this->cache->delete($key) && $success;
}
return $success;
}
/**
* @param mixed[] $keys
*
* @return mixed[]
*/
private function doFetchMultiple(array $keys): array
{
if ($this->cache instanceof MultiGetCache) {
return $this->cache->fetchMultiple($keys);
}
$values = [];
foreach ($keys as $key) {
$value = $this->cache->fetch($key);
if (! $value) {
continue;
}
$values[$key] = $value;
}
return $values;
}
/**
* @param mixed[] $keysAndValues
*/
private function doSaveMultiple(array $keysAndValues, int $lifetime = 0): bool
{
if ($this->cache instanceof MultiPutCache) {
return $this->cache->saveMultiple($keysAndValues, $lifetime);
}
$success = true;
foreach ($keysAndValues as $key => $value) {
$success = $this->cache->save($key, $value, $lifetime) && $success;
}
return $success;
}
}
================================================
FILE: lib/Doctrine/Common/Cache/Psr6/CacheItem.php
================================================
key = $key;
$this->value = $data;
$this->isHit = $isHit;
}
public function getKey(): string
{
return $this->key;
}
/**
* {@inheritDoc}
*
* @return mixed
*/
public function get()
{
return $this->value;
}
public function isHit(): bool
{
return $this->isHit;
}
/**
* {@inheritDoc}
*/
public function set($value): self
{
$this->value = $value;
return $this;
}
/**
* {@inheritDoc}
*/
public function expiresAt($expiration): self
{
if ($expiration === null) {
$this->expiry = null;
} elseif ($expiration instanceof DateTimeInterface) {
$this->expiry = (float) $expiration->format('U.u');
} else {
throw new TypeError(sprintf(
'Expected $expiration to be an instance of DateTimeInterface or null, got %s',
is_object($expiration) ? get_class($expiration) : gettype($expiration)
));
}
return $this;
}
/**
* {@inheritDoc}
*/
public function expiresAfter($time): self
{
if ($time === null) {
$this->expiry = null;
} elseif ($time instanceof DateInterval) {
$this->expiry = microtime(true) + DateTime::createFromFormat('U', 0)->add($time)->format('U.u');
} elseif (is_int($time)) {
$this->expiry = $time + microtime(true);
} else {
throw new TypeError(sprintf(
'Expected $time to be either an integer, an instance of DateInterval or null, got %s',
is_object($time) ? get_class($time) : gettype($time)
));
}
return $this;
}
/**
* @internal
*/
public function getExpiry(): ?float
{
return $this->expiry;
}
}
================================================
FILE: lib/Doctrine/Common/Cache/Psr6/DoctrineProvider.php
================================================
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Doctrine\Common\Cache\Psr6;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\CacheProvider;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\DoctrineAdapter as SymfonyDoctrineAdapter;
use Symfony\Contracts\Service\ResetInterface;
use function rawurlencode;
/**
* This class was copied from the Symfony Framework, see the original copyright
* notice above. The code is distributed subject to the license terms in
* https://github.com/symfony/symfony/blob/ff0cf61278982539c49e467db9ab13cbd342f76d/LICENSE
*/
final class DoctrineProvider extends CacheProvider
{
/** @var CacheItemPoolInterface */
private $pool;
public static function wrap(CacheItemPoolInterface $pool): Cache
{
if ($pool instanceof CacheAdapter) {
return $pool->getCache();
}
if ($pool instanceof SymfonyDoctrineAdapter) {
$getCache = function () {
// phpcs:ignore Squiz.Scope.StaticThisUsage.Found
return $this->provider;
};
return $getCache->bindTo($pool, SymfonyDoctrineAdapter::class)();
}
return new self($pool);
}
private function __construct(CacheItemPoolInterface $pool)
{
$this->pool = $pool;
}
/** @internal */
public function getPool(): CacheItemPoolInterface
{
return $this->pool;
}
public function reset(): void
{
if ($this->pool instanceof ResetInterface) {
$this->pool->reset();
}
$this->setNamespace($this->getNamespace());
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$item = $this->pool->getItem(rawurlencode($id));
return $item->isHit() ? $item->get() : false;
}
/**
* {@inheritdoc}
*
* @return bool
*/
protected function doContains($id)
{
return $this->pool->hasItem(rawurlencode($id));
}
/**
* {@inheritdoc}
*
* @return bool
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$item = $this->pool->getItem(rawurlencode($id));
if (0 < $lifeTime) {
$item->expiresAfter($lifeTime);
}
return $this->pool->save($item->set($data));
}
/**
* {@inheritdoc}
*
* @return bool
*/
protected function doDelete($id)
{
return $this->pool->deleteItem(rawurlencode($id));
}
/**
* {@inheritdoc}
*
* @return bool
*/
protected function doFlush()
{
return $this->pool->clear();
}
/**
* {@inheritdoc}
*
* @return array|null
*/
protected function doGetStats()
{
return null;
}
}
================================================
FILE: lib/Doctrine/Common/Cache/Psr6/InvalidArgument.php
================================================
key;
}
public function get(): mixed
{
return $this->value;
}
public function isHit(): bool
{
return $this->isHit;
}
public function set(mixed $value): static
{
$this->value = $value;
return $this;
}
/**
* {@inheritDoc}
*/
public function expiresAt($expiration): static
{
if ($expiration === null) {
$this->expiry = null;
} elseif ($expiration instanceof DateTimeInterface) {
$this->expiry = (float) $expiration->format('U.u');
} else {
throw new TypeError(sprintf(
'Expected $expiration to be an instance of DateTimeInterface or null, got %s',
get_debug_type($expiration)
));
}
return $this;
}
/**
* {@inheritDoc}
*/
public function expiresAfter($time): static
{
if ($time === null) {
$this->expiry = null;
} elseif ($time instanceof DateInterval) {
$this->expiry = microtime(true) + DateTime::createFromFormat('U', 0)->add($time)->format('U.u');
} elseif (is_int($time)) {
$this->expiry = $time + microtime(true);
} else {
throw new TypeError(sprintf(
'Expected $time to be either an integer, an instance of DateInterval or null, got %s',
get_debug_type($time)
));
}
return $this;
}
/**
* @internal
*/
public function getExpiry(): ?float
{
return $this->expiry;
}
}
================================================
FILE: phpcs.xml.dist
================================================
Coding Standards for doctrine.
lib
tests
tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php
tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php
================================================
FILE: phpunit.xml.dist
================================================
./tests/Doctrine/
./lib/Doctrine/
lib/Doctrine/Common/Cache/ApcCache.php
lib/Doctrine/Common/Cache/CouchbaseCache.php
lib/Doctrine/Common/Cache/XcacheCache.php
================================================
FILE: tests/Doctrine/Tests/Common/Cache/ArrayCache.php
================================================
> $data each element being a tuple of [$data, $expiration], where the expiration is int|bool */
private $data = [];
/** @var int */
private $hitsCount = 0;
/** @var int */
private $missesCount = 0;
/** @var int */
private $upTime;
/**
* {@inheritdoc}
*/
public function __construct()
{
$this->upTime = time();
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
if (! $this->doContains($id)) {
$this->missesCount += 1;
return false;
}
$this->hitsCount += 1;
return $this->data[$id][0];
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
if (! isset($this->data[$id])) {
return false;
}
$expiration = $this->data[$id][1];
if ($expiration && $expiration < time()) {
$this->doDelete($id);
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$this->data[$id] = [$data, $lifeTime ? time() + $lifeTime : false];
return true;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
unset($this->data[$id]);
return true;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$this->data = [];
return true;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
return [
Cache::STATS_HITS => $this->hitsCount,
Cache::STATS_MISSES => $this->missesCount,
Cache::STATS_UPTIME => $this->upTime,
Cache::STATS_MEMORY_USAGE => null,
Cache::STATS_MEMORY_AVAILABLE => null,
];
}
}
================================================
FILE: tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php
================================================
getCacheDriver();
$cache->fetch('test1');
$cache->fetch('test2');
$cache->fetch('test3');
$cache->save('test1', 123);
$cache->save('test2', 123);
$cache->fetch('test1');
$cache->fetch('test2');
$cache->fetch('test3');
$stats = $cache->getStats();
self::assertEquals(2, $stats[Cache::STATS_HITS]);
self::assertEquals(5, $stats[Cache::STATS_MISSES]); // +1 for internal call to DoctrineNamespaceCacheKey
self::assertNotNull($stats[Cache::STATS_UPTIME]);
self::assertNull($stats[Cache::STATS_MEMORY_USAGE]);
self::assertNull($stats[Cache::STATS_MEMORY_AVAILABLE]);
$cache->delete('test1');
$cache->delete('test2');
$cache->fetch('test1');
$cache->fetch('test2');
$cache->fetch('test3');
$stats = $cache->getStats();
self::assertEquals(2, $stats[Cache::STATS_HITS]);
self::assertEquals(8, $stats[Cache::STATS_MISSES]); // +1 for internal call to DoctrineNamespaceCacheKey
}
protected function isSharedStorage(): bool
{
return false;
}
}
================================================
FILE: tests/Doctrine/Tests/Common/Cache/CacheProviderTest.php
================================================
getMockForAbstractClass(
CacheProvider::class,
[],
'',
true,
true,
true,
['doFetchMultiple']
);
assert($cache instanceof CacheProvider || $cache instanceof PHPUnit_Framework_MockObject_MockObject);
$cache
->expects($this->once())
->method('doFetchMultiple')
->will($this->returnValue([
'[foo][1]' => 'bar',
'[bar][1]' => 'baz',
'[baz][1]' => 'tab',
]));
self::assertEquals(
['foo' => 'bar', 'bar' => 'baz'],
$cache->fetchMultiple(['foo', 'bar'])
);
}
public function testFailedDeleteAllDoesNotChangeNamespaceVersion(): void
{
$cache = $this->getMockForAbstractClass(
CacheProvider::class,
[],
'',
true,
true,
true,
['doFetch', 'doSave', 'doContains']
);
assert($cache instanceof CacheProvider || $cache instanceof PHPUnit_Framework_MockObject_MockObject);
$cache
->expects($this->once())
->method('doFetch')
->with('DoctrineNamespaceCacheKey[]')
->will($this->returnValue(false));
// doSave is only called once from deleteAll as we do not need to persist the default version in getNamespaceVersion()
$cache
->expects($this->once())
->method('doSave')
->with('DoctrineNamespaceCacheKey[]')
->will($this->returnValue(false));
// After a failed deleteAll() the local namespace version is not increased (still 1). Otherwise all data written afterwards
// would be lost outside the current instance.
$cache
->expects($this->once())
->method('doContains')
->with('[key][1]')
->will($this->returnValue(true));
self::assertFalse($cache->deleteAll(), 'deleteAll() returns false when saving the namespace version fails');
$cache->contains('key');
}
public function testSaveMultipleNoFail(): void
{
$cache = $this->getMockForAbstractClass(
CacheProvider::class,
[],
'',
true,
true,
true,
['doSave']
);
assert($cache instanceof CacheProvider || $cache instanceof PHPUnit_Framework_MockObject_MockObject);
$cache
->expects($this->at(1))
->method('doSave')
->with('[kerr][1]', 'verr', 0)
->will($this->returnValue(false));
$cache
->expects($this->at(2))
->method('doSave')
->with('[kok][1]', 'vok', 0)
->will($this->returnValue(true));
$cache->saveMultiple([
'kerr' => 'verr',
'kok' => 'vok',
]);
}
public function testDeleteMultipleNoFail(): void
{
$cache = $this
->getMockBuilder(CacheProvider::class)
->setMethods(['doDelete'])
->getMockForAbstractClass();
assert($cache instanceof CacheProvider || $cache instanceof PHPUnit_Framework_MockObject_MockObject);
$cache
->expects($this->at(1))
->method('doDelete')
->with('[kerr][1]')
->will($this->returnValue(false));
$cache
->expects($this->at(2))
->method('doDelete')
->with('[kok][1]')
->will($this->returnValue(true));
$cache->deleteMultiple(['kerr', 'kok']);
}
public function testInvalidNamespaceVersionCacheEntry(): void
{
$cache = $this->getMockForAbstractClass(CacheProvider::class);
assert($cache instanceof CacheProvider || $cache instanceof PHPUnit_Framework_MockObject_MockObject);
$cache->expects($this->once())
->method('doFetch')
->with('DoctrineNamespaceCacheKey[]')
->willReturn('corruptedStringKey');
$cache->expects($this->once())
->method('doSave')
->with('DoctrineNamespaceCacheKey[]', 2, 0)
->willReturn(true);
self::assertTrue($cache->deleteAll());
}
}
================================================
FILE: tests/Doctrine/Tests/Common/Cache/CacheTest.php
================================================
getCacheDriver();
// Test saving a value, checking if it exists, and fetching it back
self::assertTrue($cache->save('key', $value));
self::assertTrue($cache->contains('key'));
if (is_object($value)) {
self::assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference');
} else {
self::assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type');
}
// Test deleting a value
self::assertTrue($cache->delete('key'));
self::assertFalse($cache->contains('key'));
self::assertFalse($cache->fetch('key'));
}
/**
* @param mixed $value
*
* @dataProvider provideDataToCache
*/
public function testUpdateExistingEntry($value): void
{
$cache = $this->getCacheDriver();
self::assertTrue($cache->save('key', 'old-value'));
self::assertTrue($cache->contains('key'));
self::assertTrue($cache->save('key', $value));
self::assertTrue($cache->contains('key'));
if (is_object($value)) {
self::assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference');
} else {
self::assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type');
}
}
public function testCacheKeyIsCaseSensitive(): void
{
$cache = $this->getCacheDriver();
self::assertTrue($cache->save('key', 'value'));
self::assertTrue($cache->contains('key'));
self::assertSame('value', $cache->fetch('key'));
self::assertFalse($cache->contains('KEY'));
self::assertFalse($cache->fetch('KEY'));
$cache->delete('KEY');
self::assertTrue($cache->contains('key'), 'Deleting cache item with different case must not affect other cache item');
}
public function testFetchMultiple(): void
{
$cache = $this->getCacheDriver();
$values = $this->provideDataToCache();
$saved = [];
foreach ($values as $key => $value) {
$cache->save($key, $value[0]);
$saved[$key] = $value[0];
}
$keys = array_keys($saved);
self::assertEquals(
$saved,
$cache->fetchMultiple($keys),
'Testing fetchMultiple with different data types'
);
self::assertEquals(
array_slice($saved, 0, 1),
$cache->fetchMultiple(array_slice($keys, 0, 1)),
'Testing fetchMultiple with a single key'
);
$keysWithNonExisting = [];
$keysWithNonExisting[] = 'non_existing1';
$keysWithNonExisting[] = $keys[0];
$keysWithNonExisting[] = 'non_existing2';
$keysWithNonExisting[] = $keys[1];
$keysWithNonExisting[] = 'non_existing3';
self::assertEquals(
array_slice($saved, 0, 2),
$cache->fetchMultiple($keysWithNonExisting),
'Testing fetchMultiple with a subset of keys and mixed with non-existing ones'
);
}
public function testFetchMultipleWithNoKeys(): void
{
$cache = $this->getCacheDriver();
self::assertSame([], $cache->fetchMultiple([]));
}
public function testSaveMultiple(): void
{
$cache = $this->getCacheDriver();
$cache->deleteAll();
$data = array_map(static function ($value) {
return $value[0];
}, $this->provideDataToCache());
self::assertTrue($cache->saveMultiple($data));
$keys = array_keys($data);
self::assertEquals($data, $cache->fetchMultiple($keys));
}
/**
* @return array
*/
public function provideDataToCache(): array
{
$obj = new stdClass();
$obj->foo = 'bar';
$obj2 = new stdClass();
$obj2->bar = 'foo';
$obj2->obj = $obj;
$obj->obj2 = $obj2;
return [
'array' => [['one', 2, 3.01]],
'string' => ['value'],
'string_invalid_utf8' => ["\xc3\x28"],
'string_null_byte' => ['with' . "\0" . 'null char'],
'integer' => [1],
'float' => [1.5],
'object' => [new ArrayObject(['one', 2, 3.01])],
'object_recursive' => [$obj],
'true' => [true],
// the following are considered FALSE in boolean context, but caches should still recognize their existence
'null' => [null],
'false' => [false],
'array_empty' => [[]],
'string_zero' => ['0'],
'integer_zero' => [0],
'float_zero' => [0.0],
'string_empty' => [''],
];
}
public function testDeleteIsSuccessfulWhenKeyDoesNotExist(): void
{
$cache = $this->getCacheDriver();
$cache->delete('key');
self::assertFalse($cache->contains('key'));
self::assertTrue($cache->delete('key'));
}
public function testDeleteAll(): void
{
$cache = $this->getCacheDriver();
self::assertTrue($cache->save('key1', 1));
self::assertTrue($cache->save('key2', 2));
self::assertTrue($cache->deleteAll());
self::assertFalse($cache->contains('key1'), sprintf(
'key1 should have disappeared but did not. The namespace is "%s"',
$cache->getNamespace()
));
self::assertFalse($cache->contains('key2'));
}
public function testDeleteMulti(): void
{
$cache = $this->getCacheDriver();
self::assertTrue($cache->save('key1', 1));
self::assertTrue($cache->save('key2', 1));
self::assertTrue($cache->deleteMultiple(['key1', 'key2', 'key3']));
self::assertFalse($cache->contains('key1'));
self::assertFalse($cache->contains('key2'));
self::assertFalse($cache->contains('key3'));
}
/**
* @dataProvider provideCacheIds
*/
public function testCanHandleSpecialCacheIds(string $id): void
{
$cache = $this->getCacheDriver();
self::assertTrue($cache->save($id, 'value'));
self::assertTrue($cache->contains($id));
self::assertEquals('value', $cache->fetch($id));
self::assertTrue($cache->delete($id));
self::assertFalse($cache->contains($id));
self::assertFalse($cache->fetch($id));
}
public function testNoCacheIdCollisions(): void
{
$cache = $this->getCacheDriver();
$ids = $this->provideCacheIds();
// fill cache with each id having a different value
foreach ($ids as $index => $id) {
$cache->save($id[0], $index);
}
// then check value of each cache id
foreach ($ids as $index => $id) {
$value = $cache->fetch($id[0]);
self::assertNotFalse($value, sprintf('Failed to retrieve data for cache id "%s".', $id[0]));
if ($index === $value) {
continue;
}
$this->fail(sprintf('Cache id "%s" collides with id "%s".', $id[0], $ids[$value][0]));
}
}
/**
* Returns cache ids with special characters that should still work.
*
* For example, the characters :\/<>"*?| are not valid in Windows filenames. So they must be encoded properly.
* Each cache id should be considered different from the others.
*
* @psalm-return list
*/
public function provideCacheIds(): array
{
return [
[':'],
['\\'],
['/'],
['<'],
['>'],
['"'],
['*'],
['?'],
['|'],
['['],
[']'],
['ä'],
['a'],
['é'],
['e'],
['.'], // directory traversal
['..'], // directory traversal
['-'],
['_'],
['$'],
['%'],
[' '],
["\0"],
[''],
[str_repeat('a', 300)], // long key
[str_repeat('a', 113)],
];
}
public function testLifetime(): void
{
$cache = $this->getCacheDriver();
$cache->save('expire', 'value', 1);
self::assertTrue($cache->contains('expire'), 'Data should not be expired yet');
// @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead
sleep(2);
self::assertFalse($cache->contains('expire'), 'Data should be expired');
}
public function testNoExpire(): void
{
$cache = $this->getCacheDriver();
$cache->save('noexpire', 'value', 0);
// @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead
sleep(1);
self::assertTrue($cache->contains('noexpire'), 'Data with lifetime of zero should not expire');
}
public function testLongLifetime(): void
{
$cache = $this->getCacheDriver();
$cache->save('longlifetime', 'value', 30 * 24 * 3600 + 1);
self::assertTrue($cache->contains('longlifetime'), 'Data with lifetime > 30 days should be accepted');
}
public function testDeleteAllAndNamespaceVersioningBetweenCaches(): void
{
if (! $this->isSharedStorage()) {
$this->markTestSkipped('The cache storage needs to be shared.');
}
$cache1 = $this->getCacheDriver();
$cache2 = $this->getCacheDriver();
self::assertTrue($cache1->save('key1', 1));
self::assertTrue($cache2->save('key2', 2));
/* Both providers are initialized with the same namespace version, so
* they can see entries set by each other.
*/
self::assertTrue($cache1->contains('key1'));
self::assertTrue($cache1->contains('key2'));
self::assertTrue($cache2->contains('key1'));
self::assertTrue($cache2->contains('key2'));
/* Deleting all entries through one provider will only increment the
* namespace version on that object (and in the cache itself, which new
* instances will use to initialize). The second provider will retain
* its original version and still see stale data.
*/
self::assertTrue($cache1->deleteAll());
self::assertFalse($cache1->contains('key1'));
self::assertFalse($cache1->contains('key2'));
self::assertTrue($cache2->contains('key1'));
self::assertTrue($cache2->contains('key2'));
/* A new cache provider should not see the deleted entries, since its
* namespace version will be initialized.
*/
$cache3 = $this->getCacheDriver();
self::assertFalse($cache3->contains('key1'));
self::assertFalse($cache3->contains('key2'));
}
public function testFlushAll(): void
{
$cache = $this->getCacheDriver();
self::assertTrue($cache->save('key1', 1));
self::assertTrue($cache->save('key2', 2));
self::assertTrue($cache->flushAll());
self::assertFalse($cache->contains('key1'));
self::assertFalse($cache->contains('key2'));
}
public function testFlushAllAndNamespaceVersioningBetweenCaches(): void
{
if (! $this->isSharedStorage()) {
$this->markTestSkipped('The cache storage needs to be shared.');
}
$cache1 = $this->getCacheDriver();
$cache2 = $this->getCacheDriver();
/* Deleting all elements from the first provider should increment its
* namespace version before saving the first entry.
*/
$cache1->deleteAll();
self::assertTrue($cache1->save('key1', 1));
/* The second provider will be initialized with the same namespace
* version upon its first save operation.
*/
self::assertTrue($cache2->save('key2', 2));
/* Both providers have the same namespace version and can see entries
* set by each other.
*/
self::assertTrue($cache1->contains('key1'));
self::assertTrue($cache1->contains('key2'));
self::assertTrue($cache2->contains('key1'));
self::assertTrue($cache2->contains('key2'));
/* Flushing all entries through one cache will remove all entries from
* the cache but leave their namespace version as-is.
*/
self::assertTrue($cache1->flushAll());
self::assertFalse($cache1->contains('key1'));
self::assertFalse($cache1->contains('key2'));
self::assertFalse($cache2->contains('key1'));
self::assertFalse($cache2->contains('key2'));
/* Inserting a new entry will use the same, incremented namespace
* version, and it will be visible to both providers.
*/
self::assertTrue($cache1->save('key1', 1));
self::assertTrue($cache1->contains('key1'));
self::assertTrue($cache2->contains('key1'));
/* A new cache provider will be initialized with the original namespace
* version and not share any visibility with the first two providers.
*/
$cache3 = $this->getCacheDriver();
self::assertFalse($cache3->contains('key1'));
self::assertFalse($cache3->contains('key2'));
self::assertTrue($cache3->save('key3', 3));
self::assertTrue($cache3->contains('key3'));
}
public function testNamespace(): void
{
$cache = $this->getCacheDriver();
$cache->setNamespace('ns1_');
self::assertTrue($cache->save('key1', 1));
self::assertTrue($cache->contains('key1'));
$cache->setNamespace('ns2_');
self::assertFalse($cache->contains('key1'));
}
public function testDeleteAllNamespace(): void
{
$cache = $this->getCacheDriver();
$cache->setNamespace('ns1');
self::assertFalse($cache->contains('key1'));
$cache->save('key1', 'test');
self::assertTrue($cache->contains('key1'));
$cache->setNamespace('ns2');
self::assertFalse($cache->contains('key1'));
$cache->save('key1', 'test');
self::assertTrue($cache->contains('key1'));
$cache->setNamespace('ns1');
self::assertTrue($cache->contains('key1'));
$cache->deleteAll();
self::assertFalse($cache->contains('key1'));
$cache->setNamespace('ns2');
self::assertTrue($cache->contains('key1'));
$cache->deleteAll();
self::assertFalse($cache->contains('key1'));
}
/**
* @group DCOM-43
*/
public function testGetStats(): void
{
$cache = $this->getCacheDriver();
$stats = $cache->getStats();
self::assertArrayHasKey(Cache::STATS_HITS, $stats);
self::assertArrayHasKey(Cache::STATS_MISSES, $stats);
self::assertArrayHasKey(Cache::STATS_UPTIME, $stats);
self::assertArrayHasKey(Cache::STATS_MEMORY_USAGE, $stats);
self::assertArrayHasKey(Cache::STATS_MEMORY_AVAILABLE, $stats);
}
public function testSaveReturnsTrueWithAndWithoutTTlSet(): void
{
$cache = $this->getCacheDriver();
$cache->deleteAll();
self::assertTrue($cache->save('without_ttl', 'without_ttl'));
self::assertTrue($cache->save('with_ttl', 'with_ttl', 3600));
}
public function testValueThatIsFalseBooleanIsProperlyRetrieved()
{
$cache = $this->getCacheDriver();
$cache->deleteAll();
self::assertTrue($cache->save('key1', false));
self::assertTrue($cache->contains('key1'));
self::assertFalse($cache->fetch('key1'));
}
/**
* @group 147
* @group 152
*/
public function testFetchingANonExistingKeyShouldNeverCauseANoticeOrWarning(): void
{
$cache = $this->getCacheDriver();
$errorHandler = function () {
restore_error_handler();
$this->fail('include failure captured');
};
set_error_handler($errorHandler);
$cache->fetch('key');
self::assertSame(
$errorHandler,
set_error_handler(static function () {
}),
'The error handler is the one set by this test, and wasn\'t replaced'
);
restore_error_handler();
restore_error_handler();
}
/**
* Return whether multiple cache providers share the same storage.
*
* This is used for skipping certain tests for shared storage behavior.
*/
protected function isSharedStorage(): bool
{
return true;
}
abstract protected function getCacheDriver(): CacheProvider;
}
================================================
FILE: tests/Doctrine/Tests/Common/Cache/Psr6/CacheAdapterTest.php
================================================
arrayCache) {
$this->arrayCache = new ArrayCache();
}
return CacheAdapter::wrap($this->arrayCache);
}
public function testWithWrappedCache()
{
$rootCache = new ArrayAdapter();
$wrapped = DoctrineProvider::wrap($rootCache);
self::assertSame($rootCache, CacheAdapter::wrap($wrapped));
}
/**
* @requires function Symfony\Component\Cache\DoctrineProvider::__construct
*/
public function testWithWrappedSymfonyCache()
{
$rootCache = new ArrayAdapter();
$wrapped = new SymfonyDoctrineProvider($rootCache);
self::assertSame($rootCache, CacheAdapter::wrap($wrapped));
}
public function testWithWrappedMinimalCache()
{
$rootCache = new class implements Cache {
/** @var mixed[] */
public $values = [];
/** @inheritdoc **/
public function fetch($id)
{
return $values[$id] ?? false;
}
/** @inheritdoc **/
public function contains($id)
{
return array_key_exists($id, $this->values);
}
/** @inheritdoc **/
public function save($id, $data, $lifeTime = 0)
{
$this->values[$id] = $data;
return true;
}
/** @inheritdoc **/
public function delete($id)
{
unset($this->values[$id]);
return true;
}
/** @inheritdoc **/
public function getStats()
{
return null;
}
};
$adapter = CacheAdapter::wrap($rootCache);
self::assertInstanceOf(CacheAdapter::class, $adapter);
assert($adapter instanceof CacheAdapter);
/** @var CacheItemInterface[] $items */
$items = $adapter->getItems(['1', '2', '3']);
self::assertCount(3, $items);
foreach ($items as $key => $item) {
$item->set($key);
$adapter->saveDeferred($item);
}
self::assertTrue($adapter->commit());
self::assertCount(3, $rootCache->values);
self::assertFalse($adapter->clear());
self::assertCount(3, $rootCache->values);
self::assertTrue($adapter->deleteItems(['1', '2']));
self::assertCount(1, $rootCache->values);
}
public function testItemsAreFlushedToTheUnderlyingCacheOnce(): void
{
$wrapped = $this->createMock(Cache::class);
$adapter = CacheAdapter::wrap($wrapped);
$cacheItem = $adapter->getItem('answer-to-life-universe-everything');
$cacheItem->set(42);
$adapter->saveDeferred($cacheItem);
$wrapped->expects(self::once())
->method('save')
->willReturn(true);
$adapter->commit();
$adapter->commit();
}
public function testNamespacingFeatureIsPreservedWithDoctrineProvider(): void
{
$wrapped = new ArrayAdapter();
$cacheApp1 = DoctrineProvider::wrap($wrapped);
$cacheApp1->setNamespace('app 1');
$cacheApp2 = DoctrineProvider::wrap($wrapped);
$cacheApp2->setNamespace('app 2');
$psrCacheApp1 = CacheAdapter::wrap($cacheApp1);
$psrCacheApp2 = CacheAdapter::wrap($cacheApp2);
$item = $psrCacheApp1->getItem('some key')->set('some value');
$psrCacheApp1->save($item);
self::assertFalse($psrCacheApp2->getItem('some key')->isHit());
}
/**
* @requires function Symfony\Component\Cache\DoctrineProvider::__construct
*/
public function testNamespacingFeatureIsPreservedWithSymfonyDoctrineProvider(): void
{
$wrapped = new ArrayAdapter();
$cacheApp1 = new SymfonyDoctrineProvider($wrapped);
$cacheApp1->setNamespace('app 1');
$cacheApp2 = new SymfonyDoctrineProvider($wrapped);
$cacheApp2->setNamespace('app 2');
$psrCacheApp1 = CacheAdapter::wrap($cacheApp1);
$psrCacheApp2 = CacheAdapter::wrap($cacheApp2);
$item = $psrCacheApp1->getItem('some key')->set('some value');
$psrCacheApp1->save($item);
self::assertFalse($psrCacheApp2->getItem('some key')->isHit());
}
}
================================================
FILE: tests/Doctrine/Tests/Common/Cache/Psr6/DoctrineProviderTest.php
================================================
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Doctrine\Tests\Common\Cache\Psr6;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\Tests\Common\Cache\ArrayCache;
use Doctrine\Tests\Common\Cache\CacheTest;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\DoctrineAdapter as SymfonyDoctrineAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use function class_exists;
use function sprintf;
use function sys_get_temp_dir;
class DoctrineProviderTest extends CacheTest
{
protected function getCacheDriver(): CacheProvider
{
$pool = new ArrayAdapter();
return DoctrineProvider::wrap($pool);
}
public function testProvider()
{
$cache = $this->getCacheDriver();
$this->assertInstanceOf(CacheProvider::class, $cache);
$key = '{}()/\@:';
$this->assertTrue($cache->delete($key));
$this->assertFalse($cache->contains($key));
$this->assertTrue($cache->save($key, 'bar'));
$this->assertTrue($cache->contains($key));
$this->assertSame('bar', $cache->fetch($key));
$this->assertTrue($cache->delete($key));
$this->assertFalse($cache->fetch($key));
$this->assertTrue($cache->save($key, 'bar'));
$cache->flushAll();
$this->assertFalse($cache->fetch($key));
$this->assertFalse($cache->contains($key));
}
public function testWithWrappedCache()
{
$rootCache = new ArrayCache();
$wrapped = CacheAdapter::wrap($rootCache);
self::assertSame($rootCache, DoctrineProvider::wrap($wrapped));
}
public function testWithWrappedSymfonyCache()
{
if (! class_exists(SymfonyDoctrineAdapter::class)) {
self::markTestSkipped('This test requires Symfony 5 or lower.');
}
$rootCache = new ArrayCache();
$wrapped = new SymfonyDoctrineAdapter($rootCache);
self::assertSame($rootCache, DoctrineProvider::wrap($wrapped));
}
public function testGetStats(): void
{
$this->markTestSkipped(sprintf('"%s" does not expose statistics', DoctrineProvider::class));
}
public function testResetArrayAdapter()
{
$cache = $this->getCacheDriver();
$cache->save('test', 'test');
$cache->reset();
$this->assertSame(false, $cache->fetch('test'));
}
public function testResetFilesystemAdapter()
{
$pool = new FilesystemAdapter('', 0, sys_get_temp_dir() . '/doctrine-cache-test');
$pool2 = new FilesystemAdapter('', 0, sys_get_temp_dir() . '/doctrine-cache-test');
$cache = DoctrineProvider::wrap($pool);
$cache2 = DoctrineProvider::wrap($pool2);
$cache->save('test', 'test');
$cache->reset();
// we make sure with the next assertion the cache behave like expected and the test is not accidentally changed
// to use ArrayAdapter as this test scenario requires a persisted cache adapter
$this->assertSame('test', $cache->fetch('test'));
// the second cache instance will now remove all exist files via namespaceVersion still the first cache
// will receive the data until then the reset is called. the assertion after deleteAll is not required
// but better show why the reset is even needed when cache service is used in long-running processes.
$cache2->deleteAll();
$this->assertSame('test', $cache->fetch('test'));
$cache->reset();
// the previous called reset will reset the namespaceVersion and so the cache is correctly false now
$this->assertSame(false, $cache->fetch('test'));
}
protected function isSharedStorage(): bool
{
return false;
}
}
================================================
FILE: tests/Doctrine/Tests/DoctrineTestCase.php
================================================