ALPHA 3.0.2

This commit is contained in:
TheGamecraft
2018-09-05 11:35:41 -04:00
parent 4dbdc8fd19
commit 8ef8057bfa
771 changed files with 9284 additions and 5857 deletions

View File

@@ -12,10 +12,12 @@
namespace Symfony\Component\Console\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\TypedReference;
@@ -28,7 +30,7 @@ class AddConsoleCommandPassTest extends TestCase
public function testProcess($public)
{
$container = new ContainerBuilder();
$container->addCompilerPass(new AddConsoleCommandPass());
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->setParameter('my-command.class', 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand');
$id = 'my-command';
@@ -124,7 +126,7 @@ class AddConsoleCommandPassTest extends TestCase
{
$container = new ContainerBuilder();
$container->setResourceTracking(false);
$container->addCompilerPass(new AddConsoleCommandPass());
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$definition = new Definition('Symfony\Component\Console\Tests\DependencyInjection\MyCommand');
$definition->addTag('console.command');
@@ -142,7 +144,7 @@ class AddConsoleCommandPassTest extends TestCase
{
$container = new ContainerBuilder();
$container->setResourceTracking(false);
$container->addCompilerPass(new AddConsoleCommandPass());
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$definition = new Definition('SplObjectStorage');
$definition->addTag('console.command');
@@ -171,6 +173,79 @@ class AddConsoleCommandPassTest extends TestCase
$this->assertTrue($container->hasAlias($aliasPrefix.'my-command1'));
$this->assertTrue($container->hasAlias($aliasPrefix.'my-command2'));
}
public function testProcessOnChildDefinitionWithClass()
{
$container = new ContainerBuilder();
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$className = 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand';
$parentId = 'my-parent-command';
$childId = 'my-child-command';
$parentDefinition = new Definition(/* no class */);
$parentDefinition->setAbstract(true)->setPublic(false);
$childDefinition = new ChildDefinition($parentId);
$childDefinition->addTag('console.command')->setPublic(true);
$childDefinition->setClass($className);
$container->setDefinition($parentId, $parentDefinition);
$container->setDefinition($childId, $childDefinition);
$container->compile();
$command = $container->get($childId);
$this->assertInstanceOf($className, $command);
}
public function testProcessOnChildDefinitionWithParentClass()
{
$container = new ContainerBuilder();
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$className = 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand';
$parentId = 'my-parent-command';
$childId = 'my-child-command';
$parentDefinition = new Definition($className);
$parentDefinition->setAbstract(true)->setPublic(false);
$childDefinition = new ChildDefinition($parentId);
$childDefinition->addTag('console.command')->setPublic(true);
$container->setDefinition($parentId, $parentDefinition);
$container->setDefinition($childId, $childDefinition);
$container->compile();
$command = $container->get($childId);
$this->assertInstanceOf($className, $command);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage The definition for "my-child-command" has no class.
*/
public function testProcessOnChildDefinitionWithoutClass()
{
$container = new ContainerBuilder();
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
$parentId = 'my-parent-command';
$childId = 'my-child-command';
$parentDefinition = new Definition();
$parentDefinition->setAbstract(true)->setPublic(false);
$childDefinition = new ChildDefinition($parentId);
$childDefinition->addTag('console.command')->setPublic(true);
$container->setDefinition($parentId, $parentDefinition);
$container->setDefinition($childId, $childDefinition);
$container->compile();
}
}
class MyCommand extends Command

View File

@@ -26,6 +26,9 @@ class ClassNotFoundException extends FatalErrorException
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
null,
true,
null,
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());

View File

@@ -18,9 +18,9 @@ namespace Symfony\Component\Debug\Exception;
*/
class FatalErrorException extends \ErrorException
{
public function __construct(string $message, int $code, int $severity, string $filename, int $lineno, int $traceOffset = null, bool $traceArgs = true, array $trace = null)
public function __construct(string $message, int $code, int $severity, string $filename, int $lineno, int $traceOffset = null, bool $traceArgs = true, array $trace = null, \Throwable $previous = null)
{
parent::__construct($message, $code, $severity, $filename, $lineno);
parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
if (null !== $trace) {
if (!$traceArgs) {

View File

@@ -26,6 +26,9 @@ class UndefinedFunctionException extends FatalErrorException
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
null,
true,
null,
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());

View File

@@ -26,6 +26,9 @@ class UndefinedMethodException extends FatalErrorException
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
null,
true,
null,
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());

View File

@@ -60,11 +60,20 @@ abstract class RealIteratorTestCase extends IteratorTestCase
public static function tearDownAfterClass()
{
foreach (array_reverse(self::$files) as $file) {
if (DIRECTORY_SEPARATOR === $file[strlen($file) - 1]) {
@rmdir($file);
$paths = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(self::$tmpDir, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($paths as $path) {
if ($path->isDir()) {
if ($path->isLink()) {
@unlink($path);
} else {
@rmdir($path);
}
} else {
@unlink($file);
@unlink($path);
}
}
}

View File

@@ -210,7 +210,7 @@ class ResponseHeaderBag extends HeaderBag
*
* @param string $format
*
* @return array
* @return Cookie[]
*
* @throws \InvalidArgumentException When the $format is invalid
*/

View File

@@ -29,7 +29,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
private $flashName;
private $attributeName;
private $data = array();
private $hasBeenStarted;
private $usageIndex = 0;
/**
* @param SessionStorageInterface $storage A SessionStorageInterface instance
@@ -54,6 +54,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function start()
{
++$this->usageIndex;
return $this->storage->start();
}
@@ -142,13 +144,13 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
}
/**
* @return bool
* @return int
*
* @internal
*/
public function hasBeenStarted()
public function getUsageIndex()
{
return $this->hasBeenStarted;
return $this->usageIndex;
}
/**
@@ -158,6 +160,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function isEmpty()
{
++$this->usageIndex;
foreach ($this->data as &$data) {
if (!empty($data)) {
return false;
@@ -182,6 +185,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function migrate($destroy = false, $lifetime = null)
{
++$this->usageIndex;
return $this->storage->regenerate($destroy, $lifetime);
}
@@ -190,6 +195,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function save()
{
++$this->usageIndex;
$this->storage->save();
}
@@ -230,6 +237,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function getMetadataBag()
{
++$this->usageIndex;
return $this->storage->getMetadataBag();
}
@@ -238,7 +247,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/
public function registerBag(SessionBagInterface $bag)
{
$this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->hasBeenStarted));
$this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex));
}
/**

View File

@@ -20,13 +20,13 @@ final class SessionBagProxy implements SessionBagInterface
{
private $bag;
private $data;
private $hasBeenStarted;
private $usageIndex;
public function __construct(SessionBagInterface $bag, array &$data, &$hasBeenStarted)
public function __construct(SessionBagInterface $bag, array &$data, &$usageIndex)
{
$this->bag = $bag;
$this->data = &$data;
$this->hasBeenStarted = &$hasBeenStarted;
$this->usageIndex = &$usageIndex;
}
/**
@@ -34,6 +34,8 @@ final class SessionBagProxy implements SessionBagInterface
*/
public function getBag()
{
++$this->usageIndex;
return $this->bag;
}
@@ -42,6 +44,8 @@ final class SessionBagProxy implements SessionBagInterface
*/
public function isEmpty()
{
++$this->usageIndex;
return empty($this->data[$this->bag->getStorageKey()]);
}
@@ -58,7 +62,7 @@ final class SessionBagProxy implements SessionBagInterface
*/
public function initialize(array &$array)
{
$this->hasBeenStarted = true;
++$this->usageIndex;
$this->data[$this->bag->getStorageKey()] = &$array;
$this->bag->initialize($array);
@@ -77,6 +81,8 @@ final class SessionBagProxy implements SessionBagInterface
*/
public function clear()
{
++$this->usageIndex;
return $this->bag->clear();
}
}

View File

@@ -100,6 +100,6 @@ class RedisSessionHandler extends AbstractSessionHandler
*/
public function updateTimestamp($sessionId, $data)
{
return $this->redis->expire($this->prefix.$sessionId, (int) ini_get('session.gc_maxlifetime'));
return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ini_get('session.gc_maxlifetime'));
}
}

View File

@@ -21,7 +21,6 @@ class RequestTest extends TestCase
{
protected function tearDown()
{
// reset
Request::setTrustedProxies(array(), -1);
Request::setTrustedHosts(array());
}

View File

@@ -5,7 +5,7 @@ CHANGELOG
-----
* added orphaned events support to `EventDataCollector`
* `ExceptionListener` now logs and collects exceptions at priority `2048` (previously logged at `-128` and collected at `0`)
* `ExceptionListener` now logs exceptions at priority `0` (previously logged at `-128`)
* Deprecated `service:action` syntax with a single colon to reference controllers. Use `service::method` instead.
* Added the ability to profile individual argument value resolvers via the
`Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver`

View File

@@ -68,7 +68,7 @@ final class ServiceValueResolver implements ArgumentValueResolverInterface
yield $this->container->get($controller)->get($argument->getName());
} catch (RuntimeException $e) {
$what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller);
$message = preg_replace('/service "service_locator\.[^"]++"/', $what, $e->getMessage());
$message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $e->getMessage());
if ($e->getMessage() === $message) {
$message = sprintf('Cannot resolve %s: %s', $what, $message);

View File

@@ -21,7 +21,7 @@ use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
use Symfony\Component\VarDumper\Dumper\ServerDumper;
use Symfony\Component\VarDumper\Server\Connection;
/**
* @author Nicolas Grekas <p@tchwork.com>
@@ -38,17 +38,18 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
private $charset;
private $requestStack;
private $dumper;
private $dumperIsInjected;
private $sourceContextProvider;
public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, string $charset = null, RequestStack $requestStack = null, DataDumperInterface $dumper = null)
/**
* @param DataDumperInterface|Connection|null $dumper
*/
public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, string $charset = null, RequestStack $requestStack = null, $dumper = null)
{
$this->stopwatch = $stopwatch;
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$this->charset = $charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8';
$this->requestStack = $requestStack;
$this->dumper = $dumper;
$this->dumperIsInjected = null !== $dumper;
// All clones share these properties by reference:
$this->rootRefs = array(
@@ -58,7 +59,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
&$this->clonesCount,
);
$this->sourceContextProvider = $dumper instanceof ServerDumper && isset($dumper->getContextProviders()['source']) ? $dumper->getContextProviders()['source'] : new SourceContextProvider($this->charset);
$this->sourceContextProvider = $dumper instanceof Connection && isset($dumper->getContextProviders()['source']) ? $dumper->getContextProviders()['source'] : new SourceContextProvider($this->charset);
}
public function __clone()
@@ -71,14 +72,17 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
if ($this->stopwatch) {
$this->stopwatch->start('dump');
}
if ($this->isCollected && !$this->dumper) {
$this->isCollected = false;
}
list('name' => $name, 'file' => $file, 'line' => $line, 'file_excerpt' => $fileExcerpt) = $this->sourceContextProvider->getContext();
if ($this->dumper) {
if ($this->dumper instanceof Connection) {
if (!$this->dumper->write($data)) {
$this->isCollected = false;
}
} elseif ($this->dumper) {
$this->doDump($this->dumper, $data, $name, $file, $line);
} else {
$this->isCollected = false;
}
$this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');
@@ -124,7 +128,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
}
$this->data = array();
$this->dataCount = 0;
$this->isCollected = false;
$this->isCollected = true;
$this->clonesCount = 0;
$this->clonesIndex = 0;
}
@@ -141,9 +145,6 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
$this->data = array();
$this->dataCount = 0;
$this->isCollected = true;
if (!$this->dumperIsInjected) {
$this->dumper = null;
}
return $ser;
}
@@ -245,7 +246,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
};
$contextDumper = $contextDumper->bindTo($dumper, $dumper);
$contextDumper($name, $file, $line, $this->fileLinkFormat);
} elseif (!$dumper instanceof ServerDumper) {
} else {
$cloner = new VarCloner();
$dumper->dump($cloner->cloneVar($name.' on line '.$line.':'));
}

