Skip to content

Commit

Permalink
Merge branch '4.2' into 4.3
Browse files Browse the repository at this point in the history
* 4.2:
  [Cache] replace getNsSeparator by NS_SEPARATOR on AbstractTrait
  [Cache] fix versioning with SimpleCacheAdapter
  Fix expired lock not cleaned
  [HttpFoundation] Fix SA/phpdoc JsonResponse
  SimpleCacheAdapter fails to cache any item if a namespace is used
  validate composite constraints in all groups
  [Serializer] Handle true and false appropriately in CSV encoder
  Fix binary operation `+`, `-` or `*` on string
  [VarDumper] fix dumping objects that implement __debugInfo()
  [Routing] fix absolute url generation when scheme is not known
  • Loading branch information
nicolas-grekas committed Jun 17, 2019
2 parents 12b852f + 6b61439 commit 953ac3e
Show file tree
Hide file tree
Showing 30 changed files with 295 additions and 68 deletions.
7 changes: 6 additions & 1 deletion src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
Expand Up @@ -26,6 +26,11 @@
*/
abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
{
/**
* @internal
*/
protected const NS_SEPARATOR = ':';

use AbstractAdapterTrait;
use ContractsTrait;

Expand All @@ -34,7 +39,7 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg

protected function __construct(string $namespace = '', int $defaultLifetime = 0)
{
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
$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));
}
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/Cache/Adapter/Psr16Adapter.php
Expand Up @@ -23,6 +23,11 @@
*/
class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
{
/**
* @internal
*/
protected const NS_SEPARATOR = '_';

use ProxyTrait;

private $miss;
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/Cache/Simple/AbstractCache.php
Expand Up @@ -27,6 +27,11 @@
*/
abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
{
/**
* @internal
*/
protected const NS_SEPARATOR = ':';

use AbstractTrait {
deleteItems as private;
AbstractTrait::deleteItem as delete;
Expand Down
Expand Up @@ -56,7 +56,7 @@ public function testLongKeyVersioning()
$reflectionProperty->setValue($cache, true);

// Versioning enabled
$this->assertEquals('--------------------------:1/------------', $reflectionMethod->invokeArgs($cache, [str_repeat('-', 12)]));
$this->assertEquals('--------------------------:1:------------', $reflectionMethod->invokeArgs($cache, [str_repeat('-', 12)]));
$this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 12)])));
$this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 23)])));
$this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 40)])));
Expand Down
11 changes: 11 additions & 0 deletions src/Symfony/Component/Cache/Tests/Adapter/Psr16AdapterTest.php
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Component\Cache\Tests\Adapter;

use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\Psr16Adapter;
use Symfony\Component\Cache\Psr16Cache;
Expand All @@ -28,4 +29,14 @@ public function createCachePool($defaultLifetime = 0)
{
return new Psr16Adapter(new Psr16Cache(new FilesystemAdapter()), '', $defaultLifetime);
}

public function testValidCacheKeyWithNamespace()
{
$cache = new Psr16Adapter(new Psr16Cache(new ArrayAdapter()), 'some_namespace', 0);
$item = $cache->getItem('my_key');
$item->set('someValue');
$cache->save($item);

$this->assertTrue($cache->getItem('my_key')->isHit(), 'Stored item is successfully retrieved.');
}
}
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\Cache\Adapter\SimpleCacheAdapter;
use Symfony\Component\Cache\Simple\FilesystemCache;
use Symfony\Component\Cache\Simple\ArrayCache;

