From 8e04222976043af4b5a23c63ecbc3f35891d7db0 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Wed, 12 Jun 2019 03:01:18 +0200 Subject: [PATCH 01/10] [Routing] fix absolute url generation when scheme is not known --- .../Routing/Generator/UrlGenerator.php | 20 ++++---- .../Tests/Generator/UrlGeneratorTest.php | 48 +++++++++++++++---- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php index b87f4bb5c4..3a826d86f6 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -222,16 +222,18 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt } } - if ((self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) && !empty($host)) { - $port = ''; - if ('http' === $scheme && 80 != $this->context->getHttpPort()) { - $port = ':'.$this->context->getHttpPort(); - } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { - $port = ':'.$this->context->getHttpsPort(); - } + if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) { + if ('' !== $host || ('' !== $scheme && 'http' !== $scheme && 'https' !== $scheme)) { + $port = ''; + if ('http' === $scheme && 80 !== $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 !== $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } - $schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "$scheme://"; - $schemeAuthority .= $host.$port; + $schemeAuthority = self::NETWORK_PATH === $referenceType || '' === $scheme ? '//' : "$scheme://"; + $schemeAuthority .= $host.$port; + } } if (self::RELATIVE_PATH === $referenceType) { diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index 7f64a1f378..a4d754cb14 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -480,28 +480,27 @@ class UrlGeneratorTest extends TestCase public function testDefaultHostIsUsedWhenContextHostIsEmpty() { - $routes = $this->getRoutes('test', new Route('/route', ['domain' => 'my.fallback.host'], ['domain' => '.+'], [], '{domain}', ['http'])); + $routes = $this->getRoutes('test', new Route('/path', ['domain' => 'my.fallback.host'], ['domain' => '.+'], [], '{domain}')); $generator = $this->getGenerator($routes); $generator->getContext()->setHost(''); - $this->assertSame('http://my.fallback.host/app.php/route', $generator->generate('test', [], UrlGeneratorInterface::ABSOLUTE_URL)); + $this->assertSame('http://my.fallback.host/app.php/path', $generator->generate('test', [], UrlGeneratorInterface::ABSOLUTE_URL)); } - public function testDefaultHostIsUsedWhenContextHostIsEmptyAndSchemeIsNot() + public function testDefaultHostIsUsedWhenContextHostIsEmptyAndPathReferenceType() { - $routes = $this->getRoutes('test', new Route('/route', ['domain' => 'my.fallback.host'], ['domain' => '.+'], [], '{domain}', ['http', 'https'])); + $routes = $this->getRoutes('test', new Route('/path', ['domain' => 'my.fallback.host'], ['domain' => '.+'], [], '{domain}')); $generator = $this->getGenerator($routes); $generator->getContext()->setHost(''); - $generator->getContext()->setScheme('https'); - $this->assertSame('https://my.fallback.host/app.php/route', $generator->generate('test', [], UrlGeneratorInterface::ABSOLUTE_URL)); + $this->assertSame('//my.fallback.host/app.php/path', $generator->generate('test', [], UrlGeneratorInterface::ABSOLUTE_PATH)); } - public function testAbsoluteUrlFallbackToRelativeIfHostIsEmptyAndSchemeIsNot() + public function testAbsoluteUrlFallbackToPathIfHostIsEmptyAndSchemeIsHttp() { - $routes = $this->getRoutes('test', new Route('/route', [], [], [], '', ['http', 'https'])); + $routes = $this->getRoutes('test', new Route('/route')); $generator = $this->getGenerator($routes); $generator->getContext()->setHost(''); @@ -510,6 +509,39 @@ class UrlGeneratorTest extends TestCase $this->assertSame('/app.php/route', $generator->generate('test', [], UrlGeneratorInterface::ABSOLUTE_URL)); } + public function testAbsoluteUrlFallbackToNetworkIfSchemeIsEmptyAndHostIsNot() + { + $routes = $this->getRoutes('test', new Route('/path')); + + $generator = $this->getGenerator($routes); + $generator->getContext()->setHost('example.com'); + $generator->getContext()->setScheme(''); + + $this->assertSame('//example.com/app.php/path', $generator->generate('test', [], UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function testAbsoluteUrlFallbackToPathIfSchemeAndHostAreEmpty() + { + $routes = $this->getRoutes('test', new Route('/path')); + + $generator = $this->getGenerator($routes); + $generator->getContext()->setHost(''); + $generator->getContext()->setScheme(''); + + $this->assertSame('/app.php/path', $generator->generate('test', [], UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function testAbsoluteUrlWithNonHttpSchemeAndEmptyHost() + { + $routes = $this->getRoutes('test', new Route('/path', [], [], [], '', ['file'])); + + $generator = $this->getGenerator($routes); + $generator->getContext()->setBaseUrl(''); + $generator->getContext()->setHost(''); + + $this->assertSame('file:///path', $generator->generate('test', [], UrlGeneratorInterface::ABSOLUTE_URL)); + } + public function testGenerateNetworkPath() { $routes = $this->getRoutes('test', new Route('/{name}', [], [], [], '{locale}.example.com', ['http'])); From a9d0038ec0042b39a96c0141e0d8c78a6fa3f362 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 13 Jun 2019 14:31:28 +0200 Subject: [PATCH 02/10] [VarDumper] fix dumping objects that implement __debugInfo() --- .../Component/VarDumper/Caster/Caster.php | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php index 93d0ce2b41..ae8b40fa0e 100644 --- a/src/Symfony/Component/VarDumper/Caster/Caster.php +++ b/src/Symfony/Component/VarDumper/Caster/Caster.php @@ -53,13 +53,9 @@ class Caster $hasDebugInfo = $class->hasMethod('__debugInfo'); $class = $class->name; } - if ($hasDebugInfo) { - $a = $obj->__debugInfo(); - } elseif ($obj instanceof \Closure) { - $a = []; - } else { - $a = (array) $obj; - } + + $a = $obj instanceof \Closure ? [] : (array) $obj; + if ($obj instanceof \__PHP_Incomplete_Class) { return $a; } @@ -93,6 +89,17 @@ class Caster } } + if ($hasDebugInfo && \is_array($debugInfo = $obj->__debugInfo())) { + foreach ($debugInfo as $k => $v) { + if (!isset($k[0]) || "\0" !== $k[0]) { + $k = self::PREFIX_VIRTUAL.$k; + } + + unset($a[$k]); + $a[$k] = $v; + } + } + return $a; } From d445465ef459b83ce2b9b4342bbb38fc9b8c1d02 Mon Sep 17 00:00:00 2001 From: Stefano Degenkamp Date: Wed, 12 Jun 2019 11:28:52 +0200 Subject: [PATCH 03/10] Fix binary operation `+`, `-` or `*` on string By type casting to integer. --- .../Component/Form/Extension/Core/Type/BirthdayType.php | 2 +- src/Symfony/Component/Form/Extension/Core/Type/DateType.php | 2 +- src/Symfony/Component/HttpFoundation/Response.php | 4 ++-- .../Component/HttpKernel/HttpCache/ResponseCacheStrategy.php | 2 +- .../Intl/DateFormatter/DateFormat/DayOfYearTransformer.php | 2 +- .../Intl/DateFormatter/DateFormat/FullTransformer.php | 2 +- src/Symfony/Component/Validator/Constraints/IssnValidator.php | 2 +- src/Symfony/Component/Validator/Constraints/LuhnValidator.php | 2 +- src/Symfony/Component/VarDumper/Caster/DateCaster.php | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php b/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php index 841bd0d85f..acb8d02b8c 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php @@ -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'); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index 09f5e1de5c..8ab5e9cc8a 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -256,7 +256,7 @@ class DateType extends AbstractType }; $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', diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index b7d116259d..4ab05066f4 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -707,7 +707,7 @@ class Response return (int) $age; } - return max(time() - $this->getDate()->format('U'), 0); + return max(time() - (int) $this->getDate()->format('U'), 0); } /** @@ -788,7 +788,7 @@ class Response } if (null !== $this->getExpires()) { - return $this->getExpires()->format('U') - $this->getDate()->format('U'); + return (int) $this->getExpires()->format('U') - (int) $this->getDate()->format('U'); } } diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php index 7037c497cc..25c071c335 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php @@ -85,7 +85,7 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface $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); } diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfYearTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfYearTransformer.php index 8b48371f60..1443cc79ec 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfYearTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayOfYearTransformer.php @@ -25,7 +25,7 @@ class DayOfYearTransformer extends Transformer */ public function format(\DateTime $dateTime, $length) { - $dayOfYear = $dateTime->format('z') + 1; + $dayOfYear = (int) $dateTime->format('z') + 1; return $this->padLeft($dayOfYear, $length); } diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php index 9c0d86ef05..13854ff719 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php @@ -315,7 +315,7 @@ class FullTransformer 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); diff --git a/src/Symfony/Component/Validator/Constraints/IssnValidator.php b/src/Symfony/Component/Validator/Constraints/IssnValidator.php index f045e62e2c..41da39ebd8 100644 --- a/src/Symfony/Component/Validator/Constraints/IssnValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IssnValidator.php @@ -120,7 +120,7 @@ class IssnValidator extends ConstraintValidator for ($i = 0; $i < 7; ++$i) { // Multiply the first digit by 8, the second by 7, etc. - $checkSum += (8 - $i) * $canonical[$i]; + $checkSum += (8 - $i) * (int) $canonical[$i]; } if (0 !== $checkSum % 11) { diff --git a/src/Symfony/Component/Validator/Constraints/LuhnValidator.php b/src/Symfony/Component/Validator/Constraints/LuhnValidator.php index 554fc6f48b..6e69f9ee74 100644 --- a/src/Symfony/Component/Validator/Constraints/LuhnValidator.php +++ b/src/Symfony/Component/Validator/Constraints/LuhnValidator.php @@ -83,7 +83,7 @@ class LuhnValidator extends ConstraintValidator // ^ ^ ^ ^ ^ // = 1+8 + 4 + 6 + 1+6 + 2 for ($i = $length - 2; $i >= 0; $i -= 2) { - $checkSum += array_sum(str_split($value[$i] * 2)); + $checkSum += array_sum(str_split((int) $value[$i] * 2)); } if (0 === $checkSum || 0 !== $checkSum % 10) { diff --git a/src/Symfony/Component/VarDumper/Caster/DateCaster.php b/src/Symfony/Component/VarDumper/Caster/DateCaster.php index 3b030b734a..40289a930b 100644 --- a/src/Symfony/Component/VarDumper/Caster/DateCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/DateCaster.php @@ -95,7 +95,7 @@ class DateCaster if (3 === $i) { $now = new \DateTimeImmutable(); $dates[] = sprintf('%s more', ($end = $p->getEndDate()) - ? ceil(($end->format('U.u') - $d->format('U.u')) / ($now->add($p->getDateInterval())->format('U.u') - $now->format('U.u'))) + ? ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u'))) : $p->recurrences - $i ); break; From 89cba00c6890bc85a5f35a57bd3a4db3e7250b00 Mon Sep 17 00:00:00 2001 From: battye Date: Thu, 13 Jun 2019 08:42:28 +0000 Subject: [PATCH 04/10] [Serializer] Handle true and false appropriately in CSV encoder --- .../Serializer/Encoder/CsvEncoder.php | 3 ++- .../Tests/Encoder/CsvEncoderTest.php | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php b/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php index 1e47d35f17..2552e30094 100644 --- a/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php @@ -189,7 +189,8 @@ class CsvEncoder implements EncoderInterface, DecoderInterface if (\is_array($value)) { $this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator); } else { - $result[$parentKey.$key] = $value; + // Ensures an actual value is used when dealing with true and false + $result[$parentKey.$key] = false === $value ? 0 : (true === $value ? 1 : $value); } } } diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php index 1eb673e8e2..7b5131cb53 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php @@ -29,6 +29,24 @@ class CsvEncoderTest extends TestCase $this->encoder = new CsvEncoder(); } + public function testTrueFalseValues() + { + $data = [ + 'string' => 'foo', + 'int' => 2, + 'false' => false, + 'true' => true, + ]; + + // Check that true and false are appropriately handled + $this->assertEquals(<<<'CSV' +string,int,false,true +foo,2,0,1 + +CSV + , $this->encoder->encode($data, 'csv')); + } + public function testSupportEncoding() { $this->assertTrue($this->encoder->supportsEncoding('csv')); From 94ded00216ef36bf61f2bb12fb3f6788fc38c577 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 14 Jun 2019 09:11:59 +0200 Subject: [PATCH 05/10] validate composite constraints in all groups --- .../Validator/Constraints/FormValidator.php | 5 +- .../Constraints/FormValidatorTest.php | 58 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index 14adf123ec..9e167c8215 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Extension\Validator\Constraints; 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; @@ -90,7 +91,9 @@ class FormValidator extends ConstraintValidator $validator->atPath('data')->validate($form->getData(), $constraint, $group); // Prevent duplicate validation - continue 2; + if (!$constraint instanceof Composite) { + continue 2; + } } } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index 4ab56f555a..8a8af9ed68 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -24,6 +24,7 @@ use Symfony\Component\Form\FormFactoryInterface; 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; @@ -673,6 +674,63 @@ class FormValidatorTest extends ConstraintValidatorTestCase $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(); From ffd3469ddfb96aa25deb3a0f22f83e26e5d0f040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 13 Jun 2019 15:23:10 +0200 Subject: [PATCH 06/10] SimpleCacheAdapter fails to cache any item if a namespace is used --- .../Cache/Adapter/AbstractAdapter.php | 2 +- .../Cache/Adapter/SimpleCacheAdapter.php | 8 ++++++++ .../Tests/Adapter/SimpleCacheAdapterTest.php | 11 +++++++++++ .../Component/Cache/Traits/AbstractTrait.php | 18 +++++++++++++----- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 099c97a4da..70af0c7314 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -39,7 +39,7 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface */ protected function __construct($namespace = '', $defaultLifetime = 0) { - $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':'; + $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::getNsSeparator(); 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)); } diff --git a/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php b/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php index bdb62a4bb3..de4dd745cd 100644 --- a/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php @@ -75,4 +75,12 @@ class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface { return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime); } + + /** + * @return string the namespace separator for cache keys + */ + protected static function getNsSeparator() + { + return '_'; + } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php index 84713416cd..d8470a2e7d 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Cache\Tests\Adapter; use Symfony\Component\Cache\Adapter\SimpleCacheAdapter; +use Symfony\Component\Cache\Simple\ArrayCache; use Symfony\Component\Cache\Simple\FilesystemCache; /** @@ -27,4 +28,14 @@ class SimpleCacheAdapterTest extends AdapterTestCase { 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.'); + } } diff --git a/src/Symfony/Component/Cache/Traits/AbstractTrait.php b/src/Symfony/Component/Cache/Traits/AbstractTrait.php index be576098e4..83a86a5ee9 100644 --- a/src/Symfony/Component/Cache/Traits/AbstractTrait.php +++ b/src/Symfony/Component/Cache/Traits/AbstractTrait.php @@ -106,7 +106,7 @@ trait AbstractTrait { $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::getNsSeparator(), 5); try { $cleared = $this->doSave(['@'.$this->namespace => $namespaceVersion], 0); } catch (\Exception $e) { @@ -235,13 +235,13 @@ trait AbstractTrait CacheItem::validateKey($key); if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { - $this->namespaceVersion = '1:'; + $this->namespaceVersion = '1'.static::getNsSeparator(); try { foreach ($this->doFetch(['@'.$this->namespace]) as $v) { $this->namespaceVersion = $v; } - if ('1:' === $this->namespaceVersion) { - $this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), ':', 5); + if ('1'.static::getNsSeparator() === $this->namespaceVersion) { + $this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), static::getNsSeparator(), 5); $this->doSave(['@'.$this->namespace => $this->namespaceVersion], 0); } } catch (\Exception $e) { @@ -252,7 +252,7 @@ trait AbstractTrait return $this->namespace.$this->namespaceVersion.$key; } if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { - $id = $this->namespace.$this->namespaceVersion.substr_replace(base64_encode(hash('sha256', $key, true)), ':', -(\strlen($this->namespaceVersion) + 22)); + $id = $this->namespace.$this->namespaceVersion.substr_replace(base64_encode(hash('sha256', $key, true)), static::getNsSeparator(), -(\strlen($this->namespaceVersion) + 22)); } return $id; @@ -265,4 +265,12 @@ trait AbstractTrait { throw new \DomainException('Class not found: '.$class); } + + /** + * @return string the namespace separator for cache keys + */ + protected static function getNsSeparator() + { + return ':'; + } } From 270f10cc81b2222e98dffb15f3746b97107c0764 Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Sat, 15 Jun 2019 08:54:32 +0200 Subject: [PATCH 07/10] [HttpFoundation] Fix SA/phpdoc JsonResponse --- .../Component/HttpFoundation/JsonResponse.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/JsonResponse.php b/src/Symfony/Component/HttpFoundation/JsonResponse.php index 24798eea42..9c7c8e4c9b 100644 --- a/src/Symfony/Component/HttpFoundation/JsonResponse.php +++ b/src/Symfony/Component/HttpFoundation/JsonResponse.php @@ -55,10 +55,10 @@ class JsonResponse extends Response * * 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 * @@ -70,7 +70,18 @@ class JsonResponse extends Response } /** - * 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 = []) { From 9f960f34e7785d3bde67b850ac4754cc46f853bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Mon, 17 Jun 2019 16:05:50 +0200 Subject: [PATCH 08/10] Fix expired lock not cleaned --- src/Symfony/Component/Lock/Lock.php | 10 +++++++ .../Component/Lock/Store/CombinedStore.php | 8 ++--- .../Lock/Store/ExpiringStoreTrait.php | 30 +++++++++++++++++++ .../Component/Lock/Store/MemcachedStore.php | 11 +++---- .../Component/Lock/Store/RedisStore.php | 11 +++---- .../Tests/Store/ExpiringStoreTestTrait.php | 25 ++++++++++++++++ 6 files changed, 77 insertions(+), 18 deletions(-) create mode 100644 src/Symfony/Component/Lock/Store/ExpiringStoreTrait.php diff --git a/src/Symfony/Component/Lock/Lock.php b/src/Symfony/Component/Lock/Lock.php index 1a6c0e26ee..0d5714bde2 100644 --- a/src/Symfony/Component/Lock/Lock.php +++ b/src/Symfony/Component/Lock/Lock.php @@ -83,6 +83,11 @@ final class Lock implements LockInterface, LoggerAwareInterface } 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)); } @@ -117,6 +122,11 @@ final class Lock implements LockInterface, LoggerAwareInterface $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)); } diff --git a/src/Symfony/Component/Lock/Store/CombinedStore.php b/src/Symfony/Component/Lock/Store/CombinedStore.php index 38f3f35e75..6038b3be93 100644 --- a/src/Symfony/Component/Lock/Store/CombinedStore.php +++ b/src/Symfony/Component/Lock/Store/CombinedStore.php @@ -16,7 +16,6 @@ use Psr\Log\LoggerAwareTrait; 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; @@ -30,6 +29,7 @@ use Symfony\Component\Lock\Strategy\StrategyInterface; class CombinedStore implements StoreInterface, LoggerAwareInterface { use LoggerAwareTrait; + use ExpiringStoreTrait; /** @var StoreInterface[] */ private $stores; @@ -78,6 +78,8 @@ class CombinedStore implements StoreInterface, LoggerAwareInterface } } + $this->checkNotExpired($key); + if ($this->strategy->isMet($successCount, $storesCount)) { return; } @@ -125,9 +127,7 @@ class CombinedStore implements StoreInterface, LoggerAwareInterface } } - 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; diff --git a/src/Symfony/Component/Lock/Store/ExpiringStoreTrait.php b/src/Symfony/Component/Lock/Store/ExpiringStoreTrait.php new file mode 100644 index 0000000000..e22c4405e4 --- /dev/null +++ b/src/Symfony/Component/Lock/Store/ExpiringStoreTrait.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Lock\Store; + +use Symfony\Component\Lock\Exception\LockExpiredException; +use Symfony\Component\Lock\Key; + +trait ExpiringStoreTrait +{ + private function checkNotExpired(Key $key) + { + if ($key->isExpired()) { + try { + $this->delete($key); + } catch (\Exception $e) { + // swallow exception to not hide the original issue + } + throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key)); + } + } +} diff --git a/src/Symfony/Component/Lock/Store/MemcachedStore.php b/src/Symfony/Component/Lock/Store/MemcachedStore.php index 9b795504c9..7a51d6149b 100644 --- a/src/Symfony/Component/Lock/Store/MemcachedStore.php +++ b/src/Symfony/Component/Lock/Store/MemcachedStore.php @@ -13,7 +13,6 @@ namespace Symfony\Component\Lock\Store; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Exception\LockConflictedException; -use Symfony\Component\Lock\Exception\LockExpiredException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\StoreInterface; @@ -24,6 +23,8 @@ use Symfony\Component\Lock\StoreInterface; */ class MemcachedStore implements StoreInterface { + use ExpiringStoreTrait; + private $memcached; private $initialTtl; /** @var bool */ @@ -64,9 +65,7 @@ class MemcachedStore implements StoreInterface $this->putOffExpiration($key, $this->initialTtl); } - if ($key->isExpired()) { - throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key)); - } + $this->checkNotExpired($key); } public function waitAndSave(Key $key) @@ -110,9 +109,7 @@ class MemcachedStore implements StoreInterface throw new LockConflictedException(); } - 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); } /** diff --git a/src/Symfony/Component/Lock/Store/RedisStore.php b/src/Symfony/Component/Lock/Store/RedisStore.php index 2d7ba45366..6070c2e74e 100644 --- a/src/Symfony/Component/Lock/Store/RedisStore.php +++ b/src/Symfony/Component/Lock/Store/RedisStore.php @@ -14,7 +14,6 @@ namespace Symfony\Component\Lock\Store; use Symfony\Component\Cache\Traits\RedisProxy; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Exception\LockConflictedException; -use Symfony\Component\Lock\Exception\LockExpiredException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\StoreInterface; @@ -25,6 +24,8 @@ use Symfony\Component\Lock\StoreInterface; */ class RedisStore implements StoreInterface { + use ExpiringStoreTrait; + private $redis; private $initialTtl; @@ -66,9 +67,7 @@ class RedisStore implements StoreInterface throw new LockConflictedException(); } - if ($key->isExpired()) { - throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key)); - } + $this->checkNotExpired($key); } public function waitAndSave(Key $key) @@ -94,9 +93,7 @@ class RedisStore implements StoreInterface throw new LockConflictedException(); } - 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); } /** diff --git a/src/Symfony/Component/Lock/Tests/Store/ExpiringStoreTestTrait.php b/src/Symfony/Component/Lock/Tests/Store/ExpiringStoreTestTrait.php index f523780ce1..bc96ff66f5 100644 --- a/src/Symfony/Component/Lock/Tests/Store/ExpiringStoreTestTrait.php +++ b/src/Symfony/Component/Lock/Tests/Store/ExpiringStoreTestTrait.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Lock\Tests\Store; +use Symfony\Component\Lock\Exception\LockExpiredException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\StoreInterface; @@ -105,4 +106,28 @@ trait ExpiringStoreTestTrait $this->assertGreaterThanOrEqual(0, $key->getRemainingLifetime()); $this->assertLessThanOrEqual(1, $key->getRemainingLifetime()); } + + public function testExpiredLockCleaned() + { + $resource = uniqid(__METHOD__, true); + + $key1 = new Key($resource); + $key2 = new Key($resource); + + /** @var StoreInterface $store */ + $store = $this->getStore(); + $key1->reduceLifetime(0); + + $this->assertTrue($key1->isExpired()); + try { + $store->save($key1); + $this->fail('The store shouldn\'t have save an expired key'); + } catch (LockExpiredException $e) { + } + + $this->assertFalse($store->exists($key1)); + + $store->save($key2); + $this->assertTrue($store->exists($key2)); + } } From 02a6f248b5041d68bf6e18d3e254aa7a79039a78 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 17 Jun 2019 19:18:24 +0200 Subject: [PATCH 09/10] [Cache] fix versioning with SimpleCacheAdapter --- .../Component/Cache/Adapter/SimpleCacheAdapter.php | 2 ++ src/Symfony/Component/Cache/Traits/AbstractTrait.php | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php b/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php index de4dd745cd..dc3a9cec2d 100644 --- a/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php @@ -78,6 +78,8 @@ class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface /** * @return string the namespace separator for cache keys + * + * @internal */ protected static function getNsSeparator() { diff --git a/src/Symfony/Component/Cache/Traits/AbstractTrait.php b/src/Symfony/Component/Cache/Traits/AbstractTrait.php index 83a86a5ee9..e225e07542 100644 --- a/src/Symfony/Component/Cache/Traits/AbstractTrait.php +++ b/src/Symfony/Component/Cache/Traits/AbstractTrait.php @@ -108,7 +108,7 @@ trait AbstractTrait if ($cleared = $this->versioningIsEnabled) { $namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::getNsSeparator(), 5); try { - $cleared = $this->doSave(['@'.$this->namespace => $namespaceVersion], 0); + $cleared = $this->doSave([static::getNsSeparator().$this->namespace => $namespaceVersion], 0); } catch (\Exception $e) { $cleared = false; } @@ -237,12 +237,12 @@ trait AbstractTrait if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { $this->namespaceVersion = '1'.static::getNsSeparator(); try { - foreach ($this->doFetch(['@'.$this->namespace]) as $v) { + foreach ($this->doFetch([static::getNsSeparator().$this->namespace]) as $v) { $this->namespaceVersion = $v; } if ('1'.static::getNsSeparator() === $this->namespaceVersion) { $this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), static::getNsSeparator(), 5); - $this->doSave(['@'.$this->namespace => $this->namespaceVersion], 0); + $this->doSave([static::getNsSeparator().$this->namespace => $this->namespaceVersion], 0); } } catch (\Exception $e) { } @@ -268,6 +268,8 @@ trait AbstractTrait /** * @return string the namespace separator for cache keys + * + * @internal */ protected static function getNsSeparator() { From 2bf5da51da24fb8b490f18ec973086df112840dd Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 17 Jun 2019 19:26:15 +0200 Subject: [PATCH 10/10] [Cache] replace getNsSeparator by NS_SEPARATOR on AbstractTrait --- .../Cache/Adapter/AbstractAdapter.php | 7 ++++- .../Cache/Adapter/SimpleCacheAdapter.php | 15 ++++------- .../Component/Cache/Simple/AbstractCache.php | 5 ++++ .../Component/Cache/Traits/AbstractTrait.php | 26 ++++++------------- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 70af0c7314..0868c16d47 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -25,6 +25,11 @@ use Symfony\Component\Cache\Traits\AbstractTrait; */ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface { + /** + * @internal + */ + const NS_SEPARATOR = ':'; + use AbstractTrait; private static $apcuSupported; @@ -39,7 +44,7 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface */ protected function __construct($namespace = '', $defaultLifetime = 0) { - $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::getNsSeparator(); + $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)); } diff --git a/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php b/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php index dc3a9cec2d..d3d0ede648 100644 --- a/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php @@ -20,6 +20,11 @@ use Symfony\Component\Cache\Traits\ProxyTrait; */ class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface { + /** + * @internal + */ + const NS_SEPARATOR = '_'; + use ProxyTrait; private $miss; @@ -75,14 +80,4 @@ class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface { return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime); } - - /** - * @return string the namespace separator for cache keys - * - * @internal - */ - protected static function getNsSeparator() - { - return '_'; - } } diff --git a/src/Symfony/Component/Cache/Simple/AbstractCache.php b/src/Symfony/Component/Cache/Simple/AbstractCache.php index 0d715e48d2..23b401c54b 100644 --- a/src/Symfony/Component/Cache/Simple/AbstractCache.php +++ b/src/Symfony/Component/Cache/Simple/AbstractCache.php @@ -23,6 +23,11 @@ use Symfony\Component\Cache\Traits\AbstractTrait; */ abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, ResettableInterface { + /** + * @internal + */ + const NS_SEPARATOR = ':'; + use AbstractTrait { deleteItems as private; AbstractTrait::deleteItem as delete; diff --git a/src/Symfony/Component/Cache/Traits/AbstractTrait.php b/src/Symfony/Component/Cache/Traits/AbstractTrait.php index e225e07542..cd1f204139 100644 --- a/src/Symfony/Component/Cache/Traits/AbstractTrait.php +++ b/src/Symfony/Component/Cache/Traits/AbstractTrait.php @@ -106,9 +106,9 @@ trait AbstractTrait { $this->deferred = []; if ($cleared = $this->versioningIsEnabled) { - $namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::getNsSeparator(), 5); + $namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::NS_SEPARATOR, 5); try { - $cleared = $this->doSave([static::getNsSeparator().$this->namespace => $namespaceVersion], 0); + $cleared = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0); } catch (\Exception $e) { $cleared = false; } @@ -235,14 +235,14 @@ trait AbstractTrait CacheItem::validateKey($key); if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { - $this->namespaceVersion = '1'.static::getNsSeparator(); + $this->namespaceVersion = '1'.static::NS_SEPARATOR; try { - foreach ($this->doFetch([static::getNsSeparator().$this->namespace]) as $v) { + foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { $this->namespaceVersion = $v; } - if ('1'.static::getNsSeparator() === $this->namespaceVersion) { - $this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), static::getNsSeparator(), 5); - $this->doSave([static::getNsSeparator().$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) { } @@ -252,7 +252,7 @@ trait AbstractTrait return $this->namespace.$this->namespaceVersion.$key; } if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { - $id = $this->namespace.$this->namespaceVersion.substr_replace(base64_encode(hash('sha256', $key, true)), static::getNsSeparator(), -(\strlen($this->namespaceVersion) + 22)); + $id = $this->namespace.$this->namespaceVersion.substr_replace(base64_encode(hash('sha256', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 22)); } return $id; @@ -265,14 +265,4 @@ trait AbstractTrait { throw new \DomainException('Class not found: '.$class); } - - /** - * @return string the namespace separator for cache keys - * - * @internal - */ - protected static function getNsSeparator() - { - return ':'; - } }