View File

@@ -15,6 +15,7 @@ use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -37,6 +38,7 @@ abstract class AbstractSessionListener implements EventSubscriberInterface
const NO_AUTO_CACHE_CONTROL_HEADER = 'Symfony-Session-NoAutoCacheControl';
protected $container;
private $sessionUsageStack = array();
public function __construct(ContainerInterface $container = null)
{
@@ -49,6 +51,7 @@ abstract class AbstractSessionListener implements EventSubscriberInterface
return;
}
$session = null;
$request = $event->getRequest();
if ($request->hasSession()) {
// no-op
@@ -57,6 +60,9 @@ abstract class AbstractSessionListener implements EventSubscriberInterface
} elseif ($session = $this->getSession()) {
$request->setSession($session);
}
$session = $session ?? ($this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : null);
$this->sessionUsageStack[] = $session instanceof Session ? $session->getUsageIndex() : null;
}
public function onKernelResponse(FilterResponseEvent $event)
@@ -71,7 +77,7 @@ abstract class AbstractSessionListener implements EventSubscriberInterface
$response = $event->getResponse();
if ($session->isStarted() || ($session instanceof Session && $session->hasBeenStarted())) {
if ($session instanceof Session ? $session->getUsageIndex() !== end($this->sessionUsageStack) : $session->isStarted()) {
if (!$response->headers->has(self::NO_AUTO_CACHE_CONTROL_HEADER)) {
$response
->setPrivate()
@@ -113,12 +119,23 @@ abstract class AbstractSessionListener implements EventSubscriberInterface
}
}
/**
* @internal
*/
public function onFinishRequest(FinishRequestEvent $event)
{
if ($event->isMasterRequest()) {
array_pop($this->sessionUsageStack);
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array('onKernelRequest', 128),
// low priority to come after regular response listeners, but higher than StreamedResponseListener
KernelEvents::RESPONSE => array('onKernelResponse', -1000),
KernelEvents::FINISH_REQUEST => array('onFinishRequest'),
);
}

View File

@@ -15,6 +15,7 @@ use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
use Symfony\Component\VarDumper\Server\Connection;
use Symfony\Component\VarDumper\VarDumper;
/**
@@ -26,20 +27,27 @@ class DumpListener implements EventSubscriberInterface
{
private $cloner;
private $dumper;
private $connection;
public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper)
public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper, Connection $connection = null)
{
$this->cloner = $cloner;
$this->dumper = $dumper;
$this->connection = $connection;
}
public function configure()
{
$cloner = $this->cloner;
$dumper = $this->dumper;
$connection = $this->connection;
VarDumper::setHandler(function ($var) use ($cloner, $dumper) {
$dumper->dump($cloner->cloneVar($var));
VarDumper::setHandler(static function ($var) use ($cloner, $dumper, $connection) {
$data = $cloner->cloneVar($var);
if (!$connection || !$connection->write($data)) {
$dumper->dump($data);
}
});
}

View File

@@ -12,11 +12,9 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
@@ -35,20 +33,17 @@ class ExceptionListener implements EventSubscriberInterface
protected $controller;
protected $logger;
protected $debug;
private $charset;
public function __construct($controller, LoggerInterface $logger = null, $debug = false, $charset = null)
public function __construct($controller, LoggerInterface $logger = null, $debug = false)
{
$this->controller = $controller;
$this->logger = $logger;
$this->debug = $debug;
$this->charset = $charset;
}
public function logKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
$request = $event->getRequest();
$this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine()));
}
@@ -72,7 +67,7 @@ class ExceptionListener implements EventSubscriberInterface
}
}
$prev = new \ReflectionProperty('Exception', 'previous');
$prev = new \ReflectionProperty($wrapper instanceof \Exception ? \Exception::class : \Error::class, 'previous');
$prev->setAccessible(true);
$prev->setValue($wrapper, $exception);
@@ -94,7 +89,7 @@ class ExceptionListener implements EventSubscriberInterface
{
return array(
KernelEvents::EXCEPTION => array(
array('logKernelException', 2048),
array('logKernelException', 0),
array('onKernelException', -128),
),
);
@@ -128,12 +123,8 @@ class ExceptionListener implements EventSubscriberInterface
protected function duplicateRequest(\Exception $exception, Request $request)
{
$attributes = array(
'exception' => $exception = FlattenException::create($exception),
'_controller' => $this->controller ?: function () use ($exception) {
$handler = new ExceptionHandler($this->debug, $this->charset);
return new Response($handler->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders());
},
'_controller' => $this->controller,
'exception' => FlattenException::create($exception),
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
);
$request = $request->duplicate(null, null, $attributes);

View File

@@ -121,7 +121,7 @@ class ProfilerListener implements EventSubscriberInterface
{
return array(
KernelEvents::RESPONSE => array('onKernelResponse', -100),
KernelEvents::EXCEPTION => array('onKernelException', 2048),
KernelEvents::EXCEPTION => array('onKernelException', 0),
KernelEvents::TERMINATE => array('onKernelTerminate', -1024),
);
}

View File

@@ -115,7 +115,9 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
$server['HTTP_X_FORWARDED_FOR'] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp();
}
$server['REMOTE_ADDR'] = '127.0.0.1';
$trustedProxies = Request::getTrustedProxies();
$server['REMOTE_ADDR'] = $trustedProxies ? reset($trustedProxies) : '127.0.0.1';
unset($server['HTTP_IF_MODIFIED_SINCE']);
unset($server['HTTP_IF_NONE_MATCH']);

View File

@@ -165,7 +165,11 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
// FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism
if (HttpKernelInterface::MASTER_REQUEST === $type) {
$this->traces = array();
$this->request = $request;
// Keep a clone of the original request for surrogates so they can access it.
// We must clone here to get a separate instance because the application will modify the request during
// the application flow (we know it always does because we do ourselves by setting REMOTE_ADDR to 127.0.0.1
// and adding the X-Forwarded-For header, see HttpCache::forward()).
$this->request = clone $request;
if (null !== $this->surrogate) {
$this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy();
}

View File

@@ -63,11 +63,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
private $requestStackSize = 0;
private $resetServices = false;
const VERSION = '4.1.0';
const VERSION_ID = 40100;
const VERSION = '4.1.1';
const VERSION_ID = 40101;
const MAJOR_VERSION = 4;
const MINOR_VERSION = 1;
const RELEASE_VERSION = 0;
const RELEASE_VERSION = 1;
const EXTRA_VERSION = '';
const END_OF_MAINTENANCE = '01/2019';

View File

@@ -12,10 +12,12 @@
namespace Symfony\Component\HttpKernel\Tests\Controller\ArgumentResolver;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass;
class ServiceValueResolverTest extends TestCase
{
@@ -85,6 +87,25 @@ class ServiceValueResolverTest extends TestCase
$this->assertYieldEquals(array(new DummyService()), $resolver->resolve($request, $argument));
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Cannot autowire argument $dummy of "Symfony\Component\HttpKernel\Tests\Controller\ArgumentResolver\DummyController::index()": it references class "Symfony\Component\HttpKernel\Tests\Controller\ArgumentResolver\DummyService" but no such service exists.
*/
public function testErrorIsTruncated()
{
$container = new ContainerBuilder();
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
$container->register('argument_resolver.service', ServiceValueResolver::class)->addArgument(null)->setPublic(true);
$container->register(DummyController::class)->addTag('controller.service_arguments')->setPublic(true);
$container->compile();
$request = $this->requestWithAttributes(array('_controller' => array(DummyController::class, 'index')));
$argument = new ArgumentMetadata('dummy', DummyService::class, false, false, null);
$container->get('argument_resolver.service')->resolve($request, $argument)->current();
}
private function requestWithAttributes(array $attributes)
{
$request = Request::create('/');
@@ -110,3 +131,10 @@ class ServiceValueResolverTest extends TestCase
class DummyService
{
}
class DummyController
{
public function index(DummyService $dummy)
{
}
}

View File

@@ -17,7 +17,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\ServerDumper;
use Symfony\Component\VarDumper\Server\Connection;
/**
* @author Nicolas Grekas <p@tchwork.com>
@@ -57,13 +57,13 @@ class DumpDataCollectorTest extends TestCase
$this->assertSame('a:2:{i:0;b:0;i:1;s:5:"UTF-8";}', $collector->serialize());
}
public function testDumpWithServerDumper()
public function testDumpWithServerConnection()
{
$data = new Data(array(array(123)));
// Server is up, server dumper is used
$serverDumper = $this->getMockBuilder(ServerDumper::class)->disableOriginalConstructor()->getMock();
$serverDumper->expects($this->once())->method('dump');
$serverDumper = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock();
$serverDumper->expects($this->once())->method('write')->willReturn(true);
$collector = new DumpDataCollector(null, null, null, null, $serverDumper);
$collector->dump($data);

View File

@@ -155,23 +155,6 @@ class ExceptionListenerTest extends TestCase
$this->assertFalse($response->headers->has('content-security-policy'), 'CSP header has been removed');
$this->assertFalse($dispatcher->hasListeners(KernelEvents::RESPONSE), 'CSP removal listener has been removed');
}
public function testNullController()
{
$listener = new ExceptionListener(null);
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
$kernel->expects($this->once())->method('handle')->will($this->returnCallback(function (Request $request) {
$controller = $request->attributes->get('_controller');
return $controller();
}));
$request = Request::create('/');
$event = new GetResponseForExceptionEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, new \Exception('foo'));
$listener->onKernelException($event);
$this->assertContains('Whoops, looks like something went wrong.', $event->getResponse()->getContent());
}
}
class TestLogger extends Logger implements DebugLoggerInterface

View File

@@ -19,6 +19,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener;
use Symfony\Component\HttpKernel\EventListener\SessionListener;
use Symfony\Component\HttpKernel\HttpKernelInterface;
@@ -59,8 +60,7 @@ class SessionListenerTest extends TestCase
public function testResponseIsPrivateIfSessionStarted()
{
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
$session->expects($this->exactly(2))->method('isStarted')->willReturn(false);
$session->expects($this->once())->method('hasBeenStarted')->willReturn(true);
$session->expects($this->exactly(2))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1));
$container = new Container();
$container->set('initialized_session', $session);
@@ -68,6 +68,9 @@ class SessionListenerTest extends TestCase
$listener = new SessionListener($container);
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
$request = new Request();
$listener->onKernelRequest(new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST));
$response = new Response();
$listener->onKernelResponse(new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response));
@@ -80,8 +83,7 @@ class SessionListenerTest extends TestCase
public function testResponseIsStillPublicIfSessionStartedAndHeaderPresent()
{
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
$session->expects($this->exactly(2))->method('isStarted')->willReturn(false);
$session->expects($this->once())->method('hasBeenStarted')->willReturn(true);
$session->expects($this->exactly(2))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1));
$container = new Container();
$container->set('initialized_session', $session);
@@ -89,6 +91,9 @@ class SessionListenerTest extends TestCase
$listener = new SessionListener($container);
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
$request = new Request();
$listener->onKernelRequest(new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST));
$response = new Response();
$response->setSharedMaxAge(60);
$response->headers->set(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER, 'true');
@@ -113,4 +118,39 @@ class SessionListenerTest extends TestCase
$listener = new SessionListener($container);
$listener->onKernelResponse($event);
}
public function testSurrogateMasterRequestIsPublic()
{
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
$session->expects($this->exactly(4))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1, 1, 1));
$container = new Container();
$container->set('initialized_session', $session);
$container->set('session', $session);
$listener = new SessionListener($container);
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
$request = new Request();
$response = new Response();
$response->setCache(array('public' => true, 'max_age' => '30'));
$listener->onKernelRequest(new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST));
$this->assertTrue($request->hasSession());
$subRequest = clone $request;
$this->assertSame($request->getSession(), $subRequest->getSession());
$listener->onKernelRequest(new GetResponseEvent($kernel, $subRequest, HttpKernelInterface::MASTER_REQUEST));
$listener->onKernelResponse(new FilterResponseEvent($kernel, $subRequest, HttpKernelInterface::MASTER_REQUEST, $response));
$listener->onFinishRequest(new FinishRequestEvent($kernel, $subRequest, HttpKernelInterface::MASTER_REQUEST));
$this->assertFalse($response->headers->hasCacheControlDirective('private'));
$this->assertFalse($response->headers->hasCacheControlDirective('must-revalidate'));
$this->assertSame('30', $response->headers->getCacheControlDirective('max-age'));
$listener->onKernelResponse(new FilterResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response));
$this->assertTrue($response->headers->hasCacheControlDirective('private'));
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
$this->assertSame('0', $response->headers->getCacheControlDirective('max-age'));
}
}

View File

@@ -21,6 +21,11 @@ use Symfony\Component\HttpKernel\KernelEvents;
class ValidateRequestListenerTest extends TestCase
{
protected function tearDown()
{
Request::setTrustedProxies(array(), -1);
}
/**
* @expectedException \Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException
*/

View File

@@ -168,16 +168,33 @@ class InlineFragmentRendererTest extends TestCase
public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest()
{
$expectedSubRequest = Request::create('/');
if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) {
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
$expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1');
}
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
$expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1');
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
$request = Request::create('/', 'GET', array(), array(), array(), array('HTTP_IF_MODIFIED_SINCE' => 'Fri, 01 Jan 2016 00:00:00 GMT', 'HTTP_IF_NONE_MATCH' => '*'));
$strategy->render('/', $request);
}
public function testFirstTrustedProxyIsSetAsRemote()
{
Request::setTrustedProxies(array('1.1.1.1'), -1);
$expectedSubRequest = Request::create('/');
$expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
$expectedSubRequest->server->set('REMOTE_ADDR', '1.1.1.1');
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
$expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1');
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
$request = Request::create('/');
$request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
$strategy->render('/', $request);
Request::setTrustedProxies(array(), -1);
}
/**
* Creates a Kernel expecting a request equals to $request
* Allows delta in comparison in case REQUEST_TIME changed by 1 second.

View File

@@ -11,9 +11,11 @@
namespace Symfony\Component\HttpKernel\Tests\HttpCache;
use Symfony\Component\HttpKernel\HttpCache\Esi;
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpCache\Store;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
@@ -1350,6 +1352,8 @@ class HttpCacheTest extends HttpCacheTestCase
$this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1'));
$this->assertEquals($expected, Request::getTrustedProxies());
Request::setTrustedProxies(array(), -1);
}
public function getTrustedProxyData()
@@ -1465,6 +1469,42 @@ class HttpCacheTest extends HttpCacheTestCase
$this->assertHttpKernelIsNotCalled();
$this->assertSame('get', $this->response->getContent());
}
public function testUsesOriginalRequestForSurrogate()
{
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
$store = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpCache\StoreInterface')->getMock();
$kernel
->expects($this->exactly(2))
->method('handle')
->willReturnCallback(function (Request $request) {
$this->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR'));
return new Response();
});
$cache = new HttpCache($kernel,
$store,
new Esi()
);
$request = Request::create('/');
$request->server->set('REMOTE_ADDR', '10.0.0.1');
// Main request
$cache->handle($request, HttpKernelInterface::MASTER_REQUEST);
// Main request was now modified by HttpCache
// The surrogate will ask for the request using $this->cache->getRequest()
// which MUST return the original request so the surrogate
// can actually behave like a reverse proxy like e.g. Varnish would.
$this->assertSame('10.0.0.1', $cache->getRequest()->getClientIp());
$this->assertSame('10.0.0.1', $cache->getRequest()->server->get('REMOTE_ADDR'));
// Surrogate request
$cache->handle($request, HttpKernelInterface::SUB_REQUEST);
}
}
class TestKernel implements HttpKernelInterface

View File

@@ -331,6 +331,8 @@ class HttpKernelTest extends TestCase
$kernel = $this->getHttpKernel($dispatcher);
$kernel->handle($request, $kernel::MASTER_REQUEST, false);
Request::setTrustedProxies(array(), -1);
}
private function getHttpKernel(EventDispatcherInterface $eventDispatcher, $controller = null, RequestStack $requestStack = null, array $arguments = array())

View File

@@ -18,7 +18,7 @@
"require": {
"php": "^7.1.3",
"symfony/event-dispatcher": "~4.1",
"symfony/http-foundation": "~4.1",
"symfony/http-foundation": "^4.1.1",
"symfony/debug": "~3.4|~4.0",
"symfony/polyfill-ctype": "~1.8",
"psr/log": "~1.0"
@@ -37,7 +37,7 @@
"symfony/stopwatch": "~3.4|~4.0",
"symfony/templating": "~3.4|~4.0",
"symfony/translation": "~3.4|~4.0",
"symfony/var-dumper": "~4.1",
"symfony/var-dumper": "^4.1.1",
"psr/cache": "~1.0"
},
"provide": {
@@ -46,7 +46,7 @@
"conflict": {
"symfony/config": "<3.4",
"symfony/dependency-injection": "<4.1",
"symfony/var-dumper": "<4.1",
"symfony/var-dumper": "<4.1.1",
"twig/twig": "<1.34|<2.4,>=2"
},
"suggest": {

View File

@@ -73,7 +73,7 @@ class ExecutableFinder
$suffixes = array('');
if ('\\' === DIRECTORY_SEPARATOR) {
$pathExt = getenv('PATHEXT');
$suffixes = array_merge($suffixes, $pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes);
$suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
}
foreach ($suffixes as $suffix) {
foreach ($dirs as $dir) {

View File

@@ -117,6 +117,36 @@ class ExecutableFinderTest extends TestCase
$this->assertSamePath(PHP_BINARY, $result);
}
/**
* @requires PHP 5.4
*/
public function testFindBatchExecutableOnWindows()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
if ('\\' !== DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Can be only tested on windows');
}
$target = tempnam(sys_get_temp_dir(), 'example-windows-executable');
touch($target);
touch($target.'.BAT');
$this->assertFalse(is_executable($target));
$this->setPath(sys_get_temp_dir());
$finder = new ExecutableFinder();
$result = $finder->find(basename($target), false);
unlink($target);
unlink($target.'.BAT');
$this->assertSamePath($target.'.BAT', $result);
}
private function assertSamePath($expected, $tested)
{
if ('\\' === DIRECTORY_SEPARATOR) {

View File

@@ -376,10 +376,10 @@ EOF;
if ($hostRegex) {
preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $hostRegex, $rx);
$state->vars = array();
$hostRegex = '(?i:'.preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]).')';
$hostRegex = '(?i:'.preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]).')\.';
$state->hostVars = $state->vars;
} else {
$hostRegex = '[^/]*+';
$hostRegex = '(?:(?:[^.]*+\.)++)';
$state->hostVars = array();
}
$state->mark += strlen($rx = ($prev ? ')' : '')."|{$hostRegex}(?");
@@ -406,6 +406,7 @@ EOF;
$rx = ")$}{$modifiers}";
$code .= "\n .'{$rx}',";
$state->regex .= $rx;
$state->markTail = 0;
// if the regex is too large, throw a signaling exception to recompute with smaller chunk size
set_error_handler(function ($type, $message) { throw 0 === strpos($message, $this->signalingException->getMessage()) ? $this->signalingException : new \ErrorException($message); });
@@ -427,7 +428,7 @@ EOF;
EOF;
}
$matchedPathinfo = $matchHost ? '$host.$pathinfo' : '$pathinfo';
$matchedPathinfo = $matchHost ? '$host.\'.\'.$pathinfo' : '$pathinfo';
unset($state->getVars);
return <<<EOF

