Merge branch '3.1'

* 3.1:
  [TwigBundle] Removed redundant return statement.
  enable property info
  [Cache] Fix default lifetime being ignored
  [DependencyInjection] Fixed deprecated default message template with XML
  Reference the actual location of the documentation
  [TwigBridge] Removed extra arguments in 2 places.
  [Cache] Fix incorrect timestamps generated by FilesystemAdapter
  [Process] Fix write access check for pipes on Windows
  [HttpKernel] Use flock() for HttpCache's lock files

Conflicts:
	src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php
This commit is contained in:
Nicolas Grekas 2016-07-28 13:15:50 +02:00
commit b6bbe43b4b
28 changed files with 209 additions and 93 deletions

View File

@ -11,7 +11,6 @@
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
/**
@ -48,7 +47,7 @@ class LogoutUrlExtension extends \Twig_Extension
*/
public function getLogoutPath($key = null)
{
return $this->generator->getLogoutPath($key, UrlGeneratorInterface::ABSOLUTE_PATH);
return $this->generator->getLogoutPath($key);
}
/**
@ -60,7 +59,7 @@ class LogoutUrlExtension extends \Twig_Extension
*/
public function getLogoutUrl($key = null)
{
return $this->generator->getLogoutUrl($key, UrlGeneratorInterface::ABSOLUTE_URL);
return $this->generator->getLogoutUrl($key);
}
/**

View File

@ -21,6 +21,7 @@
<argument type="service" id="serializer.mapping.class_metadata_factory" />
<argument>null</argument> <!-- name converter -->
<argument type="service" id="serializer.property_accessor" />
<argument type="service" id="property_info" on-invalid="ignore" />
<!-- Run after all custom serializers -->
<tag name="serializer.normalizer" priority="-1000" />

View File

@ -68,6 +68,7 @@ $container->loadFromExtension('framework', array(
'enable_annotations' => true,
'name_converter' => 'serializer.name_converter.camel_case_to_snake_case',
),
'property_info' => true,
'ide' => 'file%%link%%format',
'request' => array(
'formats' => array(

View File

@ -41,5 +41,6 @@
<framework:validation enabled="true" cache="validator.mapping.cache.doctrine.apc" />
<framework:annotations cache="file" debug="true" file-cache-dir="%kernel.cache_dir%/annotations" />
<framework:serializer enabled="true" enable-annotations="true" name-converter="serializer.name_converter.camel_case_to_snake_case" />
<framework:property-info />
</framework:config>
</container>

View File

@ -53,6 +53,7 @@ framework:
enabled: true
enable_annotations: true
name_converter: serializer.name_converter.camel_case_to_snake_case
property_info: ~
ide: file%%link%%format
request:
formats:

View File

@ -469,6 +469,7 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertEquals('Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', $argument[0]->getClass());
$this->assertNull($container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1));
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.normalizer.object')->getArgument(1));
$this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3));
}
public function testRegisterSerializerExtractor()

View File

@ -48,7 +48,7 @@ class FilesystemLoaderTest extends TestCase
;
$loader = new FilesystemLoader($locator, $parser);
return $this->assertTrue($loader->exists($template));
$this->assertTrue($loader->exists($template));
}
/**

View File

@ -56,7 +56,7 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
foreach ($deferred as $key => $item) {
if (null === $item->expiry) {
$byLifetime[0][$namespace.$key] = $item->value;
$byLifetime[0 < $item->defaultLifetime ? $item->defaultLifetime : 0][$namespace.$key] = $item->value;
} elseif ($item->expiry > $now) {
$byLifetime[$item->expiry - $now][$namespace.$key] = $item->value;
} else {

View File

@ -151,6 +151,9 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface
return false;
}
}
if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) {
$expiry = time() + $item["\0*\0defaultLifetime"];
}
$this->values[$key] = $value;
$this->expiries[$key] = null !== $expiry ? $expiry : PHP_INT_MAX;

View File

@ -71,7 +71,7 @@ class FilesystemAdapter extends AbstractAdapter
protected function doSave(array $values, $lifetime)
{
$ok = true;
$expiresAt = $lifetime ? time() + $lifetime : PHP_INT_MAX;
$expiresAt = time() + ($lifetime ?: 31557600); // 31557600s = 1 year
foreach ($values as $id => $value) {
$ok = $this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".serialize($value), $expiresAt) && $ok;

View File

@ -144,6 +144,9 @@ class ProxyAdapter implements AdapterInterface
}
$item = (array) $item;
$expiry = $item["\0*\0expiry"];
if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) {
$expiry = time() + $item["\0*\0defaultLifetime"];
}
$innerItem = $item["\0*\0poolHash"] === $this->poolHash ? $item["\0*\0innerItem"] : $this->pool->getItem($this->namespace.$item["\0*\0key"]);
$innerItem->set($item["\0*\0value"]);
$innerItem->expiresAt(null !== $expiry ? \DateTime::createFromFormat('U', $expiry) : null);

View File

@ -11,20 +11,19 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Cache\IntegrationTests\CachePoolTest;
use Symfony\Component\Cache\Adapter\RedisAdapter;
abstract class AbstractRedisAdapterTest extends CachePoolTest
abstract class AbstractRedisAdapterTest extends AdapterTestCase
{
protected static $redis;
public function createCachePool()
public function createCachePool($defaultLifetime = 0)
{
if (defined('HHVM_VERSION')) {
$this->skippedTests['testDeferredSaveWithoutCommit'] = 'Fails on HHVM';
}
return new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__));
return new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
}
public static function setupBeforeClass()

View File

@ -0,0 +1,40 @@
<?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 Cache\IntegrationTests\CachePoolTest;
abstract class AdapterTestCase extends CachePoolTest
{
public function testDefaultLifeTime()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
return;
}
$this->cache = $this->createCachePool(2);
$item = $this->cache->getItem('key.dlt');
$item->set('value');
$this->cache->save($item);
sleep(1);
$item = $this->cache->getItem('key.dlt');
$this->assertTrue($item->isHit());
sleep(2);
$item = $this->cache->getItem('key.dlt');
$this->assertFalse($item->isHit());
}
}

View File

@ -11,12 +11,11 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Cache\IntegrationTests\CachePoolTest;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
class ApcuAdapterTest extends CachePoolTest
class ApcuAdapterTest extends AdapterTestCase
{
public function createCachePool()
public function createCachePool($defaultLifetime = 0)
{
if (defined('HHVM_VERSION')) {
$this->skippedTests['testDeferredSaveWithoutCommit'] = 'Fails on HHVM';
@ -28,7 +27,7 @@ class ApcuAdapterTest extends CachePoolTest
$this->markTestSkipped('Fails transiently on Windows.');
}
return new ApcuAdapter(str_replace('\\', '.', __CLASS__));
return new ApcuAdapter(str_replace('\\', '.', __CLASS__), $defaultLifetime);
}
public function testUnserializable()

View File

@ -11,21 +11,20 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Cache\IntegrationTests\CachePoolTest;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
/**
* @group time-sensitive
*/
class ArrayAdapterTest extends CachePoolTest
class ArrayAdapterTest extends AdapterTestCase
{
protected $skippedTests = array(
'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.',
'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayAdapter is not.',
);
public function createCachePool()
public function createCachePool($defaultLifetime = 0)
{
return new ArrayAdapter();
return new ArrayAdapter($defaultLifetime);
}
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Cache\IntegrationTests\CachePoolTest;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\ChainAdapter;
@ -20,15 +19,15 @@ use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ChainAdapterTest extends CachePoolTest
class ChainAdapterTest extends AdapterTestCase
{
public function createCachePool()
public function createCachePool($defaultLifetime = 0)
{
if (defined('HHVM_VERSION')) {
$this->skippedTests['testDeferredSaveWithoutCommit'] = 'Fails on HHVM';
}
return new ChainAdapter(array(new ArrayAdapter(), new ExternalAdapter(), new FilesystemAdapter()));
return new ChainAdapter(array(new ArrayAdapter($defaultLifetime), new ExternalAdapter(), new FilesystemAdapter('', $defaultLifetime)), $defaultLifetime);
}
/**

View File

@ -11,22 +11,21 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Cache\IntegrationTests\CachePoolTest;
use Doctrine\Common\Cache\ArrayCache;
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
/**
* @group time-sensitive
*/
class DoctrineAdapterTest extends CachePoolTest
class DoctrineAdapterTest extends AdapterTestCase
{
protected $skippedTests = array(
'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayCache is not.',
'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayCache is not.',
);
public function createCachePool()
public function createCachePool($defaultLifetime = 0)
{
return new DoctrineAdapter(new ArrayCache());
return new DoctrineAdapter(new ArrayCache(), '', $defaultLifetime);
}
}

View File

@ -11,21 +11,20 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Cache\IntegrationTests\CachePoolTest;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
/**
* @group time-sensitive
*/
class FilesystemAdapterTest extends CachePoolTest
class FilesystemAdapterTest extends AdapterTestCase
{
public function createCachePool()
public function createCachePool($defaultLifetime = 0)
{
if (defined('HHVM_VERSION')) {
$this->skippedTests['testDeferredSaveWithoutCommit'] = 'Fails on HHVM';
}
return new FilesystemAdapter('sf-cache');
return new FilesystemAdapter('', $defaultLifetime);
}
public static function tearDownAfterClass()

View File

@ -19,8 +19,8 @@ use Symfony\Component\Cache\Adapter\ProxyAdapter;
*/
class NamespacedProxyAdapterTest extends ProxyAdapterTest
{
public function createCachePool()
public function createCachePool($defaultLifetime = 0)
{
return new ProxyAdapter(new ArrayAdapter(), 'foo');
return new ProxyAdapter(new ArrayAdapter(), 'foo', $defaultLifetime);
}
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Cache\IntegrationTests\CachePoolTest;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
@ -20,16 +19,16 @@ use Symfony\Component\Cache\CacheItem;
/**
* @group time-sensitive
*/
class ProxyAdapterTest extends CachePoolTest
class ProxyAdapterTest extends AdapterTestCase
{
protected $skippedTests = array(
'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.',
'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayAdapter is not.',
);
public function createCachePool()
public function createCachePool($defaultLifetime = 0)
{
return new ProxyAdapter(new ArrayAdapter());
return new ProxyAdapter(new ArrayAdapter(), '', $defaultLifetime);
}
/**

View File

@ -166,7 +166,7 @@ class XmlFileLoader extends FileLoader
}
if ($deprecated = $this->getChildren($service, 'deprecated')) {
$definition->setDeprecated(true, $deprecated[0]->nodeValue);
$definition->setDeprecated(true, $deprecated[0]->nodeValue ?: null);
}
$definition->setArguments($this->getArgumentsAsPhp($service, 'argument'));

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="foo" class="Foo">
<deprecated />
</service>
<service id="bar" class="Bar">
<deprecated>The "%service_id%" service is deprecated.</deprecated>
</service>
</services>
</container>

View File

@ -302,6 +302,21 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
$loader->load('tag_with_empty_name.xml');
}
public function testDeprecated()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('services_deprecated.xml');
$this->assertTrue($container->getDefinition('foo')->isDeprecated());
$message = 'The "foo" service is deprecated. You should stop using it, as it will soon be removed.';
$this->assertSame($message, $container->getDefinition('foo')->getDeprecationMessage('foo'));
$this->assertTrue($container->getDefinition('bar')->isDeprecated());
$message = 'The "bar" service is deprecated.';
$this->assertSame($message, $container->getDefinition('bar')->getDeprecationMessage('bar'));
}
public function testConvertDomElementToArray()
{
$doc = new \DOMDocument('1.0');

View File

@ -38,7 +38,7 @@ class Store implements StoreInterface
public function __construct($root)
{
$this->root = $root;
if (!is_dir($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) {
if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) {
throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root));
}
$this->keyCache = new \SplObjectStorage();
@ -52,22 +52,15 @@ class Store implements StoreInterface
{
// unlock everything
foreach ($this->locks as $lock) {
if (file_exists($lock)) {
@unlink($lock);
}
flock($lock, LOCK_UN);
fclose($lock);
}
$error = error_get_last();
if (1 === $error['type'] && false === headers_sent()) {
// send a 503
header('HTTP/1.0 503 Service Unavailable');
header('Retry-After: 10');
echo '503 Service Unavailable';
}
$this->locks = array();
}
/**
* Locks the cache for a given Request.
* Tries to lock the cache for a given Request, without blocking.
*
* @param Request $request A Request instance
*
@ -75,21 +68,24 @@ class Store implements StoreInterface
*/
public function lock(Request $request)
{
$path = $this->getPath($this->getCacheKey($request).'.lck');
if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
return false;
$key = $this->getCacheKey($request);
if (!isset($this->locks[$key])) {
$path = $this->getPath($key);
if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
return $path;
}
$h = fopen($path, 'cb');
if (!flock($h, LOCK_EX | LOCK_NB)) {
fclose($h);
return $path;
}
$this->locks[$key] = $h;
}
$lock = @fopen($path, 'x');
if (false !== $lock) {
fclose($lock);
$this->locks[] = $path;
return true;
}
return !file_exists($path) ?: $path;
return true;
}
/**
@ -101,17 +97,37 @@ class Store implements StoreInterface
*/
public function unlock(Request $request)
{
$file = $this->getPath($this->getCacheKey($request).'.lck');
$key = $this->getCacheKey($request);
return is_file($file) ? @unlink($file) : false;
if (isset($this->locks[$key])) {
flock($this->locks[$key], LOCK_UN);
fclose($this->locks[$key]);
unset($this->locks[$key]);
return true;
}
return false;
}
public function isLocked(Request $request)
{
$path = $this->getPath($this->getCacheKey($request).'.lck');
clearstatcache(true, $path);
$key = $this->getCacheKey($request);
return is_file($path);
if (isset($this->locks[$key])) {
return true; // shortcut if lock held by this process
}
if (!file_exists($path = $this->getPath($key))) {
return false;
}
$h = fopen($path, 'rb');
flock($h, LOCK_EX | LOCK_NB, $wouldBlock);
flock($h, LOCK_UN); // release the lock we just acquired
fclose($h);
return (bool) $wouldBlock;
}
/**
@ -144,7 +160,7 @@ class Store implements StoreInterface
}
list($req, $headers) = $match;
if (is_file($body = $this->getPath($headers['x-content-digest'][0]))) {
if (file_exists($body = $this->getPath($headers['x-content-digest'][0]))) {
return $this->restoreResponse($headers, $body);
}
@ -291,7 +307,7 @@ class Store implements StoreInterface
*/
private function getMetadata($key)
{
if (false === $entries = $this->load($key)) {
if (!$entries = $this->load($key)) {
return array();
}
@ -307,7 +323,15 @@ class Store implements StoreInterface
*/
public function purge($url)
{
if (is_file($path = $this->getPath($this->getCacheKey(Request::create($url))))) {
$key = $this->getCacheKey(Request::create($url));
if (isset($this->locks[$key])) {
flock($this->locks[$key], LOCK_UN);
fclose($this->locks[$key]);
unset($this->locks[$key]);
}
if (file_exists($path = $this->getPath($key))) {
unlink($path);
return true;
@ -327,7 +351,7 @@ class Store implements StoreInterface
{
$path = $this->getPath($key);
return is_file($path) ? file_get_contents($path) : false;
return file_exists($path) ? file_get_contents($path) : false;
}
/**
@ -341,23 +365,36 @@ class Store implements StoreInterface
private function save($key, $data)
{
$path = $this->getPath($key);
if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
return false;
}
$tmpFile = tempnam(dirname($path), basename($path));
if (false === $fp = @fopen($tmpFile, 'wb')) {
return false;
}
@fwrite($fp, $data);
@fclose($fp);
if (isset($this->locks[$key])) {
$fp = $this->locks[$key];
@ftruncate($fp, 0);
@fseek($fp, 0);
$len = @fwrite($fp, $data);
if (strlen($data) !== $len) {
@ftruncate($fp, 0);
if ($data != file_get_contents($tmpFile)) {
return false;
}
return false;
}
} else {
if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
return false;
}
if (false === @rename($tmpFile, $path)) {
return false;
$tmpFile = tempnam(dirname($path), basename($path));
if (false === $fp = @fopen($tmpFile, 'wb')) {
return false;
}
@fwrite($fp, $data);
@fclose($fp);
if ($data != file_get_contents($tmpFile)) {
return false;
}
if (false === @rename($tmpFile, $path)) {
return false;
}
}
@chmod($path, 0666 & ~umask());

View File

@ -50,6 +50,9 @@ class HttpCacheTestCase extends \PHPUnit_Framework_TestCase
protected function tearDown()
{
if ($this->cache) {
$this->cache->getStore()->cleanup();
}
$this->kernel = null;
$this->cache = null;
$this->caches = null;

View File

@ -19,6 +19,10 @@ class StoreTest extends \PHPUnit_Framework_TestCase
{
protected $request;
protected $response;
/**
* @var Store
*/
protected $store;
protected function setUp()

View File

@ -14,7 +14,7 @@ previous version of the component will break with this version.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/ldap/index.html)
* [Documentation](https://symfony.com/doc/current/components/ldap)
* [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)

View File

@ -52,27 +52,30 @@ class WindowsPipes extends AbstractPipes
Process::STDERR => Process::ERR,
);
$tmpDir = sys_get_temp_dir();
if (!@fopen($file = $tmpDir.'\\sf_proc_00.check', 'wb')) {
throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
}
@unlink($file);
$error = 'unknown reason';
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
for ($i = 0;; ++$i) {
foreach ($pipes as $pipe => $name) {
$file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
if (file_exists($file) && !@unlink($file)) {
if (file_exists($file) && !unlink($file)) {
continue 2;
}
$h = @fopen($file, 'xb');
$h = fopen($file, 'xb');
if (!$h && false === strpos($error, 'File exists')) {
restore_error_handler();
throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error));
}
if (!$h || !$this->fileHandles[$pipe] = fopen($file, 'rb')) {
continue 2;
}
if (isset($this->files[$pipe])) {
@unlink($this->files[$pipe]);
unlink($this->files[$pipe]);
}
$this->files[$pipe] = $file;
}
break;
}
restore_error_handler();
}
parent::__construct($input);