Merge branch '5.0'

* 5.0:
  Avoid stale-if-error if kernel.debug = true, because it hides errors
  [Console] Fix SymfonyQuestionHelper tests sometimes failing on AppVeyor
  [SecurityBundle] Fix collecting traceable listeners info using anonymous: lazy
  [Filesystem][FilesystemCommonTrait] Use a dedicated directory when there are no namespace
  [Workflow] Fix configuration node reference for "initial_marking"
  expand listener in place
  [DI] deferred exceptions in ResolveParameterPlaceHoldersPass
  Do not throw exception on valut generate key
This commit is contained in:
Robin Chalas 2020-01-10 22:57:55 +01:00
commit de4c45c90c
13 changed files with 216 additions and 56 deletions

View File

@ -602,7 +602,7 @@ Workflow
* `ClassInstanceSupportStrategy` has been removed, use `InstanceOfSupportStrategy` instead. * `ClassInstanceSupportStrategy` has been removed, use `InstanceOfSupportStrategy` instead.
* `WorkflowInterface::apply()` has a third argument: `array $context = []`. * `WorkflowInterface::apply()` has a third argument: `array $context = []`.
* `MarkingStoreInterface::setMarking()` has a third argument: `array $context = []`. * `MarkingStoreInterface::setMarking()` has a third argument: `array $context = []`.
* Removed support of `initial_place`. Use `initial_places` instead. * Removed support of `initial_place`. Use `initial_marking` instead.
* `MultipleStateMarkingStore` has been removed. Use `MethodMarkingStore` instead. * `MultipleStateMarkingStore` has been removed. Use `MethodMarkingStore` instead.
* `DefinitionBuilder::setInitialPlace()` has been removed, use `DefinitionBuilder::setInitialPlaces()` instead. * `DefinitionBuilder::setInitialPlace()` has been removed, use `DefinitionBuilder::setInitialPlaces()` instead.

View File