View File

@@ -63,11 +63,9 @@ class StaticPrefixCollection
*
* @param array|self $route
*/
public function addRoute(string $prefix, $route, string $staticPrefix = null)
public function addRoute(string $prefix, $route)
{
if (null === $staticPrefix) {
list($prefix, $staticPrefix) = $this->getCommonPrefix($prefix, $prefix);
}
list($prefix, $staticPrefix) = $this->getCommonPrefix($prefix, $prefix);
for ($i = \count($this->items) - 1; 0 <= $i; --$i) {
$item = $this->items[$i];
@@ -102,7 +100,7 @@ class StaticPrefixCollection
if ($item instanceof self && $this->prefixes[$i] === $commonPrefix) {
// the new route is a child of a previous one, let's nest it
$item->addRoute($prefix, $route, $staticPrefix);
$item->addRoute($prefix, $route);
} else {
// the new route and a previous one have a common prefix, let's merge them
$child = new self($commonPrefix);
@@ -187,6 +185,12 @@ class StaticPrefixCollection
}
}
restore_error_handler();
if ($i < $end && 0b10 === (\ord($prefix[$i]) >> 6) && preg_match('//u', $prefix.' '.$anotherPrefix)) {
do {
// Prevent cutting in the middle of an UTF-8 characters
--$i;
} while (0b10 === (\ord($prefix[$i]) >> 6));
}
return array(substr($prefix, 0, $i), substr($prefix, 0, $staticLength ?? $i));
}

