Merge branch '5.1'

* 5.1:
  [HttpClient] Support for cURL handler objects.
  [HttpClient] unset activity list when creating CurlResponse
  Fixed typo in test name
  [DI] Fix call to sprintf in ServicesConfigurator::stack()
  add .body wrapper element
  [HttpFondation] Change file extension of "audio/mpeg" from "mpga" to "mp3"
  [VarDumper] Support for cURL handler objects.
  Check whether path is file in DataPart::fromPath()
  [DI][FrameworkBundle] Remove whitelist occurrences
  Avoid accessibility errors on debug toolbar
  Resolve event bubbling logic in a compiler pass
  update cookie test
This commit is contained in:
Fabien Potencier 2020-06-24 15:36:31 +02:00
commit 809b4d6748
20 changed files with 321 additions and 115 deletions

View File

@ -10,54 +10,56 @@
</head>
<body>
<spacer size="32"></spacer>
<container class="body_{{ ("urgent" == importance ? "alert" : ("high" == importance ? "warning" : "default")) }}">
<spacer size="16"></spacer>
<row>
<columns large="12" small="12">
{% block lead %}
<small><strong>{{ importance|upper }}</strong></small>
<p class="lead">
{{ email.subject }}
</p>
{% endblock %}
{% block content %}
{% if markdown %}
{{ include('@email/zurb_2/notification/content_markdown.html.twig') }}
{% else %}
{{ (raw ? content|raw : content)|nl2br }}
{% endif %}
{% endblock %}
{% block action %}
{% if action_url %}
<spacer size="16"></spacer>
<button href="{{ action_url }}">{{ action_text }}</button>
{% endif %}
{% endblock %}
{% block exception %}
{% if exception %}
<spacer size="16"></spacer>
<p><em>Exception stack trace attached.</em></p>
{% endif %}
{% endblock %}
</columns>
</row>
<wrapper class="secondary">
<wrapper class="body">
<container class="body_{{ ("urgent" == importance ? "alert" : ("high" == importance ? "warning" : "default")) }}">
<spacer size="16"></spacer>
{% block footer %}
<row>
<columns small="12" large="6">
{% block footer_content %}
<p><small>Notification e-mail sent by Symfony</small></p>
{% endblock %}
</columns>
</row>
{% endblock %}
</wrapper>
</container>
<row>
<columns large="12" small="12">
{% block lead %}
<small><strong>{{ importance|upper }}</strong></small>
<p class="lead">
{{ email.subject }}
</p>
{% endblock %}
{% block content %}
{% if markdown %}
{{ include('@email/zurb_2/notification/content_markdown.html.twig') }}
{% else %}
{{ (raw ? content|raw : content)|nl2br }}
{% endif %}
{% endblock %}
{% block action %}
{% if action_url %}
<spacer size="16"></spacer>
<button href="{{ action_url }}">{{ action_text }}</button>
{% endif %}
{% endblock %}
{% block exception %}
{% if exception %}
<spacer size="16"></spacer>
<p><em>Exception stack trace attached.</em></p>
{% endif %}
{% endblock %}
</columns>
</row>
<wrapper class="secondary">
<spacer size="16"></spacer>
{% block footer %}
<row>
<columns small="12" large="6">
{% block footer_content %}
<p><small>Notification e-mail sent by Symfony</small></p>
{% endblock %}
</columns>
</row>
{% endblock %}
</wrapper>
</container>
</wrapper>
</body>
</html>
{% endapply %}

View File

