Skip to content

Commit 06c17cd

Browse files
Merge branch '3.4' into 4.2
* 3.4: 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
2 parents 37fa45b + cfc8ac0 commit 06c17cd

File tree

28 files changed

+285
-68
lines changed

28 files changed

+285
-68
lines changed

src/Symfony/Component/Cache/Adapter/AbstractAdapter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
3838

3939
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
4040
{
41-
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
41+
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::getNsSeparator();
4242
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
4343
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
4444
}

src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,12 @@ protected function doSave(array $values, $lifetime)
7575
{
7676
return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
7777
}
78+
79+
/**
80+
* @return string the namespace separator for cache keys
81+
*/
82+
protected static function getNsSeparator()
83+
{
84+
return '_';
85+
}
7886
}

src/Symfony/Component/Cache/Tests/Adapter/MaxIdLengthAdapterTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function testLongKeyVersioning()
5656
$reflectionProperty->setValue($cache, true);
5757

5858
// Versioning enabled
59-
$this->assertEquals('--------------------------:1/------------', $reflectionMethod->invokeArgs($cache, [str_repeat('-', 12)]));
59+
$this->assertEquals('--------------------------:1:------------', $reflectionMethod->invokeArgs($cache, [str_repeat('-', 12)]));
6060
$this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 12)])));
6161
$this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 23)])));
6262
$this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 40)])));

src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
1515
use Symfony\Component\Cache\Adapter\SimpleCacheAdapter;
16+
use Symfony\Component\Cache\Simple\ArrayCache;
1617
use Symfony\Component\Cache\Simple\Psr6Cache;
1718

1819
/**
@@ -28,4 +29,14 @@ public function createCachePool($defaultLifetime = 0)
2829
{
2930
return new SimpleCacheAdapter(new Psr6Cache(new FilesystemAdapter()), '', $defaultLifetime);
3031
}
32+
33+
public function testValidCacheKeyWithNamespace()
34+
{
35+
$cache = new SimpleCacheAdapter(new ArrayCache(), 'some_namespace', 0);
36+
$item = $cache->getItem('my_key');
37+
$item->set('someValue');
38+
$cache->save($item);
39+
40+
$this->assertTrue($cache->getItem('my_key')->isHit(), 'Stored item is successfully retrieved.');
41+
}
3142
}

src/Symfony/Component/Cache/Traits/AbstractTrait.php

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ public function clear()
107107
{
108108
$this->deferred = [];
109109
if ($cleared = $this->versioningIsEnabled) {
110-
$namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), ':', 5);
110+
$namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::getNsSeparator(), 5);
111111
try {
112-
$cleared = $this->doSave(['/'.$this->namespace => $namespaceVersion], 0);
112+
$cleared = $this->doSave([static::getNsSeparator().$this->namespace => $namespaceVersion], 0);
113113
} catch (\Exception $e) {
114114
$cleared = false;
115115
}
@@ -242,14 +242,14 @@ private function getId($key)
242242
{
243243
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
244244
$this->ids = [];
245-
$this->namespaceVersion = '1/';
245+
$this->namespaceVersion = '1'.static::getNsSeparator();
246246
try {
247-
foreach ($this->doFetch(['/'.$this->namespace]) as $v) {
247+
foreach ($this->doFetch([static::getNsSeparator().$this->namespace]) as $v) {
248248
$this->namespaceVersion = $v;
249249
}
250-
if ('1:' === $this->namespaceVersion) {
251-
$this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), ':', 5);
252-
$this->doSave(['@'.$this->namespace => $this->namespaceVersion], 0);
250+
if ('1'.static::getNsSeparator() === $this->namespaceVersion) {
251+
$this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), static::getNsSeparator(), 5);
252+
$this->doSave([static::getNsSeparator().$this->namespace => $this->namespaceVersion], 0);
253253
}
254254
} catch (\Exception $e) {
255255
}
@@ -266,7 +266,7 @@ private function getId($key)
266266
}
267267
if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
268268
// Use MD5 to favor speed over security, which is not an issue here
269-
$this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), ':', -(\strlen($this->namespaceVersion) + 2));
269+
$this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), static::getNsSeparator(), -(\strlen($this->namespaceVersion) + 2));
270270
$id = $this->namespace.$this->namespaceVersion.$id;
271271
}
272272

@@ -280,4 +280,12 @@ public static function handleUnserializeCallback($class)
280280
{
281281
throw new \DomainException('Class not found: '.$class);
282282
}
283+
284+
/**
285+
* @return string the namespace separator for cache keys
286+
*/
287+
protected static function getNsSeparator()
288+
{
289+
return ':';
290+
}
283291
}