/**
* @group time-sensitive
Expand All @@ -28,4 +29,14 @@ public function createCachePool($defaultLifetime = 0)
{
return new SimpleCacheAdapter(new FilesystemCache(), '', $defaultLifetime);
}

public function testValidCacheKeyWithNamespace()
{
$cache = new SimpleCacheAdapter(new ArrayCache(), 'some_namespace', 0);
$item = $cache->getItem('my_key');
$item->set('someValue');
$cache->save($item);

$this->assertTrue($cache->getItem('my_key')->isHit(), 'Stored item is successfully retrieved.');
}
}
16 changes: 8 additions & 8 deletions src/Symfony/Component/Cache/Traits/AbstractTrait.php
Expand Up @@ -107,9 +107,9 @@ public function clear()
{
$this->deferred = [];
if ($cleared = $this->versioningIsEnabled) {
$namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), ':', 5);
$namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::NS_SEPARATOR, 5);
try {
$cleared = $this->doSave(['/'.$this->namespace => $namespaceVersion], 0);
$cleared = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0);
} catch (\Exception $e) {
$cleared = false;
}
Expand Down Expand Up @@ -243,14 +243,14 @@ private function getId($key)
{
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
$this->ids = [];
$this->namespaceVersion = '1/';
$this->namespaceVersion = '1'.static::NS_SEPARATOR;
try {
foreach ($this->doFetch(['/'.$this->namespace]) as $v) {
foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
$this->namespaceVersion = $v;
}
if ('1:' === $this->namespaceVersion) {
$this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), ':', 5);
$this->doSave(['@'.$this->namespace => $this->namespaceVersion], 0);
if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) {
$this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), static::NS_SEPARATOR, 5);
$this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0);
}
} catch (\Exception $e) {
}
Expand All @@ -267,7 +267,7 @@ private function getId($key)
}
if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
// Use MD5 to favor speed over security, which is not an issue here
$this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), ':', -(\strlen($this->namespaceVersion) + 2));
$this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2));
$id = $this->namespace.$this->namespaceVersion.$id;
}

Expand Down
Expand Up @@ -21,7 +21,7 @@ class BirthdayType extends AbstractType
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('years', range(date('Y') - 120, date('Y')));
$resolver->setDefault('years', range((int) date('Y') - 120, date('Y')));

$resolver->setAllowedTypes('years', 'array');
}
Expand Down
Expand Up @@ -259,7 +259,7 @@ public function configureOptions(OptionsResolver $resolver)
};

$resolver->setDefaults([
'years' => range(date('Y') - 5, date('Y') + 5),
'years' => range((int) date('Y') - 5, (int) date('Y') + 5),
'months' => range(1, 12),
'days' => range(1, 31),
'widget' => 'choice',
Expand Down
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\Form\FormInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\ConstraintValidator;
Expand Down Expand Up @@ -90,7 +91,9 @@ public function validate($form, Constraint $formConstraint)
$validator->atPath('data')->validate($form->getData(), $constraint, $group);

// Prevent duplicate validation
continue 2;
if (!$constraint instanceof Composite) {
continue 2;
}
}
}
}
Expand Down
Expand Up @@ -24,6 +24,7 @@
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\SubmitButtonBuilder;
use Symfony\Component\Translation\IdentityTranslator;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
Expand Down Expand Up @@ -714,6 +715,63 @@ public function testCauseForNotAllowedExtraFieldsIsTheFormConstraint()
$this->assertSame($constraint, $context->getViolations()->get(0)->getConstraint());
}

public function testNonCompositeConstraintValidatedOnce()
{
$form = $this
->getBuilder('form', null, [
'constraints' => [new NotBlank(['groups' => ['foo', 'bar']])],
'validation_groups' => ['foo', 'bar'],
])
->setCompound(false)
->getForm();
$form->submit('');

$context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator());
$this->validator->initialize($context);
$this->validator->validate($form, new Form());

$this->assertCount(1, $context->getViolations());
$this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage());
$this->assertSame('data', $context->getViolations()[0]->getPropertyPath());
}

public function testCompositeConstraintValidatedInEachGroup()
{
$form = $this->getBuilder('form', null, [
'constraints' => [
new Collection([
'field1' => new NotBlank([
'groups' => ['field1'],
]),
'field2' => new NotBlank([
'groups' => ['field2'],
]),
]),
],
'validation_groups' => ['field1', 'field2'],
])
->setData([])
->setCompound(true)
->setDataMapper(new PropertyPathMapper())
->getForm();
$form->add($this->getForm('field1'));
$form->add($this->getForm('field2'));
$form->submit([
'field1' => '',
'field2' => '',
]);

$context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator());
$this->validator->initialize($context);
$this->validator->validate($form, new Form());

$this->assertCount(2, $context->getViolations());
$this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage());
$this->assertSame('data[field1]', $context->getViolations()[0]->getPropertyPath());
$this->assertSame('This value should not be blank.', $context->getViolations()[1]->getMessage());
$this->assertSame('data[field2]', $context->getViolations()[1]->getPropertyPath());
}

protected function createValidator()
{
return new FormValidator();
Expand Down
17 changes: 14 additions & 3 deletions src/Symfony/Component/HttpFoundation/JsonResponse.php
Expand Up @@ -55,10 +55,10 @@ public function __construct($data = null, int $status = 200, array $headers = []
*
* Example:
*
* return JsonResponse::create($data, 200)
* return JsonResponse::create(['key' => 'value'])
* ->setSharedMaxAge(300);
*
* @param mixed $data The json response data
* @param mixed $data The JSON response data
* @param int $status The response status code
* @param array $headers An array of response headers
*
Expand All @@ -70,7 +70,18 @@ public static function create($data = null, $status = 200, $headers = [])
}

/**
* Make easier the creation of JsonResponse from raw json.
* Factory method for chainability.
*
* Example:
*
* return JsonResponse::fromJsonString('{"key": "value"}')
* ->setSharedMaxAge(300);
*
* @param string|null $data The JSON response string
* @param int $status The response status code
* @param array $headers An array of response headers
*
* @return static
*/
public static function fromJsonString($data = null, $status = 200, $headers = [])
{
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/HttpFoundation/Response.php
Expand Up @@ -684,7 +684,7 @@ public function getAge(): int
return (int) $age;
}

