Skip to content

Commit 953ac3e

Browse files
Merge branch '4.2' into 4.3
* 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
2 parents 12b852f + 6b61439 commit 953ac3e

File tree

30 files changed

+295
-68
lines changed

30 files changed

+295
-68
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
*/
2727
abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
2828
{
29+
/**
30+
* @internal
31+
*/
32+
protected const NS_SEPARATOR = ':';
33+
2934
use AbstractAdapterTrait;
3035
use ContractsTrait;
3136

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

3540
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
3641
{
37-
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
42+
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR;
3843
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
3944
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
4045
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
*/
2424
class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
2525
{
26+
/**
27+
* @internal
28+
*/
29+
protected const NS_SEPARATOR = '_';
30+
2631
use ProxyTrait;
2732

2833
private $miss;

src/Symfony/Component/Cache/Simple/AbstractCache.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
*/
2828
abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
2929
{
30+
/**
31+
* @internal
32+
*/
33+
protected const NS_SEPARATOR = ':';
34+
3035
use AbstractTrait {
3136
deleteItems as private;
3237
AbstractTrait::deleteItem as delete;

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/Psr16AdapterTest.php

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

1212
namespace Symfony\Component\Cache\Tests\Adapter;
1313

14+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
1415
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
1516
use Symfony\Component\Cache\Adapter\Psr16Adapter;
1617
use Symfony\Component\Cache\Psr16Cache;
@@ -28,4 +29,14 @@ public function createCachePool($defaultLifetime = 0)
2829
{
2930
return new Psr16Adapter(new Psr16Cache(new FilesystemAdapter()), '', $defaultLifetime);
3031
}
32+
33+
public function testValidCacheKeyWithNamespace()
34+
{
35+
$cache = new Psr16Adapter(new Psr16Cache(new ArrayAdapter()), '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/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\SimpleCacheAdapter;
1515
use Symfony\Component\Cache\Simple\FilesystemCache;
16+
use Symfony\Component\Cache\Simple\ArrayCache;
1617

1718
/**
1819
* @group time-sensitive
@@ -28,4 +29,14 @@ public function createCachePool($defaultLifetime = 0)
2829
{
2930
return new SimpleCacheAdapter(new FilesystemCache(), '', $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: 8 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::NS_SEPARATOR, 5);
111111
try {
112-
$cleared = $this->doSave(['/'.$this->namespace => $namespaceVersion], 0);
112+
$cleared = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0);
113113
} catch (\Exception $e) {
114114
$cleared = false;
115115
}
@@ -243,14 +243,14 @@ private function getId($key)
243243
{
244244
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
245245
$this->ids = [];
246-
$this->namespaceVersion = '1/';
246+
$this->namespaceVersion = '1'.static::NS_SEPARATOR;
247247
try {
248-
foreach ($this->doFetch(['/'.$this->namespace]) as $v) {
248+
foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
249249
$this->namespaceVersion = $v;
250250
}
251-
if ('1:' === $this->namespaceVersion) {
252-
$this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), ':', 5);
253-
$this->doSave(['@'.$this->namespace => $this->namespaceVersion], 0);
251+
if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) {
252+
$this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), static::NS_SEPARATOR, 5);
253+
$this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0);
254254
}
255255
} catch (\Exception $e) {
256256
}
@@ -267,7 +267,7 @@ private function getId($key)
267267
}
268268
if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
269269
// Use MD5 to favor speed over security, which is not an issue here
270-
$this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), ':', -(\strlen($this->namespaceVersion) + 2));
270+
$this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2));
271271
$id = $this->namespace.$this->namespaceVersion.$id;
272272
}
273273

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;
@@ -714,6 +715,63 @@ public function testCauseForNotAllowedExtraFieldsIsTheFormConstraint()
714715
$this->assertSame($constraint, $context->getViolations()->get(0)->getConstraint());
715716
}
716717

718+
public function testNonCompositeConstraintValidatedOnce()
719+
{
720+
$form = $this
721+
->getBuilder('form', null, [
722+
'constraints' => [new NotBlank(['groups' => ['foo', 'bar']])],
723+
'validation_groups' => ['foo', 'bar'],
724+
])
725+
->setCompound(false)
726+
->getForm();
727+
$form->submit('');
728+
729+
$context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator());
730+
$this->validator->initialize($context);
731+
$this->validator->validate($form, new Form());
732+
733+
$this->assertCount(1, $context->getViolations());
734+
$this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage());
735+
$this->assertSame('data', $context->getViolations()[0]->getPropertyPath());
736+
}
737+
738+
public function testCompositeConstraintValidatedInEachGroup()
739+
{
740+
$form = $this->getBuilder('form', null, [
741+
'constraints' => [
742+
new Collection([
743+
'field1' => new NotBlank([
744+
'groups' => ['field1'],
745+
]),
746+
'field2' => new NotBlank([
747+
'groups' => ['field2'],
748+
]),
749+
]),
750+
],
751+
'validation_groups' => ['field1', 'field2'],
752+
])
753+
->setData([])
754+
->setCompound(true)
755+
->setDataMapper(new PropertyPathMapper())
756+
->getForm();
757+
$form->add($this->getForm('field1'));
758+
$form->add($this->getForm('field2'));
759+
$form->submit([
760+
'field1' => '',
761+
'field2' => '',
762+
]);
763+
764+
$context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator());
765+
$this->validator->initialize($context);
766+
$this->validator->validate($form, new Form());
767+
768+
$this->assertCount(2, $context->getViolations());
769+
$this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage());
770+
$this->assertSame('data[field1]', $context->getViolations()[0]->getPropertyPath());
771+
$this->assertSame('This value should not be blank.', $context->getViolations()[1]->getMessage());
772+
$this->assertSame('data[field2]', $context->getViolations()[1]->getPropertyPath());
773+
}
774+
717775
protected function createValidator()
718776
{
719777
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;

0 commit comments

Comments
 (0)