View File

@@ -321,7 +321,7 @@ class RouteCompiler implements RouteCompilerInterface
continue;
}
$regexp = substr_replace($regexp, '?:', $i, 0);
$i += 2;
++$i;
}
return $regexp;

View File

@@ -79,50 +79,50 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
return $ret;
}
$matchedPathinfo = $host.$pathinfo;
$matchedPathinfo = $host.'.'.$pathinfo;
$regexList = array(
0 => '{^(?'
.'|[^/]*+(?'
.'|/foo/(baz|symfony)(*:34)'
.'|(?:(?:[^.]*+\\.)++)(?'
.'|/foo/(baz|symfony)(*:46)'
.'|/bar(?'
.'|/([^/]++)(*:57)'
.'|head/([^/]++)(*:77)'
.'|/([^/]++)(*:69)'
.'|head/([^/]++)(*:89)'
.')'
.'|/test/([^/]++)/(?'
.'|(*:103)'
.'|(*:115)'
.')'
.'|/([\']+)(*:119)'
.'|/([\']+)(*:131)'
.'|/a/(?'
.'|b\'b/([^/]++)(?'
.'|(*:148)'
.'|(*:156)'
.'|(*:160)'
.'|(*:168)'
.')'
.'|(.*)(*:169)'
.'|(.*)(*:181)'
.'|b\'b/([^/]++)(?'
.'|(*:192)'
.'|(*:200)'
.'|(*:204)'
.'|(*:212)'
.')'
.')'
.'|/multi/hello(?:/([^/]++))?(*:236)'
.'|/multi/hello(?:/([^/]++))?(*:248)'
.'|/([^/]++)/b/([^/]++)(?'
.'|(*:267)'
.'|(*:275)'
.'|(*:279)'
.'|(*:287)'
.')'
.'|/aba/([^/]++)(*:297)'
.')|(?i:([^\\.]++)\\.example\\.com)(?'
.'|/aba/([^/]++)(*:309)'
.')|(?i:([^\\.]++)\\.example\\.com)\\.(?'
.'|/route1(?'
.'|3/([^/]++)(*:357)'
.'|4/([^/]++)(*:375)'
.'|3/([^/]++)(*:371)'
.'|4/([^/]++)(*:389)'
.')'
.')|(?i:c\\.example\\.com)(?'
.'|/route15/([^/]++)(*:425)'
.')|[^/]*+(?'
.'|/route16/([^/]++)(*:460)'
.')|(?i:c\\.example\\.com)\\.(?'
.'|/route15/([^/]++)(*:441)'
.')|(?:(?:[^.]*+\\.)++)(?'
.'|/route16/([^/]++)(*:488)'
.'|/a/(?'
.'|a\\.\\.\\.(*:481)'
.'|a\\.\\.\\.(*:509)'
.'|b/(?'
.'|([^/]++)(*:502)'
.'|c/([^/]++)(*:520)'
.'|([^/]++)(*:530)'
.'|c/([^/]++)(*:548)'
.')'
.')'
.')'
@@ -132,7 +132,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
case 103:
case 115:
$matches = array('foo' => $matches[1] ?? null);
// baz4
@@ -159,7 +159,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
not_bazbaz6:
break;
case 148:
case 160:
$matches = array('foo' => $matches[1] ?? null);
// foo1
@@ -173,14 +173,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
not_foo1:
break;
case 192:
case 204:
$matches = array('foo1' => $matches[1] ?? null);
// foo2
return $this->mergeDefaults(array('_route' => 'foo2') + $matches, array());
break;
case 267:
case 279:
$matches = array('_locale' => $matches[1] ?? null, 'foo' => $matches[2] ?? null);
// foo3
@@ -189,23 +189,23 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
break;
default:
$routes = array(
34 => array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null),
57 => array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null),
77 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null),
119 => array(array('_route' => 'quoter'), array('quoter'), null, null),
156 => array(array('_route' => 'bar1'), array('bar'), null, null),
169 => array(array('_route' => 'overridden'), array('var'), null, null),
200 => array(array('_route' => 'bar2'), array('bar1'), null, null),
236 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null),
275 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null),
297 => array(array('_route' => 'foo4'), array('foo'), null, null),
357 => array(array('_route' => 'route13'), array('var1', 'name'), null, null),
375 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null),
425 => array(array('_route' => 'route15'), array('name'), null, null),
460 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null),
481 => array(array('_route' => 'a'), array(), null, null),
502 => array(array('_route' => 'b'), array('var'), null, null),
520 => array(array('_route' => 'c'), array('var'), null, null),
46 => array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null),
69 => array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null),
89 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null),
131 => array(array('_route' => 'quoter'), array('quoter'), null, null),
168 => array(array('_route' => 'bar1'), array('bar'), null, null),
181 => array(array('_route' => 'overridden'), array('var'), null, null),
212 => array(array('_route' => 'bar2'), array('bar1'), null, null),
248 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null),
287 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null),
309 => array(array('_route' => 'foo4'), array('foo'), null, null),
371 => array(array('_route' => 'route13'), array('var1', 'name'), null, null),
389 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null),
441 => array(array('_route' => 'route15'), array('name'), null, null),
488 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null),
509 => array(array('_route' => 'a'), array(), null, null),
530 => array(array('_route' => 'b'), array('var'), null, null),
548 => array(array('_route' => 'c'), array('var'), null, null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
@@ -231,7 +231,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
return $ret;
}
if (520 === $m) {
if (548 === $m) {
break;
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));