@ -36,7 +36,14 @@ class HttpCache extends BaseHttpCache
$this->kernel = $kernel; $this->kernel = $kernel;
$this->cacheDir = $cacheDir; $this->cacheDir = $cacheDir;
parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge(['debug' => $kernel->isDebug()], $this->getOptions())); $isDebug = $kernel->isDebug();
$options = ['debug' => $isDebug];
if ($isDebug) {
$options['stale_if_error'] = 0;
}
parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge($options, $this->getOptions()));
} }
/** /**

View File

@ -47,7 +47,9 @@ class SodiumVault extends AbstractVault implements EnvVarLoaderInterface
$this->lastMessage = null; $this->lastMessage = null;
if (null === $this->encryptionKey && '' !== $this->decryptionKey = (string) $this->decryptionKey) { if (null === $this->encryptionKey && '' !== $this->decryptionKey = (string) $this->decryptionKey) {
throw new \LogicException('Cannot generate keys when a decryption key has been provided while instantiating the vault.'); $this->lastMessage = 'Cannot generate keys when a decryption key has been provided while instantiating the vault.';
return false;
} }
try { try {

View File

@ -12,7 +12,10 @@
namespace Symfony\Bundle\SecurityBundle\Debug; namespace Symfony\Bundle\SecurityBundle\Debug;
use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener; use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener;
use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext;
use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Http\Firewall\AbstractListener;
/** /**
* Firewall collecting called listeners. * Firewall collecting called listeners.
@ -21,7 +24,7 @@ use Symfony\Component\HttpKernel\Event\RequestEvent;
*/ */
final class TraceableFirewallListener extends FirewallListener final class TraceableFirewallListener extends FirewallListener
{ {
private $wrappedListeners; private $wrappedListeners = [];
public function getWrappedListeners() public function getWrappedListeners()
{ {
@ -30,14 +33,47 @@ final class TraceableFirewallListener extends FirewallListener
protected function callListeners(RequestEvent $event, iterable $listeners) protected function callListeners(RequestEvent $event, iterable $listeners)
{ {
$wrappedListeners = [];
$wrappedLazyListeners = [];
foreach ($listeners as $listener) { foreach ($listeners as $listener) {
$wrappedListener = new WrappedListener($listener); if ($listener instanceof LazyFirewallContext) {
$wrappedListener($event); \Closure::bind(function () use (&$wrappedLazyListeners, &$wrappedListeners) {
$this->wrappedListeners[] = $wrappedListener->getInfo(); $listeners = [];
foreach ($this->listeners as $listener) {
if ($listener instanceof AbstractListener) {
$listener = new WrappedLazyListener($listener);
$listeners[] = $listener;
$wrappedLazyListeners[] = $listener;
} else {
$listeners[] = function (RequestEvent $event) use ($listener, &$wrappedListeners) {
$wrappedListener = new WrappedListener($listener);
$wrappedListener($event);
$wrappedListeners[] = $wrappedListener->getInfo();
};
}
}
$this->listeners = $listeners;
}, $listener, FirewallContext::class)();
$listener($event);
} else {
$wrappedListener = $listener instanceof AbstractListener ? new WrappedLazyListener($listener) : new WrappedListener($listener);
$wrappedListener($event);
$wrappedListeners[] = $wrappedListener->getInfo();
}
if ($event->hasResponse()) { if ($event->hasResponse()) {
break; break;
} }
} }
if ($wrappedLazyListeners) {
foreach ($wrappedLazyListeners as $lazyListener) {
$this->wrappedListeners[] = $lazyListener->getInfo();
}
}
$this->wrappedListeners = array_merge($this->wrappedListeners, $wrappedListeners);
} }
} }

View File

@ -0,0 +1,38 @@
<?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\Debug;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*
* @internal
*/
trait TraceableListenerTrait
{
private $response;
private $listener;
private $time;
private $stub;
/**
* Proxies all method calls to the original listener.
*/
public function __call(string $method, array $arguments)
{
return $this->listener->{$method}(...$arguments);
}
public function getWrappedListener()
{
return $this->listener;
}
}

View File

@ -0,0 +1,71 @@
<?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\Debug;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Exception\LazyResponseException;
use Symfony\Component\Security\Http\Firewall\AbstractListener;
use Symfony\Component\VarDumper\Caster\ClassStub;
/**
* Wraps a lazy security listener.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*
* @internal
*/
final class WrappedLazyListener extends AbstractListener
{
use TraceableListenerTrait;
public function __construct(AbstractListener $listener)
{
$this->listener = $listener;
}
public function supports(Request $request): ?bool
{
return $this->listener->supports($request);
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestEvent $event)
{
$startTime = microtime(true);
try {
$ret = $this->listener->authenticate($event);
} catch (LazyResponseException $e) {
$this->response = $e->getResponse();
throw $e;
} finally {
$this->time = microtime(true) - $startTime;
}
$this->response = $event->getResponse();
return $ret;
}
public function getInfo(): array
{
return [
'response' => $this->response,
'time' => $this->time,
'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener),
];
}
}

View File

@ -23,11 +23,7 @@ use Symfony\Component\VarDumper\Caster\ClassStub;
*/ */
final class WrappedListener final class WrappedListener
{ {
private $response; use TraceableListenerTrait;
private $listener;
private $time;
private $stub;
private static $hasVarDumper;
public function __construct(callable $listener) public function __construct(callable $listener)
{ {
@ -42,46 +38,12 @@ final class WrappedListener
$this->response = $event->getResponse(); $this->response = $event->getResponse();
} }
/**
* Proxies all method calls to the original listener.
*/
public function __call(string $method, array $arguments)
{
return $this->listener->{$method}(...$arguments);
}
public function getWrappedListener(): callable
{
return $this->listener;
}
public function getInfo(): array public function getInfo(): array
{ {
if (null !== $this->stub) {
// no-op
} elseif (self::$hasVarDumper ?? self::$hasVarDumper = class_exists(ClassStub::class)) {
$this->stub = ClassStub::wrapCallable($this->listener);
} elseif (\is_array($this->listener)) {
$this->stub = (\is_object($this->listener[0]) ? \get_class($this->listener[0]) : $this->listener[0]).'::'.$this->listener[1];
} elseif ($this->listener instanceof \Closure) {
$r = new \ReflectionFunction($this->listener);
if (false !== strpos($r->name, '{closure}')) {
$this->stub = 'closure';
} elseif ($class = $r->getClosureScopeClass()) {
$this->stub = $class->name.'::'.$r->name;
} else {
$this->stub = $r->name;
}
} elseif (\is_string($this->listener)) {
$this->stub = $this->listener;
} else {
$this->stub = \get_class($this->listener).'::__invoke';
}
return [ return [
'response' => $this->response, 'response' => $this->response,
'time' => $this->time, 'time' => $this->time,
'stub' => $this->stub, 'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener),
]; ];
} }
} }

View File

@ -35,6 +35,8 @@ trait FilesystemCommonTrait
throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
} }
$directory .= \DIRECTORY_SEPARATOR.$namespace; $directory .= \DIRECTORY_SEPARATOR.$namespace;
} else {
$directory .= \DIRECTORY_SEPARATOR.'@';
} }
if (!file_exists($directory)) { if (!file_exists($directory)) {
@mkdir($directory, 0777, true); @mkdir($directory, 0777, true);

View File

@ -145,13 +145,13 @@ class SymfonyQuestionHelperTest extends AbstractQuestionHelperTest
); );
$this->assertOutputContains(<<<EOT $this->assertOutputContains(<<<EOT
qqq: qqq:
[foo ] foo [foo ] foo
[żółw ] bar [żółw ] bar
[łabądź] baz [łabądź] baz
> >
EOT EOT
, $output); , $output, true);
} }
public function testChoiceQuestionCustomPrompt() public function testChoiceQuestionCustomPrompt()
@ -168,9 +168,9 @@ EOT
$this->assertOutputContains(<<<EOT $this->assertOutputContains(<<<EOT
qqq: qqq:
[0] foo [0] foo
>ccc> >ccc>
EOT EOT
, $output); , $output, true);
} }
protected function getInputStream($input) protected function getInputStream($input)
@ -200,10 +200,15 @@ EOT
return $mock; return $mock;
} }
private function assertOutputContains($expected, StreamOutput $output) private function assertOutputContains($expected, StreamOutput $output, $normalize = false)
{ {
rewind($output->getStream()); rewind($output->getStream());
$stream = stream_get_contents($output->getStream()); $stream = stream_get_contents($output->getStream());
if ($normalize) {
$stream = str_replace(PHP_EOL, "\n", $stream);
}
$this->assertStringContainsString($expected, $stream); $this->assertStringContainsString($expected, $stream);
} }
} }