return max(time() - $this->getDate()->format('U'), 0);
return max(time() - (int) $this->getDate()->format('U'), 0);
}

/**
Expand Down Expand Up @@ -764,7 +764,7 @@ public function getMaxAge(): ?int
}

if (null !== $this->getExpires()) {
return (int) ($this->getExpires()->format('U') - $this->getDate()->format('U'));
return (int) $this->getExpires()->format('U') - (int) $this->getDate()->format('U');
}

return null;
Expand Down
Expand Up @@ -85,7 +85,7 @@ public function add(Response $response)
$this->storeRelativeAgeDirective('s-maxage', $response->headers->getCacheControlDirective('s-maxage') ?: $response->headers->getCacheControlDirective('max-age'), $age);

$expires = $response->getExpires();
$expires = null !== $expires ? $expires->format('U') - $response->getDate()->format('U') : null;
$expires = null !== $expires ? (int) $expires->format('U') - (int) $response->getDate()->format('U') : null;
$this->storeRelativeAgeDirective('expires', $expires >= 0 ? $expires : null, 0);
}

Expand Down
Expand Up @@ -25,7 +25,7 @@ class DayOfYearTransformer extends Transformer
*/
public function format(\DateTime $dateTime, int $length): string
{
$dayOfYear = $dateTime->format('z') + 1;
$dayOfYear = (int) $dateTime->format('z') + 1;

return $this->padLeft($dayOfYear, $length);
}
Expand Down
Expand Up @@ -315,7 +315,7 @@ protected function calculateUnixTimestamp(\DateTime $dateTime, array $options)
preg_match_all($this->regExp, $this->pattern, $matches);
if (\in_array('yy', $matches[0])) {
$dateTime->setTimestamp(time());
$year = $year > $dateTime->format('y') + 20 ? 1900 + $year : 2000 + $year;
$year = $year > (int) $dateTime->format('y') + 20 ? 1900 + $year : 2000 + $year;
}

$dateTime->setDate($year, $month, $day);
Expand Down
10 changes: 10 additions & 0 deletions src/Symfony/Component/Lock/Lock.php
Expand Up @@ -83,6 +83,11 @@ public function acquire($blocking = false)
}

if ($this->key->isExpired()) {
try {
$this->release();
} catch (\Exception $e) {
// swallow exception to not hide the original issue
}
throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $this->key));
}

Expand Down Expand Up @@ -120,6 +125,11 @@ public function refresh($ttl = null)
$this->dirty = true;

if ($this->key->isExpired()) {
try {
$this->release();
} catch (\Exception $e) {
// swallow exception to not hide the original issue
}
throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $this->key));
}

Expand Down
8 changes: 4 additions & 4 deletions src/Symfony/Component/Lock/Store/CombinedStore.php
Expand Up @@ -16,7 +16,6 @@
use Psr\Log\NullLogger;
use Symfony\Component\Lock\Exception\InvalidArgumentException;
use Symfony\Component\Lock\Exception\LockConflictedException;
use Symfony\Component\Lock\Exception\LockExpiredException;
use Symfony\Component\Lock\Exception\NotSupportedException;
use Symfony\Component\Lock\Key;
use Symfony\Component\Lock\StoreInterface;
Expand All @@ -30,6 +29,7 @@
class CombinedStore implements StoreInterface, LoggerAwareInterface
{
use LoggerAwareTrait;
use ExpiringStoreTrait;

/** @var StoreInterface[] */
private $stores;
Expand Down Expand Up @@ -78,6 +78,8 @@ public function save(Key $key)
}
}

$this->checkNotExpired($key);

if ($this->strategy->isMet($successCount, $storesCount)) {
return;
}
Expand Down Expand Up @@ -125,9 +127,7 @@ public function putOffExpiration(Key $key, $ttl)
}
}

if ($key->isExpired()) {
throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $key));
}
$this->checkNotExpired($key);

if ($this->strategy->isMet($successCount, $storesCount)) {
return;
Expand Down

0 comments on commit 953ac3e

Please sign in to comment.