src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class BirthdayType extends AbstractType
2121
*/
2222
public function configureOptions(OptionsResolver $resolver)
2323
{
24-
$resolver->setDefault('years', range(date('Y') - 120, date('Y')));
24+
$resolver->setDefault('years', range((int) date('Y') - 120, date('Y')));
2525

2626
$resolver->setAllowedTypes('years', 'array');
2727
}

src/Symfony/Component/Form/Extension/Core/Type/DateType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ public function configureOptions(OptionsResolver $resolver)
259259
};
260260

261261
$resolver->setDefaults([
262-
'years' => range(date('Y') - 5, date('Y') + 5),
262+
'years' => range((int) date('Y') - 5, (int) date('Y') + 5),
263263
'months' => range(1, 12),
264264
'days' => range(1, 31),
265265
'widget' => 'choice',

src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Form\FormInterface;
1515
use Symfony\Component\Validator\Constraint;
16+
use Symfony\Component\Validator\Constraints\Composite;
1617
use Symfony\Component\Validator\Constraints\GroupSequence;
1718
use Symfony\Component\Validator\Constraints\Valid;
1819
use Symfony\Component\Validator\ConstraintValidator;
@@ -90,7 +91,9 @@ public function validate($form, Constraint $formConstraint)
9091
$validator->atPath('data')->validate($form->getData(), $constraint, $group);
9192

9293
// Prevent duplicate validation
93-
continue 2;
94+
if (!$constraint instanceof Composite) {
95+
continue 2;
96+
}
9497
}
9598
}
9699
}

src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Symfony\Component\Form\FormInterface;
2525
use Symfony\Component\Form\SubmitButtonBuilder;
2626
use Symfony\Component\Translation\IdentityTranslator;
27+
use Symfony\Component\Validator\Constraints\Collection;
2728
use Symfony\Component\Validator\Constraints\GroupSequence;
2829
use Symfony\Component\Validator\Constraints\NotBlank;
2930
use Symfony\Component\Validator\Constraints\NotNull;
@@ -673,6 +674,63 @@ public function testCauseForNotAllowedExtraFieldsIsTheFormConstraint()
673674
$this->assertSame($constraint, $context->getViolations()->get(0)->getConstraint());
674675
}
675676