File diff suppressed because it is too large Load Diff

View File

@@ -27,12 +27,12 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
$canonicalMethod = 'GET';
}
$matchedPathinfo = $host.$pathinfo;
$matchedPathinfo = $host.'.'.$pathinfo;
$regexList = array(
0 => '{^(?'
.'|(?i:([^\\.]++)\\.exampple\\.com)(?'
.'|(?i:([^\\.]++)\\.exampple\\.com)\\.(?'
.'|/abc([^/]++)(?'
.'|(*:54)'
.'|(*:56)'
.')'
.')'
.')$}sD',
@@ -41,7 +41,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
case 54:
case 56:
$matches = array('foo' => $matches[1] ?? null, 'foo' => $matches[2] ?? null);
// r1
@@ -53,7 +53,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
break;
}
if (54 === $m) {
if (56 === $m) {
break;
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));

View File

@@ -116,50 +116,50 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
return $ret;
}
$matchedPathinfo = $host.$pathinfo;
$matchedPathinfo = $host.'.'.$pathinfo;
$regexList = array(
0 => '{^(?'
.'|[^/]*+(?'
.'|/foo/(baz|symfony)(*:34)'
.'|(?:(?:[^.]*+\\.)++)(?'
.'|/foo/(baz|symfony)(*:46)'
.'|/bar(?'
.'|/([^/]++)(*:57)'
.'|head/([^/]++)(*:77)'
.'|/([^/]++)(*:69)'
.'|head/([^/]++)(*:89)'
.')'
.'|/test/([^/]++)/(?'
.'|(*:103)'
.'|(*:115)'
.')'
.'|/([\']+)(*:119)'
.'|/([\']+)(*:131)'
.'|/a/(?'
.'|b\'b/([^/]++)(?'
.'|(*:148)'
.'|(*:156)'
.'|(*:160)'
.'|(*:168)'
.')'
.'|(.*)(*:169)'
.'|(.*)(*:181)'
.'|b\'b/([^/]++)(?'
.'|(*:192)'
.'|(*:200)'
.'|(*:204)'
.'|(*:212)'
.')'
.')'
.'|/multi/hello(?:/([^/]++))?(*:236)'
.'|/multi/hello(?:/([^/]++))?(*:248)'
.'|/([^/]++)/b/([^/]++)(?'
.'|(*:267)'
.'|(*:275)'
.'|(*:279)'
.'|(*:287)'
.')'
.'|/aba/([^/]++)(*:297)'
.')|(?i:([^\\.]++)\\.example\\.com)(?'
.'|/aba/([^/]++)(*:309)'
.')|(?i:([^\\.]++)\\.example\\.com)\\.(?'
.'|/route1(?'
.'|3/([^/]++)(*:357)'
.'|4/([^/]++)(*:375)'
.'|3/([^/]++)(*:371)'
.'|4/([^/]++)(*:389)'
.')'
.')|(?i:c\\.example\\.com)(?'
.'|/route15/([^/]++)(*:425)'
.')|[^/]*+(?'
.'|/route16/([^/]++)(*:460)'
.')|(?i:c\\.example\\.com)\\.(?'
.'|/route15/([^/]++)(*:441)'
.')|(?:(?:[^.]*+\\.)++)(?'
.'|/route16/([^/]++)(*:488)'
.'|/a/(?'
.'|a\\.\\.\\.(*:481)'
.'|a\\.\\.\\.(*:509)'
.'|b/(?'
.'|([^/]++)(*:502)'
.'|c/([^/]++)(*:520)'
.'|([^/]++)(*:530)'
.'|c/([^/]++)(*:548)'
.')'
.')'
.')'
@@ -169,7 +169,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
case 103:
case 115:
$matches = array('foo' => $matches[1] ?? null);
// baz4
@@ -196,7 +196,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
not_bazbaz6:
break;
case 148:
case 160:
$matches = array('foo' => $matches[1] ?? null);
// foo1
@@ -210,14 +210,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
not_foo1:
break;
case 192:
case 204:
$matches = array('foo1' => $matches[1] ?? null);
// foo2
return $this->mergeDefaults(array('_route' => 'foo2') + $matches, array());
break;
case 267:
case 279:
$matches = array('_locale' => $matches[1] ?? null, 'foo' => $matches[2] ?? null);
// foo3
@@ -226,23 +226,23 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
break;
default:
$routes = array(
34 => array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null),
57 => array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null),
77 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null),
119 => array(array('_route' => 'quoter'), array('quoter'), null, null),
156 => array(array('_route' => 'bar1'), array('bar'), null, null),
169 => array(array('_route' => 'overridden'), array('var'), null, null),
200 => array(array('_route' => 'bar2'), array('bar1'), null, null),
236 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null),
275 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null),
297 => array(array('_route' => 'foo4'), array('foo'), null, null),
357 => array(array('_route' => 'route13'), array('var1', 'name'), null, null),
375 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null),
425 => array(array('_route' => 'route15'), array('name'), null, null),
460 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null),
481 => array(array('_route' => 'a'), array(), null, null),
502 => array(array('_route' => 'b'), array('var'), null, null),
520 => array(array('_route' => 'c'), array('var'), null, null),
46 => array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null),
69 => array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null),
89 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null),
131 => array(array('_route' => 'quoter'), array('quoter'), null, null),
168 => array(array('_route' => 'bar1'), array('bar'), null, null),
181 => array(array('_route' => 'overridden'), array('var'), null, null),
212 => array(array('_route' => 'bar2'), array('bar1'), null, null),
248 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null),
287 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null),
309 => array(array('_route' => 'foo4'), array('foo'), null, null),
371 => array(array('_route' => 'route13'), array('var1', 'name'), null, null),
389 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null),
441 => array(array('_route' => 'route15'), array('name'), null, null),
488 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null),
509 => array(array('_route' => 'a'), array(), null, null),
530 => array(array('_route' => 'b'), array('var'), null, null),
548 => array(array('_route' => 'c'), array('var'), null, null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
@@ -268,7 +268,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
return $ret;
}
if (520 === $m) {
if (548 === $m) {
break;
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));