@ -21,7 +21,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
*/
class UnusedTagsPass implements CompilerPassInterface
{
private $whitelist = [
private $knownTags = [
'annotations.cached_reader',
'auto_alias',
'cache.pool',
@ -91,11 +91,11 @@ class UnusedTagsPass implements CompilerPassInterface
public function process(ContainerBuilder $container)
{
$tags = array_unique(array_merge($container->findTags(), $this->whitelist));
$tags = array_unique(array_merge($container->findTags(), $this->knownTags));
foreach ($container->findUnusedTags() as $tag) {
// skip whitelisted tags
if (\in_array($tag, $this->whitelist)) {
// skip known tags
if (\in_array($tag, $this->knownTags)) {
continue;
}

View File

@ -15,5 +15,5 @@ use Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\UnusedTags
$target = dirname(__DIR__, 2).'/DependencyInjection/Compiler/UnusedTagsPass.php';
$contents = file_get_contents($target);
$contents = preg_replace('{private \$whitelist = \[(.+?)\];}sm', "private \$whitelist = [\n '".implode("',\n '", UnusedTagsPassUtils::getDefinedTags())."',\n ];", $contents);
$contents = preg_replace('{private \$knownTags = \[(.+?)\];}sm', "private \$knownTags = [\n '".implode("',\n '", UnusedTagsPassUtils::getDefinedTags())."',\n ];", $contents);
file_put_contents($target, $contents);

View File

@ -32,13 +32,13 @@ class UnusedTagsPassTest extends TestCase
$this->assertSame([sprintf('%s: Tag "kenrel.event_subscriber" was defined on service(s) "foo", "bar", but was never used. Did you mean "kernel.event_subscriber"?', UnusedTagsPass::class)], $container->getCompiler()->getLog());
}
public function testMissingWhitelistTags()
public function testMissingKnownTags()
{
if (\dirname((new \ReflectionClass(ContainerBuilder::class))->getFileName(), 3) !== \dirname(__DIR__, 5)) {
$this->markTestSkipped('Tests are not run from the root symfony/symfony metapackage.');
}
$this->assertSame(UnusedTagsPassUtils::getDefinedTags(), $this->getWhitelistTags(), 'The src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php file must be updated; run src/Symfony/Bundle/FrameworkBundle/Resources/bin/check-unused-tags-whitelist.php.');
$this->assertSame(UnusedTagsPassUtils::getDefinedTags(), $this->getKnownTags(), 'The src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php file must be updated; run src/Symfony/Bundle/FrameworkBundle/Resources/bin/check-unused-known-tags.php.');
}
private function getWhitelistTags(): array
@ -46,7 +46,7 @@ class UnusedTagsPassTest extends TestCase
// get tags in UnusedTagsPass
$target = \dirname(__DIR__, 3).'/DependencyInjection/Compiler/UnusedTagsPass.php';
$contents = file_get_contents($target);
preg_match('{private \$whitelist = \[(.+?)\];}sm', $contents, $matches);
preg_match('{private \$knownTags = \[(.+?)\];}sm', $contents, $matches);
$tags = array_values(array_filter(array_map(function ($str) {
return trim(preg_replace('{^ +\'(.+)\',}', '$1', $str));
}, explode("\n", $matches[1]))));

View File

@ -0,0 +1,70 @@
<?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\Bundle\SecurityBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\Event\LogoutEvent;
/**
* Makes sure all event listeners on the global dispatcher are also listening
* to events on the firewall-specific dipatchers.
*
* This compiler pass must be run after RegisterListenersPass of the
* EventDispatcher component.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @internal
*/
class RegisterGlobalSecurityEventListenersPass implements CompilerPassInterface
{
private static $eventBubblingEvents = [CheckPassportEvent::class, LoginFailureEvent::class, LoginSuccessEvent::class, LogoutEvent::class];
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) {
return;
}
$firewallDispatchers = [];
foreach ($container->getParameter('security.firewalls') as $firewallName) {
if (!$container->has('security.event_dispatcher.'.$firewallName)) {
continue;
}
$firewallDispatchers[] = $container->findDefinition('security.event_dispatcher.'.$firewallName);
}
$globalDispatcher = $container->findDefinition('event_dispatcher');
foreach ($globalDispatcher->getMethodCalls() as $methodCall) {
if ('addListener' !== $methodCall[0]) {
continue;
}
$methodCallArguments = $methodCall[1];
if (!\in_array($methodCallArguments[0], self::$eventBubblingEvents, true)) {
continue;
}
foreach ($firewallDispatchers as $firewallDispatcher) {
$firewallDispatcher->addMethodCall('addListener', $methodCallArguments);
}
}
}
}

View File

@ -241,6 +241,8 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
$firewalls = $config['firewalls'];
$providerIds = $this->createUserProviders($config, $container);
$container->setParameter('security.firewalls', array_keys($firewalls));
// make the ContextListener aware of the configured user providers
$contextListenerDefinition = $container->getDefinition('security.context_listener');
$arguments = $contextListenerDefinition->getArguments();
@ -353,8 +355,6 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
// Register Firewall-specific event dispatcher
$firewallEventDispatcherId = 'security.event_dispatcher.'.$id;
$container->register($firewallEventDispatcherId, EventDispatcher::class);
$container->setDefinition($firewallEventDispatcherId.'.event_bubbling_listener', new ChildDefinition('security.event_dispatcher.event_bubbling_listener'))
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
// Register listeners
$listeners = [];

View File

@ -1,50 +0,0 @@
<?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\Bundle\SecurityBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\Event\LogoutEvent;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* A listener that dispatches all security events from the firewall-specific
* dispatcher on the global event dispatcher.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class FirewallEventBubblingListener implements EventSubscriberInterface
{
private $eventDispatcher;
public function __construct(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
public static function getSubscribedEvents(): array
{
return [
LogoutEvent::class => 'bubbleEvent',
LoginFailureEvent::class => 'bubbleEvent',
LoginSuccessEvent::class => 'bubbleEvent',
CheckPassportEvent::class => 'bubbleEvent',
];
}
public function bubbleEvent($event): void
{
$this->eventDispatcher->dispatch($event);
}
}

View File

@ -15,6 +15,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLang
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfFeaturesPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterGlobalSecurityEventListenersPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterLdapLocatorPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AnonymousFactory;
@ -75,6 +76,8 @@ class SecurityBundle extends Bundle
$container->addCompilerPass(new RegisterCsrfFeaturesPass());
$container->addCompilerPass(new RegisterTokenUsageTrackingPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200);
$container->addCompilerPass(new RegisterLdapLocatorPass());
// must be registered after RegisterListenersPass (in the FrameworkBundle)
$container->addCompilerPass(new RegisterGlobalSecurityEventListenersPass(), PassConfig::TYPE_BEFORE_REMOVING, -200);
$container->addCompilerPass(new AddEventAliasesPass([
AuthenticationSuccessEvent::class => AuthenticationEvents::AUTHENTICATION_SUCCESS,

View File

@ -0,0 +1,166 @@
<?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\Bundle\SecurityBundle\Tests\DependencyInjection\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\Event\LogoutEvent;
class RegisterGlobalSecurtyEventListenersPassTest extends TestCase
{
private $container;
protected function setUp(): void
{
$this->container = new ContainerBuilder();
$this->container->setParameter('kernel.debug', false);
$this->container->register('request_stack', \stdClass::class);
$this->container->register('event_dispatcher', EventDispatcher::class);
$this->container->registerExtension(new SecurityExtension());
$this->container->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING);
$this->container->getCompilerPassConfig()->setRemovingPasses([]);
$this->container->getCompilerPassConfig()->setAfterRemovingPasses([]);
$securityBundle = new SecurityBundle();
$securityBundle->build($this->container);
}
public function testRegisterCustomListener()
{
$this->container->loadFromExtension('security', [
'enable_authenticator_manager' => true,
'firewalls' => ['main' => ['pattern' => '/', 'http_basic' => true]],
]);
$this->container->register('app.security_listener', \stdClass::class)
->addTag('kernel.event_listener', ['method' => 'onLogout', 'event' => LogoutEvent::class])
->addTag('kernel.event_listener', ['method' => 'onLoginSuccess', 'event' => LoginSuccessEvent::class, 'priority' => 20]);
$this->container->compile();
$this->assertListeners([
[LogoutEvent::class, ['app.security_listener', 'onLogout'], 0],
[LoginSuccessEvent::class, ['app.security_listener', 'onLoginSuccess'], 20],
]);
}
public function testRegisterCustomSubscriber()
{
$this->container->loadFromExtension('security', [
'enable_authenticator_manager' => true,
'firewalls' => ['main' => ['pattern' => '/', 'http_basic' => true]],
]);
$this->container->register(TestSubscriber::class)
->addTag('kernel.event_subscriber');
$this->container->compile();
$this->assertListeners([
[LogoutEvent::class, [TestSubscriber::class, 'onLogout'], -200],
[CheckPassportEvent::class, [TestSubscriber::class, 'onCheckPassport'], 120],
[LoginSuccessEvent::class, [TestSubscriber::class, 'onLoginSuccess'], 0],
]);
}
public function testMultipleFirewalls()
{
$this->container->loadFromExtension('security', [
'enable_authenticator_manager' => true,
'firewalls' => ['main' => ['pattern' => '/', 'http_basic' => true], 'api' => ['pattern' => '/api', 'http_basic' => true]],
]);
$this->container->register('security.event_dispatcher.api', EventDispatcher::class)
->addTag('security.event_dispatcher')
->setPublic(true);
$this->container->register('app.security_listener', \stdClass::class)
->addTag('kernel.event_listener', ['method' => 'onLogout', 'event' => LogoutEvent::class])
->addTag('kernel.event_listener', ['method' => 'onLoginSuccess', 'event' => LoginSuccessEvent::class, 'priority' => 20]);
$this->container->compile();
$this->assertListeners([
[LogoutEvent::class, ['app.security_listener', 'onLogout'], 0],
[LoginSuccessEvent::class, ['app.security_listener', 'onLoginSuccess'], 20],
], 'security.event_dispatcher.main');
$this->assertListeners([
[LogoutEvent::class, ['app.security_listener', 'onLogout'], 0],
[LoginSuccessEvent::class, ['app.security_listener', 'onLoginSuccess'], 20],
], 'security.event_dispatcher.api');
}
public function testListenerAlreadySpecific()
{
$this->container->loadFromExtension('security', [
'enable_authenticator_manager' => true,
'firewalls' => ['main' => ['pattern' => '/', 'http_basic' => true]],
]);
$this->container->register('security.event_dispatcher.api', EventDispatcher::class)
->addTag('security.event_dispatcher')
->setPublic(true);
$this->container->register('app.security_listener', \stdClass::class)
->addTag('kernel.event_listener', ['method' => 'onLogout', 'event' => LogoutEvent::class, 'dispatcher' => 'security.event_dispatcher.main'])
->addTag('kernel.event_listener', ['method' => 'onLoginSuccess', 'event' => LoginSuccessEvent::class, 'priority' => 20]);
$this->container->compile();
$this->assertListeners([
[LogoutEvent::class, ['app.security_listener', 'onLogout'], 0],
[LoginSuccessEvent::class, ['app.security_listener', 'onLoginSuccess'], 20],
], 'security.event_dispatcher.main');
}
private function assertListeners(array $expectedListeners, string $dispatcherId = 'security.event_dispatcher.main')
{
$actualListeners = [];
foreach ($this->container->findDefinition($dispatcherId)->getMethodCalls() as $methodCall) {
[$method, $arguments] = $methodCall;
if ('addListener' !== $method) {
continue;
}
$arguments[1] = [(string) $arguments[1][0]->getValues()[0], $arguments[1][1]];
$actualListeners[] = $arguments;
}
$foundListeners = array_uintersect($expectedListeners, $actualListeners, function (array $a, array $b) {
return $a === $b;
});
$this->assertEquals($expectedListeners, $foundListeners);
}
}
class TestSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
LogoutEvent::class => ['onLogout', -200],
CheckPassportEvent::class => ['onCheckPassport', 120],
LoginSuccessEvent::class => 'onLoginSuccess',
];
}
}

View File

@ -1,4 +1,4 @@
<div id="sfwdt{{ token }}" class="sf-toolbar sf-display-none"></div>
<div id="sfwdt{{ token }}" class="sf-toolbar sf-display-none" role="region" aria-label="Symfony Web Debug Toolbar"></div>
{{ include('@WebProfiler/Profiler/base_js.html.twig') }}
<style{% if csp_style_nonce %} nonce="{{ csp_style_nonce }}"{% endif %}>
{{ include('@WebProfiler/Profiler/toolbar.css.twig') }}

View File

@ -47,6 +47,7 @@ class CookieTest extends TestCase
return [
['foo=bar; path=/'],
['foo=bar; path=/foo'],
['foo="Test"; path=/'],
['foo=bar; domain=example.com; path=/'],
['foo=bar; domain=example.com; path=/; secure', 'https://example.com/'],
['foo=bar; path=/; httponly'],

View File

@ -801,7 +801,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
$this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
}
public function testDisableSttby()
public function testDisableStty()
{
if (!Terminal::hasSttyAvailable()) {
$this->markTestSkipped('`stty` is required to test autocomplete functionality');

View File

@ -150,7 +150,7 @@ class ServicesConfigurator extends AbstractConfigurator
$services[$i] = $definition;
} elseif (!$service instanceof ReferenceConfigurator) {
throw new InvalidArgumentException(sprintf('"%s()" expects a list of definitions as returned by "%s()" or "%s()", "%s" given at index "%s" for service "%s".', __METHOD__, InlineServiceConfigurator::FACTORY, ReferenceConfigurator::FACTORY, $service instanceof AbstractConfigurator ? $service::FACTORY.'()' : get_debug_type($service)), $i, $id);
throw new InvalidArgumentException(sprintf('"%s()" expects a list of definitions as returned by "%s()" or "%s()", "%s" given at index "%s" for service "%s".', __METHOD__, InlineServiceConfigurator::FACTORY, ReferenceConfigurator::FACTORY, $service instanceof AbstractConfigurator ? $service::FACTORY.'()' : get_debug_type($service), $i, $id));
}
}

View File

@ -337,7 +337,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
$this->multi->dnsCache->evictions = $this->multi->dnsCache->evictions ?: $this->multi->dnsCache->removals;
$this->multi->dnsCache->removals = $this->multi->dnsCache->hostnames = [];
if (\is_resource($this->multi->handle)) {
if (\is_resource($this->multi->handle) || $this->multi->handle instanceof \CurlMultiHandle) {
if (\defined('CURLMOPT_PUSHFUNCTION')) {
curl_multi_setopt($this->multi->handle, CURLMOPT_PUSHFUNCTION, null);
}
@ -347,7 +347,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
}
foreach ($this->multi->openHandles as [$ch]) {
if (\is_resource($ch)) {
if (\is_resource($ch) || $ch instanceof \CurlHandle) {
curl_setopt($ch, CURLOPT_VERBOSE, false);
}
}

View File

@ -20,7 +20,7 @@ namespace Symfony\Component\HttpClient\Internal;
*/
final class CurlClientState extends ClientState
{
/** @var resource */
/** @var \CurlMultiHandle|resource */
public $handle;
/** @var PushedResponse[] */
public $pushedResponses = [];

View File

@ -37,13 +37,16 @@ final class CurlResponse implements ResponseInterface
private $debugBuffer;
/**
* @param \CurlHandle|resource|string $ch
*
* @internal
*/
public function __construct(CurlClientState $multi, $ch, array $options = null, LoggerInterface $logger = null, string $method = 'GET', callable $resolveRedirect = null, int $curlVersion = null)
{
$this->multi = $multi;
if (\is_resource($ch)) {
if (\is_resource($ch) || $ch instanceof \CurlHandle) {
unset($multi->handlesActivity[(int) $ch]);
$this->handle = $ch;
$this->debugBuffer = fopen('php://temp', 'w+');
if (0x074000 === $curlVersion) {

View File

@ -35,7 +35,7 @@ trait TransportResponseTrait
'canceled' => false,
];
/** @var resource */
/** @var object|resource */
private $handle;
private $id;
private $timeout = 0;

View File

@ -56,6 +56,10 @@ class DataPart extends TextPart
$contentType = self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream';
}
if (false === is_readable($path)) {
throw new InvalidArgumentException(sprintf('Path "%s" is not readable.', $path));
}
if (false === $handle = @fopen($path, 'r', false)) {
throw new InvalidArgumentException(sprintf('Unable to open path "%s".', $path));
}

View File

@ -22,6 +22,11 @@ use Symfony\Component\VarDumper\Cloner\Stub;
*/
class ResourceCaster
{
/**
* @param \CurlHandle|resource $h
*
* @return array
*/
public static function castCurl($h, array $a, Stub $stub, bool $isNested)
{
return curl_getinfo($h);

View File

@ -145,7 +145,9 @@ abstract class AbstractCloner implements ClonerInterface
'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'],
'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'],
'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'],
':curl' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'],
':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'],
':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'],
':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'],