677+
public function testNonCompositeConstraintValidatedOnce()
678+
{
679+
$form = $this
680+
->getBuilder('form', null, [
681+
'constraints' => [new NotBlank(['groups' => ['foo', 'bar']])],
682+
'validation_groups' => ['foo', 'bar'],
683+
])
684+
->setCompound(false)
685+
->getForm();
686+
$form->submit('');
687+
688+
$context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator());
689+
$this->validator->initialize($context);
690+
$this->validator->validate($form, new Form());
691+
692+
$this->assertCount(1, $context->getViolations());
693+
$this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage());
694+
$this->assertSame('data', $context->getViolations()[0]->getPropertyPath());
695+
}
696+
697+
public function testCompositeConstraintValidatedInEachGroup()
698+
{
699+
$form = $this->getBuilder('form', null, [
700+
'constraints' => [
701+
new Collection([
702+
'field1' => new NotBlank([
703+
'groups' => ['field1'],
704+
]),
705+
'field2' => new NotBlank([
706+
'groups' => ['field2'],
707+
]),
708+
]),
709+
],
710+
'validation_groups' => ['field1', 'field2'],
711+
])
712+
->setData([])
713+
->setCompound(true)
714+
->setDataMapper(new PropertyPathMapper())
715+
->getForm();
716+
$form->add($this->getForm('field1'));
717+
$form->add($this->getForm('field2'));
718+
$form->submit([
719+
'field1' => '',
720+
'field2' => '',
721+
]);
722+
723+
$context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator());
724+
$this->validator->initialize($context);
725+
$this->validator->validate($form, new Form());
726+
727+
$this->assertCount(2, $context->getViolations());
728+
$this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage());
729+
$this->assertSame('data[field1]', $context->getViolations()[0]->getPropertyPath());
730+
$this->assertSame('This value should not be blank.', $context->getViolations()[1]->getMessage());
731+
$this->assertSame('data[field2]', $context->getViolations()[1]->getPropertyPath());
732+
}
733+
676734
protected function createValidator()
677735
{
678736
return new FormValidator();

src/Symfony/Component/HttpFoundation/JsonResponse.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ public function __construct($data = null, int $status = 200, array $headers = []
5555
*
5656
* Example:
5757
*
58-
* return JsonResponse::create($data, 200)
58+
* return JsonResponse::create(['key' => 'value'])
5959
* ->setSharedMaxAge(300);
6060
*
61-
* @param mixed $data The json response data
61+
* @param mixed $data The JSON response data
6262
* @param int $status The response status code
6363
* @param array $headers An array of response headers
6464
*
@@ -70,7 +70,18 @@ public static function create($data = null, $status = 200, $headers = [])
7070
}
7171

7272
/**
73-
* Make easier the creation of JsonResponse from raw json.
73+
* Factory method for chainability.
74+
*
75+
* Example:
76+
*
77+
* return JsonResponse::fromJsonString('{"key": "value"}')
78+
* ->setSharedMaxAge(300);
79+
*
80+
* @param string|null $data The JSON response string
81+
* @param int $status The response status code
82+
* @param array $headers An array of response headers
83+
*
84+
* @return static
7485
*/
7586
public static function fromJsonString($data = null, $status = 200, $headers = [])
7687
{

src/Symfony/Component/HttpFoundation/Response.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ public function getAge(): int
684684
return (int) $age;
685685
}
686686

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

690690
/**
@@ -764,7 +764,7 @@ public function getMaxAge(): ?int
764764
}
765765

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

770770
return null;

src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public function add(Response $response)
8585
$this->storeRelativeAgeDirective('s-maxage', $response->headers->getCacheControlDirective('s-maxage') ?: $response->headers->getCacheControlDirective('max-age'), $age);
8686

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

src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfYearTransformer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class DayOfYearTransformer extends Transformer
2525
*/
2626
public function format(\DateTime $dateTime, int $length): string
2727
{
28-
$dayOfYear = $dateTime->format('z') + 1;
28+
$dayOfYear = (int) $dateTime->format('z') + 1;
2929

3030
return $this->padLeft($dayOfYear, $length);
3131
}

src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ protected function calculateUnixTimestamp(\DateTime $dateTime, array $options)
315315
preg_match_all($this->regExp, $this->pattern, $matches);
316316
if (\in_array('yy', $matches[0])) {
317317
$dateTime->setTimestamp(time());
318-
$year = $year > $dateTime->format('y') + 20 ? 1900 + $year : 2000 + $year;
318+
$year = $year > (int) $dateTime->format('y') + 20 ? 1900 + $year : 2000 + $year;
319319
}
320320

321321
$dateTime->setDate($year, $month, $day);

src/Symfony/Component/Lock/Lock.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ public function acquire($blocking = false)
8383
}
8484

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

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

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

src/Symfony/Component/Lock/Store/CombinedStore.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use Psr\Log\NullLogger;
1717
use Symfony\Component\Lock\Exception\InvalidArgumentException;
1818
use Symfony\Component\Lock\Exception\LockConflictedException;
19-
use Symfony\Component\Lock\Exception\LockExpiredException;
2019
use Symfony\Component\Lock\Exception\NotSupportedException;
2120
use Symfony\Component\Lock\Key;
2221
use Symfony\Component\Lock\StoreInterface;
@@ -30,6 +29,7 @@
3029
class CombinedStore implements StoreInterface, LoggerAwareInterface
3130
{
3231
use LoggerAwareTrait;
32+
use ExpiringStoreTrait;
3333

3434
/** @var StoreInterface[] */
3535
private $stores;
@@ -78,6 +78,8 @@ public function save(Key $key)
7878
}
7979
}
8080

81+
$this->checkNotExpired($key);
82+
8183
if ($this->strategy->isMet($successCount, $storesCount)) {
8284
return;
8385
}
@@ -125,9 +127,7 @@ public function putOffExpiration(Key $key, $ttl)
125127
}
126128
}
127129

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

132132
if ($this->strategy->isMet($successCount, $storesCount)) {
133133
return;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Lock\Store;
13+
14+
use Symfony\Component\Lock\Exception\LockExpiredException;
15+
use Symfony\Component\Lock\Key;
16+
17+
trait ExpiringStoreTrait
18+
{
19+
private function checkNotExpired(Key $key)
20+
{
21+
if ($key->isExpired()) {
22+
try {
23+
$this->delete($key);
24+
} catch (\Exception $e) {
25+
// swallow exception to not hide the original issue
26+
}
27+
throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key));
28+
}
29+
}
30+
}

0 commit comments

Comments
 (0)