bug #27614 [VarDumper] Fix dumping by splitting Server/Connection out of Dumper/ServerDumper (nicolas-grekas)
This PR was merged into the 4.1 branch.
Discussion
----------
[VarDumper] Fix dumping by splitting Server/Connection out of Dumper/ServerDumper
| Q | A
| ------------- | ---
| Branch? | 4.1
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #27622
| License | MIT
| Doc PR | -
Right now, the `dump()` function is broken on 4.1 as soon as one sets up a `dump_destination` for the dump server (as done by default by our Flex recipe). #27397 describes the issue and proposes a tentative fix. Yet, I think the issue is deeper and exists at the design level. Writting to the server should not happen in a `DumperInterface`, that's not its semantics. Instead, I propose a `Connection` object that will allow `DumpDataCollector` to have all the info it requires to do everything on its own.
My bad for not spotting this at the review stage.
Commits
-------
1435d677be
[VarDumper] Fix dumping by splitting Server/Connection out of Dumper/ServerDumper
This commit is contained in:
commit
4660857ffa
@ -17,7 +17,6 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\VarDumper\Dumper\ServerDumper;
|
||||
|
||||
/**
|
||||
* DebugExtension.
|
||||
@ -43,20 +42,21 @@ class DebugExtension extends Extension
|
||||
->addMethodCall('setMaxString', array($config['max_string_length']));
|
||||
|
||||
if (null === $config['dump_destination']) {
|
||||
//no-op
|
||||
$container->getDefinition('var_dumper.command.server_dump')
|
||||
->setClass(ServerDumpPlaceholderCommand::class)
|
||||
;
|
||||
} elseif (0 === strpos($config['dump_destination'], 'tcp://')) {
|
||||
$serverDumperHost = $config['dump_destination'];
|
||||
$container->getDefinition('debug.dump_listener')
|
||||
->replaceArgument(1, new Reference('var_dumper.server_dumper'))
|
||||
->replaceArgument(2, new Reference('var_dumper.server_connection'))
|
||||
;
|
||||
$container->getDefinition('data_collector.dump')
|
||||
->replaceArgument(4, new Reference('var_dumper.server_dumper'))
|
||||
->replaceArgument(4, new Reference('var_dumper.server_connection'))
|
||||
;
|
||||
$container->getDefinition('var_dumper.dump_server')
|
||||
->replaceArgument(0, $serverDumperHost)
|
||||
->replaceArgument(0, $config['dump_destination'])
|
||||
;
|
||||
$container->getDefinition('var_dumper.server_dumper')
|
||||
->replaceArgument(0, $serverDumperHost)
|
||||
$container->getDefinition('var_dumper.server_connection')
|
||||
->replaceArgument(0, $config['dump_destination'])
|
||||
;
|
||||
} else {
|
||||
$container->getDefinition('var_dumper.cli_dumper')
|
||||
@ -65,13 +65,9 @@ class DebugExtension extends Extension
|
||||
$container->getDefinition('data_collector.dump')
|
||||
->replaceArgument(4, new Reference('var_dumper.cli_dumper'))
|
||||
;
|
||||
}
|
||||
|
||||
if (!isset($serverDumperHost)) {
|
||||
$container->getDefinition('var_dumper.command.server_dump')->setClass(ServerDumpPlaceholderCommand::class);
|
||||
if (!class_exists(ServerDumper::class)) {
|
||||
$container->removeDefinition('var_dumper.command.server_dump');
|
||||
}
|
||||
$container->getDefinition('var_dumper.command.server_dump')
|
||||
->setClass(ServerDumpPlaceholderCommand::class)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,13 +23,14 @@
|
||||
<argument type="service" id="debug.file_link_formatter" on-invalid="ignore"></argument>
|
||||
<argument>%kernel.charset%</argument>
|
||||
<argument type="service" id="request_stack" />
|
||||
<argument>null</argument><!-- var_dumper.cli_dumper or var_dumper.server_dumper when debug.dump_destination is set -->
|
||||
<argument>null</argument><!-- var_dumper.cli_dumper or var_dumper.server_connection when debug.dump_destination is set -->
|
||||
</service>
|
||||
|
||||
<service id="debug.dump_listener" class="Symfony\Component\HttpKernel\EventListener\DumpListener">
|
||||
<tag name="kernel.event_subscriber" />
|
||||
<argument type="service" id="var_dumper.cloner" />
|
||||
<argument type="service" id="var_dumper.cli_dumper" />
|
||||
<argument>null</argument>
|
||||
</service>
|
||||
|
||||
<service id="var_dumper.cloner" class="Symfony\Component\VarDumper\Cloner\VarCloner" public="true" />
|
||||
@ -50,9 +51,8 @@
|
||||
</call>
|
||||
</service>
|
||||
|
||||
<service id="var_dumper.server_dumper" class="Symfony\Component\VarDumper\Dumper\ServerDumper">
|
||||
<argument>null</argument> <!-- server host -->
|
||||
<argument type="service" id="var_dumper.cli_dumper" />
|
||||
<service id="var_dumper.server_connection" class="Symfony\Component\VarDumper\Server\Connection">
|
||||
<argument /> <!-- server host -->
|
||||
<argument type="collection">
|
||||
<argument type="service" key="source">
|
||||
<service class="Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider">
|
||||
|
@ -20,7 +20,7 @@
|
||||
"ext-xml": "*",
|
||||
"symfony/http-kernel": "~3.4|~4.0",
|
||||
"symfony/twig-bridge": "~3.4|~4.0",
|
||||
"symfony/var-dumper": "~4.1"
|
||||
"symfony/var-dumper": "^4.1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/config": "~3.4|~4.0",
|
||||
|
@ -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');
|
||||
@ -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.':'));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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": {
|
||||
|
@ -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 = '';
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
97
src/Symfony/Component/VarDumper/Server/Connection.php
Normal file
97
src/Symfony/Component/VarDumper/Server/Connection.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\VarDumper\Server;
|
||||
|
||||
use Symfony\Component\VarDumper\Cloner\Data;
|
||||
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
|
||||
|
||||
/**
|
||||
* Forwards serialized Data clones to a server.
|
||||
*
|
||||
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
private $host;
|
||||
private $contextProviders;
|
||||
private $socket;
|
||||
|
||||
/**
|
||||
* @param string $host The server host
|
||||
* @param ContextProviderInterface[] $contextProviders Context providers indexed by context name
|
||||
*/
|
||||
public function __construct(string $host, array $contextProviders = array())
|
||||
{
|
||||
if (false === strpos($host, '://')) {
|
||||
$host = 'tcp://'.$host;
|
||||
}
|
||||
|
||||
$this->host = $host;
|
||||
$this->contextProviders = $contextProviders;
|
||||
}
|
||||
|
||||
public function getContextProviders(): array
|
||||
{
|
||||
return $this->contextProviders;
|
||||
}
|
||||
|
||||
public function write(Data $data): bool
|
||||
{
|
||||
$socketIsFresh = !$this->socket;
|
||||
if (!$this->socket = $this->socket ?: $this->createSocket()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$context = array('timestamp' => microtime(true));
|
||||
foreach ($this->contextProviders as $name => $provider) {
|
||||
$context[$name] = $provider->getContext();
|
||||
}
|
||||
$context = array_filter($context);
|
||||
$encodedPayload = base64_encode(serialize(array($data, $context)))."\n";
|
||||
|
||||
set_error_handler(array(self::class, 'nullErrorHandler'));
|
||||
try {
|
||||
if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) {
|
||||
return true;
|
||||
}
|
||||
if (!$socketIsFresh) {
|
||||
stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
|
||||
fclose($this->socket);
|
||||
$this->socket = $this->createSocket();
|
||||
}
|
||||
if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) {
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function nullErrorHandler($t, $m)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
private function createSocket()
|
||||
{
|
||||
set_error_handler(array(self::class, 'nullErrorHandler'));
|
||||
try {
|
||||
return stream_socket_client($this->host, $errno, $errstr, 3, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
return $socket;
|
||||
}
|
||||
}
|
@ -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"
|
||||
]
|
||||
|
@ -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);
|
||||
|
||||
|
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\VarDumper\Tests\Server;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Process\PhpProcess;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
|
||||
use Symfony\Component\VarDumper\Server\Connection;
|
||||
|
||||
class ConnectionTest extends TestCase
|
||||
{
|
||||
private const VAR_DUMPER_SERVER = 'tcp://127.0.0.1:9913';
|
||||
|
||||
public function testDump()
|
||||
{
|
||||
$cloner = new VarCloner();
|
||||
$data = $cloner->cloneVar('foo');
|
||||
$connection = new Connection(self::VAR_DUMPER_SERVER, array(
|
||||
'foo_provider' => new class() implements ContextProviderInterface {
|
||||
public function getContext(): ?array
|
||||
{
|
||||
return array('foo');
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
$dumped = null;
|
||||
$process = $this->getServerProcess();
|
||||
$process->start(function ($type, $buffer) use ($process, &$dumped, $connection, $data) {
|
||||
if (Process::ERR === $type) {
|
||||
$process->stop();
|
||||
$this->fail();
|
||||
} elseif ("READY\n" === $buffer) {
|
||||
$connection->write($data);
|
||||
} else {
|
||||
$dumped .= $buffer;
|
||||
}
|
||||
});
|
||||
|
||||
$process->wait();
|
||||
|
||||
$this->assertTrue($process->isSuccessful());
|
||||
$this->assertStringMatchesFormat(<<<'DUMP'
|
||||
(3) "foo"
|
||||
[
|
||||
"timestamp" => %d.%d
|
||||
"foo_provider" => [
|
||||
(3) "foo"
|
||||
]
|
||||
]
|
||||
%d
|
||||
|
||||
DUMP
|
||||
, $dumped);
|
||||
}
|
||||
|
||||
public function testNoServer()
|
||||
{
|
||||
$cloner = new VarCloner();
|
||||
$data = $cloner->cloneVar('foo');
|
||||
$connection = new Connection(self::VAR_DUMPER_SERVER);
|
||||
$start = microtime(true);
|
||||
$this->assertFalse($connection->write($data));
|
||||
$this->assertLessThan(1, microtime(true) - $start);
|
||||
}
|
||||
|
||||
private function getServerProcess(): Process
|
||||
{
|
||||
$process = new PhpProcess(file_get_contents(__DIR__.'/../Fixtures/dump_server.php'), null, array(
|
||||
'COMPONENT_ROOT' => __DIR__.'/../../',
|
||||
'VAR_DUMPER_SERVER' => self::VAR_DUMPER_SERVER,
|
||||
));
|
||||
$process->inheritEnvironmentVariables(true);
|
||||
|
||||
return $process->setTimeout(9);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user