View File

@ -52,7 +52,7 @@ class PassConfig
new ValidateEnvPlaceholdersPass(), new ValidateEnvPlaceholdersPass(),
new ResolveChildDefinitionsPass(), new ResolveChildDefinitionsPass(),
new RegisterServiceSubscribersPass(), new RegisterServiceSubscribersPass(),
new ResolveParameterPlaceHoldersPass(false), new ResolveParameterPlaceHoldersPass(false, false),
new ResolveFactoryClassPass(), new ResolveFactoryClassPass(),
new ResolveNamedArgumentsPass(), new ResolveNamedArgumentsPass(),
new AutowireRequiredMethodsPass(), new AutowireRequiredMethodsPass(),

View File

@ -24,10 +24,12 @@ class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass
{ {
private $bag; private $bag;
private $resolveArrays; private $resolveArrays;
private $throwOnResolveException;
public function __construct(bool $resolveArrays = true) public function __construct($resolveArrays = true, $throwOnResolveException = true)
{ {
$this->resolveArrays = $resolveArrays; $this->resolveArrays = $resolveArrays;
$this->throwOnResolveException = $throwOnResolveException;
} }
/** /**
@ -61,7 +63,16 @@ class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass
protected function processValue($value, bool $isRoot = false) protected function processValue($value, bool $isRoot = false)
{ {
if (\is_string($value)) { if (\is_string($value)) {
$v = $this->bag->resolveValue($value); try {
$v = $this->bag->resolveValue($value);
} catch (ParameterNotFoundException $e) {
if ($this->throwOnResolveException) {
throw $e;
}
$v = null;
$this->container->getDefinition($this->currentId)->addError($e->getMessage());
}
return $this->resolveArrays || !$v || !\is_array($v) ? $v : $value; return $this->resolveArrays || !$v || !\is_array($v) ? $v : $value;
} }

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass; use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
class ResolveParameterPlaceHoldersPassTest extends TestCase class ResolveParameterPlaceHoldersPassTest extends TestCase
{ {
@ -71,6 +72,31 @@ class ResolveParameterPlaceHoldersPassTest extends TestCase
$this->assertSame($this->container->getParameterBag()->resolveValue('%env(BAZ)%'), $boundValue); $this->assertSame($this->container->getParameterBag()->resolveValue('%env(BAZ)%'), $boundValue);
} }
public function testParameterNotFoundExceptionsIsThrown()
{
$this->expectException(ParameterNotFoundException::class);
$this->expectExceptionMessage('The service "baz_service_id" has a dependency on a non-existent parameter "non_existent_param".');
$containerBuilder = new ContainerBuilder();
$definition = $containerBuilder->register('baz_service_id');
$definition->setArgument(0, '%non_existent_param%');
$pass = new ResolveParameterPlaceHoldersPass();
$pass->process($containerBuilder);
}
public function testParameterNotFoundExceptionsIsNotThrown()
{
$containerBuilder = new ContainerBuilder();
$definition = $containerBuilder->register('baz_service_id');
$definition->setArgument(0, '%non_existent_param%');
$pass = new ResolveParameterPlaceHoldersPass(true, false);
$pass->process($containerBuilder);
$this->assertCount(1, $definition->getErrors());
}
private function createContainerBuilder(): ContainerBuilder private function createContainerBuilder(): ContainerBuilder
{ {
$containerBuilder = new ContainerBuilder(); $containerBuilder = new ContainerBuilder();

View File

@ -240,7 +240,7 @@ class EventDispatcher implements EventDispatcherInterface
$this->sorted[$eventName] = []; $this->sorted[$eventName] = [];
foreach ($this->listeners[$eventName] as &$listeners) { foreach ($this->listeners[$eventName] as &$listeners) {
foreach ($listeners as $k => $listener) { foreach ($listeners as $k => &$listener) {
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0](); $listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke'; $listener[1] = $listener[1] ?? '__invoke';