View File

@@ -32,10 +32,10 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
.'|/(a)(*:11)'
.')$}sD',
11 => '{^(?'
.'|/(.)(*:26)'
.'|/(.)(*:22)'
.')$}sDu',
26 => '{^(?'
.'|/(.)(*:41)'
22 => '{^(?'
.'|/(.)(*:33)'
.')$}sD',
);
@@ -45,8 +45,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
default:
$routes = array(
11 => array(array('_route' => 'a'), array('a'), null, null),
26 => array(array('_route' => 'b'), array('a'), null, null),
41 => array(array('_route' => 'c'), array('a'), null, null),
22 => array(array('_route' => 'b'), array('a'), null, null),
33 => array(array('_route' => 'c'), array('a'), null, null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
@@ -72,7 +72,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
return $ret;
}
if (41 === $m) {
if (33 === $m) {
break;
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));

View File

@@ -245,6 +245,18 @@ class UrlMatcherTest extends TestCase
}
}
public function testMultipleParams()
{
$coll = new RouteCollection();
$coll->add('foo1', new Route('/foo/{a}/{b}'));
$coll->add('foo2', new Route('/foo/{a}/test/test/{b}'));
$coll->add('foo3', new Route('/foo/{a}/{b}/{c}/{d}'));
$route = $this->getUrlMatcher($coll)->match('/foo/test/test/test/bar')['_route'];
$this->assertEquals('foo2', $route);
}
public function testDefaultRequirementForOptionalVariables()
{
$coll = new RouteCollection();
@@ -621,6 +633,43 @@ class UrlMatcherTest extends TestCase
$this->assertEquals(array('_route' => 'a', 'id' => 'foo/bar'), $matcher->match('/foo/bar.html'));
}
public function testHostPattern()
{
$coll = new RouteCollection();
$coll->add('a', new Route('/{app}/{action}/{unused}', array(), array(), array(), '{host}'));
$expected = array(
'_route' => 'a',
'app' => 'an_app',
'action' => 'an_action',
'unused' => 'unused',
'host' => 'foo',
);
$matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'foo'));
$this->assertEquals($expected, $matcher->match('/an_app/an_action/unused'));
}
public function testUtf8Prefix()
{
$coll = new RouteCollection();
$coll->add('a', new Route('/é{foo}', array(), array(), array('utf8' => true)));
$coll->add('b', new Route('/è{bar}', array(), array(), array('utf8' => true)));
$matcher = $this->getUrlMatcher($coll);
$this->assertEquals('a', $matcher->match('/éo')['_route']);
}
public function testUtf8AndMethodMatching()
{
$coll = new RouteCollection();
$coll->add('a', new Route('/admin/api/list/{shortClassName}/{id}.{_format}', array(), array(), array('utf8' => true), '', array(), array('PUT')));
$coll->add('b', new Route('/admin/api/package.{_format}', array(), array(), array(), '', array(), array('POST')));
$coll->add('c', new Route('/admin/api/package.{_format}', array('_format' => 'json'), array(), array(), '', array(), array('GET')));
$matcher = $this->getUrlMatcher($coll);
$this->assertEquals('c', $matcher->match('/admin/api/package.json')['_route']);
}
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
{
return new UrlMatcher($routes, $context ?: new RequestContext());

View File

@@ -398,6 +398,7 @@ class RouteCompilerTest extends TestCase
yield array('#^/(?P<foo>(?:b))$#sD', '(?:b)');
yield array('#^/(?P<foo>(?(b)b))$#sD', '(?(b)b)');
yield array('#^/(?P<foo>(*F))$#sD', '(*F)');
yield array('#^/(?P<foo>(?:(?:foo)))$#sD', '((foo))');
}
}

