Merge branch '4.3' into 4.4

* 4.3:
  gracefully handle missing event dispatchers
  [Cache] fix memory leak when using PhpArrayAdapter
  fix parsing negative octal numbers
  [SecurityBundle] Properly escape regex in AddSessionDomainConstraintPass
  [Config] never try loading failed classes twice with ClassExistenceResource
This commit is contained in:
Nicolas Grekas 2019-12-07 17:27:44 +01:00
commit 6e44447e5d
25 changed files with 192 additions and 44 deletions

View File

@ -31,7 +31,7 @@ class AddSessionDomainConstraintPass implements CompilerPassInterface
} }
$sessionOptions = $container->getParameter('session.storage.options'); $sessionOptions = $container->getParameter('session.storage.options');
$domainRegexp = empty($sessionOptions['cookie_domain']) ? '%s' : sprintf('(?:%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.'))); $domainRegexp = empty($sessionOptions['cookie_domain']) ? '%%s' : sprintf('(?:%%%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.')));
if ('auto' === ($sessionOptions['cookie_secure'] ?? null)) { if ('auto' === ($sessionOptions['cookie_secure'] ?? null)) {
$secureDomainRegexp = sprintf('{^https://%s$}i', $domainRegexp); $secureDomainRegexp = sprintf('{^https://%s$}i', $domainRegexp);

View File

@ -61,22 +61,17 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
* This adapter takes advantage of how PHP stores arrays in its latest versions. * This adapter takes advantage of how PHP stores arrays in its latest versions.
* *
* @param string $file The PHP file were values are cached * @param string $file The PHP file were values are cached
* @param CacheItemPoolInterface $fallbackPool Fallback when opcache is disabled * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit
* *
* @return CacheItemPoolInterface * @return CacheItemPoolInterface
*/ */
public static function create($file, CacheItemPoolInterface $fallbackPool) public static function create($file, CacheItemPoolInterface $fallbackPool)
{ {
// Shared memory is available in PHP 7.0+ with OPCache enabled if (!$fallbackPool instanceof AdapterInterface) {
if (filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) { $fallbackPool = new ProxyAdapter($fallbackPool);
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
return new static($file, $fallbackPool);
} }
return $fallbackPool; return new static($file, $fallbackPool);
} }
/** /**

View File

@ -41,18 +41,14 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
/** /**
* This adapter takes advantage of how PHP stores arrays in its latest versions. * This adapter takes advantage of how PHP stores arrays in its latest versions.
* *
* @param string $file The PHP file were values are cached * @param string $file The PHP file were values are cached
* @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
* *
* @return Psr16CacheInterface * @return Psr16CacheInterface
*/ */
public static function create($file, Psr16CacheInterface $fallbackPool) public static function create($file, Psr16CacheInterface $fallbackPool)
{ {
// Shared memory is available in PHP 7.0+ with OPCache enabled return new static($file, $fallbackPool);
if (filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
return new static($file, $fallbackPool);
}
return $fallbackPool;
} }
/** /**

View File

@ -66,6 +66,8 @@ class PhpArrayAdapterTest extends AdapterTestCase
protected function tearDown(): void protected function tearDown(): void
{ {
$this->createCachePool()->clear();
if (file_exists(sys_get_temp_dir().'/symfony-cache')) { if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache'); FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
} }

View File

@ -38,6 +38,8 @@ class PhpArrayAdapterWithFallbackTest extends AdapterTestCase
protected function tearDown(): void protected function tearDown(): void
{ {
$this->createCachePool()->clear();
if (file_exists(sys_get_temp_dir().'/symfony-cache')) { if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache'); FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
} }

View File

@ -58,6 +58,8 @@ class PhpArrayCacheTest extends CacheTestCase
protected function tearDown(): void protected function tearDown(): void
{ {
$this->createSimpleCache()->clear();
if (file_exists(sys_get_temp_dir().'/symfony-cache')) { if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache'); FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
} }

View File

@ -45,6 +45,8 @@ class PhpArrayCacheWithFallbackTest extends CacheTestCase
protected function tearDown(): void protected function tearDown(): void
{ {
$this->createSimpleCache()->clear();
if (file_exists(sys_get_temp_dir().'/symfony-cache')) { if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache'); FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
} }

View File

@ -30,6 +30,8 @@ trait PhpArrayTrait
private $keys; private $keys;
private $values; private $values;
private static $valuesCache = [];
/** /**
* Store an array of cached values. * Store an array of cached values.
* *
@ -116,6 +118,7 @@ EOF;
unset($serialized, $value, $dump); unset($serialized, $value, $dump);
@rename($tmpFile, $this->file); @rename($tmpFile, $this->file);
unset(self::$valuesCache[$this->file]);
$this->initialize(); $this->initialize();
} }
@ -133,6 +136,7 @@ EOF;
$this->keys = $this->values = []; $this->keys = $this->values = [];
$cleared = @unlink($this->file) || !file_exists($this->file); $cleared = @unlink($this->file) || !file_exists($this->file);
unset(self::$valuesCache[$this->file]);
if ($this->pool instanceof AdapterInterface) { if ($this->pool instanceof AdapterInterface) {
return $this->pool->clear($prefix) && $cleared; return $this->pool->clear($prefix) && $cleared;
@ -146,12 +150,15 @@ EOF;
*/ */
private function initialize() private function initialize()
{ {
if (!file_exists($this->file)) { if (isset(self::$valuesCache[$this->file])) {
$values = self::$valuesCache[$this->file];
} elseif (!file_exists($this->file)) {
$this->keys = $this->values = []; $this->keys = $this->values = [];
return; return;
} else {
$values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []];
} }
$values = (include $this->file) ?: [[], []];
if (2 !== \count($values) || !isset($values[0], $values[1])) { if (2 !== \count($values) || !isset($values[0], $values[1])) {
$this->keys = $this->values = []; $this->keys = $this->values = [];

View File

@ -37,7 +37,9 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
public function __construct(string $resource, bool $exists = null) public function __construct(string $resource, bool $exists = null)
{ {
$this->resource = $resource; $this->resource = $resource;
$this->exists = $exists; if (null !== $exists) {
$this->exists = [(bool) $exists, null];
}
} }
/** /**
@ -65,9 +67,13 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
{ {
$loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false); $loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
if (null !== $exists = &self::$existsCache[(int) (0 >= $timestamp)][$this->resource]) { if (null !== $exists = &self::$existsCache[$this->resource]) {
$exists = $exists || $loaded; if ($loaded) {
} elseif (!$exists = $loaded) { $exists = [true, null];
} elseif (0 >= $timestamp && !$exists[0] && null !== $exists[1]) {
throw new \ReflectionException($exists[1]);
}
} elseif ([false, null] === $exists = [$loaded, null]) {
if (!self::$autoloadLevel++) { if (!self::$autoloadLevel++) {
spl_autoload_register(__CLASS__.'::throwOnRequiredClass'); spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
} }
@ -75,16 +81,19 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
self::$autoloadedClass = ltrim($this->resource, '\\'); self::$autoloadedClass = ltrim($this->resource, '\\');
try { try {
$exists = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false); $exists[0] = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
} catch (\Exception $e) { } catch (\Exception $e) {
$exists[1] = $e->getMessage();
try { try {
self::throwOnRequiredClass($this->resource, $e); self::throwOnRequiredClass($this->resource, $e);
} catch (\ReflectionException $e) { } catch (\ReflectionException $e) {
if (0 >= $timestamp) { if (0 >= $timestamp) {
unset(self::$existsCache[1][$this->resource]);
throw $e; throw $e;
} }
} }
} catch (\Throwable $e) {
$exists[1] = $e->getMessage();
} finally { } finally {
self::$autoloadedClass = $autoloadedClass; self::$autoloadedClass = $autoloadedClass;
if (!--self::$autoloadLevel) { if (!--self::$autoloadLevel) {
@ -97,7 +106,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
$this->exists = $exists; $this->exists = $exists;
} }
return $this->exists xor !$exists; return $this->exists[0] xor !$exists[0];
} }
/** /**
@ -112,6 +121,16 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
return ['resource', 'exists']; return ['resource', 'exists'];
} }
/**
* @internal
*/
public function __wakeup()
{
if (\is_bool($this->exists)) {
$this->exists = [$this->exists, null];
}
}
/** /**
* Throws a reflection exception when the passed class does not exist but is required. * Throws a reflection exception when the passed class does not exist but is required.
* *
@ -147,7 +166,17 @@ class ClassExistenceResource implements SelfCheckingResourceInterface
throw $previous; throw $previous;
} }
$e = new \ReflectionException(sprintf('Class "%s" not found while loading "%s".', $class, self::$autoloadedClass), 0, $previous); $message = sprintf('Class "%s" not found.', $class);
if (self::$autoloadedClass !== $class) {
$message = substr_replace($message, sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0);
}
if (null !== $previous) {
$message = $previous->getMessage();
}
$e = new \ReflectionException($message, 0, $previous);
if (null !== $previous) { if (null !== $previous) {
throw $e; throw $e;

View File

@ -0,0 +1,9 @@
<?php
namespace Symfony\Component\Config\Tests\Fixtures;
class FileNameMismatchOnPurpose
{
}
throw new \RuntimeException('Mismatch between file name and class name.');

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Config\Tests\Resource;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\Config\Tests\Fixtures\BadFileName;
use Symfony\Component\Config\Tests\Fixtures\BadParent; use Symfony\Component\Config\Tests\Fixtures\BadParent;
use Symfony\Component\Config\Tests\Fixtures\Resource\ConditionalClass; use Symfony\Component\Config\Tests\Fixtures\Resource\ConditionalClass;
@ -90,6 +91,24 @@ EOF
$res->isFresh(0); $res->isFresh(0);
} }
public function testBadFileName()
{
$this->expectException('ReflectionException');
$this->expectExceptionMessage('Mismatch between file name and class name.');
$res = new ClassExistenceResource(BadFileName::class, false);
$res->isFresh(0);
}
public function testBadFileNameBis()
{
$this->expectException('ReflectionException');
$this->expectExceptionMessage('Mismatch between file name and class name.');
$res = new ClassExistenceResource(BadFileName::class, false);
$res->isFresh(0);
}
public function testConditionalClass() public function testConditionalClass()
{ {
$res = new ClassExistenceResource(ConditionalClass::class, false); $res = new ClassExistenceResource(ConditionalClass::class, false);

View File

@ -35,7 +35,13 @@ class SendMessageMiddleware implements MiddlewareInterface
public function __construct(SendersLocatorInterface $sendersLocator, EventDispatcherInterface $eventDispatcher = null) public function __construct(SendersLocatorInterface $sendersLocator, EventDispatcherInterface $eventDispatcher = null)
{ {
$this->sendersLocator = $sendersLocator; $this->sendersLocator = $sendersLocator;
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
if (null !== $eventDispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
} else {
$this->eventDispatcher = $eventDispatcher;
}
$this->logger = new NullLogger(); $this->logger = new NullLogger();
} }

View File

@ -47,8 +47,13 @@ class Worker
{ {
$this->receivers = $receivers; $this->receivers = $receivers;
$this->bus = $bus; $this->bus = $bus;
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
$this->logger = $logger; $this->logger = $logger;
if (null !== $eventDispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
} else {
$this->eventDispatcher = $eventDispatcher;
}
} }
/** /**

View File

@ -31,7 +31,12 @@ class TraceableVoter implements VoterInterface
public function __construct(VoterInterface $voter, EventDispatcherInterface $eventDispatcher) public function __construct(VoterInterface $voter, EventDispatcherInterface $eventDispatcher)
{ {
$this->voter = $voter; $this->voter = $voter;
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
if (class_exists(LegacyEventDispatcherProxy::class)) {
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
} else {
$this->eventDispatcher = $eventDispatcher;
}
} }
public function vote(TokenInterface $token, $subject, array $attributes): int public function vote(TokenInterface $token, $subject, array $attributes): int

View File

@ -46,7 +46,13 @@ class GuardAuthenticatorHandler
public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null, array $statelessProviderKeys = []) public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null, array $statelessProviderKeys = [])
{ {
$this->tokenStorage = $tokenStorage; $this->tokenStorage = $tokenStorage;
$this->dispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
if (null !== $eventDispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->dispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
} else {
$this->dispatcher = $eventDispatcher;
}
$this->statelessProviderKeys = $statelessProviderKeys; $this->statelessProviderKeys = $statelessProviderKeys;
} }

View File

@ -93,7 +93,13 @@ abstract class AbstractAuthenticationListener extends AbstractListener implement
'require_previous_session' => true, 'require_previous_session' => true,
], $options); ], $options);
$this->logger = $logger; $this->logger = $logger;
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
} else {
$this->dispatcher = $dispatcher;
}
$this->httpUtils = $httpUtils; $this->httpUtils = $httpUtils;
} }

View File

@ -52,7 +52,12 @@ abstract class AbstractPreAuthenticatedListener extends AbstractListener impleme
$this->authenticationManager = $authenticationManager; $this->authenticationManager = $authenticationManager;
$this->providerKey = $providerKey; $this->providerKey = $providerKey;
$this->logger = $logger; $this->logger = $logger;
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
} else {
$this->dispatcher = $dispatcher;
}
} }
/** /**

View File

@ -69,7 +69,13 @@ class ContextListener extends AbstractListener implements ListenerInterface
$this->userProviders = $userProviders; $this->userProviders = $userProviders;
$this->sessionKey = '_security_'.$contextKey; $this->sessionKey = '_security_'.$contextKey;
$this->logger = $logger; $this->logger = $logger;
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
} else {
$this->dispatcher = $dispatcher;
}
$this->trustResolver = $trustResolver ?: new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class); $this->trustResolver = $trustResolver ?: new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class);
$this->sessionTrackerEnabler = $sessionTrackerEnabler; $this->sessionTrackerEnabler = $sessionTrackerEnabler;
} }

View File

@ -50,7 +50,13 @@ class RememberMeListener extends AbstractListener implements ListenerInterface
$this->rememberMeServices = $rememberMeServices; $this->rememberMeServices = $rememberMeServices;
$this->authenticationManager = $authenticationManager; $this->authenticationManager = $authenticationManager;
$this->logger = $logger; $this->logger = $logger;
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
} else {
$this->dispatcher = $dispatcher;
}
$this->catchExceptions = $catchExceptions; $this->catchExceptions = $catchExceptions;
$this->sessionStrategy = null === $sessionStrategy ? new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE) : $sessionStrategy; $this->sessionStrategy = null === $sessionStrategy ? new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE) : $sessionStrategy;
} }

View File

@ -65,7 +65,13 @@ class SimplePreAuthenticationListener extends AbstractListener implements Listen
$this->providerKey = $providerKey; $this->providerKey = $providerKey;
$this->simpleAuthenticator = $simpleAuthenticator; $this->simpleAuthenticator = $simpleAuthenticator;
$this->logger = $logger; $this->logger = $logger;
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
} else {
$this->dispatcher = $dispatcher;
}
$this->trustResolver = $trustResolver ?: new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class); $this->trustResolver = $trustResolver ?: new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class);
} }

View File

@ -70,7 +70,13 @@ class SwitchUserListener extends AbstractListener implements ListenerInterface
$this->usernameParameter = $usernameParameter; $this->usernameParameter = $usernameParameter;
$this->role = $role; $this->role = $role;
$this->logger = $logger; $this->logger = $logger;
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
} else {
$this->dispatcher = $dispatcher;
}
$this->stateless = $stateless; $this->stateless = $stateless;
} }

View File

@ -69,7 +69,13 @@ class UsernamePasswordJsonAuthenticationListener extends AbstractListener implem
$this->successHandler = $successHandler; $this->successHandler = $successHandler;
$this->failureHandler = $failureHandler; $this->failureHandler = $failureHandler;
$this->logger = $logger; $this->logger = $logger;
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
if (null !== $eventDispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
} else {
$this->eventDispatcher = $eventDispatcher;
}
$this->options = array_merge(['username_path' => 'username', 'password_path' => 'password'], $options); $this->options = array_merge(['username_path' => 'username', 'password_path' => 'password'], $options);
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
} }

View File

@ -43,7 +43,13 @@ class Workflow implements WorkflowInterface
{ {
$this->definition = $definition; $this->definition = $definition;
$this->markingStore = $markingStore ?: new MultipleStateMarkingStore(); $this->markingStore = $markingStore ?: new MultipleStateMarkingStore();
$this->dispatcher = null !== $dispatcher ? LegacyEventDispatcherProxy::decorate($dispatcher) : null;
if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) {
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
} else {
$this->dispatcher = $dispatcher;
}
$this->name = $name; $this->name = $name;
} }

View File

@ -617,11 +617,11 @@ class Inline
// Optimize for returning strings. // Optimize for returning strings.
// no break // no break
case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]): case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]):
if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) {
$scalar = str_replace('_', '', (string) $scalar);
}
switch (true) { switch (true) {
case Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar):
$scalar = str_replace('_', '', (string) $scalar);
// omitting the break / return as integers are handled in the next case
// no break
case ctype_digit($scalar): case ctype_digit($scalar):
$raw = $scalar; $raw = $scalar;
$cast = (int) $scalar; $cast = (int) $scalar;
@ -631,7 +631,7 @@ class Inline
$raw = $scalar; $raw = $scalar;
$cast = (int) $scalar; $cast = (int) $scalar;
return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw); return '0' == $scalar[1] ? -octdec(substr($scalar, 1)) : (($raw === (string) $cast) ? $cast : $raw);
case is_numeric($scalar): case is_numeric($scalar):
case Parser::preg_match(self::getHexRegex(), $scalar): case Parser::preg_match(self::getHexRegex(), $scalar):
$scalar = str_replace('_', '', $scalar); $scalar = str_replace('_', '', $scalar);

View File

@ -720,4 +720,20 @@ class InlineTest extends TestCase
$this->expectExceptionMessage("Unexpected end of line, expected one of \",}\n\" at line 1 (near \"{abc: 'def'\")."); $this->expectExceptionMessage("Unexpected end of line, expected one of \",}\n\" at line 1 (near \"{abc: 'def'\").");
Inline::parse("{abc: 'def'"); Inline::parse("{abc: 'def'");
} }
/**
* @dataProvider getTestsForOctalNumbers
*/
public function testParseOctalNumbers($expected, $yaml)
{
self::assertSame($expected, Inline::parse($yaml));
}
public function getTestsForOctalNumbers()
{
return [
'positive octal number' => [28, '034'],
'negative octal number' => [-28, '-034'],
];
}
} }