Upgrade 1-11.38

This commit is contained in:
xesmyd
2026-03-30 14:10:30 +02:00
parent f2a7e6d1fc
commit ac648ef29d
24665 changed files with 69682 additions and 2205004 deletions
-3
View File
@@ -1,3 +0,0 @@
vendor/
composer.lock
phpunit.xml
+5
View File
@@ -1,6 +1,11 @@
CHANGELOG
=========
4.2.0
-----
* added different protocols to be allowed as asset base_urls
3.4.0
-----
+1 -5
View File
@@ -24,11 +24,7 @@ class RequestStackContext implements ContextInterface
private $basePath;
private $secure;
/**
* @param string $basePath
* @param bool $secure
*/
public function __construct(RequestStack $requestStack, $basePath = '', $secure = false)
public function __construct(RequestStack $requestStack, string $basePath = '', bool $secure = false)
{
$this->requestStack = $requestStack;
$this->basePath = $basePath;
+1 -1
View File
@@ -16,6 +16,6 @@ namespace Symfony\Component\Asset\Exception;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ExceptionInterface
interface ExceptionInterface extends \Throwable
{
}
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2004-2020 Fabien Potencier
Copyright (c) 2004-2022 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+5 -2
View File
@@ -29,7 +29,7 @@ class Package implements PackageInterface
public function __construct(VersionStrategyInterface $versionStrategy, ContextInterface $context = null)
{
$this->versionStrategy = $versionStrategy;
$this->context = $context ?: new NullContext();
$this->context = $context ?? new NullContext();
}
/**
@@ -68,8 +68,11 @@ class Package implements PackageInterface
return $this->versionStrategy;
}
/**
* @return bool
*/
protected function isAbsoluteUrl($url)
{
return false !== strpos($url, '://') || '//' === substr($url, 0, 2);
return str_contains($url, '://') || '//' === substr($url, 0, 2);
}
}
+2 -4
View File
@@ -26,8 +26,7 @@ class Packages
private $packages = [];
/**
* @param PackageInterface $defaultPackage The default package
* @param PackageInterface[] $packages Additional packages indexed by name
* @param PackageInterface[] $packages Additional packages indexed by name
*/
public function __construct(PackageInterface $defaultPackage = null, array $packages = [])
{
@@ -46,8 +45,7 @@ class Packages
/**
* Adds a package.
*
* @param string $name The package name
* @param PackageInterface $package The package
* @param string $name The package name
*/
public function addPackage($name, PackageInterface $package)
{
+3 -9
View File
@@ -29,11 +29,9 @@ class PathPackage extends Package
private $basePath;
/**
* @param string $basePath The base path to be prepended to relative paths
* @param VersionStrategyInterface $versionStrategy The version strategy
* @param ContextInterface|null $context The context
* @param string $basePath The base path to be prepended to relative paths
*/
public function __construct($basePath, VersionStrategyInterface $versionStrategy, ContextInterface $context = null)
public function __construct(string $basePath, VersionStrategyInterface $versionStrategy, ContextInterface $context = null)
{
parent::__construct($versionStrategy, $context);
@@ -53,11 +51,7 @@ class PathPackage extends Package
*/
public function getUrl($path)
{
if ($this->isAbsoluteUrl($path)) {
return $path;
}
$versionedPath = $this->getVersionStrategy()->applyVersion($path);
$versionedPath = parent::getUrl($path);
// if absolute or begins with /, we're done
if ($this->isAbsoluteUrl($versionedPath) || ($versionedPath && '/' === $versionedPath[0])) {
+5 -5
View File
@@ -7,8 +7,8 @@ CSS stylesheets, JavaScript files and image files.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/asset/introduction.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
* [Documentation](https://symfony.com/doc/current/components/asset/introduction.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
-32
View File
@@ -1,32 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Asset\Tests\Context;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Asset\Context\NullContext;
class NullContextTest extends TestCase
{
public function testGetBasePath()
{
$nullContext = new NullContext();
$this->assertEmpty($nullContext->getBasePath());
}
public function testIsSecure()
{
$nullContext = new NullContext();
$this->assertFalse($nullContext->isSecure());
}
}
@@ -1,73 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Asset\Tests\Context;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Asset\Context\RequestStackContext;
class RequestStackContextTest extends TestCase
{
public function testGetBasePathEmpty()
{
$requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock();
$requestStackContext = new RequestStackContext($requestStack);
$this->assertEmpty($requestStackContext->getBasePath());
}
public function testGetBasePathSet()
{
$testBasePath = 'test-path';
$request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock();
$request->method('getBasePath')
->willReturn($testBasePath);
$requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock();
$requestStack->method('getMasterRequest')
->willReturn($request);
$requestStackContext = new RequestStackContext($requestStack);
$this->assertSame($testBasePath, $requestStackContext->getBasePath());
}
public function testIsSecureFalse()
{
$requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock();
$requestStackContext = new RequestStackContext($requestStack);
$this->assertFalse($requestStackContext->isSecure());
}
public function testIsSecureTrue()
{
$request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock();
$request->method('isSecure')
->willReturn(true);
$requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock();
$requestStack->method('getMasterRequest')
->willReturn($request);
$requestStackContext = new RequestStackContext($requestStack);
$this->assertTrue($requestStackContext->isSecure());
}
public function testDefaultContext()
{
$requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock();
$requestStackContext = new RequestStackContext($requestStack, 'default-path', true);
$this->assertSame('default-path', $requestStackContext->getBasePath());
$this->assertTrue($requestStackContext->isSecure());
}
}
-55
View File
@@ -1,55 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Asset\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
class PackageTest extends TestCase
{
/**
* @dataProvider getConfigs
*/
public function testGetUrl($version, $format, $path, $expected)
{
$package = new Package($version ? new StaticVersionStrategy($version, $format) : new EmptyVersionStrategy());
$this->assertSame($expected, $package->getUrl($path));
}
public function getConfigs()
{
return [
['v1', '', 'http://example.com/foo', 'http://example.com/foo'],
['v1', '', 'https://example.com/foo', 'https://example.com/foo'],
['v1', '', '//example.com/foo', '//example.com/foo'],
['v1', '', '/foo', '/foo?v1'],
['v1', '', 'foo', 'foo?v1'],
[null, '', '/foo', '/foo'],
[null, '', 'foo', 'foo'],
['v1', 'version-%2$s/%1$s', '/foo', '/version-v1/foo'],
['v1', 'version-%2$s/%1$s', 'foo', 'version-v1/foo'],
['v1', 'version-%2$s/%1$s', 'foo/', 'version-v1/foo/'],
['v1', 'version-%2$s/%1$s', '/foo/', '/version-v1/foo/'],
];
}
public function testGetVersion()
{
$package = new Package(new StaticVersionStrategy('v1'));
$this->assertSame('v1', $package->getVersion('/foo'));
}
}
-71
View File
@@ -1,71 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Asset\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\Packages;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
class PackagesTest extends TestCase
{
public function testGetterSetters()
{
$packages = new Packages();
$packages->setDefaultPackage($default = $this->getMockBuilder('Symfony\Component\Asset\PackageInterface')->getMock());
$packages->addPackage('a', $a = $this->getMockBuilder('Symfony\Component\Asset\PackageInterface')->getMock());
$this->assertSame($default, $packages->getPackage());
$this->assertSame($a, $packages->getPackage('a'));
$packages = new Packages($default, ['a' => $a]);
$this->assertSame($default, $packages->getPackage());
$this->assertSame($a, $packages->getPackage('a'));
}
public function testGetVersion()
{
$packages = new Packages(
new Package(new StaticVersionStrategy('default')),
['a' => new Package(new StaticVersionStrategy('a'))]
);
$this->assertSame('default', $packages->getVersion('/foo'));
$this->assertSame('a', $packages->getVersion('/foo', 'a'));
}
public function testGetUrl()
{
$packages = new Packages(
new Package(new StaticVersionStrategy('default')),
['a' => new Package(new StaticVersionStrategy('a'))]
);
$this->assertSame('/foo?default', $packages->getUrl('/foo'));
$this->assertSame('/foo?a', $packages->getUrl('/foo', 'a'));
}
public function testNoDefaultPackage()
{
$this->expectException('Symfony\Component\Asset\Exception\LogicException');
$packages = new Packages();
$packages->getPackage();
}
public function testUndefinedPackage()
{
$this->expectException('Symfony\Component\Asset\Exception\InvalidArgumentException');
$packages = new Packages();
$packages->getPackage('a');
}
}
-96
View File
@@ -1,96 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Asset\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
class PathPackageTest extends TestCase
{
/**
* @dataProvider getConfigs
*/
public function testGetUrl($basePath, $format, $path, $expected)
{
$package = new PathPackage($basePath, new StaticVersionStrategy('v1', $format));
$this->assertSame($expected, $package->getUrl($path));
}
public function getConfigs()
{
return [
['/foo', '', 'http://example.com/foo', 'http://example.com/foo'],
['/foo', '', 'https://example.com/foo', 'https://example.com/foo'],
['/foo', '', '//example.com/foo', '//example.com/foo'],
['', '', '/foo', '/foo?v1'],
['/foo', '', '/bar', '/bar?v1'],
['/foo', '', 'bar', '/foo/bar?v1'],
['foo', '', 'bar', '/foo/bar?v1'],
['foo/', '', 'bar', '/foo/bar?v1'],
['/foo/', '', 'bar', '/foo/bar?v1'],
['/foo', 'version-%2$s/%1$s', '/bar', '/version-v1/bar'],
['/foo', 'version-%2$s/%1$s', 'bar', '/foo/version-v1/bar'],
['/foo', 'version-%2$s/%1$s', 'bar/', '/foo/version-v1/bar/'],
['/foo', 'version-%2$s/%1$s', '/bar/', '/version-v1/bar/'],
];
}
/**
* @dataProvider getContextConfigs
*/
public function testGetUrlWithContext($basePathRequest, $basePath, $format, $path, $expected)
{
$package = new PathPackage($basePath, new StaticVersionStrategy('v1', $format), $this->getContext($basePathRequest));
$this->assertSame($expected, $package->getUrl($path));
}
public function getContextConfigs()
{
return [
['', '/foo', '', '/baz', '/baz?v1'],
['', '/foo', '', 'baz', '/foo/baz?v1'],
['', 'foo', '', 'baz', '/foo/baz?v1'],
['', 'foo/', '', 'baz', '/foo/baz?v1'],
['', '/foo/', '', 'baz', '/foo/baz?v1'],
['/bar', '/foo', '', '/baz', '/baz?v1'],
['/bar', '/foo', '', 'baz', '/bar/foo/baz?v1'],
['/bar', 'foo', '', 'baz', '/bar/foo/baz?v1'],
['/bar', 'foo/', '', 'baz', '/bar/foo/baz?v1'],
['/bar', '/foo/', '', 'baz', '/bar/foo/baz?v1'],
];
}
public function testVersionStrategyGivesAbsoluteURL()
{
$versionStrategy = $this->getMockBuilder('Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface')->getMock();
$versionStrategy->expects($this->any())
->method('applyVersion')
->willReturn('https://cdn.com/bar/main.css');
$package = new PathPackage('/subdirectory', $versionStrategy, $this->getContext('/bar'));
$this->assertSame('https://cdn.com/bar/main.css', $package->getUrl('main.css'));
}
private function getContext($basePath)
{
$context = $this->getMockBuilder('Symfony\Component\Asset\Context\ContextInterface')->getMock();
$context->expects($this->any())->method('getBasePath')->willReturn($basePath);
return $context;
}
}
-110
View File
@@ -1,110 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Asset\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Asset\UrlPackage;
use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
class UrlPackageTest extends TestCase
{
/**
* @dataProvider getConfigs
*/
public function testGetUrl($baseUrls, $format, $path, $expected)
{
$package = new UrlPackage($baseUrls, new StaticVersionStrategy('v1', $format));
$this->assertSame($expected, $package->getUrl($path));
}
public function getConfigs()
{
return [
['http://example.net', '', 'http://example.com/foo', 'http://example.com/foo'],
['http://example.net', '', 'https://example.com/foo', 'https://example.com/foo'],
['http://example.net', '', '//example.com/foo', '//example.com/foo'],
['http://example.com', '', '/foo', 'http://example.com/foo?v1'],
['http://example.com', '', 'foo', 'http://example.com/foo?v1'],
['http://example.com/', '', 'foo', 'http://example.com/foo?v1'],
['http://example.com/foo', '', 'foo', 'http://example.com/foo/foo?v1'],
['http://example.com/foo/', '', 'foo', 'http://example.com/foo/foo?v1'],
[['http://example.com'], '', '/foo', 'http://example.com/foo?v1'],
[['http://example.com', 'http://example.net'], '', '/foo', 'http://example.com/foo?v1'],
[['http://example.com', 'http://example.net'], '', '/fooa', 'http://example.net/fooa?v1'],
['http://example.com', 'version-%2$s/%1$s', '/foo', 'http://example.com/version-v1/foo'],
['http://example.com', 'version-%2$s/%1$s', 'foo', 'http://example.com/version-v1/foo'],
['http://example.com', 'version-%2$s/%1$s', 'foo/', 'http://example.com/version-v1/foo/'],
['http://example.com', 'version-%2$s/%1$s', '/foo/', 'http://example.com/version-v1/foo/'],
];
}
/**
* @dataProvider getContextConfigs
*/
public function testGetUrlWithContext($secure, $baseUrls, $format, $path, $expected)
{
$package = new UrlPackage($baseUrls, new StaticVersionStrategy('v1', $format), $this->getContext($secure));
$this->assertSame($expected, $package->getUrl($path));
}
public function getContextConfigs()
{
return [
[false, 'http://example.com', '', 'foo', 'http://example.com/foo?v1'],
[false, ['http://example.com'], '', 'foo', 'http://example.com/foo?v1'],
[false, ['http://example.com', 'https://example.com'], '', 'foo', 'http://example.com/foo?v1'],
[false, ['http://example.com', 'https://example.com'], '', 'fooa', 'https://example.com/fooa?v1'],
[false, ['http://example.com/bar'], '', 'foo', 'http://example.com/bar/foo?v1'],
[false, ['http://example.com/bar/'], '', 'foo', 'http://example.com/bar/foo?v1'],
[false, ['//example.com/bar/'], '', 'foo', '//example.com/bar/foo?v1'],
[true, ['http://example.com'], '', 'foo', 'http://example.com/foo?v1'],
[true, ['http://example.com', 'https://example.com'], '', 'foo', 'https://example.com/foo?v1'],
];
}
public function testVersionStrategyGivesAbsoluteURL()
{
$versionStrategy = $this->getMockBuilder('Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface')->getMock();
$versionStrategy->expects($this->any())
->method('applyVersion')
->willReturn('https://cdn.com/bar/main.css');
$package = new UrlPackage('https://example.com', $versionStrategy);
$this->assertSame('https://cdn.com/bar/main.css', $package->getUrl('main.css'));
}
public function testNoBaseUrls()
{
$this->expectException('Symfony\Component\Asset\Exception\LogicException');
new UrlPackage([], new EmptyVersionStrategy());
}
public function testWrongBaseUrl()
{
$this->expectException('Symfony\Component\Asset\Exception\InvalidArgumentException');
new UrlPackage(['not-a-url'], new EmptyVersionStrategy());
}
private function getContext($secure)
{
$context = $this->getMockBuilder('Symfony\Component\Asset\Context\ContextInterface')->getMock();
$context->expects($this->any())->method('isSecure')->willReturn($secure);
return $context;
}
}
@@ -1,34 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Asset\Tests\VersionStrategy;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy;
class EmptyVersionStrategyTest extends TestCase
{
public function testGetVersion()
{
$emptyVersionStrategy = new EmptyVersionStrategy();
$path = 'test-path';
$this->assertEmpty($emptyVersionStrategy->getVersion($path));
}
public function testApplyVersion()
{
$emptyVersionStrategy = new EmptyVersionStrategy();
$path = 'test-path';
$this->assertSame($path, $emptyVersionStrategy->applyVersion($path));
}
}
@@ -1,59 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Asset\Tests\VersionStrategy;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy;
class JsonManifestVersionStrategyTest extends TestCase
{
public function testGetVersion()
{
$strategy = $this->createStrategy('manifest-valid.json');
$this->assertSame('main.123abc.js', $strategy->getVersion('main.js'));
}
public function testApplyVersion()
{
$strategy = $this->createStrategy('manifest-valid.json');
$this->assertSame('css/styles.555def.css', $strategy->getVersion('css/styles.css'));
}
public function testApplyVersionWhenKeyDoesNotExistInManifest()
{
$strategy = $this->createStrategy('manifest-valid.json');
$this->assertSame('css/other.css', $strategy->getVersion('css/other.css'));
}
public function testMissingManifestFileThrowsException()
{
$this->expectException('RuntimeException');
$strategy = $this->createStrategy('non-existent-file.json');
$strategy->getVersion('main.js');
}
public function testManifestFileWithBadJSONThrowsException()
{
$this->expectException('RuntimeException');
$this->expectExceptionMessage('Error parsing JSON');
$strategy = $this->createStrategy('manifest-invalid.json');
$strategy->getVersion('main.js');
}
private function createStrategy($manifestFilename)
{
return new JsonManifestVersionStrategy(__DIR__.'/../fixtures/'.$manifestFilename);
}
}
@@ -1,44 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Asset\Tests\VersionStrategy;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
class StaticVersionStrategyTest extends TestCase
{
public function testGetVersion()
{
$version = 'v1';
$path = 'test-path';
$staticVersionStrategy = new StaticVersionStrategy($version);
$this->assertSame($version, $staticVersionStrategy->getVersion($path));
}
/**
* @dataProvider getConfigs
*/
public function testApplyVersion($path, $version, $format)
{
$staticVersionStrategy = new StaticVersionStrategy($version, $format);
$formatted = sprintf($format ?: '%s?%s', $path, $version);
$this->assertSame($formatted, $staticVersionStrategy->applyVersion($path));
}
public function getConfigs()
{
return [
['test-path', 'v1', null],
['test-path', 'v2', '%s?test%s'],
];
}
}
@@ -1,4 +0,0 @@
{
"main.js": main.123abc.js",
"css/styles.css": "css/styles.555def.css"
}
@@ -1,4 +0,0 @@
{
"main.js": "main.123abc.js",
"css/styles.css": "css/styles.555def.css"
}
+3 -5
View File
@@ -39,9 +39,7 @@ class UrlPackage extends Package
private $sslPackage;
/**
* @param string|string[] $baseUrls Base asset URLs
* @param VersionStrategyInterface $versionStrategy The version strategy
* @param ContextInterface|null $context Context
* @param string|string[] $baseUrls Base asset URLs
*/
public function __construct($baseUrls, VersionStrategyInterface $versionStrategy, ContextInterface $context = null)
{
@@ -123,13 +121,13 @@ class UrlPackage extends Package
return (int) fmod(hexdec(substr(hash('sha256', $path), 0, 10)), \count($this->baseUrls));
}
private function getSslUrls($urls)
private function getSslUrls(array $urls)
{
$sslUrls = [];
foreach ($urls as $url) {
if ('https://' === substr($url, 0, 8) || '//' === substr($url, 0, 2)) {
$sslUrls[] = $url;
} elseif ('http://' !== substr($url, 0, 7)) {
} elseif (null === parse_url($url, \PHP_URL_SCHEME)) {
throw new InvalidArgumentException(sprintf('"%s" is not a valid URL.', $url));
}
}
@@ -30,7 +30,7 @@ class JsonManifestVersionStrategy implements VersionStrategyInterface
/**
* @param string $manifestPath Absolute path to the manifest file
*/
public function __construct($manifestPath)
public function __construct(string $manifestPath)
{
$this->manifestPath = $manifestPath;
}
@@ -50,11 +50,11 @@ class JsonManifestVersionStrategy implements VersionStrategyInterface
return $this->getManifestPath($path) ?: $path;
}
private function getManifestPath($path)
private function getManifestPath(string $path): ?string
{
if (null === $this->manifestData) {
if (!file_exists($this->manifestPath)) {
throw new \RuntimeException(sprintf('Asset manifest file "%s" does not exist.', $this->manifestPath));
throw new \RuntimeException(sprintf('Asset manifest file "%s" does not exist. Did you forget to build the assets with npm or yarn?', $this->manifestPath));
}
$this->manifestData = json_decode(file_get_contents($this->manifestPath), true);
@@ -63,6 +63,6 @@ class JsonManifestVersionStrategy implements VersionStrategyInterface
}
}
return isset($this->manifestData[$path]) ? $this->manifestData[$path] : null;
return $this->manifestData[$path] ?? null;
}
}
@@ -25,7 +25,7 @@ class StaticVersionStrategy implements VersionStrategyInterface
* @param string $version Version number
* @param string $format Url format
*/
public function __construct($version, $format = null)
public function __construct(string $version, string $format = null)
{
$this->version = $version;
$this->format = $format ?: '%s?%s';
@@ -46,7 +46,7 @@ class StaticVersionStrategy implements VersionStrategyInterface
{
$versionized = sprintf($this->format, ltrim($path, '/'), $this->getVersion($path));
if ($path && '/' == $path[0]) {
if ($path && '/' === $path[0]) {
return '/'.$versionized;
}
+5 -4
View File
@@ -1,7 +1,7 @@
{
"name": "symfony/asset",
"type": "library",
"description": "Symfony Asset Component",
"description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
@@ -16,14 +16,15 @@
}
],
"require": {
"php": "^5.5.9|>=7.0.8"
"php": ">=7.1.3",
"symfony/polyfill-php80": "^1.16"
},
"suggest": {
"symfony/http-foundation": ""
},
"require-dev": {
"symfony/http-foundation": "~2.8|~3.0|~4.0",
"symfony/http-kernel": "~2.8|~3.0|~4.0"
"symfony/http-foundation": "^3.4|^4.0|^5.0",
"symfony/http-kernel": "^3.4|^4.0|^5.0"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Asset\\": "" },
-30
View File
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony Asset Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
@@ -1,3 +0,0 @@
vendor/
composer.lock
phpunit.xml
-57
View File
@@ -1,57 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Cache;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\InvalidArgumentException;
/**
* Covers most simple to advanced caching needs.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface CacheInterface
{
/**
* Fetches a value from the pool or computes it if not found.
*
* On cache misses, a callback is called that should return the missing value.
* This callback is given a PSR-6 CacheItemInterface instance corresponding to the
* requested key, that could be used e.g. for expiration control. It could also
* be an ItemInterface instance when its additional features are needed.
*
* @param string $key The key of the item to retrieve from the cache
* @param callable|CallbackInterface $callback Should return the computed value for the given key/item
* @param float|null $beta A float that, as it grows, controls the likeliness of triggering
* early expiration. 0 disables it, INF forces immediate expiration.
* The default (or providing null) is implementation dependent but should
* typically be 1.0, which should provide optimal stampede protection.
* See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration
* @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()}
*
* @return mixed The value corresponding to the provided key
*
* @throws InvalidArgumentException When $key is not valid or when $beta is negative
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null);
/**
* Removes an item from the pool.
*
* @param string $key The key to delete
*
* @throws InvalidArgumentException When $key is not valid
*
* @return bool True if the item was successfully removed, false if there was any error
*/
public function delete(string $key): bool;
}
-80
View File
@@ -1,80 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Cache;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException;
use Psr\Log\LoggerInterface;
// Help opcache.preload discover always-needed symbols
class_exists(InvalidArgumentException::class);
/**
* An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
trait CacheTrait
{
/**
* {@inheritdoc}
*
* @return mixed
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
return $this->doGet($this, $key, $callback, $beta, $metadata);
}
/**
* {@inheritdoc}
*/
public function delete(string $key): bool
{
return $this->deleteItem($key);
}
private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null)
{
if (0 > $beta = $beta ?? 1.0) {
throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)) extends \InvalidArgumentException implements InvalidArgumentException { };
}
$item = $pool->getItem($key);
$recompute = !$item->isHit() || \INF === $beta;
$metadata = $item instanceof ItemInterface ? $item->getMetadata() : [];
if (!$recompute && $metadata) {
$expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? false;
$ctime = $metadata[ItemInterface::METADATA_CTIME] ?? false;
if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, \PHP_INT_MAX) / \PHP_INT_MAX)) {
// force applying defaultLifetime to expiry
$item->expiresAt(null);
$logger && $logger->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [
'key' => $key,
'delta' => sprintf('%.1f', $expiry - $now),
]);
}
}
if ($recompute) {
$save = true;
$item->set($callback($item, $save));
if ($save) {
$pool->save($item);
}
}
return $item->get();
}
}
-30
View File
@@ -1,30 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Cache;
use Psr\Cache\CacheItemInterface;
/**
* Computes and returns the cached value of an item.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface CallbackInterface
{
/**
* @param CacheItemInterface|ItemInterface $item The item to compute the value for
* @param bool &$save Should be set to false when the value should not be saved in the pool
*
* @return mixed The computed value for the passed item
*/
public function __invoke(CacheItemInterface $item, bool &$save);
}
-65
View File
@@ -1,65 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Cache;
use Psr\Cache\CacheException;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\InvalidArgumentException;
/**
* Augments PSR-6's CacheItemInterface with support for tags and metadata.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface ItemInterface extends CacheItemInterface
{
/**
* References the Unix timestamp stating when the item will expire.
*/
public const METADATA_EXPIRY = 'expiry';
/**
* References the time the item took to be created, in milliseconds.
*/
public const METADATA_CTIME = 'ctime';
/**
* References the list of tags that were assigned to the item, as string[].
*/
public const METADATA_TAGS = 'tags';
/**
* Reserved characters that cannot be used in a key or tag.
*/
public const RESERVED_CHARACTERS = '{}()/\@:';
/**
* Adds a tag to a cache item.
*
* Tags are strings that follow the same validation rules as keys.
*
* @param string|string[] $tags A tag or array of tags
*
* @return $this
*
* @throws InvalidArgumentException When $tag is not valid
* @throws CacheException When the item comes from a pool that is not tag-aware
*/
public function tag($tags): self;
/**
* Returns a list of metadata info that were saved alongside with the cached value.
*
* See ItemInterface::METADATA_* consts for keys potentially found in the returned array.
*/
public function getMetadata(): array;
}
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2018-2022 Fabien Potencier
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.
-9
View File
@@ -1,9 +0,0 @@
Symfony Cache Contracts
=======================
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful - and
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/main/README.md for more information.
@@ -1,38 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Cache;
use Psr\Cache\InvalidArgumentException;
/**
* Allows invalidating cached items using tags.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface TagAwareCacheInterface extends CacheInterface
{
/**
* Invalidates cached items using tags.
*
* When implemented on a PSR-6 pool, invalidation should not apply
* to deferred items. Instead, they should be committed as usual.
* This allows replacing old tagged values by new ones without
* race conditions.
*
* @param string[] $tags An array of tags to invalidate
*
* @return bool True on success
*
* @throws InvalidArgumentException When $tags is not valid
*/
public function invalidateTags(array $tags);
}
-38
View File
@@ -1,38 +0,0 @@
{
"name": "symfony/cache-contracts",
"type": "library",
"description": "Generic abstractions related to caching",
"keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.1.3",
"psr/cache": "^1.0|^2.0|^3.0"
},
"suggest": {
"symfony/cache-implementation": ""
},
"autoload": {
"psr-4": { "Symfony\\Contracts\\Cache\\": "" }
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "1.1-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
}
}
+167 -54
View File
@@ -11,80 +11,71 @@
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Component\Cache\Traits\AbstractTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface
{
/**
* @internal
*/
protected const NS_SEPARATOR = ':';
const NS_SEPARATOR = ':';
use AbstractAdapterTrait;
use ContractsTrait;
use AbstractTrait;
private static $apcuSupported;
private static $phpFilesSupported;
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
private $createCacheItem;
private $mergeByLifetime;
/**
* @param string $namespace
* @param int $defaultLifetime
*/
protected function __construct($namespace = '', $defaultLifetime = 0)
{
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR;
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace));
}
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) use ($defaultLifetime) {
static function ($key, $value, $isHit) {
$item = new CacheItem();
$item->key = $key;
$item->value = $v = $value;
$item->value = $value;
$item->isHit = $isHit;
$item->defaultLifetime = $defaultLifetime;
// Detect wrapped values that encode for their expiry and creation duration
// For compactness, these values are packed in the key of an array using
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
$item->value = $v[$k];
$v = unpack('Ve/Nc', substr($k, 1, -1));
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
}
return $item;
},
null,
CacheItem::class
);
$getId = \Closure::fromCallable([$this, 'getId']);
$getId = function ($key) { return $this->getId((string) $key); };
$this->mergeByLifetime = \Closure::bind(
static function ($deferred, $namespace, &$expiredIds) use ($getId) {
static function ($deferred, $namespace, &$expiredIds) use ($getId, $defaultLifetime) {
$byLifetime = [];
$now = microtime(true);
$now = time();
$expiredIds = [];
foreach ($deferred as $key => $item) {
$key = (string) $key;
if (null === $item->expiry) {
$ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
} elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
$byLifetime[0 < $defaultLifetime ? $defaultLifetime : 0][$getId($key)] = $item->value;
} elseif (0 === $item->expiry) {
$byLifetime[0][$getId($key)] = $item->value;
} elseif ($item->expiry > $now) {
$byLifetime[$item->expiry - $now][$getId($key)] = $item->value;
} else {
$expiredIds[] = $getId($key);
continue;
}
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
unset($metadata[CacheItem::METADATA_TAGS]);
}
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
$byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value;
}
return $byLifetime;
@@ -95,10 +86,6 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
}
/**
* Returns the best possible adapter that your runtime supports.
*
* Using ApcuAdapter makes system caches compatible with read-only filesystems.
*
* @param string $namespace
* @param int $defaultLifetime
* @param string $version
@@ -108,38 +95,127 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
*/
public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null)
{
$opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true);
if (null !== $logger) {
$opcache->setLogger($logger);
if (null === self::$apcuSupported) {
self::$apcuSupported = ApcuAdapter::isSupported();
}
if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) {
if (!self::$apcuSupported && null === self::$phpFilesSupported) {
self::$phpFilesSupported = PhpFilesAdapter::isSupported();
}
if (self::$phpFilesSupported) {
$opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory);
if (null !== $logger) {
$opcache->setLogger($logger);
}
return $opcache;
}
$fs = new FilesystemAdapter($namespace, $defaultLifetime, $directory);
if (null !== $logger) {
$fs->setLogger($logger);
}
if (!self::$apcuSupported || (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) {
return $fs;
}
$apcu = new ApcuAdapter($namespace, (int) $defaultLifetime / 5, $version);
if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
$apcu->setLogger(new NullLogger());
} elseif (null !== $logger) {
if (null !== $logger) {
$apcu->setLogger($logger);
}
return new ChainAdapter([$apcu, $opcache]);
return new ChainAdapter([$apcu, $fs]);
}
public static function createConnection($dsn, array $options = [])
{
if (!\is_string($dsn)) {
throw new InvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.', __METHOD__, \gettype($dsn)));
throw new InvalidArgumentException(sprintf('The "%s()" method expect argument #1 to be string, "%s" given.', __METHOD__, \gettype($dsn)));
}
if (0 === strpos($dsn, 'redis:') || 0 === strpos($dsn, 'rediss:')) {
if (0 === strpos($dsn, 'redis://')) {
return RedisAdapter::createConnection($dsn, $options);
}
if (0 === strpos($dsn, 'memcached:')) {
if (0 === strpos($dsn, 'memcached://')) {
return MemcachedAdapter::createConnection($dsn, $options);
}
throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn));
throw new InvalidArgumentException(sprintf('Unsupported DSN: "%s".', $dsn));
}
/**
* {@inheritdoc}
*/
public function getItem($key)
{
if ($this->deferred) {
$this->commit();
}
$id = $this->getId($key);
$f = $this->createCacheItem;
$isHit = false;
$value = null;
try {
foreach ($this->doFetch([$id]) as $value) {
$isHit = true;
}
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]);
}
return $f($key, $value, $isHit);
}
/**
* {@inheritdoc}
*/
public function getItems(array $keys = [])
{
if ($this->deferred) {
$this->commit();
}
$ids = [];
foreach ($keys as $key) {
$ids[] = $this->getId($key);
}
try {
$items = $this->doFetch($ids);
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => $keys, 'exception' => $e]);
$items = [];
}
$ids = array_combine($ids, $keys);
return $this->generateItems($items, $ids);
}
/**
* {@inheritdoc}
*/
public function save(CacheItemInterface $item)
{
if (!$item instanceof CacheItem) {
return false;
}
$this->deferred[$item->getKey()] = $item;
return $this->commit();
}
/**
* {@inheritdoc}
*/
public function saveDeferred(CacheItemInterface $item)
{
if (!$item instanceof CacheItem) {
return false;
}
$this->deferred[$item->getKey()] = $item;
return true;
}
/**
@@ -168,8 +244,7 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
$ok = false;
$v = $values[$id];
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
}
} else {
foreach ($values as $id => $v) {
@@ -191,11 +266,49 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
}
$ok = false;
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
}
}
return $ok;
}
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
if ($this->deferred) {
$this->commit();
}
}
private function generateItems($items, &$keys)
{
$f = $this->createCacheItem;
try {
foreach ($items as $id => $value) {
if (!isset($keys[$id])) {
$id = key($keys);
}
$key = $keys[$id];
unset($keys[$id]);
yield $key => $f($key, $value, true);
}
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => array_values($keys), 'exception' => $e]);
}
foreach ($keys as $key) {
yield $key => $f($key, null, false);
}
}
}
-309
View File
@@ -1,309 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Psr\Log\LoggerAwareInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
/**
* Abstract for native TagAware adapters.
*
* To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids
* to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate().
*
* @author Nicolas Grekas <p@tchwork.com>
* @author André Rømcke <andre.romcke+symfony@gmail.com>
*
* @internal
* @experimental in 4.3
*/
abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface
{
use AbstractAdapterTrait;
use ContractsTrait;
private const TAGS_PREFIX = "\0tags\0";
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
{
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
}
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) use ($defaultLifetime) {
$item = new CacheItem();
$item->key = $key;
$item->defaultLifetime = $defaultLifetime;
$item->isTaggable = true;
// If structure does not match what we expect return item as is (no value and not a hit)
if (!\is_array($value) || !\array_key_exists('value', $value)) {
return $item;
}
$item->isHit = $isHit;
// Extract value, tags and meta data from the cache value
$item->value = $value['value'];
$item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? [];
if (isset($value['meta'])) {
// For compactness these values are packed, & expiry is offset to reduce size
$v = unpack('Ve/Nc', $value['meta']);
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
}
return $item;
},
null,
CacheItem::class
);
$getId = \Closure::fromCallable([$this, 'getId']);
$tagPrefix = self::TAGS_PREFIX;
$this->mergeByLifetime = \Closure::bind(
static function ($deferred, &$expiredIds) use ($getId, $tagPrefix) {
$byLifetime = [];
$now = microtime(true);
$expiredIds = [];
foreach ($deferred as $key => $item) {
$key = (string) $key;
if (null === $item->expiry) {
$ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
} elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
$expiredIds[] = $getId($key);
continue;
}
// Store Value and Tags on the cache value
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
$value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]];
unset($metadata[CacheItem::METADATA_TAGS]);
} else {
$value = ['value' => $item->value, 'tags' => []];
}
if ($metadata) {
// For compactness, expiry and creation duration are packed, using magic numbers as separators
$value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]);
}
// Extract tag changes, these should be removed from values in doSave()
$value['tag-operations'] = ['add' => [], 'remove' => []];
$oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? [];
foreach (array_diff($value['tags'], $oldTags) as $addedTag) {
$value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag);
}
foreach (array_diff($oldTags, $value['tags']) as $removedTag) {
$value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag);
}
$byLifetime[$ttl][$getId($key)] = $value;
}
return $byLifetime;
},
null,
CacheItem::class
);
}
/**
* Persists several cache items immediately.
*
* @param array $values The values to cache, indexed by their cache identifier
* @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
* @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag
* @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag
*
* @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not
*/
abstract protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array;
/**
* Removes multiple items from the pool and their corresponding tags.
*
* @param array $ids An array of identifiers that should be removed from the pool
* @param array $tagData Optional array of tag identifiers => key identifiers that should be removed from the pool
*
* @return bool True if the items were successfully removed, false otherwise
*/
abstract protected function doDelete(array $ids, array $tagData = []): bool;
/**
* Invalidates cached items using tags.
*
* @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id
*
* @return bool True on success
*/
abstract protected function doInvalidate(array $tagIds): bool;
/**
* {@inheritdoc}
*/
public function commit()
{
$ok = true;
$byLifetime = $this->mergeByLifetime;
$byLifetime = $byLifetime($this->deferred, $expiredIds);
$retry = $this->deferred = [];
if ($expiredIds) {
// Tags are not cleaned up in this case, however that is done on invalidateTags().
$this->doDelete($expiredIds);
}
foreach ($byLifetime as $lifetime => $values) {
try {
$values = $this->extractTagData($values, $addTagData, $removeTagData);
$e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
} catch (\Exception $e) {
}
if (true === $e || [] === $e) {
continue;
}
if (\is_array($e) || 1 === \count($values)) {
foreach (\is_array($e) ? $e : array_keys($values) as $id) {
$ok = false;
$v = $values[$id];
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
}
} else {
foreach ($values as $id => $v) {
$retry[$lifetime][] = $id;
}
}
}
// When bulk-save failed, retry each item individually
foreach ($retry as $lifetime => $ids) {
foreach ($ids as $id) {
try {
$v = $byLifetime[$lifetime][$id];
$values = $this->extractTagData([$id => $v], $addTagData, $removeTagData);
$e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
} catch (\Exception $e) {
}
if (true === $e || [] === $e) {
continue;
}
$ok = false;
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
}
}
return $ok;
}
/**
* {@inheritdoc}
*
* Overloaded in order to deal with tags for adjusted doDelete() signature.
*/
public function deleteItems(array $keys)
{
if (!$keys) {
return true;
}
$ids = [];
$tagData = [];
foreach ($keys as $key) {
$ids[$key] = $this->getId($key);
unset($this->deferred[$key]);
}
try {
foreach ($this->doFetch($ids) as $id => $value) {
foreach ($value['tags'] ?? [] as $tag) {
$tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id;
}
}
} catch (\Exception $e) {
// ignore unserialization failures
}
try {
if ($this->doDelete(array_values($ids), $tagData)) {
return true;
}
} catch (\Exception $e) {
}
$ok = true;
// When bulk-delete failed, retry each item individually
foreach ($ids as $key => $id) {
try {
$e = null;
if ($this->doDelete([$id])) {
continue;
}
} catch (\Exception $e) {
}
$message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
$ok = false;
}
return $ok;
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags)
{
if (empty($tags)) {
return false;
}
$tagIds = [];
foreach (array_unique($tags) as $tag) {
$tagIds[] = $this->getId(self::TAGS_PREFIX.$tag);
}
if ($this->doInvalidate($tagIds)) {
return true;
}
return false;
}
/**
* Extracts tags operation data from $values set in mergeByLifetime, and returns values without it.
*/
private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array
{
$addTagData = $removeTagData = [];
foreach ($values as $id => $value) {
foreach ($value['tag-operations']['add'] as $tag => $tagId) {
$addTagData[$tagId][] = $id;
}
foreach ($value['tag-operations']['remove'] as $tag => $tagId) {
$removeTagData[$tagId][] = $id;
}
unset($values[$id]['tag-operations']);
}
return $values;
}
}
+5 -1
View File
@@ -18,9 +18,13 @@ class ApcuAdapter extends AbstractAdapter
use ApcuTrait;
/**
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $version
*
* @throws CacheException if APCu is not enabled
*/
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
public function __construct($namespace = '', $defaultLifetime = 0, $version = null)
{
$this->init($namespace, $defaultLifetime, $version);
}
+40 -43
View File
@@ -16,30 +16,31 @@ use Psr\Log\LoggerAwareInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ArrayTrait;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface
{
use ArrayTrait;
private $createCacheItem;
private $defaultLifetime;
/**
* @param int $defaultLifetime
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
*/
public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true)
public function __construct($defaultLifetime = 0, $storeSerialized = true)
{
$this->defaultLifetime = $defaultLifetime;
$this->storeSerialized = $storeSerialized;
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) use ($defaultLifetime) {
static function ($key, $value, $isHit) {
$item = new CacheItem();
$item->key = $key;
$item->value = $value;
$item->isHit = $isHit;
$item->defaultLifetime = $defaultLifetime;
return $item;
},
@@ -48,32 +49,27 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
$item = $this->getItem($key);
$metadata = $item->getMetadata();
// ArrayAdapter works in memory, we don't care about stampede protection
if (INF === $beta || !$item->isHit()) {
$save = true;
$this->save($item->set($callback($item, $save)));
}
return $item->get();
}
/**
* {@inheritdoc}
*/
public function getItem($key)
{
if (!$isHit = $this->hasItem($key)) {
$isHit = $this->hasItem($key);
try {
if (!$isHit) {
$this->values[$key] = $value = null;
} elseif (!$this->storeSerialized) {
$value = $this->values[$key];
} elseif ('b:0;' === $value = $this->values[$key]) {
$value = false;
} elseif (false === $value = unserialize($value)) {
$this->values[$key] = $value = null;
$isHit = false;
}
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to unserialize key "{key}"', ['key' => $key, 'exception' => $e]);
$this->values[$key] = $value = null;
} else {
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
$isHit = false;
}
$f = $this->createCacheItem;
@@ -86,12 +82,10 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
public function getItems(array $keys = [])
{
foreach ($keys as $key) {
if (!\is_string($key) || !isset($this->expiries[$key])) {
CacheItem::validateKey($key);
}
CacheItem::validateKey($key);
}
return $this->generateItems($keys, microtime(true), $this->createCacheItem);
return $this->generateItems($keys, time(), $this->createCacheItem);
}
/**
@@ -119,20 +113,31 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
$value = $item["\0*\0value"];
$expiry = $item["\0*\0expiry"];
if (null !== $expiry && $expiry <= microtime(true)) {
if (0 === $expiry) {
$expiry = \PHP_INT_MAX;
}
if (null !== $expiry && $expiry <= time()) {
$this->deleteItem($key);
return true;
}
if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
return false;
if ($this->storeSerialized) {
try {
$value = serialize($value);
} catch (\Exception $e) {
$type = \is_object($value) ? \get_class($value) : \gettype($value);
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => $key, 'type' => $type, 'exception' => $e]);
return false;
}
}
if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) {
$expiry = microtime(true) + $item["\0*\0defaultLifetime"];
if (null === $expiry && 0 < $this->defaultLifetime) {
$expiry = time() + $this->defaultLifetime;
}
$this->values[$key] = $value;
$this->expiries[$key] = null !== $expiry ? $expiry : PHP_INT_MAX;
$this->expiries[$key] = null !== $expiry ? $expiry : \PHP_INT_MAX;
return true;
}
@@ -152,12 +157,4 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
{
return true;
}
/**
* {@inheritdoc}
*/
public function delete(string $key): bool
{
return $this->deleteItem($key);
}
}
+9 -48
View File
@@ -17,9 +17,6 @@ use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* Chains several adapters together.
@@ -29,10 +26,8 @@ use Symfony\Contracts\Service\ResetInterface;
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
use ContractsTrait;
private $adapters = [];
private $adapterCount;
private $syncItem;
@@ -41,7 +36,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
* @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items
* @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones
*/
public function __construct(array $adapters, int $defaultLifetime = 0)
public function __construct(array $adapters, $defaultLifetime = 0)
{
if (!$adapters) {
throw new InvalidArgumentException('At least one adapter must be specified.');
@@ -51,6 +46,9 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
if (!$adapter instanceof CacheItemPoolInterface) {
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($adapter), CacheItemPoolInterface::class));
}
if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $adapter instanceof ApcuAdapter && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
continue; // skip putting APCu in the chain when the backend is disabled
}
if ($adapter instanceof AdapterInterface) {
$this->adapters[] = $adapter;
@@ -61,21 +59,12 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
$this->adapterCount = \count($this->adapters);
$this->syncItem = \Closure::bind(
static function ($sourceItem, $item, $sourceMetadata = null) use ($defaultLifetime) {
$sourceItem->isTaggable = false;
$sourceMetadata = $sourceMetadata ?? $sourceItem->metadata;
unset($sourceMetadata[CacheItem::METADATA_TAGS]);
static function ($sourceItem, $item) use ($defaultLifetime) {
$item->value = $sourceItem->value;
$item->expiry = $sourceMetadata[CacheItem::METADATA_EXPIRY] ?? $sourceItem->expiry;
$item->isHit = $sourceItem->isHit;
$item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata;
if (0 < $sourceItem->defaultLifetime && $sourceItem->defaultLifetime < $defaultLifetime) {
$defaultLifetime = $sourceItem->defaultLifetime;
}
if (0 < $defaultLifetime && ($item->defaultLifetime <= 0 || $defaultLifetime < $item->defaultLifetime)) {
$item->defaultLifetime = $defaultLifetime;
if (0 < $defaultLifetime) {
$item->expiresAfter($defaultLifetime);
}
return $item;
@@ -85,34 +74,6 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
$lastItem = null;
$i = 0;
$wrap = function (CacheItem $item = null) use ($key, $callback, $beta, &$wrap, &$i, &$lastItem, &$metadata) {
$adapter = $this->adapters[$i];
if (isset($this->adapters[++$i])) {
$callback = $wrap;
$beta = INF === $beta ? INF : 0;
}
if ($adapter instanceof CacheInterface) {
$value = $adapter->get($key, $callback, $beta, $metadata);
} else {
$value = $this->doGet($adapter, $key, $callback, $beta, $metadata);
}
if (null !== $item) {
($this->syncItem)($lastItem = $lastItem ?? $item, $item, $metadata);
}
return $value;
};
return $wrap();
}
/**
* {@inheritdoc}
*/
@@ -303,7 +264,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
public function reset()
{
foreach ($this->adapters as $adapter) {
if ($adapter instanceof ResetInterface) {
if ($adapter instanceof ResettableInterface) {
$adapter->reset();
}
}
+5 -1
View File
@@ -18,7 +18,11 @@ class DoctrineAdapter extends AbstractAdapter
{
use DoctrineTrait;
public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
/**
* @param string $namespace
* @param int $defaultLifetime
*/
public function __construct(CacheProvider $provider, $namespace = '', $defaultLifetime = 0)
{
parent::__construct('', $defaultLifetime);
$this->provider = $provider;
+6 -4
View File
@@ -11,8 +11,6 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\FilesystemTrait;
@@ -20,9 +18,13 @@ class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
{
use FilesystemTrait;
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
/**
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $directory
*/
public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
{
$this->marshaller = $marshaller ?? new DefaultMarshaller();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
}
@@ -1,152 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\FilesystemTrait;
/**
* Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author André Rømcke <andre.romcke+symfony@gmail.com>
*
* @experimental in 4.3
*/
class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
{
use FilesystemTrait {
doSave as private doSaveCache;
doDelete as private doDeleteCache;
}
/**
* Folder used for tag symlinks.
*/
private const TAG_FOLDER = 'tags';
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
{
$this->marshaller = $marshaller ?? new DefaultMarshaller();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
}
/**
* {@inheritdoc}
*/
protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array
{
$failed = $this->doSaveCache($values, $lifetime);
// Add Tags as symlinks
foreach ($addTagData as $tagId => $ids) {
$tagFolder = $this->getTagFolder($tagId);
foreach ($ids as $id) {
if ($failed && \in_array($id, $failed, true)) {
continue;
}
$file = $this->getFile($id);
if (!@symlink($file, $this->getFile($id, true, $tagFolder))) {
@unlink($file);
$failed[] = $id;
}
}
}
// Unlink removed Tags
foreach ($removeTagData as $tagId => $ids) {
$tagFolder = $this->getTagFolder($tagId);
foreach ($ids as $id) {
if ($failed && \in_array($id, $failed, true)) {
continue;
}
@unlink($this->getFile($id, false, $tagFolder));
}
}
return $failed;
}
/**
* {@inheritdoc}
*/
protected function doDelete(array $ids, array $tagData = []): bool
{
$ok = $this->doDeleteCache($ids);
// Remove tags
foreach ($tagData as $tagId => $idMap) {
$tagFolder = $this->getTagFolder($tagId);
foreach ($idMap as $id) {
@unlink($this->getFile($id, false, $tagFolder));
}
}
return $ok;
}
/**
* {@inheritdoc}
*/
protected function doInvalidate(array $tagIds): bool
{
foreach ($tagIds as $tagId) {
if (!file_exists($tagFolder = $this->getTagFolder($tagId))) {
continue;
}
set_error_handler(static function () {});
try {
if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -1))) {
$tagFolder = $renamed.\DIRECTORY_SEPARATOR;
} else {
$renamed = null;
}
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($tagFolder, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $itemLink) {
unlink(realpath($itemLink) ?: $itemLink);
unlink($itemLink);
}
if (null === $renamed) {
continue;
}
$chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
for ($i = 0; $i < 38; ++$i) {
for ($j = 0; $j < 38; ++$j) {
rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]);
}
rmdir($tagFolder.$chars[$i]);
}
rmdir($renamed);
} finally {
restore_error_handler();
}
}
return true;
}
private function getTagFolder(string $tagId): string
{
return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
}
}
+2 -3
View File
@@ -11,7 +11,6 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\MemcachedTrait;
class MemcachedAdapter extends AbstractAdapter
@@ -30,8 +29,8 @@ class MemcachedAdapter extends AbstractAdapter
*
* Using a MemcachedAdapter as a pure items store is fine.
*/
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0)
{
$this->init($client, $namespace, $defaultLifetime, $marshaller);
$this->init($client, $namespace, $defaultLifetime);
}
}
+1 -20
View File
@@ -13,12 +13,11 @@ namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class NullAdapter implements AdapterInterface, CacheInterface
class NullAdapter implements AdapterInterface
{
private $createCacheItem;
@@ -37,16 +36,6 @@ class NullAdapter implements AdapterInterface, CacheInterface
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
$save = true;
return $callback(($this->createCacheItem)($key), $save);
}
/**
* {@inheritdoc}
*/
@@ -121,14 +110,6 @@ class NullAdapter implements AdapterInterface, CacheInterface
return false;
}
/**
* {@inheritdoc}
*/
public function delete(string $key): bool
{
return $this->deleteItem($key);
}
private function generateItems(array $keys)
{
$f = $this->createCacheItem;
+6 -7
View File
@@ -13,7 +13,6 @@ namespace Symfony\Component\Cache\Adapter;
use Doctrine\DBAL\Connection;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\PdoTrait;
@@ -28,9 +27,6 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface
* a Doctrine DBAL Connection or a DSN string that will be used to
* lazy-connect to the database when the cache is actually used.
*
* When a Doctrine DBAL Connection is passed, the cache table is created
* automatically when possible. Otherwise, use the createTable() method.
*
* List of available options:
* * db_table: The name of the table [default: cache_items]
* * db_id_col: The column where to store the cache id [default: item_id]
@@ -41,14 +37,17 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface
* * db_password: The password when lazy-connect [default: '']
* * db_connection_options: An array of driver-specific connection options [default: []]
*
* @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null
* @param \PDO|Connection|string $connOrDsn A \PDO or Connection instance or DSN string or null
* @param string $namespace
* @param int $defaultLifetime
* @param array $options An associative array of options
*
* @throws InvalidArgumentException When first argument is not PDO nor Connection nor string
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
* @throws InvalidArgumentException When namespace contains invalid characters
*/
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
public function __construct($connOrDsn, $namespace = '', $defaultLifetime = 0, array $options = [])
{
$this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
$this->init($connOrDsn, $namespace, $defaultLifetime, $options);
}
}
+49 -60
View File
@@ -17,9 +17,7 @@ use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Component\Cache\Traits\PhpArrayTrait;
use Symfony\Contracts\Cache\CacheInterface;
/**
* Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
@@ -28,10 +26,9 @@ use Symfony\Contracts\Cache\CacheInterface;
* @author Titouan Galopin <galopintitouan@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
class PhpArrayAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
use PhpArrayTrait;
use ContractsTrait;
private $createCacheItem;
@@ -39,10 +36,11 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
* @param string $file The PHP file were values are cached
* @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
*/
public function __construct(string $file, AdapterInterface $fallbackPool)
public function __construct($file, AdapterInterface $fallbackPool)
{
$this->file = $file;
$this->pool = $fallbackPool;
$this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) {
$item = new CacheItem();
@@ -58,7 +56,9 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
}
/**
* This adapter takes advantage of how PHP stores arrays in its latest versions.
* This adapter should only be used on PHP 7.0+ to take advantage of how PHP
* stores arrays in its latest versions. This factory method decorates the given
* fallback pool with this adapter only if the current PHP version is supported.
*
* @param string $file The PHP file were values are cached
* @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit
@@ -67,44 +67,15 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
*/
public static function create($file, CacheItemPoolInterface $fallbackPool)
{
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
return new static($file, $fallbackPool);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
if (null === $this->values) {
$this->initialize();
}
if (!isset($this->keys[$key])) {
get_from_pool:
if ($this->pool instanceof CacheInterface) {
return $this->pool->get($key, $callback, $beta, $metadata);
if (\PHP_VERSION_ID >= 70000) {
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
return $this->doGet($this->pool, $key, $callback, $beta, $metadata);
}
$value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
return null;
}
try {
if ($value instanceof \Closure) {
return $value();
}
} catch (\Throwable $e) {
unset($this->keys[$key]);
goto get_from_pool;
return new static($file, $fallbackPool);
}
return $value;
return $fallbackPool;
}
/**
@@ -118,19 +89,23 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
if (null === $this->values) {
$this->initialize();
}
if (!isset($this->keys[$key])) {
if (!isset($this->values[$key])) {
return $this->pool->getItem($key);
}
$value = $this->values[$this->keys[$key]];
$value = $this->values[$key];
$isHit = true;
if ('N;' === $value) {
$value = null;
} elseif ($value instanceof \Closure) {
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
$value = $value();
} catch (\Throwable $e) {
$e = null;
$value = unserialize($value);
} catch (\Error $e) {
} catch (\Exception $e) {
}
if (null !== $e) {
$value = null;
$isHit = false;
}
@@ -170,7 +145,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return isset($this->keys[$key]) || $this->pool->hasItem($key);
return isset($this->values[$key]) || $this->pool->hasItem($key);
}
/**
@@ -185,7 +160,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
return !isset($this->values[$key]) && $this->pool->deleteItem($key);
}
/**
@@ -201,7 +176,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if (isset($this->keys[$key])) {
if (isset($this->values[$key])) {
$deleted = false;
} else {
$fallbackKeys[] = $key;
@@ -227,7 +202,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
return !isset($this->values[$item->getKey()]) && $this->pool->save($item);
}
/**
@@ -239,7 +214,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
return !isset($this->values[$item->getKey()]) && $this->pool->saveDeferred($item);
}
/**
@@ -250,21 +225,26 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
return $this->pool->commit();
}
private function generateItems(array $keys): \Generator
/**
* @return \Generator
*/
private function generateItems(array $keys)
{
$f = $this->createCacheItem;
$fallbackKeys = [];
foreach ($keys as $key) {
if (isset($this->keys[$key])) {
$value = $this->values[$this->keys[$key]];
if (isset($this->values[$key])) {
$value = $this->values[$key];
if ('N;' === $value) {
yield $key => $f($key, null, true);
} elseif ($value instanceof \Closure) {
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
yield $key => $f($key, $value(), true);
} catch (\Throwable $e) {
yield $key => $f($key, unserialize($value), true);
} catch (\Error $e) {
yield $key => $f($key, null, false);
} catch (\Exception $e) {
yield $key => $f($key, null, false);
}
} else {
@@ -276,7 +256,9 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
}
if ($fallbackKeys) {
yield from $this->pool->getItems($fallbackKeys);
foreach ($this->pool->getItems($fallbackKeys) as $key => $item) {
yield $key => $item;
}
}
}
@@ -293,10 +275,17 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
'function' => 'spl_autoload_call',
'args' => [$class],
];
$i = 1 + array_search($autoloadFrame, $trace, true);
if (isset($trace[$i]['function']) && !isset($trace[$i]['class'])) {
switch ($trace[$i]['function']) {
if (\PHP_VERSION_ID >= 80000 && isset($trace[1])) {
$callerFrame = $trace[1];
} elseif (false !== $i = array_search($autoloadFrame, $trace, true)) {
$callerFrame = $trace[++$i];
} else {
throw $e;
}
if (isset($callerFrame['function']) && !isset($callerFrame['class'])) {
switch ($callerFrame['function']) {
case 'get_class_methods':
case 'get_class_vars':
case 'get_parent_class':
+11 -8
View File
@@ -20,19 +20,22 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
use PhpFilesTrait;
/**
* @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
* Doing so is encouraged because it fits perfectly OPcache's memory model.
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $directory
*
* @throws CacheException if OPcache is not enabled
*/
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
{
$this->appendOnly = $appendOnly;
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
if (!static::isSupported()) {
throw new CacheException('OPcache is not enabled.');
}
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
$this->includeHandler = static function ($type, $msg, $file, $line) {
throw new \ErrorException($msg, 0, $type, $file, $line);
};
$e = new \Exception();
$this->includeHandler = function () use ($e) { throw $e; };
$this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
}
}
+19 -67
View File
@@ -16,100 +16,50 @@ use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Component\Cache\Traits\ProxyTrait;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
use ProxyTrait;
use ContractsTrait;
private $namespace;
private $namespaceLen;
private $createCacheItem;
private $setInnerItem;
private $poolHash;
private $defaultLifetime;
public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0)
/**
* @param string $namespace
* @param int $defaultLifetime
*/
public function __construct(CacheItemPoolInterface $pool, $namespace = '', $defaultLifetime = 0)
{
$this->pool = $pool;
$this->poolHash = $poolHash = spl_object_hash($pool);
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace);
$this->namespaceLen = \strlen($namespace);
$this->defaultLifetime = $defaultLifetime;
$this->createCacheItem = \Closure::bind(
static function ($key, $innerItem) use ($defaultLifetime, $poolHash) {
static function ($key, $innerItem) use ($poolHash) {
$item = new CacheItem();
$item->key = $key;
if (null === $innerItem) {
return $item;
}
$item->value = $v = $innerItem->get();
$item->isHit = $innerItem->isHit();
$item->innerItem = $innerItem;
$item->defaultLifetime = $defaultLifetime;
$item->poolHash = $poolHash;
// Detect wrapped values that encode for their expiry and creation duration
// For compactness, these values are packed in the key of an array using
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
$item->value = $v[$k];
$v = unpack('Ve/Nc', substr($k, 1, -1));
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
} elseif ($innerItem instanceof CacheItem) {
$item->metadata = $innerItem->metadata;
if (null !== $innerItem) {
$item->value = $innerItem->get();
$item->isHit = $innerItem->isHit();
$item->innerItem = $innerItem;
$innerItem->set(null);
}
$innerItem->set(null);
return $item;
},
null,
CacheItem::class
);
$this->setInnerItem = \Closure::bind(
/**
* @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix
*/
static function (CacheItemInterface $innerItem, array $item) {
// Tags are stored separately, no need to account for them when considering this item's newly set metadata
if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) {
unset($metadata[CacheItem::METADATA_TAGS]);
}
if ($metadata) {
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
$item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]];
}
$innerItem->set($item["\0*\0value"]);
$innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6f', $item["\0*\0expiry"])) : null);
},
null,
CacheItem::class
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
if (!$this->pool instanceof CacheInterface) {
return $this->doGet($this, $key, $callback, $beta, $metadata);
}
return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) {
$item = ($this->createCacheItem)($key, $innerItem);
$item->set($value = $callback($item, $save));
($this->setInnerItem)($innerItem, (array) $item);
return $value;
}, $beta, $metadata);
}
/**
@@ -205,8 +155,9 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
return false;
}
$item = (array) $item;
if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) {
$item["\0*\0expiry"] = microtime(true) + $item["\0*\0defaultLifetime"];
$expiry = $item["\0*\0expiry"];
if (null === $expiry && 0 < $this->defaultLifetime) {
$expiry = time() + $this->defaultLifetime;
}
if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
@@ -220,7 +171,8 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
$innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
}
($this->setInnerItem)($innerItem, $item);
$innerItem->set($item["\0*\0value"]);
$innerItem->expiresAt(null !== $expiry ? \DateTime::createFromFormat('U', $expiry) : null);
return $this->pool->$method($innerItem);
}
-86
View File
@@ -1,86 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* Turns a PSR-16 cache into a PSR-6 one.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
{
/**
* @internal
*/
protected const NS_SEPARATOR = '_';
use ProxyTrait;
private $miss;
public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
{
parent::__construct($namespace, $defaultLifetime);
$this->pool = $pool;
$this->miss = new \stdClass();
}
/**
* {@inheritdoc}
*/
protected function doFetch(array $ids)
{
foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
if ($this->miss !== $value) {
yield $key => $value;
}
}
}
/**
* {@inheritdoc}
*/
protected function doHave($id)
{
return $this->pool->has($id);
}
/**
* {@inheritdoc}
*/
protected function doClear($namespace)
{
return $this->pool->clear();
}
/**
* {@inheritdoc}
*/
protected function doDelete(array $ids)
{
return $this->pool->deleteMultiple($ids);
}
/**
* {@inheritdoc}
*/
protected function doSave(array $values, $lifetime)
{
return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
}
}
+5 -6
View File
@@ -11,7 +11,6 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\RedisTrait;
class RedisAdapter extends AbstractAdapter
@@ -19,12 +18,12 @@ class RedisAdapter extends AbstractAdapter
use RedisTrait;
/**
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client
* @param string $namespace The default namespace
* @param int $defaultLifetime The default lifetime
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient The redis client
* @param string $namespace The default namespace
* @param int $defaultLifetime The default lifetime
*/
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
public function __construct($redisClient, $namespace = '', $defaultLifetime = 0)
{
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
$this->init($redisClient, $namespace, $defaultLifetime);
}
}
-212
View File
@@ -1,212 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Adapter;
use Predis;
use Predis\Connection\Aggregate\ClusterInterface;
use Predis\Response\Status;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\LogicException;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\RedisTrait;
/**
* Stores tag id <> cache id relationship as a Redis Set, lookup on invalidation using sPOP.
*
* Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even
* if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache
* relationship survives eviction (cache cleanup when Redis runs out of memory).
*
* Requirements:
* - Server: Redis 3.2+
* - Client: PHP Redis 3.1.3+ OR Predis
* - Redis Server(s) configured with any `volatile-*` eviction policy, OR `noeviction` if it will NEVER fill up memory
*
* Design limitations:
* - Max 2 billion cache keys per cache tag
* E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 2 billion cache items as well
*
* @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies.
* @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype.
* @see https://redis.io/commands/spop Documentation for sPOP operation, capable of retriving AND emptying a Set at once.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author André Rømcke <andre.romcke+symfony@gmail.com>
*
* @experimental in 4.3
*/
class RedisTagAwareAdapter extends AbstractTagAwareAdapter
{
use RedisTrait;
/**
* Redis "Set" can hold more than 4 billion members, here we limit ourselves to PHP's > 2 billion max int (32Bit).
*/
private const POP_MAX_LIMIT = 2147483647 - 1;
/**
* Limits for how many keys are deleted in batch.
*/
private const BULK_DELETE_LIMIT = 10000;
/**
* On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are
* preferred to be evicted over tag Sets, if eviction policy is configured according to requirements.
*/
private const DEFAULT_CACHE_TTL = 8640000;
/**
* @var bool|null
*/
private $redisServerSupportSPOP = null;
/**
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client
* @param string $namespace The default namespace
* @param int $defaultLifetime The default lifetime
*
* @throws \Symfony\Component\Cache\Exception\LogicException If phpredis with version lower than 3.1.3.
*/
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
// Make sure php-redis is 3.1.3 or higher configured for Redis classes
if (!$this->redis instanceof \Predis\ClientInterface && version_compare(phpversion('redis'), '3.1.3', '<')) {
throw new LogicException('RedisTagAwareAdapter requires php-redis 3.1.3 or higher, alternatively use predis/predis');
}
}
/**
* {@inheritdoc}
*/
protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $delTagData = []): array
{
// serialize values
if (!$serialized = $this->marshaller->marshall($values, $failed)) {
return $failed;
}
// While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op
$results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData, $failed) {
// Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one
foreach ($serialized as $id => $value) {
yield 'setEx' => [
$id,
0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime,
$value,
];
}
// Add and Remove Tags
foreach ($addTagData as $tagId => $ids) {
if (!$failed || $ids = array_diff($ids, $failed)) {
yield 'sAdd' => array_merge([$tagId], $ids);
}
}
foreach ($delTagData as $tagId => $ids) {
if (!$failed || $ids = array_diff($ids, $failed)) {
yield 'sRem' => array_merge([$tagId], $ids);
}
}
});
foreach ($results as $id => $result) {
// Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not
if (is_numeric($result)) {
continue;
}
// setEx results
if (true !== $result && (!$result instanceof Status || $result !== Status::get('OK'))) {
$failed[] = $id;
}
}
return $failed;
}
/**
* {@inheritdoc}
*/
protected function doDelete(array $ids, array $tagData = []): bool
{
if (!$ids) {
return true;
}
$predisCluster = $this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface;
$this->pipeline(static function () use ($ids, $tagData, $predisCluster) {
if ($predisCluster) {
foreach ($ids as $id) {
yield 'del' => [$id];
}
} else {
yield 'del' => $ids;
}
foreach ($tagData as $tagId => $idList) {
yield 'sRem' => array_merge([$tagId], $idList);
}
})->rewind();
return true;
}
/**
* {@inheritdoc}
*/
protected function doInvalidate(array $tagIds): bool
{
if (!$this->redisServerSupportSPOP()) {
return false;
}
// Pop all tag info at once to avoid race conditions
$tagIdSets = $this->pipeline(static function () use ($tagIds) {
foreach ($tagIds as $tagId) {
// Client: Predis or PHP Redis 3.1.3+ (https://github.com/phpredis/phpredis/commit/d2e203a6)
// Server: Redis 3.2 or higher (https://redis.io/commands/spop)
yield 'sPop' => [$tagId, self::POP_MAX_LIMIT];
}
});
// Flatten generator result from pipeline, ignore keys (tag ids)
$ids = array_unique(array_merge(...iterator_to_array($tagIdSets, false)));
// Delete cache in chunks to avoid overloading the connection
foreach (array_chunk($ids, self::BULK_DELETE_LIMIT) as $chunkIds) {
$this->doDelete($chunkIds);
}
return true;
}
private function redisServerSupportSPOP(): bool
{
if (null !== $this->redisServerSupportSPOP) {
return $this->redisServerSupportSPOP;
}
foreach ($this->getHosts() as $host) {
$info = $host->info('Server');
$info = isset($info['Server']) ? $info['Server'] : $info;
if (version_compare($info['redis_version'], '3.2', '<')) {
CacheItem::log($this->logger, 'Redis server needs to be version 3.2 or higher, your Redis server was detected as '.$info['redis_version']);
return $this->redisServerSupportSPOP = false;
}
}
return $this->redisServerSupportSPOP = true;
}
}
+65 -3
View File
@@ -11,11 +11,73 @@
namespace Symfony\Component\Cache\Adapter;
@trigger_error(sprintf('The "%s" class is @deprecated since Symfony 4.3, use "Psr16Adapter" instead.', SimpleCacheAdapter::class), E_USER_DEPRECATED);
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* @deprecated since Symfony 4.3, use Psr16Adapter instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
class SimpleCacheAdapter extends Psr16Adapter
class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface
{
/**
* @internal
*/
const NS_SEPARATOR = '_';
use ProxyTrait;
private $miss;
public function __construct(CacheInterface $pool, $namespace = '', $defaultLifetime = 0)
{
parent::__construct($namespace, $defaultLifetime);
$this->pool = $pool;
$this->miss = new \stdClass();
}
/**
* {@inheritdoc}
*/
protected function doFetch(array $ids)
{
foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
if ($this->miss !== $value) {
yield $key => $value;
}
}
}
/**
* {@inheritdoc}
*/
protected function doHave($id)
{
return $this->pool->has($id);
}
/**
* {@inheritdoc}
*/
protected function doClear($namespace)
{
return $this->pool->clear();
}
/**
* {@inheritdoc}
*/
protected function doDelete(array $ids)
{
return $this->pool->deleteMultiple($ids);
}
/**
* {@inheritdoc}
*/
protected function doSave(array $values, $lifetime)
{
return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
}
}
+5 -11
View File
@@ -16,19 +16,16 @@ use Psr\Cache\InvalidArgumentException;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Component\Cache\Traits\ProxyTrait;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface
class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, ResettableInterface
{
const TAGS_PREFIX = "\0tags\0";
use ProxyTrait;
use ContractsTrait;
private $deferred = [];
private $createCacheItem;
@@ -39,7 +36,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
private $knownTagVersions = [];
private $knownTagVersionsTtl;
public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15)
public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, $knownTagVersionsTtl = 0.15)
{
$this->pool = $itemsPool;
$this->tags = $tagsPool ?: $itemsPool;
@@ -49,7 +46,6 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
$item = new CacheItem();
$item->key = $key;
$item->value = $value;
$item->defaultLifetime = $protoItem->defaultLifetime;
$item->expiry = $protoItem->expiry;
$item->poolHash = $protoItem->poolHash;
@@ -60,13 +56,12 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
);
$this->setCacheItemTags = \Closure::bind(
static function (CacheItem $item, $key, array &$itemTags) {
$item->isTaggable = true;
if (!$item->isHit) {
return $item;
}
if (isset($itemTags[$key])) {
foreach ($itemTags[$key] as $tag => $version) {
$item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag;
$item->prevTags[$tag] = $tag;
}
unset($itemTags[$key]);
} else {
@@ -83,7 +78,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
static function ($deferred) {
$tagsByKey = [];
foreach ($deferred as $key => $item) {
$tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? [];
$tagsByKey[$key] = $item->tags;
}
return $tagsByKey;
@@ -94,8 +89,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
$this->invalidateTags = \Closure::bind(
static function (AdapterInterface $tagsAdapter, array $tags) {
foreach ($tags as $v) {
$v->defaultLifetime = 0;
$v->expiry = null;
$v->expiry = 0;
$tagsAdapter->saveDeferred($v);
}
+3 -55
View File
@@ -12,11 +12,8 @@
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* An adapter that collects data about all cache calls.
@@ -25,7 +22,7 @@ use Symfony\Contracts\Service\ResetInterface;
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
class TraceableAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
protected $pool;
private $calls = [];
@@ -35,38 +32,6 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt
$this->pool = $pool;
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
if (!$this->pool instanceof CacheInterface) {
throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', \get_class($this->pool), CacheInterface::class));
}
$isHit = true;
$callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) {
$isHit = $item->isHit();
return $callback($item, $save);
};
$event = $this->start(__FUNCTION__);
try {
$value = $this->pool->get($key, $callback, $beta, $metadata);
$event->result[$key] = \is_object($value) ? \get_class($value) : \gettype($value);
} finally {
$event->end = microtime(true);
}
if ($isHit) {
++$event->hits;
} else {
++$event->misses;
}
return $value;
}
/**
* {@inheritdoc}
*/
@@ -226,28 +191,11 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt
*/
public function reset()
{
if (!$this->pool instanceof ResetInterface) {
return;
}
$event = $this->start(__FUNCTION__);
try {
if ($this->pool instanceof ResettableInterface) {
$this->pool->reset();
} finally {
$event->end = microtime(true);
}
}
/**
* {@inheritdoc}
*/
public function delete(string $key): bool
{
$event = $this->start(__FUNCTION__);
try {
return $event->result[$key] = $this->pool->deleteItem($key);
} finally {
$event->end = microtime(true);
}
$this->clearCalls();
}
public function getCalls()
+1 -3
View File
@@ -11,12 +11,10 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface
class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface
{
public function __construct(TagAwareAdapterInterface $pool)
{
+1 -26
View File
@@ -1,31 +1,6 @@
CHANGELOG
=========
4.3.0
-----
* removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it
* deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead
* deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead
4.2.0
-----
* added support for connecting to Redis clusters via DSN
* added support for configuring multiple Memcached servers via DSN
* added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available
* implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
* added sub-second expiry accuracy for backends that support it
* added support for phpredis 4 `compression` and `tcp_keepalive` options
* added automatic table creation when using Doctrine DBAL with PDO-based backends
* throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
* deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
* deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods
* added `CacheCollectorPass` (originally in `FrameworkBundle`)
* added `CachePoolClearerPass` (originally in `FrameworkBundle`)
* added `CachePoolPass` (originally in `FrameworkBundle`)
* added `CachePoolPrunerPass` (originally in `FrameworkBundle`)
3.4.0
-----
@@ -38,7 +13,7 @@ CHANGELOG
3.3.0
-----
* added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
* [EXPERIMENTAL] added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
* added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters
* added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16
* added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16)
+30 -44
View File
@@ -11,28 +11,23 @@
namespace Symfony\Component\Cache;
use Psr\Cache\CacheItemInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Exception\LogicException;
use Symfony\Contracts\Cache\ItemInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
final class CacheItem implements ItemInterface
final class CacheItem implements CacheItemInterface
{
private const METADATA_EXPIRY_OFFSET = 1527506807;
protected $key;
protected $value;
protected $isHit = false;
protected $expiry;
protected $defaultLifetime;
protected $metadata = [];
protected $newMetadata = [];
protected $tags = [];
protected $prevTags = [];
protected $innerItem;
protected $poolHash;
protected $isTaggable = false;
/**
* {@inheritdoc}
@@ -78,11 +73,11 @@ final class CacheItem implements ItemInterface
public function expiresAt($expiration)
{
if (null === $expiration) {
$this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null;
$this->expiry = null;
} elseif ($expiration instanceof \DateTimeInterface) {
$this->expiry = (float) $expiration->format('U.u');
$this->expiry = (int) $expiration->format('U');
} else {
throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given', \is_object($expiration) ? \get_class($expiration) : \gettype($expiration)));
throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', \is_object($expiration) ? \get_class($expiration) : \gettype($expiration)));
}
return $this;
@@ -96,68 +91,59 @@ final class CacheItem implements ItemInterface
public function expiresAfter($time)
{
if (null === $time) {
$this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null;
$this->expiry = null;
} elseif ($time instanceof \DateInterval) {
$this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u');
$this->expiry = (int) \DateTime::createFromFormat('U', time())->add($time)->format('U');
} elseif (\is_int($time)) {
$this->expiry = $time + microtime(true);
$this->expiry = $time + time();
} else {
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($time) ? \get_class($time) : \gettype($time)));
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($time) ? \get_class($time) : \gettype($time)));
}
return $this;
}
/**
* {@inheritdoc}
* Adds a tag to a cache item.
*
* @param string|string[] $tags A tag or array of tags
*
* @return $this
*
* @throws InvalidArgumentException When $tag is not valid
*/
public function tag($tags): ItemInterface
public function tag($tags)
{
if (!$this->isTaggable) {
throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key));
}
if (!is_iterable($tags)) {
if (!\is_array($tags)) {
$tags = [$tags];
}
foreach ($tags as $tag) {
if (!\is_string($tag)) {
throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given', \is_object($tag) ? \get_class($tag) : \gettype($tag)));
throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag)));
}
if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) {
if (isset($this->tags[$tag])) {
continue;
}
if ('' === $tag) {
throw new InvalidArgumentException('Cache tag length must be greater than zero');
throw new InvalidArgumentException('Cache tag length must be greater than zero.');
}
if (false !== strpbrk($tag, '{}()/\@:')) {
throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:', $tag));
throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:.', $tag));
}
$this->newMetadata[self::METADATA_TAGS][$tag] = $tag;
$this->tags[$tag] = $tag;
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getMetadata(): array
{
return $this->metadata;
}
/**
* Returns the list of tags bound to the value coming from the pool storage if any.
*
* @return array
*
* @deprecated since Symfony 4.2, use the "getMetadata()" method instead.
*/
public function getPreviousTags()
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "getMetadata()" method instead.', __METHOD__), E_USER_DEPRECATED);
return $this->metadata[self::METADATA_TAGS] ?? [];
return $this->prevTags;
}
/**
@@ -172,13 +158,13 @@ final class CacheItem implements ItemInterface
public static function validateKey($key)
{
if (!\is_string($key)) {
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given', \is_object($key) ? \get_class($key) : \gettype($key)));
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if ('' === $key) {
throw new InvalidArgumentException('Cache key length must be greater than zero');
throw new InvalidArgumentException('Cache key length must be greater than zero.');
}
if (false !== strpbrk($key, '{}()/\@:')) {
throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters {}()/\@:', $key));
throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters {}()/\@:.', $key));
}
return $key;
@@ -200,7 +186,7 @@ final class CacheItem implements ItemInterface
$replace['{'.$k.'}'] = $v;
}
}
@trigger_error(strtr($message, $replace), E_USER_WARNING);
@trigger_error(strtr($message, $replace), \E_USER_WARNING);
}
}
}
+9 -11
View File
@@ -103,7 +103,10 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
return $this->data['instances']['calls'];
}
private function calculateStatistics(): array
/**
* @return array
*/
private function calculateStatistics()
{
$statistics = [];
foreach ($this->data['instances']['calls'] as $name => $calls) {
@@ -120,15 +123,7 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
foreach ($calls as $call) {
++$statistics[$name]['calls'];
$statistics[$name]['time'] += $call->end - $call->start;
if ('get' === $call->name) {
++$statistics[$name]['reads'];
if ($call->hits) {
++$statistics[$name]['hits'];
} else {
++$statistics[$name]['misses'];
++$statistics[$name]['writes'];
}
} elseif ('getItem' === $call->name) {
if ('getItem' === $call->name) {
++$statistics[$name]['reads'];
if ($call->hits) {
++$statistics[$name]['hits'];
@@ -162,7 +157,10 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
return $statistics;
}
private function calculateTotalStatistics(): array
/**
* @return array
*/
private function calculateTotalStatistics()
{
$statistics = $this->getStatistics();
$totals = [
@@ -1,72 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\DependencyInjection;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
use Symfony\Component\Cache\Adapter\TraceableAdapter;
use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Inject a data collector to all the cache services to be able to get detailed statistics.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class CacheCollectorPass implements CompilerPassInterface
{
private $dataCollectorCacheId;
private $cachePoolTag;
private $cachePoolRecorderInnerSuffix;
public function __construct(string $dataCollectorCacheId = 'data_collector.cache', string $cachePoolTag = 'cache.pool', string $cachePoolRecorderInnerSuffix = '.recorder_inner')
{
$this->dataCollectorCacheId = $dataCollectorCacheId;
$this->cachePoolTag = $cachePoolTag;
$this->cachePoolRecorderInnerSuffix = $cachePoolRecorderInnerSuffix;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->dataCollectorCacheId)) {
return;
}
$collectorDefinition = $container->getDefinition($this->dataCollectorCacheId);
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) {
$definition = $container->getDefinition($id);
if ($definition->isAbstract()) {
continue;
}
$recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class);
$recorder->setTags($definition->getTags());
$recorder->setPublic($definition->isPublic());
$recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]);
$definition->setTags([]);
$definition->setPublic(false);
$container->setDefinition($innerId, $definition);
$container->setDefinition($id, $recorder);
// Tell the collector to add the new instance
$collectorDefinition->addMethodCall('addInstance', [$id, new Reference($id)]);
$collectorDefinition->setPublic(false);
}
}
}
@@ -1,48 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class CachePoolClearerPass implements CompilerPassInterface
{
private $cachePoolClearerTag;
public function __construct(string $cachePoolClearerTag = 'cache.pool.clearer')
{
$this->cachePoolClearerTag = $cachePoolClearerTag;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$container->getParameterBag()->remove('cache.prefix.seed');
foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) as $id => $attr) {
$clearer = $container->getDefinition($id);
$pools = [];
foreach ($clearer->getArgument(0) as $name => $ref) {
if ($container->hasDefinition($ref)) {
$pools[$name] = new Reference($ref);
}
}
$clearer->replaceArgument(0, $pools);
}
}
}
@@ -1,178 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\DependencyInjection;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class CachePoolPass implements CompilerPassInterface
{
private $cachePoolTag;
private $kernelResetTag;
private $cacheClearerId;
private $cachePoolClearerTag;
private $cacheSystemClearerId;
private $cacheSystemClearerTag;
public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer')
{
$this->cachePoolTag = $cachePoolTag;
$this->kernelResetTag = $kernelResetTag;
$this->cacheClearerId = $cacheClearerId;
$this->cachePoolClearerTag = $cachePoolClearerTag;
$this->cacheSystemClearerId = $cacheSystemClearerId;
$this->cacheSystemClearerTag = $cacheSystemClearerTag;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if ($container->hasParameter('cache.prefix.seed')) {
$seed = '.'.$container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed'));
} else {
$seed = '_'.$container->getParameter('kernel.project_dir');
}
$seed .= '.'.$container->getParameter('kernel.container_class');
$allPools = [];
$clearers = [];
$attributes = [
'provider',
'name',
'namespace',
'default_lifetime',
'reset',
];
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
$adapter = $pool = $container->getDefinition($id);
if ($pool->isAbstract()) {
continue;
}
$class = $adapter->getClass();
while ($adapter instanceof ChildDefinition) {
$adapter = $container->findDefinition($adapter->getParent());
$class = $class ?: $adapter->getClass();
if ($t = $adapter->getTag($this->cachePoolTag)) {
$tags[0] += $t[0];
}
}
$name = $tags[0]['name'] ?? $id;
if (!isset($tags[0]['namespace'])) {
$namespaceSeed = $seed;
if (null !== $class) {
$namespaceSeed .= '.'.$class;
}
$tags[0]['namespace'] = $this->getNamespace($namespaceSeed, $name);
}
if (isset($tags[0]['clearer'])) {
$clearer = $tags[0]['clearer'];
while ($container->hasAlias($clearer)) {
$clearer = (string) $container->getAlias($clearer);
}
} else {
$clearer = null;
}
unset($tags[0]['clearer'], $tags[0]['name']);
if (isset($tags[0]['provider'])) {
$tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider']));
}
$i = 0;
foreach ($attributes as $attr) {
if (!isset($tags[0][$attr])) {
// no-op
} elseif ('reset' === $attr) {
if ($tags[0][$attr]) {
$pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]);
}
} elseif ('namespace' !== $attr || ArrayAdapter::class !== $adapter->getClass()) {
$pool->replaceArgument($i++, $tags[0][$attr]);
}
unset($tags[0][$attr]);
}
if (!empty($tags[0])) {
throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime" and "reset", found "%s".', $this->cachePoolTag, $id, implode('", "', array_keys($tags[0]))));
}
if (null !== $clearer) {
$clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
}
$allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
}
$notAliasedCacheClearerId = $this->cacheClearerId;
while ($container->hasAlias($this->cacheClearerId)) {
$this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId);
}
if ($container->hasDefinition($this->cacheClearerId)) {
$clearers[$notAliasedCacheClearerId] = $allPools;
}
foreach ($clearers as $id => $pools) {
$clearer = $container->getDefinition($id);
if ($clearer instanceof ChildDefinition) {
$clearer->replaceArgument(0, $pools);
} else {
$clearer->setArgument(0, $pools);
}
$clearer->addTag($this->cachePoolClearerTag);
if ($this->cacheSystemClearerId === $id) {
$clearer->addTag($this->cacheSystemClearerTag);
}
}
if ($container->hasDefinition('console.command.cache_pool_list')) {
$container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, array_keys($allPools));
}
}
private function getNamespace($seed, $id)
{
return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10);
}
/**
* @internal
*/
public static function getServiceProvider(ContainerBuilder $container, $name)
{
$container->resolveEnvPlaceholders($name, null, $usedEnvs);
if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) {
$dsn = $name;
if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) {
$definition = new Definition(AbstractAdapter::class);
$definition->setPublic(false);
$definition->setFactory([AbstractAdapter::class, 'createConnection']);
$definition->setArguments([$dsn, ['lazy' => true]]);
$container->setDefinition($name, $definition);
}
}
return $name;
}
}
@@ -1,60 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\DependencyInjection;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Rob Frawley 2nd <rmf@src.run>
*/
class CachePoolPrunerPass implements CompilerPassInterface
{
private $cacheCommandServiceId;
private $cachePoolTag;
public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool')
{
$this->cacheCommandServiceId = $cacheCommandServiceId;
$this->cachePoolTag = $cachePoolTag;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->cacheCommandServiceId)) {
return;
}
$services = [];
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
$class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass());
if (!$reflection = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if ($reflection->implementsInterface(PruneableInterface::class)) {
$services[$id] = new Reference($id);
}
}
$container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services));
}
}
+1 -2
View File
@@ -13,7 +13,6 @@ namespace Symfony\Component\Cache;
use Doctrine\Common\Cache\CacheProvider;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
@@ -40,7 +39,7 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
*/
public function reset()
{
if ($this->pool instanceof ResetInterface) {
if ($this->pool instanceof ResettableInterface) {
$this->pool->reset();
}
$this->setNamespace($this->getNamespace());
+2 -8
View File
@@ -14,12 +14,6 @@ namespace Symfony\Component\Cache\Exception;
use Psr\Cache\CacheException as Psr6CacheInterface;
use Psr\SimpleCache\CacheException as SimpleCacheInterface;
if (interface_exists(SimpleCacheInterface::class)) {
class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
{
}
} else {
class CacheException extends \Exception implements Psr6CacheInterface
{
}
class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
{
}
@@ -14,12 +14,6 @@ namespace Symfony\Component\Cache\Exception;
use Psr\Cache\InvalidArgumentException as Psr6CacheInterface;
use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface;
if (interface_exists(SimpleCacheInterface::class)) {
class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
{
}
} else {
class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface
{
}
class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
{
}
-25
View File
@@ -1,25 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Exception;
use Psr\Cache\CacheException as Psr6CacheInterface;
use Psr\SimpleCache\CacheException as SimpleCacheInterface;
if (interface_exists(SimpleCacheInterface::class)) {
class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface
{
}
} else {
class LogicException extends \LogicException implements Psr6CacheInterface
{
}
}
-152
View File
@@ -1,152 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
/**
* LockRegistry is used internally by existing adapters to protect against cache stampede.
*
* It does so by wrapping the computation of items in a pool of locks.
* Foreach each apps, there can be at most 20 concurrent processes that
* compute items at the same time and only one per cache-key.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class LockRegistry
{
private static $openedFiles = [];
private static $lockedFiles = [];
/**
* The number of items in this list controls the max number of concurrent processes.
*/
private static $files = [
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ApcuAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ArrayAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'MemcachedAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'NullAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PdoAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpArrayAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpFilesAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ProxyAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'Psr16Adapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisTagAwareAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'SimpleCacheAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapterInterface.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableTagAwareAdapter.php',
];
/**
* Defines a set of existing files that will be used as keys to acquire locks.
*
* @return array The previously defined set of files
*/
public static function setFiles(array $files): array
{
$previousFiles = self::$files;
self::$files = $files;
foreach (self::$openedFiles as $file) {
if ($file) {
flock($file, LOCK_UN);
fclose($file);
}
}
self::$openedFiles = self::$lockedFiles = [];
return $previousFiles;
}
public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null)
{
$key = self::$files ? crc32($item->getKey()) % \count(self::$files) : -1;
if ($key < 0 || (self::$lockedFiles[$key] ?? false) || !$lock = self::open($key)) {
return $callback($item, $save);
}
while (true) {
try {
// race to get the lock in non-blocking mode
$locked = flock($lock, LOCK_EX | LOCK_NB, $wouldBlock);
if ($locked || !$wouldBlock) {
$logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]);
self::$lockedFiles[$key] = true;
$value = $callback($item, $save);
if ($save) {
if ($setMetadata) {
$setMetadata($item);
}
$pool->save($item->set($value));
$save = false;
}
return $value;
}
// if we failed the race, retry locking in blocking mode to wait for the winner
$logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]);
flock($lock, LOCK_SH);
} finally {
flock($lock, LOCK_UN);
unset(self::$lockedFiles[$key]);
}
static $signalingException, $signalingCallback;
$signalingException = $signalingException ?? unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}");
$signalingCallback = $signalingCallback ?? function () use ($signalingException) { throw $signalingException; };
try {
$value = $pool->get($item->getKey(), $signalingCallback, 0);
$logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]);
$save = false;
return $value;
} catch (\Exception $e) {
if ($signalingException !== $e) {
throw $e;
}
$logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]);
}
}
}
private static function open(int $key)
{
if (null !== $h = self::$openedFiles[$key] ?? null) {
return $h;
}
set_error_handler(function () {});
try {
$h = fopen(self::$files[$key], 'r+');
} finally {
restore_error_handler();
}
return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r');
}
}
-99
View File
@@ -1,99 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller;
use Symfony\Component\Cache\Exception\CacheException;
/**
* Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class DefaultMarshaller implements MarshallerInterface
{
private $useIgbinarySerialize = true;
public function __construct(bool $useIgbinarySerialize = null)
{
if (null === $useIgbinarySerialize) {
$useIgbinarySerialize = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.0', phpversion('igbinary'), '<='));
} elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || (\PHP_VERSION_ID >= 70400 && version_compare('3.1.0', phpversion('igbinary'), '>')))) {
throw new CacheException(\extension_loaded('igbinary') && \PHP_VERSION_ID >= 70400 ? 'Please upgrade the "igbinary" PHP extension to v3.1 or higher.' : 'The "igbinary" PHP extension is not loaded.');
}
$this->useIgbinarySerialize = $useIgbinarySerialize;
}
/**
* {@inheritdoc}
*/
public function marshall(array $values, ?array &$failed): array
{
$serialized = $failed = [];
foreach ($values as $id => $value) {
try {
if ($this->useIgbinarySerialize) {
$serialized[$id] = igbinary_serialize($value);
} else {
$serialized[$id] = serialize($value);
}
} catch (\Exception $e) {
$failed[] = $id;
}
}
return $serialized;
}
/**
* {@inheritdoc}
*/
public function unmarshall(string $value)
{
if ('b:0;' === $value) {
return false;
}
if ('N;' === $value) {
return null;
}
static $igbinaryNull;
if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.0', phpversion('igbinary'), '<=')) ? igbinary_serialize(null) : false)) {
return null;
}
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
try {
if (':' === ($value[1] ?? ':')) {
if (false !== $value = unserialize($value)) {
return $value;
}
} elseif (false === $igbinaryNull) {
throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?');
} elseif (null !== $value = igbinary_unserialize($value)) {
return $value;
}
throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.');
} catch (\Error $e) {
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
} finally {
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
}
}
/**
* @internal
*/
public static function handleUnserializeCallback($class)
{
throw new \DomainException('Class not found: '.$class);
}
}
-40
View File
@@ -1,40 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller;
/**
* Serializes/unserializes PHP values.
*
* Implementations of this interface MUST deal with errors carefully. They MUST
* also deal with forward and backward compatibility at the storage format level.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface MarshallerInterface
{
/**
* Serializes a list of values.
*
* When serialization fails for a specific value, no exception should be
* thrown. Instead, its key should be listed in $failed.
*/
public function marshall(array $values, ?array &$failed): array;
/**
* Unserializes a single value and throws an exception if anything goes wrong.
*
* @return mixed
*
* @throws \Exception Whenever unserialization fails
*/
public function unmarshall(string $value);
}
-263
View File
@@ -1,263 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache;
use Psr\Cache\CacheException as Psr6CacheException;
use Psr\Cache\CacheItemPoolInterface;
use Psr\SimpleCache\CacheException as SimpleCacheException;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* Turns a PSR-6 cache into a PSR-16 one.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface
{
use ProxyTrait;
private const METADATA_EXPIRY_OFFSET = 1527506807;
private $createCacheItem;
private $cacheItemPrototype;
public function __construct(CacheItemPoolInterface $pool)
{
$this->pool = $pool;
if (!$pool instanceof AdapterInterface) {
return;
}
$cacheItemPrototype = &$this->cacheItemPrototype;
$createCacheItem = \Closure::bind(
static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) {
$item = clone $cacheItemPrototype;
$item->key = $allowInt && \is_int($key) ? (string) $key : CacheItem::validateKey($key);
$item->value = $value;
$item->isHit = false;
return $item;
},
null,
CacheItem::class
);
$this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) {
if (null === $this->cacheItemPrototype) {
$this->get($allowInt && \is_int($key) ? (string) $key : $key);
}
$this->createCacheItem = $createCacheItem;
return $createCacheItem($key, null, $allowInt)->set($value);
};
}
/**
* {@inheritdoc}
*/
public function get($key, $default = null)
{
try {
$item = $this->pool->getItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
if (null === $this->cacheItemPrototype) {
$this->cacheItemPrototype = clone $item;
$this->cacheItemPrototype->set(null);
}
return $item->isHit() ? $item->get() : $default;
}
/**
* {@inheritdoc}
*/
public function set($key, $value, $ttl = null)
{
try {
if (null !== $f = $this->createCacheItem) {
$item = $f($key, $value);
} else {
$item = $this->pool->getItem($key)->set($value);
}
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
if (null !== $ttl) {
$item->expiresAfter($ttl);
}
return $this->pool->save($item);
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
try {
return $this->pool->deleteItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function clear()
{
return $this->pool->clear();
}
/**
* {@inheritdoc}
*/
public function getMultiple($keys, $default = null)
{
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
try {
$items = $this->pool->getItems($keys);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
$values = [];
if (!$this->pool instanceof AdapterInterface) {
foreach ($items as $key => $item) {
$values[$key] = $item->isHit() ? $item->get() : $default;
}
return $values;
}
foreach ($items as $key => $item) {
if (!$item->isHit()) {
$values[$key] = $default;
continue;
}
$values[$key] = $item->get();
if (!$metadata = $item->getMetadata()) {
continue;
}
unset($metadata[CacheItem::METADATA_TAGS]);
if ($metadata) {
$values[$key] = ["\x9D".pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME])."\x5F" => $values[$key]];
}
}
return $values;
}
/**
* {@inheritdoc}
*/
public function setMultiple($values, $ttl = null)
{
$valuesIsArray = \is_array($values);
if (!$valuesIsArray && !$values instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
}
$items = [];
try {
if (null !== $f = $this->createCacheItem) {
$valuesIsArray = false;
foreach ($values as $key => $value) {
$items[$key] = $f($key, $value, true);
}
} elseif ($valuesIsArray) {
$items = [];
foreach ($values as $key => $value) {
$items[] = (string) $key;
}
$items = $this->pool->getItems($items);
} else {
foreach ($values as $key => $value) {
if (\is_int($key)) {
$key = (string) $key;
}
$items[$key] = $this->pool->getItem($key)->set($value);
}
}
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
$ok = true;
foreach ($items as $key => $item) {
if ($valuesIsArray) {
$item->set($values[$key]);
}
if (null !== $ttl) {
$item->expiresAfter($ttl);
}
$ok = $this->pool->saveDeferred($item) && $ok;
}
return $this->pool->commit() && $ok;
}
/**
* {@inheritdoc}
*/
public function deleteMultiple($keys)
{
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
try {
return $this->pool->deleteItems($keys);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function has($key)
{
try {
return $this->pool->hasItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
}
+2 -3
View File
@@ -11,11 +11,10 @@
namespace Symfony\Component\Cache;
use Symfony\Contracts\Service\ResetInterface;
/**
* Resets a pool's local state.
*/
interface ResettableInterface extends ResetInterface
interface ResettableInterface
{
public function reset();
}
+19 -20
View File
@@ -12,25 +12,21 @@
namespace Symfony\Component\Cache\Simple;
use Psr\Log\LoggerAwareInterface;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\AbstractTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', AbstractCache::class, AbstractAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use AbstractAdapter and type-hint for CacheInterface instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, ResettableInterface
{
/**
* @internal
*/
protected const NS_SEPARATOR = ':';
const NS_SEPARATOR = ':';
use AbstractTrait {
deleteItems as private;
@@ -40,12 +36,16 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
private $defaultLifetime;
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
/**
* @param string $namespace
* @param int $defaultLifetime
*/
protected function __construct($namespace = '', $defaultLifetime = 0)
{
$this->defaultLifetime = max(0, $defaultLifetime);
$this->defaultLifetime = max(0, (int) $defaultLifetime);
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace));
}
}
@@ -61,7 +61,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
return $value;
}
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]);
}
return $default;
@@ -85,7 +85,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
$ids = [];
@@ -95,7 +95,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
try {
$values = $this->doFetch($ids);
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]);
CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => $keys, 'exception' => $e]);
$values = [];
}
$ids = array_combine($ids, $keys);
@@ -109,7 +109,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
public function setMultiple($values, $ttl = null)
{
if (!\is_array($values) && !$values instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
}
$valuesById = [];
@@ -134,8 +134,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
foreach (\is_array($e) ? $e : array_keys($valuesById) as $id) {
$keys[] = substr($id, \strlen($this->namespace));
}
$message = 'Failed to save values'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]);
CacheItem::log($this->logger, 'Failed to save values', ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]);
return false;
}
@@ -148,7 +147,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
return $this->deleteItems($keys);
@@ -166,7 +165,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
return 0 < $ttl ? $ttl : false;
}
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
}
private function generateValues($values, &$keys, $default)
@@ -181,7 +180,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
yield $key => $value;
}
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]);
CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => array_values($keys), 'exception' => $e]);
}
foreach ($keys as $key) {
+6 -8
View File
@@ -11,20 +11,18 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Traits\ApcuTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ApcuCache::class, ApcuAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use ApcuAdapter and type-hint for CacheInterface instead.
*/
class ApcuCache extends AbstractCache
{
use ApcuTrait;
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
/**
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $version
*/
public function __construct($namespace = '', $defaultLifetime = 0, $version = null)
{
$this->init($namespace, $defaultLifetime, $version);
}
+29 -40
View File
@@ -12,20 +12,16 @@
namespace Symfony\Component\Cache\Simple;
use Psr\Log\LoggerAwareInterface;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ArrayTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ArrayCache::class, ArrayAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use ArrayAdapter and type-hint for CacheInterface instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
class ArrayCache implements CacheInterface, LoggerAwareInterface, ResettableInterface
{
use ArrayTrait {
ArrayTrait::deleteItem as delete;
@@ -35,11 +31,12 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
private $defaultLifetime;
/**
* @param int $defaultLifetime
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
*/
public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true)
public function __construct($defaultLifetime = 0, $storeSerialized = true)
{
$this->defaultLifetime = $defaultLifetime;
$this->defaultLifetime = (int) $defaultLifetime;
$this->storeSerialized = $storeSerialized;
}
@@ -48,20 +45,9 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
*/
public function get($key, $default = null)
{
if (!\is_string($key) || !isset($this->expiries[$key])) {
CacheItem::validateKey($key);
foreach ($this->getMultiple([$key], $default) as $v) {
return $v;
}
if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > microtime(true) || !$this->delete($key))) {
$this->values[$key] = null;
return $default;
}
if (!$this->storeSerialized) {
return $this->values[$key];
}
$value = $this->unfreeze($key, $isHit);
return $isHit ? $value : $default;
}
/**
@@ -72,15 +58,13 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
foreach ($keys as $key) {
if (!\is_string($key) || !isset($this->expiries[$key])) {
CacheItem::validateKey($key);
}
CacheItem::validateKey($key);
}
return $this->generateItems($keys, microtime(true), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
return $this->generateItems($keys, time(), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
}
/**
@@ -89,7 +73,7 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
public function deleteMultiple($keys)
{
if (!\is_array($keys) && !$keys instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
foreach ($keys as $key) {
$this->delete($key);
@@ -103,9 +87,7 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
*/
public function set($key, $value, $ttl = null)
{
if (!\is_string($key)) {
CacheItem::validateKey($key);
}
CacheItem::validateKey($key);
return $this->setMultiple([$key => $value], $ttl);
}
@@ -116,25 +98,32 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
public function setMultiple($values, $ttl = null)
{
if (!\is_array($values) && !$values instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
}
$valuesArray = [];
foreach ($values as $key => $value) {
if (!\is_int($key) && !(\is_string($key) && isset($this->expiries[$key]))) {
CacheItem::validateKey($key);
}
\is_int($key) || CacheItem::validateKey($key);
$valuesArray[$key] = $value;
}
if (false === $ttl = $this->normalizeTtl($ttl)) {
return $this->deleteMultiple(array_keys($valuesArray));
}
$expiry = 0 < $ttl ? microtime(true) + $ttl : PHP_INT_MAX;
if ($this->storeSerialized) {
foreach ($valuesArray as $key => $value) {
try {
$valuesArray[$key] = serialize($value);
} catch (\Exception $e) {
$type = \is_object($value) ? \get_class($value) : \gettype($value);
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => $key, 'type' => $type, 'exception' => $e]);
return false;
}
}
}
$expiry = 0 < $ttl ? time() + $ttl : \PHP_INT_MAX;
foreach ($valuesArray as $key => $value) {
if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
return false;
}
$this->values[$key] = $value;
$this->expiries[$key] = $expiry;
}
@@ -154,6 +143,6 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
return 0 < $ttl ? $ttl : false;
}
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
}
}
+10 -15
View File
@@ -11,15 +11,10 @@
namespace Symfony\Component\Cache\Simple;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Symfony\Component\Cache\Adapter\ChainAdapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Service\ResetInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ChainCache::class, ChainAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* Chains several caches together.
@@ -27,9 +22,9 @@ use Symfony\Contracts\Service\ResetInterface;
* Cached items are fetched from the first cache having them in its data store.
* They are saved and deleted in all caches at once.
*
* @deprecated since Symfony 4.3, use ChainAdapter and type-hint for CacheInterface instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
class ChainCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
class ChainCache implements CacheInterface, PruneableInterface, ResettableInterface
{
private $miss;
private $caches = [];
@@ -37,25 +32,25 @@ class ChainCache implements Psr16CacheInterface, PruneableInterface, ResettableI
private $cacheCount;
/**
* @param Psr16CacheInterface[] $caches The ordered list of caches used to fetch cached items
* @param int $defaultLifetime The lifetime of items propagated from lower caches to upper ones
* @param CacheInterface[] $caches The ordered list of caches used to fetch cached items
* @param int $defaultLifetime The lifetime of items propagated from lower caches to upper ones
*/
public function __construct(array $caches, int $defaultLifetime = 0)
public function __construct(array $caches, $defaultLifetime = 0)
{
if (!$caches) {
throw new InvalidArgumentException('At least one cache must be specified.');
}
foreach ($caches as $cache) {
if (!$cache instanceof Psr16CacheInterface) {
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($cache), Psr16CacheInterface::class));
if (!$cache instanceof CacheInterface) {
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($cache), CacheInterface::class));
}
}
$this->miss = new \stdClass();
$this->caches = array_values($caches);
$this->cacheCount = \count($this->caches);
$this->defaultLifetime = 0 < $defaultLifetime ? $defaultLifetime : null;
$this->defaultLifetime = 0 < $defaultLifetime ? (int) $defaultLifetime : null;
}
/**
@@ -249,7 +244,7 @@ class ChainCache implements Psr16CacheInterface, PruneableInterface, ResettableI
public function reset()
{
foreach ($this->caches as $cache) {
if ($cache instanceof ResetInterface) {
if ($cache instanceof ResettableInterface) {
$cache->reset();
}
}
+5 -8
View File
@@ -12,20 +12,17 @@
namespace Symfony\Component\Cache\Simple;
use Doctrine\Common\Cache\CacheProvider;
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
use Symfony\Component\Cache\Traits\DoctrineTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', DoctrineCache::class, DoctrineAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use DoctrineAdapter and type-hint for CacheInterface instead.
*/
class DoctrineCache extends AbstractCache
{
use DoctrineTrait;
public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
/**
* @param string $namespace
* @param int $defaultLifetime
*/
public function __construct(CacheProvider $provider, $namespace = '', $defaultLifetime = 0)
{
parent::__construct('', $defaultLifetime);
$this->provider = $provider;
+6 -11
View File
@@ -11,25 +11,20 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\FilesystemTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', FilesystemCache::class, FilesystemAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use FilesystemAdapter and type-hint for CacheInterface instead.
*/
class FilesystemCache extends AbstractCache implements PruneableInterface
{
use FilesystemTrait;
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
/**
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $directory
*/
public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
{
$this->marshaller = $marshaller ?? new DefaultMarshaller();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
}
+6 -10
View File
@@ -11,24 +11,20 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\MemcachedTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', MemcachedCache::class, MemcachedAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use MemcachedAdapter and type-hint for CacheInterface instead.
*/
class MemcachedCache extends AbstractCache
{
use MemcachedTrait;
protected $maxIdLength = 250;
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
/**
* @param string $namespace
* @param int $defaultLifetime
*/
public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0)
{
$this->init($client, $namespace, $defaultLifetime, $marshaller);
$this->init($client, $namespace, $defaultLifetime);
}
}
+3 -7
View File
@@ -11,16 +11,12 @@
namespace Symfony\Component\Cache\Simple;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', NullCache::class, NullAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
use Psr\SimpleCache\CacheInterface;
/**
* @deprecated since Symfony 4.3, use NullAdapter and type-hint for CacheInterface instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
class NullCache implements Psr16CacheInterface
class NullCache implements CacheInterface
{
/**
* {@inheritdoc}
+6 -14
View File
@@ -11,17 +11,9 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\PdoAdapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\PdoTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PdoCache::class, PdoAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use PdoAdapter and type-hint for CacheInterface instead.
*/
class PdoCache extends AbstractCache implements PruneableInterface
{
use PdoTrait;
@@ -33,9 +25,6 @@ class PdoCache extends AbstractCache implements PruneableInterface
* a Doctrine DBAL Connection or a DSN string that will be used to
* lazy-connect to the database when the cache is actually used.
*
* When a Doctrine DBAL Connection is passed, the cache table is created
* automatically when possible. Otherwise, use the createTable() method.
*
* List of available options:
* * db_table: The name of the table [default: cache_items]
* * db_id_col: The column where to store the cache id [default: item_id]
@@ -46,14 +35,17 @@ class PdoCache extends AbstractCache implements PruneableInterface
* * db_password: The password when lazy-connect [default: '']
* * db_connection_options: An array of driver-specific connection options [default: []]
*
* @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null
* @param \PDO|Connection|string $connOrDsn A \PDO or Connection instance or DSN string or null
* @param string $namespace
* @param int $defaultLifetime
* @param array $options An associative array of options
*
* @throws InvalidArgumentException When first argument is not PDO nor Connection nor string
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
* @throws InvalidArgumentException When namespace contains invalid characters
*/
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
public function __construct($connOrDsn, $namespace = '', $defaultLifetime = 0, array $options = [])
{
$this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
$this->init($connOrDsn, $namespace, $defaultLifetime, $options);
}
}
+50 -35
View File
@@ -11,44 +11,51 @@
namespace Symfony\Component\Cache\Simple;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\PhpArrayTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpArrayCache::class, PhpArrayAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use PhpArrayAdapter and type-hint for CacheInterface instead.
* Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
* Warmed up items are read-only and run-time discovered items are cached using a fallback adapter.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInterface
{
use PhpArrayTrait;
/**
* @param string $file The PHP file were values are cached
* @param Psr16CacheInterface $fallbackPool A pool to fallback on when an item is not hit
* @param string $file The PHP file were values are cached
* @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
*/
public function __construct(string $file, Psr16CacheInterface $fallbackPool)
public function __construct($file, CacheInterface $fallbackPool)
{
$this->file = $file;
$this->pool = $fallbackPool;
$this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
}
/**
* This adapter takes advantage of how PHP stores arrays in its latest versions.
* This adapter should only be used on PHP 7.0+ to take advantage of how PHP
* stores arrays in its latest versions. This factory method decorates the given
* fallback pool with this adapter only if the current PHP version is supported.
*
* @param string $file The PHP file were values are cached
* @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
*
* @return Psr16CacheInterface
* @return CacheInterface
*/
public static function create($file, Psr16CacheInterface $fallbackPool)
public static function create($file, CacheInterface $fallbackPool)
{
return new static($file, $fallbackPool);
if (\PHP_VERSION_ID >= 70000) {
return new static($file, $fallbackPool);
}
return $fallbackPool;
}
/**
@@ -62,18 +69,22 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
if (null === $this->values) {
$this->initialize();
}
if (!isset($this->keys[$key])) {
if (!isset($this->values[$key])) {
return $this->pool->get($key, $default);
}
$value = $this->values[$this->keys[$key]];
$value = $this->values[$key];
if ('N;' === $value) {
return null;
}
if ($value instanceof \Closure) {
$value = null;
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
return $value();
} catch (\Throwable $e) {
$e = null;
$value = unserialize($value);
} catch (\Error $e) {
} catch (\Exception $e) {
}
if (null !== $e) {
return $default;
}
}
@@ -89,7 +100,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
foreach ($keys as $key) {
if (!\is_string($key)) {
@@ -115,7 +126,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
$this->initialize();
}
return isset($this->keys[$key]) || $this->pool->has($key);
return isset($this->values[$key]) || $this->pool->has($key);
}
/**
@@ -130,7 +141,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
$this->initialize();
}
return !isset($this->keys[$key]) && $this->pool->delete($key);
return !isset($this->values[$key]) && $this->pool->delete($key);
}
/**
@@ -139,7 +150,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
public function deleteMultiple($keys)
{
if (!\is_array($keys) && !$keys instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
$deleted = true;
@@ -150,7 +161,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if (isset($this->keys[$key])) {
if (isset($this->values[$key])) {
$deleted = false;
} else {
$fallbackKeys[] = $key;
@@ -179,7 +190,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
$this->initialize();
}
return !isset($this->keys[$key]) && $this->pool->set($key, $value, $ttl);
return !isset($this->values[$key]) && $this->pool->set($key, $value, $ttl);
}
/**
@@ -188,7 +199,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
public function setMultiple($values, $ttl = null)
{
if (!\is_array($values) && !$values instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
}
$saved = true;
@@ -199,7 +210,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if (isset($this->keys[$key])) {
if (isset($this->values[$key])) {
$saved = false;
} else {
$fallbackValues[$key] = $value;
@@ -218,15 +229,17 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
$fallbackKeys = [];
foreach ($keys as $key) {
if (isset($this->keys[$key])) {
$value = $this->values[$this->keys[$key]];
if (isset($this->values[$key])) {
$value = $this->values[$key];
if ('N;' === $value) {
yield $key => null;
} elseif ($value instanceof \Closure) {
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
yield $key => $value();
} catch (\Throwable $e) {
yield $key => unserialize($value);
} catch (\Error $e) {
yield $key => $default;
} catch (\Exception $e) {
yield $key => $default;
}
} else {
@@ -238,7 +251,9 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
}
if ($fallbackKeys) {
yield from $this->pool->getMultiple($fallbackKeys, $default);
foreach ($this->pool->getMultiple($fallbackKeys, $default) as $key => $item) {
yield $key => $item;
}
}
}
}
+11 -15
View File
@@ -11,35 +11,31 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
use Symfony\Component\Cache\Exception\CacheException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\PhpFilesTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpFilesCache::class, PhpFilesAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use PhpFilesAdapter and type-hint for CacheInterface instead.
*/
class PhpFilesCache extends AbstractCache implements PruneableInterface
{
use PhpFilesTrait;
/**
* @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
* Doing so is encouraged because it fits perfectly OPcache's memory model.
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $directory
*
* @throws CacheException if OPcache is not enabled
*/
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
{
$this->appendOnly = $appendOnly;
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
if (!static::isSupported()) {
throw new CacheException('OPcache is not enabled.');
}
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
$this->includeHandler = static function ($type, $msg, $file, $line) {
throw new \ErrorException($msg, 0, $type, $file, $line);
};
$e = new \Exception();
$this->includeHandler = function () use ($e) { throw $e; };
$this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
}
}
+223 -5
View File
@@ -11,13 +11,231 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Psr16Cache;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', Psr6Cache::class, Psr16Cache::class), E_USER_DEPRECATED);
use Psr\Cache\CacheException as Psr6CacheException;
use Psr\Cache\CacheItemPoolInterface;
use Psr\SimpleCache\CacheException as SimpleCacheException;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* @deprecated since Symfony 4.3, use Psr16Cache instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
class Psr6Cache extends Psr16Cache
class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterface
{
use ProxyTrait;
private $createCacheItem;
private $cacheItemPrototype;
public function __construct(CacheItemPoolInterface $pool)
{
$this->pool = $pool;
if (!$pool instanceof AdapterInterface) {
return;
}
$cacheItemPrototype = &$this->cacheItemPrototype;
$createCacheItem = \Closure::bind(
static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) {
$item = clone $cacheItemPrototype;
$item->key = $allowInt && \is_int($key) ? (string) $key : CacheItem::validateKey($key);
$item->value = $value;
$item->isHit = false;
return $item;
},
null,
CacheItem::class
);
$this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) {
if (null === $this->cacheItemPrototype) {
$this->get($allowInt && \is_int($key) ? (string) $key : $key);
}
$this->createCacheItem = $createCacheItem;
return $createCacheItem($key, $value, $allowInt);
};
}
/**
* {@inheritdoc}
*/
public function get($key, $default = null)
{
try {
$item = $this->pool->getItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
if (null === $this->cacheItemPrototype) {
$this->cacheItemPrototype = clone $item;
$this->cacheItemPrototype->set(null);
}
return $item->isHit() ? $item->get() : $default;
}
/**
* {@inheritdoc}
*/
public function set($key, $value, $ttl = null)
{
try {
if (null !== $f = $this->createCacheItem) {
$item = $f($key, $value);
} else {
$item = $this->pool->getItem($key)->set($value);
}
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
if (null !== $ttl) {
$item->expiresAfter($ttl);
}
return $this->pool->save($item);
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
try {
return $this->pool->deleteItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function clear()
{
return $this->pool->clear();
}
/**
* {@inheritdoc}
*/
public function getMultiple($keys, $default = null)
{
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
try {
$items = $this->pool->getItems($keys);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
$values = [];
foreach ($items as $key => $item) {
$values[$key] = $item->isHit() ? $item->get() : $default;
}
return $values;
}
/**
* {@inheritdoc}
*/
public function setMultiple($values, $ttl = null)
{
$valuesIsArray = \is_array($values);
if (!$valuesIsArray && !$values instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
}
$items = [];
try {
if (null !== $f = $this->createCacheItem) {
$valuesIsArray = false;
foreach ($values as $key => $value) {
$items[$key] = $f($key, $value, true);
}
} elseif ($valuesIsArray) {
$items = [];
foreach ($values as $key => $value) {
$items[] = (string) $key;
}
$items = $this->pool->getItems($items);
} else {
foreach ($values as $key => $value) {
if (\is_int($key)) {
$key = (string) $key;
}
$items[$key] = $this->pool->getItem($key)->set($value);
}
}
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
$ok = true;
foreach ($items as $key => $item) {
if ($valuesIsArray) {
$item->set($values[$key]);
}
if (null !== $ttl) {
$item->expiresAfter($ttl);
}
$ok = $this->pool->saveDeferred($item) && $ok;
}
return $this->pool->commit() && $ok;
}
/**
* {@inheritdoc}
*/
public function deleteMultiple($keys)
{
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
try {
return $this->pool->deleteItems($keys);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function has($key)
{
try {
return $this->pool->hasItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
}
+5 -11
View File
@@ -11,25 +11,19 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\RedisTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', RedisCache::class, RedisAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use RedisAdapter and type-hint for CacheInterface instead.
*/
class RedisCache extends AbstractCache
{
use RedisTrait;
/**
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient
* @param string $namespace
* @param int $defaultLifetime
*/
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
public function __construct($redisClient, $namespace = '', $defaultLifetime = 0)
{
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
$this->init($redisClient, $namespace, $defaultLifetime);
}
}
+7 -9
View File
@@ -11,24 +11,22 @@
namespace Symfony\Component\Cache\Simple;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Service\ResetInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', TraceableCache::class, TraceableAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use TraceableAdapter and type-hint for CacheInterface instead.
* An adapter that collects data about all cache calls.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class TraceableCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
class TraceableCache implements CacheInterface, PruneableInterface, ResettableInterface
{
private $pool;
private $miss;
private $calls = [];
public function __construct(Psr16CacheInterface $pool)
public function __construct(CacheInterface $pool)
{
$this->pool = $pool;
$this->miss = new \stdClass();
@@ -202,7 +200,7 @@ class TraceableCache implements Psr16CacheInterface, PruneableInterface, Resetta
*/
public function reset()
{
if (!$this->pool instanceof ResetInterface) {
if (!$this->pool instanceof ResettableInterface) {
return;
}
$event = $this->start(__FUNCTION__);
@@ -28,18 +28,19 @@ abstract class AbstractRedisAdapterTest extends AdapterTestCase
return new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
}
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
if (!\extension_loaded('redis')) {
self::markTestSkipped('Extension redis required.');
}
if (!@((new \Redis())->connect(getenv('REDIS_HOST')))) {
$e = error_get_last();
self::markTestSkipped($e['message']);
try {
(new \Redis())->connect(getenv('REDIS_HOST'));
} catch (\Exception $e) {
self::markTestSkipped($e->getMessage());
}
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
self::$redis = null;
}
+12 -100
View File
@@ -12,117 +12,24 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Cache\IntegrationTests\CachePoolTest;
use PHPUnit\Framework\Assert;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Contracts\Cache\CallbackInterface;
abstract class AdapterTestCase extends CachePoolTest
{
protected function setUp(): void
protected function setUp()
{
parent::setUp();
if (!\array_key_exists('testDeferredSaveWithoutCommit', $this->skippedTests) && \defined('HHVM_VERSION')) {
$this->skippedTests['testDeferredSaveWithoutCommit'] = 'Destructors are called late on HHVM.';
}
if (!\array_key_exists('testPrune', $this->skippedTests) && !$this->createCachePool() instanceof PruneableInterface) {
$this->skippedTests['testPrune'] = 'Not a pruneable cache pool.';
}
}
public function testGet()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$cache = $this->createCachePool();
$cache->clear();
$value = mt_rand();
$this->assertSame($value, $cache->get('foo', function (CacheItem $item) use ($value) {
$this->assertSame('foo', $item->getKey());
return $value;
}));
$item = $cache->getItem('foo');
$this->assertSame($value, $item->get());
$isHit = true;
$this->assertSame($value, $cache->get('foo', function (CacheItem $item) use (&$isHit) { $isHit = false; }, 0));
$this->assertTrue($isHit);
$this->assertNull($cache->get('foo', function (CacheItem $item) use (&$isHit, $value) {
$isHit = false;
$this->assertTrue($item->isHit());
$this->assertSame($value, $item->get());
}, INF));
$this->assertFalse($isHit);
$this->assertSame($value, $cache->get('bar', new class($value) implements CallbackInterface {
private $value;
public function __construct(int $value)
{
$this->value = $value;
}
public function __invoke(CacheItemInterface $item, bool &$save)
{
Assert::assertSame('bar', $item->getKey());
return $this->value;
}
}));
}
public function testRecursiveGet()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$cache = $this->createCachePool(0, __FUNCTION__);
$v = $cache->get('k1', function () use (&$counter, $cache) {
$cache->get('k2', function () use (&$counter) { return ++$counter; });
$v = $cache->get('k2', function () use (&$counter) { return ++$counter; }); // ensure the callback is called once
return $v;
});
$this->assertSame(1, $counter);
$this->assertSame(1, $v);
$this->assertSame(1, $cache->get('k2', function () { return 2; }));
}
public function testGetMetadata()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$cache = $this->createCachePool(0, __FUNCTION__);
$cache->deleteItem('foo');
$cache->get('foo', function ($item) {
$item->expiresAfter(10);
usleep(999000);
return 'bar';
});
$item = $cache->getItem('foo');
$expected = [
CacheItem::METADATA_EXPIRY => 9.5 + time(),
CacheItem::METADATA_CTIME => 1000,
];
$this->assertEqualsWithDelta($expected, $item->getMetadata(), .6, 'Item metadata should embed expiry and ctime.');
}
public function testDefaultLifeTime()
{
if (isset($this->skippedTests[__FUNCTION__])) {
@@ -254,9 +161,14 @@ abstract class AdapterTestCase extends CachePoolTest
}
}
class NotUnserializable
class NotUnserializable implements \Serializable
{
public function __wakeup()
public function serialize()
{
return serialize(123);
}
public function unserialize($ser)
{
throw new \Exception(__CLASS__);
}
+4 -4
View File
@@ -24,10 +24,10 @@ class ApcuAdapterTest extends AdapterTestCase
public function createCachePool($defaultLifetime = 0)
{
if (!\function_exists('apcu_fetch') || !filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN)) {
if (!\function_exists('apcu_fetch') || !filter_var(ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN)) {
$this->markTestSkipped('APCu extension is required.');
}
if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
if ('testWithCliSapi' !== $this->getName()) {
$this->markTestSkipped('apc.enable_cli=1 is required.');
}
@@ -54,7 +54,7 @@ class ApcuAdapterTest extends AdapterTestCase
public function testVersion()
{
$namespace = str_replace('\\', '.', \get_class($this));
$namespace = str_replace('\\', '.', static::class);
$pool1 = new ApcuAdapter($namespace, 0, 'p1');
@@ -79,7 +79,7 @@ class ApcuAdapterTest extends AdapterTestCase
public function testNamespace()
{
$namespace = str_replace('\\', '.', \get_class($this));
$namespace = str_replace('\\', '.', static::class);
$pool1 = new ApcuAdapter($namespace.'_1', 0, 'p1');
+3 -4
View File
@@ -19,7 +19,6 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter;
class ArrayAdapterTest extends AdapterTestCase
{
protected $skippedTests = [
'testGetMetadata' => 'ArrayAdapter does not keep metadata.',
'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.',
'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayAdapter is not.',
];
@@ -36,12 +35,12 @@ class ArrayAdapterTest extends AdapterTestCase
// Hit
$item = $cache->getItem('foo');
$item->set('::4711');
$item->set('4711');
$cache->save($item);
$fooItem = $cache->getItem('foo');
$this->assertTrue($fooItem->isHit());
$this->assertEquals('::4711', $fooItem->get());
$this->assertEquals('4711', $fooItem->get());
// Miss (should be present as NULL in $values)
$cache->getItem('bar');
@@ -50,7 +49,7 @@ class ArrayAdapterTest extends AdapterTestCase
$this->assertCount(2, $values);
$this->assertArrayHasKey('foo', $values);
$this->assertSame(serialize('::4711'), $values['foo']);
$this->assertSame(serialize('4711'), $values['foo']);
$this->assertArrayHasKey('bar', $values);
$this->assertNull($values['bar']);
}
+120 -6
View File
@@ -25,13 +25,9 @@ use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter;
*/
class ChainAdapterTest extends AdapterTestCase
{
public function createCachePool($defaultLifetime = 0, $testMethod = null)
public function createCachePool($defaultLifetime = 0)
{
if ('testGetMetadata' === $testMethod) {
return new ChainAdapter([new FilesystemAdapter('a', $defaultLifetime), new FilesystemAdapter('b', $defaultLifetime)], $defaultLifetime);
}
return new ChainAdapter([new ArrayAdapter($defaultLifetime), new ExternalAdapter(), new FilesystemAdapter('', $defaultLifetime)], $defaultLifetime);
return new ChainAdapter([new ArrayAdapter($defaultLifetime), new ExternalAdapter($defaultLifetime), new FilesystemAdapter('', $defaultLifetime)], $defaultLifetime);
}
public function testEmptyAdaptersException()
@@ -69,6 +65,124 @@ class ChainAdapterTest extends AdapterTestCase
$this->assertFalse($cache->prune());
}
public function testMultipleCachesExpirationWhenCommonTtlIsNotSet()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$adapter1 = new ArrayAdapter(4);
$adapter2 = new ArrayAdapter(2);
$cache = new ChainAdapter([$adapter1, $adapter2]);
$cache->save($cache->getItem('key')->set('value'));
$item = $adapter1->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
$item = $adapter2->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
sleep(2);
$item = $adapter1->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
$item = $adapter2->getItem('key');
$this->assertFalse($item->isHit());
sleep(2);
$item = $adapter1->getItem('key');
$this->assertFalse($item->isHit());
$adapter2->save($adapter2->getItem('key1')->set('value1'));
$item = $cache->getItem('key1');
$this->assertTrue($item->isHit());
$this->assertEquals('value1', $item->get());
sleep(2);
$item = $adapter1->getItem('key1');
$this->assertTrue($item->isHit());
$this->assertEquals('value1', $item->get());
$item = $adapter2->getItem('key1');
$this->assertFalse($item->isHit());
sleep(2);
$item = $adapter1->getItem('key1');
$this->assertFalse($item->isHit());
}
public function testMultipleCachesExpirationWhenCommonTtlIsSet()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$adapter1 = new ArrayAdapter(4);
$adapter2 = new ArrayAdapter(2);
$cache = new ChainAdapter([$adapter1, $adapter2], 6);
$cache->save($cache->getItem('key')->set('value'));
$item = $adapter1->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
$item = $adapter2->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
sleep(2);
$item = $adapter1->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
$item = $adapter2->getItem('key');
$this->assertFalse($item->isHit());
sleep(2);
$item = $adapter1->getItem('key');
$this->assertFalse($item->isHit());
$adapter2->save($adapter2->getItem('key1')->set('value1'));
$item = $cache->getItem('key1');
$this->assertTrue($item->isHit());
$this->assertEquals('value1', $item->get());
sleep(2);
$item = $adapter1->getItem('key1');
$this->assertTrue($item->isHit());
$this->assertEquals('value1', $item->get());
$item = $adapter2->getItem('key1');
$this->assertFalse($item->isHit());
sleep(2);
$item = $adapter1->getItem('key1');
$this->assertTrue($item->isHit());
$this->assertEquals('value1', $item->get());
sleep(2);
$item = $adapter1->getItem('key1');
$this->assertFalse($item->isHit());
}
/**
* @return MockObject|PruneableCacheInterface
*/
@@ -24,7 +24,7 @@ class FilesystemAdapterTest extends AdapterTestCase
return new FilesystemAdapter('', $defaultLifetime);
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
self::rmdir(sys_get_temp_dir().'/symfony-cache');
}
@@ -1,28 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Tests\Adapter;
use Symfony\Component\Cache\Adapter\FilesystemTagAwareAdapter;
use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
/**
* @group time-sensitive
*/
class FilesystemTagAwareAdapterTest extends FilesystemAdapterTest
{
use TagAwareTestTrait;
public function createCachePool($defaultLifetime = 0)
{
return new FilesystemTagAwareAdapter('', $defaultLifetime);
}
}
@@ -26,7 +26,7 @@ class MaxIdLengthAdapterTest extends TestCase
$cache->expects($this->exactly(2))
->method('doHave')
->withConsecutive(
[$this->equalTo('----------:nWfzGiCgLczv3SSUzXL3kg:')],
[$this->equalTo('----------:0GTYWa9n4ed8vqNlOT2iEr:')],
[$this->equalTo('----------:---------------------------------------')]
);
+11 -47
View File
@@ -23,7 +23,7 @@ class MemcachedAdapterTest extends AdapterTestCase
protected static $client;
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
if (!MemcachedAdapter::isSupported()) {
self::markTestSkipped('Extension memcached >=2.2.0 required.');
@@ -66,8 +66,14 @@ class MemcachedAdapterTest extends AdapterTestCase
*/
public function testBadOptions($name, $value)
{
$this->expectException('ErrorException');
$this->expectExceptionMessage('constant(): Couldn\'t find constant Memcached::');
if (\PHP_VERSION_ID < 80000) {
$this->expectException('ErrorException');
$this->expectExceptionMessage('constant(): Couldn\'t find constant Memcached::');
} else {
$this->expectException('Error');
$this->expectExceptionMessage('Undefined constant Memcached::');
}
MemcachedAdapter::createConnection([], [$name => $value]);
}
@@ -135,7 +141,7 @@ class MemcachedAdapterTest extends AdapterTestCase
'localhost',
11222,
];
if (filter_var(ini_get('memcached.use_sasl'), FILTER_VALIDATE_BOOLEAN)) {
if (filter_var(ini_get('memcached.use_sasl'), \FILTER_VALIDATE_BOOLEAN)) {
yield [
'memcached://user:password@127.0.0.1?weight=50',
'127.0.0.1',
@@ -152,7 +158,7 @@ class MemcachedAdapterTest extends AdapterTestCase
'/var/local/run/memcached.socket',
0,
];
if (filter_var(ini_get('memcached.use_sasl'), FILTER_VALIDATE_BOOLEAN)) {
if (filter_var(ini_get('memcached.use_sasl'), \FILTER_VALIDATE_BOOLEAN)) {
yield [
'memcached://user:password@/var/local/run/memcached.socket?weight=25',
'/var/local/run/memcached.socket',
@@ -195,46 +201,4 @@ class MemcachedAdapterTest extends AdapterTestCase
{
$this->assertTrue($this->createCachePool()->clear());
}
public function testMultiServerDsn()
{
$dsn = 'memcached:?host[localhost]&host[localhost:12345]&host[/some/memcached.sock:]=3';
$client = MemcachedAdapter::createConnection($dsn);
$expected = [
0 => [
'host' => 'localhost',
'port' => 11211,
'type' => 'TCP',
],
1 => [
'host' => 'localhost',
'port' => 12345,
'type' => 'TCP',
],
2 => [
'host' => '/some/memcached.sock',
'port' => 0,
'type' => 'SOCKET',
],
];
$this->assertSame($expected, $client->getServerList());
$dsn = 'memcached://localhost?host[foo.bar]=3';
$client = MemcachedAdapter::createConnection($dsn);
$expected = [
0 => [
'host' => 'localhost',
'port' => 11211,
'type' => 'TCP',
],
1 => [
'host' => 'foo.bar',
'port' => 11211,
'type' => 'TCP',
],
];
$this->assertSame($expected, $client->getServerList());
}
}
@@ -12,7 +12,6 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
/**
@@ -20,12 +19,8 @@ use Symfony\Component\Cache\Adapter\ProxyAdapter;
*/
class NamespacedProxyAdapterTest extends ProxyAdapterTest
{
public function createCachePool($defaultLifetime = 0, $testMethod = null)
public function createCachePool($defaultLifetime = 0)
{
if ('testGetMetadata' === $testMethod) {
return new ProxyAdapter(new FilesystemAdapter(), 'foo', $defaultLifetime);
}
return new ProxyAdapter(new ArrayAdapter($defaultLifetime), 'foo', $defaultLifetime);
}
}
-13
View File
@@ -34,19 +34,6 @@ class NullAdapterTest extends TestCase
$this->assertNull($item->get(), "Item's value must be null when isHit is false.");
}
public function testGet()
{
$adapter = $this->createCachePool();
$fetched = [];
$adapter->get('myKey', function ($item) use (&$fetched) { $fetched[] = $item; });
$this->assertCount(1, $fetched);
$item = $fetched[0];
$this->assertFalse($item->isHit());
$this->assertNull($item->get(), "Item's value must be null when isHit is false.");
$this->assertSame('myKey', $item->getKey());
}
public function testHasItem()
{
$this->assertFalse($this->createCachePool()->hasItem('key'));
+2 -2
View File
@@ -23,7 +23,7 @@ class PdoAdapterTest extends AdapterTestCase
protected static $dbFile;
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
if (!\extension_loaded('pdo_sqlite')) {
self::markTestSkipped('Extension pdo_sqlite required.');
@@ -35,7 +35,7 @@ class PdoAdapterTest extends AdapterTestCase
$pool->createTable();
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
@unlink(self::$dbFile);
}
+5 -2
View File
@@ -24,16 +24,19 @@ class PdoDbalAdapterTest extends AdapterTestCase
protected static $dbFile;
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
if (!\extension_loaded('pdo_sqlite')) {
self::markTestSkipped('Extension pdo_sqlite required.');
}
self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
$pool = new PdoAdapter(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]));
$pool->createTable();
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
@unlink(self::$dbFile);
}
+10 -37
View File
@@ -12,7 +12,6 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
@@ -22,8 +21,6 @@ use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
class PhpArrayAdapterTest extends AdapterTestCase
{
protected $skippedTests = [
'testGet' => 'PhpArrayAdapter is read-only.',
'testRecursiveGet' => 'PhpArrayAdapter is read-only.',
'testBasicUsage' => 'PhpArrayAdapter is read-only.',
'testBasicUsageWithLongKey' => 'PhpArrayAdapter is read-only.',
'testClear' => 'PhpArrayAdapter is read-only.',
@@ -58,12 +55,12 @@ class PhpArrayAdapterTest extends AdapterTestCase
protected static $file;
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
self::$file = sys_get_temp_dir().'/symfony-cache/php-array-adapter-test.php';
}
protected function tearDown(): void
protected function tearDown()
{
$this->createCachePool()->clear();
@@ -72,12 +69,8 @@ class PhpArrayAdapterTest extends AdapterTestCase
}
}
public function createCachePool($defaultLifetime = 0, $testMethod = null)
public function createCachePool()
{
if ('testGetMetadata' === $testMethod) {
return new PhpArrayAdapter(self::$file, new FilesystemAdapter());
}
return new PhpArrayAdapterWrapper(self::$file, new NullAdapter());
}
@@ -110,32 +103,16 @@ class PhpArrayAdapterTest extends AdapterTestCase
public function testStoredFile()
{
$data = [
$expected = [
'integer' => 42,
'float' => 42.42,
'boolean' => true,
'array_simple' => ['foo', 'bar'],
'array_associative' => ['foo' => 'bar', 'foo2' => 'bar2'],
];
$expected = [
[
'integer' => 0,
'float' => 1,
'boolean' => 2,
'array_simple' => 3,
'array_associative' => 4,
],
[
0 => 42,
1 => 42.42,
2 => true,
3 => ['foo', 'bar'],
4 => ['foo' => 'bar', 'foo2' => 'bar2'],
],
];
$adapter = $this->createCachePool();
$adapter->warmUp($data);
$adapter->warmUp($expected);
$values = eval(substr(file_get_contents(self::$file), 6));
@@ -145,17 +122,13 @@ class PhpArrayAdapterTest extends AdapterTestCase
class PhpArrayAdapterWrapper extends PhpArrayAdapter
{
protected $data = [];
public function save(CacheItemInterface $item)
{
(\Closure::bind(function () use ($item) {
$key = $item->getKey();
$this->keys[$key] = $id = \count($this->values);
$this->data[$key] = $this->values[$id] = $item->get();
$this->warmUp($this->data);
list($this->keys, $this->values) = eval(substr(file_get_contents($this->file), 6));
}, $this, PhpArrayAdapter::class))();
\call_user_func(\Closure::bind(function () use ($item) {
$this->values[$item->getKey()] = $item->get();
$this->warmUp($this->values);
$this->values = eval(substr(file_get_contents($this->file), 6));
}, $this, PhpArrayAdapter::class));
return true;
}
@@ -30,12 +30,12 @@ class PhpArrayAdapterWithFallbackTest extends AdapterTestCase
protected static $file;
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
self::$file = sys_get_temp_dir().'/symfony-cache/php-array-adapter-test.php';
}
protected function tearDown(): void
protected function tearDown()
{
$this->createCachePool()->clear();

Some files were not shown because too many files have changed in this diff Show More