View File

@@ -25,7 +25,6 @@
"symfony/expression-language": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"doctrine/annotations": "~1.0",
"doctrine/common": "~2.2",
"psr/log": "~1.0"
},
"conflict": {

View File

@@ -45,7 +45,7 @@ abstract class AbstractFileExtractor
private function toSplFileInfo(string $file): \SplFileInfo
{
return ($file instanceof \SplFileInfo) ? $file : new \SplFileInfo($file);
return new \SplFileInfo($file);
}
/**

View File

@@ -29,30 +29,12 @@ class SplCaster
public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested)
{
$prefix = Caster::PREFIX_VIRTUAL;
$class = $stub->class;
$flags = $c->getFlags();
return self::castSplArray($c, $a, $stub, $isNested);
}
$b = array(
$prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST),
$prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS),
$prefix.'iteratorClass' => new ClassStub($c->getIteratorClass()),
$prefix.'storage' => $c->getArrayCopy(),
);
if ('ArrayObject' === $class) {
$a = $b;
} else {
if (!($flags & \ArrayObject::STD_PROP_LIST)) {
$c->setFlags(\ArrayObject::STD_PROP_LIST);
$a = Caster::castObject($c, $class);
$c->setFlags($flags);
}
$a += $b;
}
return $a;
public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, $isNested)
{
return self::castSplArray($c, $a, $stub, $isNested);
}
public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested)
@@ -186,7 +168,7 @@ class SplCaster
$clone = clone $c;
foreach ($clone as $obj) {
$storage[spl_object_hash($obj)] = array(
$storage[] = array(
'object' => $obj,
'info' => $clone->getInfo(),
);
@@ -205,4 +187,27 @@ class SplCaster
return $a;
}
private static function castSplArray($c, array $a, Stub $stub, $isNested)
{
$prefix = Caster::PREFIX_VIRTUAL;
$class = $stub->class;
$flags = $c->getFlags();
if (!($flags & \ArrayObject::STD_PROP_LIST)) {
$c->setFlags(\ArrayObject::STD_PROP_LIST);
$a = Caster::castObject($c, $class);
$c->setFlags($flags);
}
$a += array(
$prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST),
$prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS),
);
if ($c instanceof \ArrayObject) {
$a[$prefix.'iteratorClass'] = new ClassStub($c->getIteratorClass());
}
$a[$prefix.'storage'] = $c->getArrayCopy();
return $a;
}
}

View File

@@ -95,6 +95,7 @@ abstract class AbstractCloner implements ClonerInterface
'AMQPEnvelope' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'),
'ArrayObject' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'),
'ArrayIterator' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'),
'SplDoublyLinkedList' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'),
'SplFileInfo' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'),
'SplFileObject' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'),

View File

@@ -164,7 +164,7 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
*/
protected function dumpLine($depth)
{
call_user_func($this->lineDumper, $this->line, $depth, $this->indentPad);
\call_user_func($this->lineDumper, $this->line, $depth, $this->indentPad);
$this->line = '';
}

View File

@@ -13,6 +13,7 @@ namespace Symfony\Component\VarDumper\Dumper;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
use Symfony\Component\VarDumper\Server\Connection;
/**
* ServerDumper forwards serialized Data clones to a server.
@@ -21,10 +22,8 @@ use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
*/
class ServerDumper implements DataDumperInterface
{
private $host;
private $connection;
private $wrappedDumper;
private $contextProviders;
private $socket;
/**
* @param string $host The server host
@@ -33,83 +32,22 @@ class ServerDumper implements DataDumperInterface
*/
public function __construct(string $host, DataDumperInterface $wrappedDumper = null, array $contextProviders = array())
{
if (false === strpos($host, '://')) {
$host = 'tcp://'.$host;
}
$this->host = $host;
$this->connection = new Connection($host, $contextProviders);
$this->wrappedDumper = $wrappedDumper;
$this->contextProviders = $contextProviders;
}
public function getContextProviders(): array
{
return $this->contextProviders;
return $this->connection->getContextProviders();
}
/**
* {@inheritdoc}
*/
public function dump(Data $data, $output = null): void
public function dump(Data $data)
{
set_error_handler(array(self::class, 'nullErrorHandler'));
$failed = false;
try {
if (!$this->socket = $this->socket ?: $this->createSocket()) {
$failed = true;
return;
}
} finally {
restore_error_handler();
if ($failed && $this->wrappedDumper) {
$this->wrappedDumper->dump($data);
}
if (!$this->connection->write($data) && $this->wrappedDumper) {
$this->wrappedDumper->dump($data);
}
set_error_handler(array(self::class, 'nullErrorHandler'));
$context = array('timestamp' => time());
foreach ($this->contextProviders as $name => $provider) {
$context[$name] = $provider->getContext();
}
$context = array_filter($context);
$encodedPayload = base64_encode(serialize(array($data, $context)))."\n";
$failed = false;
try {
$retry = 3;
while ($retry > 0 && $failed = (-1 === stream_socket_sendto($this->socket, $encodedPayload))) {
stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
if ($failed = !$this->socket = $this->createSocket()) {
break;
}
--$retry;
}
} finally {
restore_error_handler();
if ($failed && $this->wrappedDumper) {
$this->wrappedDumper->dump($data);
}
}
}
private static function nullErrorHandler()
{
// noop
}
private function createSocket()
{
$socket = stream_socket_client($this->host, $errno, $errstr, 1, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT);
if ($socket) {
stream_set_blocking($socket, false);
}
return $socket;
}
}

0
vendor/symfony/var-dumper/Resources/bin/var-dump-server vendored Normal file → Executable file
View File

View File

@@ -22,14 +22,6 @@ a {
a:hover {
text-decoration: underline;
}
code {
color: #cc2255;
background-color: #f7f7f9;
border: 1px solid #e1e1e8;
border-radius: 3px;
margin-right: 5px;
padding: 0 3px;
}
.text-small {
font-size: 12px !important;
}
@@ -60,6 +52,12 @@ article > header > .row > h2 {
article > header > .row > h2 > code {
white-space: nowrap;
user-select: none;
color: #cc2255;
background-color: #f7f7f9;
border: 1px solid #e1e1e8;
border-radius: 3px;
margin-right: 5px;
padding: 0 3px;
}
article > header > .row > time.col {
flex: 0;

View File

@@ -163,4 +163,45 @@ EOTXT;
$this->assertDumpMatchesFormat('%ADateTime%A', $var);
}
public function testCastArrayObject()
{
$var = new \ArrayObject(array(123));
$var->foo = 234;
$expected = <<<EOTXT
ArrayObject {
+"foo": 234
flag::STD_PROP_LIST: false
flag::ARRAY_AS_PROPS: false
iteratorClass: "ArrayIterator"
storage: array:1 [
0 => 123
]
}
EOTXT;
$this->assertDumpEquals($expected, $var);
}
public function testArrayIterator()
{
$var = new MyArrayIterator(array(234));
$expected = <<<EOTXT
Symfony\Component\VarDumper\Tests\Caster\MyArrayIterator {
-foo: 123
flag::STD_PROP_LIST: false
flag::ARRAY_AS_PROPS: false
storage: array:1 [
0 => 234
]
}
EOTXT;
$this->assertDumpEquals($expected, $var);
}
}
class MyArrayIterator extends \ArrayIterator
{
private $foo = 123;
}

View File

@@ -55,26 +55,24 @@ class ServerDumperTest extends TestCase
$dumped = null;
$process = $this->getServerProcess();
$process->start(function ($type, $buffer) use ($process, &$dumped) {
$process->start(function ($type, $buffer) use ($process, &$dumped, $dumper, $data) {
if (Process::ERR === $type) {
$process->stop();
$this->fail();
} elseif ("READY\n" === $buffer) {
$dumper->dump($data);
} else {
$dumped .= $buffer;
}
});
sleep(3);
$dumper->dump($data);
$process->wait();
$this->assertTrue($process->isSuccessful());
$this->assertStringMatchesFormat(<<<'DUMP'
(3) "foo"
[
"timestamp" => %d
"timestamp" => %d.%d
"foo_provider" => [
(3) "foo"
]

View File

@@ -29,6 +29,8 @@ $server = new DumpServer(getenv('VAR_DUMPER_SERVER'));
$server->start();
echo "READY\n";
$server->listen(function (Data $data, array $context, $clientId) {
dump((string) $data, $context, $clientId);