Merge branch '4.4' into 5.0
* 4.4: (38 commits) reset the kernel cache after each test [HttpKernel] Ability to define multiple kernel.reset tags [Routing] Continue supporting single colon in object route loaders [FWBundle] Remove unused parameter [Intl] [Workflow] fixes English grammar typos [Filesystem] [Serializer] fixes English grammar typo mailer: mailchimp bridge is throwing undefined index _id when setting message id in mandrill http transport has_roles should be is_granted in upgrade files [HttpClient] Fix early cleanup of pushed HTTP/2 responses skip test on incompatible PHP versions [HttpKernel] Don't cache "not-fresh" state [FrameworkBundle][Cache] Don't deep-merge cache pools configuration [Messenger] Adding exception to amqp transport in case amqp ext is not installed [SecurityBundle] Don't require a user provider for the anonymous listener [Monolog Bridge] Fixed accessing static property as non static. Improve Symfony description [Mailer] Add UPGRADE entries about Envelope and MessageEvent [FrameworkBundle] fix leftover mentioning "secret:" processor Add DateTimeZoneNormalizer into Dependency Injection [Messenger] Error when specified default bus is not among the configured ...
This commit is contained in:
commit
e5f0e60a44
12
.travis.yml
12
.travis.yml
@ -141,6 +141,12 @@ before_install:
|
||||
(cd php-$MIN_PHP && ./configure --enable-sigchild --enable-pcntl && make -j2)
|
||||
fi
|
||||
|
||||
- |
|
||||
# Install vulcain
|
||||
wget https://github.com/symfony/binary-utils/releases/download/v0.1/vulcain_0.1.3_Linux_x86_64.tar.gz -O - | tar xz
|
||||
sudo mv vulcain /usr/local/bin
|
||||
docker pull php:7.3-alpine
|
||||
|
||||
- |
|
||||
# php.ini configuration
|
||||
for PHP in $TRAVIS_PHP_VERSION $php_extra; do
|
||||
@ -306,8 +312,14 @@ install:
|
||||
PHPUNIT_X="$PHPUNIT_X,legacy"
|
||||
fi
|
||||
|
||||
if [[ $PHP = ${MIN_PHP%.*} ]]; then
|
||||
tfold src/Symfony/Component/HttpClient.h2push docker run -it --rm -v $(pwd):/app -v /usr/local/bin/vulcain:/usr/local/bin/vulcain -w /app php:7.3-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push
|
||||
fi
|
||||
|
||||
echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}"
|
||||
|
||||
tfold src/Symfony/Component/Console.tty $PHPUNIT --group tty
|
||||
|
||||
if [[ $PHP = ${MIN_PHP%.*} ]]; then
|
||||
export PHP=$MIN_PHP
|
||||
tfold src/Symfony/Component/Process.sigchild SYMFONY_DEPRECATIONS_HELPER=weak php-$MIN_PHP/sapi/cli/php ./phpunit --colors=always src/Symfony/Component/Process/
|
||||
|
@ -7,6 +7,13 @@ in 4.4 minor versions.
|
||||
To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash
|
||||
To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.4.0...v4.4.1
|
||||
|
||||
* 4.4.0 (2019-11-21)
|
||||
|
||||
* bug #34464 [Form] group constraints when calling the validator (nicolas-grekas)
|
||||
* bug #34451 [DependencyInjection] Fix dumping multiple deprecated aliases (shyim)
|
||||
* bug #34448 [Form] allow button names to start with uppercase letter (xabbuh)
|
||||
* bug #34428 [Security] Fix best encoder not wired using migrate_from (chalasr)
|
||||
|
||||
* 4.4.0-RC1 (2019-11-17)
|
||||
|
||||
* bug #34419 [Cache] Disable igbinary on PHP >= 7.4 (nicolas-grekas)
|
||||
|
@ -2,7 +2,7 @@
|
||||
<img src="https://symfony.com/logos/symfony_black_02.svg">
|
||||
</a></p>
|
||||
|
||||
[Symfony][1] is a **PHP framework** for web applications and a set of reusable
|
||||
[Symfony][1] is a **PHP framework** for web and console applications and a set of reusable
|
||||
**PHP components**. Symfony is used by thousands of web applications (including
|
||||
BlaBlaCar.com and Spotify.com) and most of the [popular PHP projects][2] (including
|
||||
Drupal and Magento).
|
||||
|
@ -168,6 +168,8 @@ Mailer
|
||||
------
|
||||
|
||||
* [BC BREAK] Changed the DSN to use for disabling delivery (using the `NullTransport`) from `smtp://null` to `null://null` (host doesn't matter).
|
||||
* [BC BREAK] Renamed class `SmtpEnvelope` to `Envelope` and `DelayedSmtpEnvelope` to `DelayedEnvelope`.
|
||||
* [BC BREAK] Added a required `string $transport` argument to `MessageEvent::__construct`.
|
||||
|
||||
Messenger
|
||||
---------
|
||||
@ -228,7 +230,7 @@ Security
|
||||
|
||||
**After**
|
||||
```php
|
||||
if ($this->authorizationChecker->isGranted(new Expression("has_role('ROLE_USER') or has_role('ROLE_ADMIN')"))) {}
|
||||
if ($this->authorizationChecker->isGranted(new Expression("is_granted('ROLE_USER') or is_granted('ROLE_ADMIN')"))) {}
|
||||
|
||||
// or:
|
||||
if ($this->authorizationChecker->isGranted('ROLE_USER')
|
||||
|
@ -410,7 +410,7 @@ Security
|
||||
|
||||
**After**
|
||||
```php
|
||||
if ($this->authorizationChecker->isGranted(new Expression("has_role('ROLE_USER') or has_role('ROLE_ADMIN')"))) {}
|
||||
if ($this->authorizationChecker->isGranted(new Expression("is_granted('ROLE_USER') or is_granted('ROLE_ADMIN')"))) {}
|
||||
|
||||
// or:
|
||||
if ($this->authorizationChecker->isGranted('ROLE_USER')
|
||||
@ -473,6 +473,7 @@ Security
|
||||
* Classes implementing the `TokenInterface` must implement the two new methods
|
||||
`__serialize` and `__unserialize`
|
||||
* Implementations of `Guard\AuthenticatorInterface::checkCredentials()` must return a boolean value now. Please explicitly return `false` to indicate invalid credentials.
|
||||
* Removed the `has_role()` function from security expressions, use `is_granted()` instead.
|
||||
|
||||
SecurityBundle
|
||||
--------------
|
||||
|
@ -41,7 +41,7 @@ class ChromePhpHandler extends BaseChromePhpHandler
|
||||
}
|
||||
|
||||
if (!preg_match(static::USER_AGENT_REGEX, $event->getRequest()->headers->get('User-Agent'))) {
|
||||
$this->sendHeaders = false;
|
||||
self::$sendHeaders = false;
|
||||
$this->headers = [];
|
||||
|
||||
return;
|
||||
@ -59,7 +59,7 @@ class ChromePhpHandler extends BaseChromePhpHandler
|
||||
*/
|
||||
protected function sendHeader($header, $content): void
|
||||
{
|
||||
if (!$this->sendHeaders) {
|
||||
if (!self::$sendHeaders) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ CHANGELOG
|
||||
* Added new `error_controller` configuration to handle system exceptions
|
||||
* Added sort option for `translation:update` command.
|
||||
* [BC Break] The `framework.messenger.routing.senders` config key is not deeply merged anymore.
|
||||
* Added `secrets:*` commands and `%env(secret:...)%` processor to deal with secrets seamlessly.
|
||||
* Added `secrets:*` commands to deal with secrets seamlessly.
|
||||
* Made `framework.session.handler_id` accept a DSN
|
||||
* Marked the `RouterDataCollector` class as `@final`.
|
||||
* [BC Break] The `framework.messenger.buses.<name>.middleware` config key is not deeply merged anymore.
|
||||
|
@ -70,7 +70,6 @@ final class ContainerLintCommand extends Command
|
||||
if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) {
|
||||
$buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, \get_class($kernel));
|
||||
$container = $buildContainer();
|
||||
$container->getCompilerPassConfig()->setRemovingPasses([]);
|
||||
} else {
|
||||
(new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ EOF
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
|
||||
|
||||
$io->comment('Use <info>"%env(secret:<name>)%"</info> to reference a secret in a config file.');
|
||||
$io->comment('Use <info>"%env(<name>)%"</info> to reference a secret in a config file.');
|
||||
|
||||
if (!$reveal = $input->getOption('reveal')) {
|
||||
$io->comment(sprintf('To reveal the secrets run <info>php %s %s --reveal</info>', $_SERVER['PHP_SELF'], $this->getName()));
|
||||
|
@ -56,7 +56,7 @@ The <info>%command.name%</info> command stores a secret in the vault.
|
||||
<info>%command.full_name% <name></info>
|
||||
|
||||
To reference secrets in services.yaml or any other config
|
||||
files, use <info>"%env(secret:<name>)%"</info>.
|
||||
files, use <info>"%env(<name>)%"</info>.
|
||||
|
||||
By default, the secret value should be entered interactively.
|
||||
Alternatively, provide a file where to read the secret from:
|
||||
|
@ -19,6 +19,7 @@ use Symfony\Component\Asset\Package;
|
||||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
@ -877,6 +878,7 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
->children()
|
||||
->arrayNode('adapters')
|
||||
->performNoDeepMerging()
|
||||
->info('One or more adapters to chain for creating the pool, defaults to "cache.app".')
|
||||
->beforeNormalization()
|
||||
->always()->then(function ($values) {
|
||||
@ -1035,6 +1037,10 @@ class Configuration implements ConfigurationInterface
|
||||
->ifTrue(function ($v) { return isset($v['buses']) && \count($v['buses']) > 1 && null === $v['default_bus']; })
|
||||
->thenInvalid('You must specify the "default_bus" if you define more than one bus.')
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(static function ($v): bool { return isset($v['buses']) && null !== $v['default_bus'] && !isset($v['buses'][$v['default_bus']]); })
|
||||
->then(static function (array $v): void { throw new InvalidConfigurationException(sprintf('The specified default bus "%s" is not configured. Available buses are "%s".', $v['default_bus'], implode('", "', array_keys($v['buses'])))); })
|
||||
->end()
|
||||
->children()
|
||||
->arrayNode('routing')
|
||||
->normalizeKeys(false)
|
||||
|
@ -302,7 +302,7 @@ class FrameworkExtension extends Extension
|
||||
}
|
||||
|
||||
if ($this->messengerConfigEnabled = $this->isConfigEnabled($container, $config['messenger'])) {
|
||||
$this->registerMessengerConfiguration($config['messenger'], $container, $loader, $config['serializer'], $config['validation']);
|
||||
$this->registerMessengerConfiguration($config['messenger'], $container, $loader, $config['validation']);
|
||||
} else {
|
||||
$container->removeDefinition('console.command.messenger_consume_messages');
|
||||
$container->removeDefinition('console.command.messenger_debug');
|
||||
@ -1558,7 +1558,7 @@ class FrameworkExtension extends Extension
|
||||
}
|
||||
}
|
||||
|
||||
private function registerMessengerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader, array $serializerConfig, array $validationConfig)
|
||||
private function registerMessengerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader, array $validationConfig)
|
||||
{
|
||||
if (!interface_exists(MessageBusInterface::class)) {
|
||||
throw new LogicException('Messenger support cannot be enabled as the Messenger component is not installed. Try running "composer require symfony/messenger".');
|
||||
|
@ -40,6 +40,11 @@
|
||||
<tag name="serializer.normalizer" priority="-915" />
|
||||
</service>
|
||||
|
||||
<service id="serializer.normalizer.datetimezone" class="Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer">
|
||||
<!-- Run before serializer.normalizer.object -->
|
||||
<tag name="serializer.normalizer" priority="-915" />
|
||||
</service>
|
||||
|
||||
<service id="serializer.normalizer.dateinterval" class="Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer">
|
||||
<!-- Run before serializer.normalizer.object -->
|
||||
<tag name="serializer.normalizer" priority="-915" />
|
||||
|
@ -314,6 +314,27 @@ class ConfigurationTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testItErrorsWhenDefaultBusDoesNotExist()
|
||||
{
|
||||
$processor = new Processor();
|
||||
$configuration = new Configuration(true);
|
||||
|
||||
$this->expectException(InvalidConfigurationException::class);
|
||||
$this->expectExceptionMessage('The specified default bus "foo" is not configured. Available buses are "bar", "baz".');
|
||||
|
||||
$processor->processConfiguration($configuration, [
|
||||
[
|
||||
'messenger' => [
|
||||
'default_bus' => 'foo',
|
||||
'buses' => [
|
||||
'bar' => null,
|
||||
'baz' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected static function getBundleDefaultConfig()
|
||||
{
|
||||
return [
|
||||
|
@ -459,8 +459,8 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
|
||||
throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall[$key]['provider']));
|
||||
}
|
||||
$userProvider = $providerIds[$normalizedName];
|
||||
} elseif ('remember_me' === $key) {
|
||||
// RememberMeFactory will use the firewall secret when created
|
||||
} elseif ('remember_me' === $key || 'anonymous' === $key) {
|
||||
// RememberMeFactory will use the firewall secret when created, AnonymousAuthenticationListener does not load users.
|
||||
$userProvider = null;
|
||||
} elseif ($defaultProvider) {
|
||||
$userProvider = $defaultProvider;
|
||||
|
@ -210,7 +210,7 @@ class SecurityExtensionTest extends TestCase
|
||||
$container->compile();
|
||||
}
|
||||
|
||||
public function testPerListenerProviderWithRememberMe()
|
||||
public function testPerListenerProviderWithRememberMeAndAnonymous()
|
||||
{
|
||||
$container = $this->getRawContainer();
|
||||
$container->loadFromExtension('security', [
|
||||
@ -223,6 +223,7 @@ class SecurityExtensionTest extends TestCase
|
||||
'default' => [
|
||||
'form_login' => ['provider' => 'second'],
|
||||
'remember_me' => ['secret' => 'baz'],
|
||||
'anonymous' => true,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
163
src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php
Normal file
163
src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?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\WebServerBundle\Command;
|
||||
|
||||
use Monolog\Formatter\FormatterInterface;
|
||||
use Monolog\Logger;
|
||||
use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter;
|
||||
use Symfony\Bridge\Monolog\Handler\ConsoleHandler;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
|
||||
/**
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
*
|
||||
* @deprecated since Symfony 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead.
|
||||
*/
|
||||
class ServerLogCommand extends Command
|
||||
{
|
||||
private static $bgColor = ['black', 'blue', 'cyan', 'green', 'magenta', 'red', 'white', 'yellow'];
|
||||
|
||||
private $el;
|
||||
private $handler;
|
||||
|
||||
protected static $defaultName = 'server:log';
|
||||
|
||||
public function isEnabled()
|
||||
{
|
||||
if (!class_exists(ConsoleFormatter::class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// based on a symfony/symfony package, it crashes due a missing FormatterInterface from monolog/monolog
|
||||
if (!interface_exists(FormatterInterface::class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::isEnabled();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
if (!class_exists(ConsoleFormatter::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this
|
||||
->addOption('host', null, InputOption::VALUE_REQUIRED, 'The server host', '0.0.0.0:9911')
|
||||
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The line format', ConsoleFormatter::SIMPLE_FORMAT)
|
||||
->addOption('date-format', null, InputOption::VALUE_REQUIRED, 'The date format', ConsoleFormatter::SIMPLE_DATE)
|
||||
->addOption('filter', null, InputOption::VALUE_REQUIRED, 'An expression to filter log. Example: "level > 200 or channel in [\'app\', \'doctrine\']"')
|
||||
->setDescription('Starts a log server that displays logs in real time')
|
||||
->setHelp(<<<'EOF'
|
||||
<info>%command.name%</info> starts a log server to display in real time the log
|
||||
messages generated by your application:
|
||||
|
||||
<info>php %command.full_name%</info>
|
||||
|
||||
To get the information as a machine readable format, use the
|
||||
<comment>--filter</> option:
|
||||
|
||||
<info>php %command.full_name% --filter=port</info>
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
@trigger_error('Using the WebserverBundle is deprecated since Symfony 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED);
|
||||
|
||||
$filter = $input->getOption('filter');
|
||||
if ($filter) {
|
||||
if (!class_exists(ExpressionLanguage::class)) {
|
||||
throw new LogicException('Package "symfony/expression-language" is required to use the "filter" option.');
|
||||
}
|
||||
$this->el = new ExpressionLanguage();
|
||||
}
|
||||
|
||||
$this->handler = new ConsoleHandler($output, true, [
|
||||
OutputInterface::VERBOSITY_NORMAL => Logger::DEBUG,
|
||||
]);
|
||||
|
||||
$this->handler->setFormatter(new ConsoleFormatter([
|
||||
'format' => str_replace('\n', "\n", $input->getOption('format')),
|
||||
'date_format' => $input->getOption('date-format'),
|
||||
'colors' => $output->isDecorated(),
|
||||
'multiline' => OutputInterface::VERBOSITY_DEBUG <= $output->getVerbosity(),
|
||||
]));
|
||||
|
||||
if (false === strpos($host = $input->getOption('host'), '://')) {
|
||||
$host = 'tcp://'.$host;
|
||||
}
|
||||
|
||||
if (!$socket = stream_socket_server($host, $errno, $errstr)) {
|
||||
throw new RuntimeException(sprintf('Server start failed on "%s": %s %s.', $host, $errstr, $errno));
|
||||
}
|
||||
|
||||
foreach ($this->getLogs($socket) as $clientId => $message) {
|
||||
$record = unserialize(base64_decode($message));
|
||||
|
||||
// Impossible to decode the message, give up.
|
||||
if (false === $record) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($filter && !$this->el->evaluate($filter, $record)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->displayLog($output, $clientId, $record);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getLogs($socket): iterable
|
||||
{
|
||||
$sockets = [(int) $socket => $socket];
|
||||
$write = [];
|
||||
|
||||
while (true) {
|
||||
$read = $sockets;
|
||||
stream_select($read, $write, $write, null);
|
||||
|
||||
foreach ($read as $stream) {
|
||||
if ($socket === $stream) {
|
||||
$stream = stream_socket_accept($socket);
|
||||
$sockets[(int) $stream] = $stream;
|
||||
} elseif (feof($stream)) {
|
||||
unset($sockets[(int) $stream]);
|
||||
fclose($stream);
|
||||
} else {
|
||||
yield (int) $stream => fgets($stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function displayLog(OutputInterface $output, int $clientId, array $record)
|
||||
{
|
||||
if (isset($record['log_id'])) {
|
||||
$clientId = unpack('H*', $record['log_id'])[1];
|
||||
}
|
||||
$logBlock = sprintf('<bg=%s> </>', self::$bgColor[$clientId % 8]);
|
||||
$output->write($logBlock);
|
||||
|
||||
$this->handler->handle($record);
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ class TaggedIteratorArgument extends IteratorArgument
|
||||
private $tag;
|
||||
private $indexAttribute;
|
||||
private $defaultIndexMethod;
|
||||
private $defaultPriorityMethod;
|
||||
private $needsIndexes = false;
|
||||
|
||||
/**
|
||||
|
@ -152,6 +152,10 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
|
||||
return;
|
||||
}
|
||||
|
||||
if ('object' === $type) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_a($class, $type, true)) {
|
||||
return;
|
||||
}
|
||||
|
@ -15,12 +15,14 @@ use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Bar;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarMethodCall;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarOptionalArgument;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarOptionalArgumentNotNull;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Foo;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\FooObject;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
@ -390,6 +392,21 @@ class CheckTypeDeclarationsPassTest extends TestCase
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 7.2
|
||||
*/
|
||||
public function testProcessSuccessWhenPassingDefinitionForObjectType()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$container->register('foo_object', FooObject::class)
|
||||
->addArgument(new Definition(Foo::class));
|
||||
|
||||
(new CheckTypeDeclarationsPass(true))->process($container);
|
||||
|
||||
$this->addToAssertionCount(1);
|
||||
}
|
||||
|
||||
public function testProcessFactory()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass;
|
||||
|
||||
class FooObject
|
||||
{
|
||||
public function __construct(object $foo)
|
||||
{
|
||||
}
|
||||
}
|
@ -461,7 +461,7 @@ final class Dotenv
|
||||
$value = '';
|
||||
}
|
||||
|
||||
if ('' === $value && isset($matches['default_value'])) {
|
||||
if ('' === $value && isset($matches['default_value']) && '' !== $matches['default_value']) {
|
||||
$unsupportedChars = strpbrk($matches['default_value'], '\'"{$');
|
||||
if (false !== $unsupportedChars) {
|
||||
throw $this->createFormatException(sprintf('Unsupported character "%s" found in the default value of variable "$%s".', $unsupportedChars[0], $name));
|
||||
|
@ -172,6 +172,7 @@ class DotenvTest extends TestCase
|
||||
["FOO=BAR\nBAR=\${NOTDEFINED:=TEST}", ['FOO' => 'BAR', 'NOTDEFINED' => 'TEST', 'BAR' => 'TEST']],
|
||||
["FOO=\nBAR=\${FOO:=TEST}", ['FOO' => 'TEST', 'BAR' => 'TEST']],
|
||||
["FOO=\nBAR=\$FOO:=TEST}", ['FOO' => 'TEST', 'BAR' => 'TEST}']],
|
||||
["FOO=foo\nFOOBAR=\${FOO}\${BAR}", ['FOO' => 'foo', 'FOOBAR' => 'foo']],
|
||||
];
|
||||
|
||||
if ('\\' !== \DIRECTORY_SEPARATOR) {
|
||||
|
@ -783,7 +783,7 @@ class FilesystemTest extends FilesystemTestCase
|
||||
$file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
|
||||
$link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
|
||||
|
||||
// $file does not exists right now: creating "broken" links is a wanted feature
|
||||
// $file does not exist right now: creating "broken" links is a wanted feature
|
||||
$this->filesystem->symlink($file, $link);
|
||||
|
||||
$this->assertTrue(is_link($link));
|
||||
|
@ -23,6 +23,7 @@ use Symfony\Component\HttpClient\Response\ResponseStream;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* A performant implementation of the HttpClientInterface contracts based on the curl extension.
|
||||
@ -32,7 +33,7 @@ use Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface
|
||||
{
|
||||
use HttpClientTrait;
|
||||
use LoggerAwareTrait;
|
||||
@ -324,9 +325,17 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
return new ResponseStream(CurlResponse::stream($responses, $timeout));
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
public function reset()
|
||||
{
|
||||
if ($this->logger) {
|
||||
foreach ($this->multi->pushedResponses as $url => $response) {
|
||||
$this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
|
||||
}
|
||||
}
|
||||
|
||||
$this->multi->pushedResponses = [];
|
||||
$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 (\defined('CURLMOPT_PUSHFUNCTION')) {
|
||||
@ -344,6 +353,11 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
private static function handlePush($parent, $pushed, array $requestHeaders, CurlClientState $multi, int $maxPendingPushes, ?LoggerInterface $logger): int
|
||||
{
|
||||
$headers = [];
|
||||
@ -363,12 +377,6 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
|
||||
$url = $headers[':scheme'][0].'://'.$headers[':authority'][0];
|
||||
|
||||
if ($maxPendingPushes <= \count($multi->pushedResponses)) {
|
||||
$logger && $logger->debug(sprintf('Rejecting pushed response from "%s" for "%s": the queue is full', $origin, $url));
|
||||
|
||||
return CURL_PUSH_DENY;
|
||||
}
|
||||
|
||||
// curl before 7.65 doesn't validate the pushed ":authority" header,
|
||||
// but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host,
|
||||
// ignoring domains mentioned as alt-name in the certificate for now (same as curl).
|
||||
@ -378,6 +386,12 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
return CURL_PUSH_DENY;
|
||||
}
|
||||
|
||||
if ($maxPendingPushes <= \count($multi->pushedResponses)) {
|
||||
$fifoUrl = key($multi->pushedResponses);
|
||||
unset($multi->pushedResponses[$fifoUrl]);
|
||||
$logger && $logger->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl));
|
||||
}
|
||||
|
||||
$url .= $headers[':path'][0];
|
||||
$logger && $logger->debug(sprintf('Queueing pushed response: "%s"', $url));
|
||||
|
||||
|
@ -15,11 +15,12 @@ use Symfony\Component\HttpClient\TraceableHttpClient;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
||||
|
||||
/**
|
||||
* @author Jérémy Romey <jeremy@free-agent.fr>
|
||||
*/
|
||||
final class HttpClientDataCollector extends DataCollector
|
||||
final class HttpClientDataCollector extends DataCollector implements LateDataCollectorInterface
|
||||
{
|
||||
/**
|
||||
* @var TraceableHttpClient[]
|
||||
@ -36,7 +37,7 @@ final class HttpClientDataCollector extends DataCollector
|
||||
*/
|
||||
public function collect(Request $request, Response $response, \Throwable $exception = null)
|
||||
{
|
||||
$this->initData();
|
||||
$this->reset();
|
||||
|
||||
foreach ($this->clients as $name => $client) {
|
||||
[$errorCount, $traces] = $this->collectOnClient($client);
|
||||
@ -51,6 +52,13 @@ final class HttpClientDataCollector extends DataCollector
|
||||
}
|
||||
}
|
||||
|
||||
public function lateCollect()
|
||||
{
|
||||
foreach ($this->clients as $client) {
|
||||
$client->reset();
|
||||
}
|
||||
}
|
||||
|
||||
public function getClients(): array
|
||||
{
|
||||
return $this->data['clients'] ?? [];
|
||||
@ -66,17 +74,6 @@ final class HttpClientDataCollector extends DataCollector
|
||||
return $this->data['error_count'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->initData();
|
||||
foreach ($this->clients as $client) {
|
||||
$client->reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -85,7 +82,7 @@ final class HttpClientDataCollector extends DataCollector
|
||||
return 'http_client';
|
||||
}
|
||||
|
||||
private function initData()
|
||||
public function reset()
|
||||
{
|
||||
$this->data = [
|
||||
'clients' => [],
|
||||
|
@ -228,15 +228,7 @@ final class CurlResponse implements ResponseInterface
|
||||
} finally {
|
||||
$this->close();
|
||||
|
||||
// Clear local caches when the only remaining handles are about pushed responses
|
||||
if (!$this->multi->openHandles) {
|
||||
if ($this->logger) {
|
||||
foreach ($this->multi->pushedResponses as $url => $response) {
|
||||
$this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
|
||||
}
|
||||
}
|
||||
|
||||
$this->multi->pushedResponses = [];
|
||||
// Schedule DNS cache eviction for the next request
|
||||
$this->multi->dnsCache->evictions = $this->multi->dnsCache->evictions ?: $this->multi->dnsCache->removals;
|
||||
$this->multi->dnsCache->removals = $this->multi->dnsCache->hostnames = [];
|
||||
|
@ -15,13 +15,14 @@ use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* Auto-configure the default options based on the requested URL.
|
||||
*
|
||||
* @author Anthony Martin <anthony.martin@sensiolabs.com>
|
||||
*/
|
||||
class ScopingHttpClient implements HttpClientInterface
|
||||
class ScopingHttpClient implements HttpClientInterface, ResetInterface
|
||||
{
|
||||
use HttpClientTrait;
|
||||
|
||||
@ -90,4 +91,11 @@ class ScopingHttpClient implements HttpClientInterface
|
||||
{
|
||||
return $this->client->stream($responses, $timeout);
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
if ($this->client instanceof ResetInterface) {
|
||||
$this->client->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,13 +13,23 @@ namespace Symfony\Component\HttpClient\Tests;
|
||||
|
||||
use Psr\Log\AbstractLogger;
|
||||
use Symfony\Component\HttpClient\CurlHttpClient;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/*
|
||||
Tests for HTTP2 Push need a recent version of both PHP and curl. This docker command should run them:
|
||||
docker run -it --rm -v $(pwd):/app -v /path/to/vulcain:/usr/local/bin/vulcain -w /app php:7.3-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push
|
||||
The vulcain binary can be found at https://github.com/symfony/binary-utils/releases/download/v0.1/vulcain_0.1.3_Linux_x86_64.tar.gz - see https://github.com/dunglas/vulcain for source
|
||||
*/
|
||||
|
||||
/**
|
||||
* @requires extension curl
|
||||
*/
|
||||
class CurlHttpClientTest extends HttpClientTestCase
|
||||
{
|
||||
private static $vulcainStarted = false;
|
||||
|
||||
protected function getHttpClient(string $testCase): HttpClientInterface
|
||||
{
|
||||
return new CurlHttpClient();
|
||||
@ -28,7 +38,81 @@ class CurlHttpClientTest extends HttpClientTestCase
|
||||
/**
|
||||
* @requires PHP 7.2.17
|
||||
*/
|
||||
public function testHttp2Push()
|
||||
public function testHttp2PushVulcain()
|
||||
{
|
||||
$client = $this->getVulcainClient();
|
||||
$logger = new TestLogger();
|
||||
$client->setLogger($logger);
|
||||
|
||||
$responseAsArray = $client->request('GET', 'https://127.0.0.1:3000/json', [
|
||||
'headers' => [
|
||||
'Preload' => '/documents/*/id',
|
||||
],
|
||||
])->toArray();
|
||||
|
||||
foreach ($responseAsArray['documents'] as $document) {
|
||||
$client->request('GET', 'https://127.0.0.1:3000'.$document['id'])->toArray();
|
||||
}
|
||||
|
||||
$client->reset();
|
||||
|
||||
$expected = [
|
||||
'Request: "GET https://127.0.0.1:3000/json"',
|
||||
'Queueing pushed response: "https://127.0.0.1:3000/json/1"',
|
||||
'Queueing pushed response: "https://127.0.0.1:3000/json/2"',
|
||||
'Queueing pushed response: "https://127.0.0.1:3000/json/3"',
|
||||
'Response: "200 https://127.0.0.1:3000/json"',
|
||||
'Accepting pushed response: "GET https://127.0.0.1:3000/json/1"',
|
||||
'Response: "200 https://127.0.0.1:3000/json/1"',
|
||||
'Accepting pushed response: "GET https://127.0.0.1:3000/json/2"',
|
||||
'Response: "200 https://127.0.0.1:3000/json/2"',
|
||||
'Accepting pushed response: "GET https://127.0.0.1:3000/json/3"',
|
||||
'Response: "200 https://127.0.0.1:3000/json/3"',
|
||||
];
|
||||
$this->assertSame($expected, $logger->logs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 7.2.17
|
||||
*/
|
||||
public function testHttp2PushVulcainWithUnusedResponse()
|
||||
{
|
||||
$client = $this->getVulcainClient();
|
||||
$logger = new TestLogger();
|
||||
$client->setLogger($logger);
|
||||
|
||||
$responseAsArray = $client->request('GET', 'https://127.0.0.1:3000/json', [
|
||||
'headers' => [
|
||||
'Preload' => '/documents/*/id',
|
||||
],
|
||||
])->toArray();
|
||||
|
||||
$i = 0;
|
||||
foreach ($responseAsArray['documents'] as $document) {
|
||||
$client->request('GET', 'https://127.0.0.1:3000'.$document['id'])->toArray();
|
||||
if (++$i >= 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$client->reset();
|
||||
|
||||
$expected = [
|
||||
'Request: "GET https://127.0.0.1:3000/json"',
|
||||
'Queueing pushed response: "https://127.0.0.1:3000/json/1"',
|
||||
'Queueing pushed response: "https://127.0.0.1:3000/json/2"',
|
||||
'Queueing pushed response: "https://127.0.0.1:3000/json/3"',
|
||||
'Response: "200 https://127.0.0.1:3000/json"',
|
||||
'Accepting pushed response: "GET https://127.0.0.1:3000/json/1"',
|
||||
'Response: "200 https://127.0.0.1:3000/json/1"',
|
||||
'Accepting pushed response: "GET https://127.0.0.1:3000/json/2"',
|
||||
'Response: "200 https://127.0.0.1:3000/json/2"',
|
||||
'Unused pushed response: "https://127.0.0.1:3000/json/3"',
|
||||
];
|
||||
$this->assertSame($expected, $logger->logs);
|
||||
}
|
||||
|
||||
private function getVulcainClient(): CurlHttpClient
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304) {
|
||||
$this->markTestSkipped('PHP 7.3.0 to 7.3.3 don\'t support HTTP/2 PUSH');
|
||||
@ -38,32 +122,44 @@ class CurlHttpClientTest extends HttpClientTestCase
|
||||
$this->markTestSkipped('curl <7.61 is used or it is not compiled with support for HTTP/2 PUSH');
|
||||
}
|
||||
|
||||
$logger = new class() extends AbstractLogger {
|
||||
public $logs = [];
|
||||
$client = new CurlHttpClient(['verify_peer' => false, 'verify_host' => false]);
|
||||
|
||||
public function log($level, $message, array $context = []): void
|
||||
{
|
||||
$this->logs[] = $message;
|
||||
}
|
||||
};
|
||||
if (static::$vulcainStarted) {
|
||||
return $client;
|
||||
}
|
||||
|
||||
$client = new CurlHttpClient([], 6, 2);
|
||||
$client->setLogger($logger);
|
||||
if (200 !== $client->request('GET', 'http://127.0.0.1:8057/json')->getStatusCode()) {
|
||||
$this->markTestSkipped('symfony/http-client-contracts >= 2.0.1 required');
|
||||
}
|
||||
|
||||
$index = $client->request('GET', 'https://http2.akamai.com/');
|
||||
$index->getContent();
|
||||
$process = new Process(['vulcain'], null, [
|
||||
'DEBUG' => 1,
|
||||
'UPSTREAM' => 'http://127.0.0.1:8057',
|
||||
'ADDR' => ':3000',
|
||||
'KEY_FILE' => __DIR__.'/Fixtures/tls/server.key',
|
||||
'CERT_FILE' => __DIR__.'/Fixtures/tls/server.crt',
|
||||
]);
|
||||
$process->start();
|
||||
|
||||
$css = $client->request('GET', 'https://http2.akamai.com/resources/push.css');
|
||||
register_shutdown_function([$process, 'stop']);
|
||||
sleep('\\' === \DIRECTORY_SEPARATOR ? 10 : 1);
|
||||
|
||||
$css->getHeaders();
|
||||
if (!$process->isRunning()) {
|
||||
throw new ProcessFailedException($process);
|
||||
}
|
||||
|
||||
$expected = [
|
||||
'Request: "GET https://http2.akamai.com/"',
|
||||
'Queueing pushed response: "https://http2.akamai.com/resources/push.css"',
|
||||
'Response: "200 https://http2.akamai.com/"',
|
||||
'Accepting pushed response: "GET https://http2.akamai.com/resources/push.css"',
|
||||
'Response: "200 https://http2.akamai.com/resources/push.css"',
|
||||
];
|
||||
$this->assertSame($expected, $logger->logs);
|
||||
static::$vulcainStarted = true;
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
||||
|
||||
class TestLogger extends AbstractLogger
|
||||
{
|
||||
public $logs = [];
|
||||
|
||||
public function log($level, $message, array $context = []): void
|
||||
{
|
||||
$this->logs[] = $message;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDPjCCAiYCCQDpVvfmCZt2GzANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV
|
||||
UzEUMBIGA1UEBwwLR290aGFtIENpdHkxEjAQBgNVBAMMCWxvY2FsaG9zdDEoMCYG
|
||||
CSqGSIb3DQEJARYZZHVuZ2xhcyttZXJjdXJlQGdtYWlsLmNvbTAeFw0xOTAxMjMx
|
||||
NTUzMzlaFw0yOTAxMjAxNTUzMzlaMGExCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtH
|
||||
b3RoYW0gQ2l0eTESMBAGA1UEAwwJbG9jYWxob3N0MSgwJgYJKoZIhvcNAQkBFhlk
|
||||
dW5nbGFzK21lcmN1cmVAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEAuKnXkBSJwOwkKfR58wP/yLYW9QFX2THoqN8iffangRmZwc5KLE6F
|
||||
1S8jYMv3JGiJ95Ij3MezAfuBCdgPqqP8JrR1XwjR1RFZMOL/4U9R9OuMVng04PLw
|
||||
L6TzKoEtZuExHUWFP0+5AYblgno2hoN/HVuox8m6zQrBNcbhTgDIjP5Hn491d9od
|
||||
MtS3OxksDLr1UIOUGUWF7MQMN7lsN7rgT5qxoCkcAGAB4GPOA23HMt2zt4afDiI7
|
||||
lAmuv8MKkTmBCcFe+q+U7o6wMxkjGstzAWRibtwzR4ejPwdO7se23MXCWGPvF16Z
|
||||
tu1ip+e+waRus9o5UnyGaVPFAw8iCTC/KwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB
|
||||
AQB42AW7E57yOky8GpsKLoa9u7okwvvg8CQJ117X8a2MElBGnmMd9tjLa/pXAx2I
|
||||
bN7jSTSadXiPNYCx4ueiJa4Dwy+C8YkwUbhRf3+mc7Chnz0SXouTjh7OUeeA06jS
|
||||
W2VAR2pKB0pdJtAkXxIy21Juu8KF5uZqVq1oimgKw2lRUIMdKaqsrVwESk6u5Ojj
|
||||
3DS40q9DzFnwKGCuZpspvMdWYLscotzLrCbnHp+guWDigEHS3CKzKbNo327nVg6X
|
||||
7UjqqtPZ2mCsnUx3QTDJsr3gcSqhzmB+Q6I/0Q2Nx/aMmbsNegu+LC3GjFtL59Bv
|
||||
B8pB/MxID0j47SwPKQghZvb3
|
||||
-----END CERTIFICATE-----
|
@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAuKnXkBSJwOwkKfR58wP/yLYW9QFX2THoqN8iffangRmZwc5K
|
||||
LE6F1S8jYMv3JGiJ95Ij3MezAfuBCdgPqqP8JrR1XwjR1RFZMOL/4U9R9OuMVng0
|
||||
4PLwL6TzKoEtZuExHUWFP0+5AYblgno2hoN/HVuox8m6zQrBNcbhTgDIjP5Hn491
|
||||
d9odMtS3OxksDLr1UIOUGUWF7MQMN7lsN7rgT5qxoCkcAGAB4GPOA23HMt2zt4af
|
||||
DiI7lAmuv8MKkTmBCcFe+q+U7o6wMxkjGstzAWRibtwzR4ejPwdO7se23MXCWGPv
|
||||
F16Ztu1ip+e+waRus9o5UnyGaVPFAw8iCTC/KwIDAQABAoIBAQCczVNGe7oRADMh
|
||||
EP/wM4ghhUTvHAndWrzFkFs4fJX1UKi34ZQoFTEdOZ6f1fHwj3f/qa8cDNJar5X9
|
||||
puJ+siotL3Suks2iT83dbhN63SCpiM2sqvuzu3Xp7vWwNOo5fqR2x46CmQ5uVn5S
|
||||
EbZ09/mbEza5FvmwnB49rLepxY6F8P+vK5ZnCZYS2SHpOxv3U9wG8gmcHRI9ejbC
|
||||
X9rwuu3oT23bfbJ0tn6Qh8O3R1kXZUUXqnxsn554cZZrXg5+ygbt4HfDVWMLpqy/
|
||||
5wG0FCpU8QvjF4L8qErP7TZRrWGFtti1RtACbu9LrWvO/74v54td5V28U6kqlDJR
|
||||
ff4Mi4whAoGBAOBzReQIxGwoYApPyhF+ohvF39JEEXYfkzk94t6hbgyBFBFvqdFY
|
||||
shT59im2P9LyDvTd5DnCIo52Sj7vM9H80tRjAA0A8okGOczk31ABbH8aZ2orU/0G
|
||||
EJe4PV4r3bpLO6DKTYsicgRpXI3aHHLvYFXOVNrQKfrKCQ+GFMVuhDdRAoGBANKe
|
||||
3Dn3XOq7EW42GZey1xUxrfQRJp491KXHvjYt7z7zSiUzqN+mqIqz6ngCjJWbyQsl
|
||||
Ud9N9U+4rNfYYLHQ0resjxGQRtmooOHlLhT6pEplXDgQb2SmCg2u22SKkkXA7zOV
|
||||
OFbNryXgkYThsA6ih8LiKM8aFn7zttRSEeTpfye7AoGBALhIzRyiuiuXpuswgdeF
|
||||
YrJs8A1jB/c1i5qXHlvurT2lCYYbaZHSQj0I0r2CvrqDNhaEzStDIz5XDzTHD4Qd
|
||||
EjmBo3wJyBkLPI/nZxb4ZE2jrz8znf0EasE3a2OTnrSjqqylDa/sMzM+EtkBORSB
|
||||
SFaLV45lFeKs2W2eiBVmXTZRAoGAJoA7qaz6Iz6G9SqWixB6GLm4HsFz2cFbueJF
|
||||
dwn2jf9TMnG7EQcaECDLX5y3rjGIEq2DxdouWaBcmChJpLeTjVfR31gMW4Vjw2dt
|
||||
gRBAMAlPTkBS3Ictl0q7eCmMi4u1Liy828FFnxrp/uxyjnpPbuSAqTsPma1bYnyO
|
||||
INY+FDkCgYAe9e39/vXe7Un3ysjqDUW+0OMM+kg4ulhiopzKY+QbHiSWmUUDtvcN
|
||||
asqrYiX1d59e2ZNiqrlBn86I8549St81bWSrRMNf7R+WVb79RApsABeUaEoyo3lq
|
||||
0UgOBM8Nt558kaja/YfJf/jwNC1DPuu5x5t38ZcqAkqrZ/HEPkFdGQ==
|
||||
-----END RSA PRIVATE KEY-----
|
@ -14,11 +14,12 @@ namespace Symfony\Component\HttpClient;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* @author Jérémy Romey <jeremy@free-agent.fr>
|
||||
*/
|
||||
final class TraceableHttpClient implements HttpClientInterface
|
||||
final class TraceableHttpClient implements HttpClientInterface, ResetInterface
|
||||
{
|
||||
private $client;
|
||||
private $tracedRequests = [];
|
||||
@ -68,6 +69,10 @@ final class TraceableHttpClient implements HttpClientInterface
|
||||
|
||||
public function reset()
|
||||
{
|
||||
if ($this->client instanceof ResetInterface) {
|
||||
$this->client->reset();
|
||||
}
|
||||
|
||||
$this->tracedRequests = [];
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,10 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte
|
||||
return 'memory';
|
||||
}
|
||||
|
||||
private function convertToBytes(string $memoryLimit): int
|
||||
/**
|
||||
* @return int|float
|
||||
*/
|
||||
private function convertToBytes(string $memoryLimit)
|
||||
{
|
||||
if ('-1' === $memoryLimit) {
|
||||
return -1;
|
||||
|
@ -43,16 +43,21 @@ class ResettableServicePass implements CompilerPassInterface
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->tagName, true) as $id => $tags) {
|
||||
$services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE);
|
||||
$attributes = $tags[0];
|
||||
|
||||
if (!isset($attributes['method'])) {
|
||||
throw new RuntimeException(sprintf('Tag %s requires the "method" attribute to be set.', $this->tagName));
|
||||
foreach ($tags as $attributes) {
|
||||
if (!isset($attributes['method'])) {
|
||||
throw new RuntimeException(sprintf('Tag "%s" requires the "method" attribute to be set.', $this->tagName));
|
||||
}
|
||||
|
||||
if (!isset($methods[$id])) {
|
||||
$methods[$id] = [];
|
||||
}
|
||||
|
||||
$methods[$id][] = $attributes['method'];
|
||||
}
|
||||
|
||||
$methods[$id] = $attributes['method'];
|
||||
}
|
||||
|
||||
if (empty($services)) {
|
||||
if (!$services) {
|
||||
$container->removeAlias('services_resetter');
|
||||
$container->removeDefinition('services_resetter');
|
||||
|
||||
|
@ -35,7 +35,9 @@ class ServicesResetter implements ResetInterface
|
||||
public function reset()
|
||||
{
|
||||
foreach ($this->resettableServices as $id => $service) {
|
||||
$service->{$this->resetMethods[$id]}();
|
||||
foreach ((array) $this->resetMethods[$id] as $resetMethod) {
|
||||
$service->$resetMethod();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -377,7 +377,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
||||
}
|
||||
|
||||
// add our cached last-modified validator
|
||||
$subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified'));
|
||||
if ($entry->headers->has('Last-Modified')) {
|
||||
$subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified'));
|
||||
}
|
||||
|
||||
// Add our cached etag validator to the environment.
|
||||
// We keep the etags from the client to handle the case when the client
|
||||
|
@ -433,8 +433,9 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
|
||||
|
||||
try {
|
||||
if (file_exists($cachePath) && \is_object($this->container = include $cachePath)
|
||||
&& (!$this->debug || (self::$freshCache[$k = $cachePath.'.'.$this->environment] ?? self::$freshCache[$k] = $cache->isFresh()))
|
||||
&& (!$this->debug || (self::$freshCache[$cachePath] ?? $cache->isFresh()))
|
||||
) {
|
||||
self::$freshCache[$cachePath] = true;
|
||||
$this->container->set('kernel', $this);
|
||||
error_reporting($errorLevel);
|
||||
|
||||
|
@ -10,6 +10,7 @@ use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\ClearableService;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\MultiResettableService;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService;
|
||||
|
||||
class ResettableServicePassTest extends TestCase
|
||||
@ -23,6 +24,10 @@ class ResettableServicePassTest extends TestCase
|
||||
$container->register('two', ClearableService::class)
|
||||
->setPublic(true)
|
||||
->addTag('kernel.reset', ['method' => 'clear']);
|
||||
$container->register('three', MultiResettableService::class)
|
||||
->setPublic(true)
|
||||
->addTag('kernel.reset', ['method' => 'resetFirst'])
|
||||
->addTag('kernel.reset', ['method' => 'resetSecond']);
|
||||
|
||||
$container->register('services_resetter', ServicesResetter::class)
|
||||
->setPublic(true)
|
||||
@ -38,10 +43,12 @@ class ResettableServicePassTest extends TestCase
|
||||
new IteratorArgument([
|
||||
'one' => new Reference('one', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
|
||||
'two' => new Reference('two', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
|
||||
'three' => new Reference('three', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
|
||||
]),
|
||||
[
|
||||
'one' => 'reset',
|
||||
'two' => 'clear',
|
||||
'one' => ['reset'],
|
||||
'two' => ['clear'],
|
||||
'three' => ['resetFirst', 'resetSecond'],
|
||||
],
|
||||
],
|
||||
$definition->getArguments()
|
||||
@ -51,7 +58,7 @@ class ResettableServicePassTest extends TestCase
|
||||
public function testMissingMethod()
|
||||
{
|
||||
$this->expectException('Symfony\Component\DependencyInjection\Exception\RuntimeException');
|
||||
$this->expectExceptionMessage('Tag kernel.reset requires the "method" attribute to be set.');
|
||||
$this->expectExceptionMessage('Tag "kernel.reset" requires the "method" attribute to be set.');
|
||||
$container = new ContainerBuilder();
|
||||
$container->register(ResettableService::class)
|
||||
->addTag('kernel.reset');
|
||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\HttpKernel\Tests\DependencyInjection;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\ClearableService;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\MultiResettableService;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService;
|
||||
|
||||
class ServicesResetterTest extends TestCase
|
||||
@ -22,6 +23,8 @@ class ServicesResetterTest extends TestCase
|
||||
{
|
||||
ResettableService::$counter = 0;
|
||||
ClearableService::$counter = 0;
|
||||
MultiResettableService::$resetFirstCounter = 0;
|
||||
MultiResettableService::$resetSecondCounter = 0;
|
||||
}
|
||||
|
||||
public function testResetServices()
|
||||
@ -29,14 +32,18 @@ class ServicesResetterTest extends TestCase
|
||||
$resetter = new ServicesResetter(new \ArrayIterator([
|
||||
'id1' => new ResettableService(),
|
||||
'id2' => new ClearableService(),
|
||||
'id3' => new MultiResettableService(),
|
||||
]), [
|
||||
'id1' => 'reset',
|
||||
'id2' => 'clear',
|
||||
'id1' => ['reset'],
|
||||
'id2' => ['clear'],
|
||||
'id3' => ['resetFirst', 'resetSecond'],
|
||||
]);
|
||||
|
||||
$resetter->reset();
|
||||
|
||||
$this->assertEquals(1, ResettableService::$counter);
|
||||
$this->assertEquals(1, ClearableService::$counter);
|
||||
$this->assertSame(1, ResettableService::$counter);
|
||||
$this->assertSame(1, ClearableService::$counter);
|
||||
$this->assertSame(1, MultiResettableService::$resetFirstCounter);
|
||||
$this->assertSame(1, MultiResettableService::$resetSecondCounter);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\Fixtures;
|
||||
|
||||
class MultiResettableService
|
||||
{
|
||||
public static $resetFirstCounter = 0;
|
||||
public static $resetSecondCounter = 0;
|
||||
|
||||
public function resetFirst()
|
||||
{
|
||||
++self::$resetFirstCounter;
|
||||
}
|
||||
|
||||
public function resetSecond()
|
||||
{
|
||||
++self::$resetSecondCounter;
|
||||
}
|
||||
}
|
@ -857,6 +857,7 @@ class HttpCacheTest extends HttpCacheTestCase
|
||||
public function testValidatesCachedResponsesWithETagAndNoFreshnessInformation()
|
||||
{
|
||||
$this->setNextResponse(200, [], 'Hello World', function ($request, $response) {
|
||||
$this->assertFalse($request->headers->has('If-Modified-Since'));
|
||||
$response->headers->set('Cache-Control', 'public');
|
||||
$response->headers->set('ETag', '"12345"');
|
||||
if ($response->getETag() == $request->headers->get('IF_NONE_MATCH')) {
|
||||
|
@ -29,7 +29,7 @@ use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService;
|
||||
|
||||
class KernelTest extends TestCase
|
||||
{
|
||||
public static function tearDownAfterClass(): void
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$fs = new Filesystem();
|
||||
$fs->remove(__DIR__.'/Fixtures/var');
|
||||
|
@ -46,7 +46,7 @@ final class Currencies extends ResourceBundle
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MissingResourceException if the currency code does not exists
|
||||
* @throws MissingResourceException if the currency code does not exist
|
||||
*/
|
||||
public static function getName(string $currency, string $displayLocale = null): string
|
||||
{
|
||||
@ -78,7 +78,7 @@ final class Currencies extends ResourceBundle
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MissingResourceException if the currency code does not exists
|
||||
* @throws MissingResourceException if the currency code does not exist
|
||||
*/
|
||||
public static function getSymbol(string $currency, string $displayLocale = null): string
|
||||
{
|
||||
@ -115,7 +115,7 @@ final class Currencies extends ResourceBundle
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MissingResourceException if the numeric code does not exists
|
||||
* @throws MissingResourceException if the numeric code does not exist
|
||||
*/
|
||||
public static function forNumericCode(int $numericCode): array
|
||||
{
|
||||
|
@ -49,7 +49,7 @@ final class Locales extends ResourceBundle
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MissingResourceException if the locale does not exists
|
||||
* @throws MissingResourceException if the locale does not exist
|
||||
*/
|
||||
public static function getName(string $locale, string $displayLocale = null): string
|
||||
{
|
||||
|
@ -41,7 +41,7 @@ final class Scripts extends ResourceBundle
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MissingResourceException if the script code does not exists
|
||||
* @throws MissingResourceException if the script code does not exist
|
||||
*/
|
||||
public static function getName(string $script, string $displayLocale = null): string
|
||||
{
|
||||
|
@ -60,7 +60,7 @@ class MandrillHttpTransport extends AbstractHttpTransport
|
||||
throw new HttpTransportException(sprintf('Unable to send an email (code %s).', $result['code']), $response);
|
||||
}
|
||||
|
||||
$message->setMessageId($result['_id']);
|
||||
$message->setMessageId($result[0]['_id']);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
@ -28,6 +28,9 @@ class AmqpTransportFactoryTest extends TestCase
|
||||
$this->assertFalse($factory->supports('invalid-dsn', []));
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires extension amqp
|
||||
*/
|
||||
public function testItCreatesTheTransport()
|
||||
{
|
||||
$factory = new AmqpTransportFactory();
|
||||
|
@ -13,6 +13,7 @@ namespace Symfony\Component\Messenger\Tests\Transport;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Tests\Fixtures\AnEnvelopeStamp;
|
||||
use Symfony\Component\Messenger\Transport\InMemoryTransport;
|
||||
|
||||
/**
|
||||
@ -50,6 +51,19 @@ class InMemoryTransportTest extends TestCase
|
||||
$this->assertSame([], $this->transport->get());
|
||||
}
|
||||
|
||||
public function testAcknowledgeSameMessageWithDifferentStamps()
|
||||
{
|
||||
$envelope1 = new Envelope(new \stdClass(), [new AnEnvelopeStamp()]);
|
||||
$this->transport->send($envelope1);
|
||||
$envelope2 = new Envelope(new \stdClass(), [new AnEnvelopeStamp()]);
|
||||
$this->transport->send($envelope2);
|
||||
$this->assertSame([$envelope1, $envelope2], $this->transport->get());
|
||||
$this->transport->ack($envelope1->with(new AnEnvelopeStamp()));
|
||||
$this->assertSame([$envelope2], $this->transport->get());
|
||||
$this->transport->reject($envelope2->with(new AnEnvelopeStamp()));
|
||||
$this->assertSame([], $this->transport->get());
|
||||
}
|
||||
|
||||
public function testAck()
|
||||
{
|
||||
$envelope = new Envelope(new \stdClass());
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Messenger\Transport\AmqpExt;
|
||||
|
||||
use Symfony\Component\Messenger\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Messenger\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* An AMQP connection.
|
||||
@ -58,6 +59,10 @@ class Connection
|
||||
|
||||
public function __construct(array $connectionOptions, array $exchangeOptions, array $queuesOptions, AmqpFactory $amqpFactory = null)
|
||||
{
|
||||
if (!\extension_loaded('amqp')) {
|
||||
throw new LogicException(sprintf('You cannot use the "%s" as the "amqp" extension is not installed.', __CLASS__));
|
||||
}
|
||||
|
||||
$this->connectionOptions = array_replace_recursive([
|
||||
'delay' => [
|
||||
'exchange_name' => 'delays',
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Messenger\Transport\Doctrine;
|
||||
|
||||
use Doctrine\Common\Persistence\ConnectionRegistry;
|
||||
use Symfony\Bridge\Doctrine\RegistryInterface;
|
||||
use Symfony\Component\Messenger\Exception\TransportException;
|
||||
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
||||
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
|
||||
@ -24,8 +25,12 @@ class DoctrineTransportFactory implements TransportFactoryInterface
|
||||
{
|
||||
private $registry;
|
||||
|
||||
public function __construct(ConnectionRegistry $registry)
|
||||
public function __construct($registry)
|
||||
{
|
||||
if (!$registry instanceof RegistryInterface && !$registry instanceof ConnectionRegistry) {
|
||||
throw new \TypeError(sprintf('Expected an instance of %s or %s, but got %s.', RegistryInterface::class, ConnectionRegistry::class, \is_object($registry) ? \get_class($registry) : \gettype($registry)));
|
||||
}
|
||||
|
||||
$this->registry = $registry;
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ class InMemoryTransport implements TransportInterface, ResetInterface
|
||||
public function ack(Envelope $envelope): void
|
||||
{
|
||||
$this->acknowledged[] = $envelope;
|
||||
$id = spl_object_hash($envelope);
|
||||
$id = spl_object_hash($envelope->getMessage());
|
||||
unset($this->queue[$id]);
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ class InMemoryTransport implements TransportInterface, ResetInterface
|
||||
public function reject(Envelope $envelope): void
|
||||
{
|
||||
$this->rejected[] = $envelope;
|
||||
$id = spl_object_hash($envelope);
|
||||
$id = spl_object_hash($envelope->getMessage());
|
||||
unset($this->queue[$id]);
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ class InMemoryTransport implements TransportInterface, ResetInterface
|
||||
public function send(Envelope $envelope): Envelope
|
||||
{
|
||||
$this->sent[] = $envelope;
|
||||
$id = spl_object_hash($envelope);
|
||||
$id = spl_object_hash($envelope->getMessage());
|
||||
$this->queue[$id] = $envelope;
|
||||
|
||||
return $envelope;
|
||||
|
@ -42,10 +42,15 @@ abstract class ObjectLoader extends Loader
|
||||
*/
|
||||
public function load($resource, string $type = null)
|
||||
{
|
||||
if (!preg_match('/^[^\:]+(?:::(?:[^\:]+))?$/', $resource)) {
|
||||
if (!preg_match('/^[^\:]+(?:::?(?:[^\:]+))?$/', $resource)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the %s route loader: use the format "object_id::method" or "object_id" if your object class has an "__invoke" method.', $resource, \is_string($type) ? '"'.$type.'"' : 'object'));
|
||||
}
|
||||
|
||||
if (1 === substr_count($resource, ':')) {
|
||||
$resource = str_replace(':', '::', $resource);
|
||||
@trigger_error(sprintf('Referencing object route loaders with a single colon is deprecated since Symfony 4.1. Use %s instead.', $resource), E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$parts = explode('::', $resource);
|
||||
$method = $parts[1] ?? '__invoke';
|
||||
|
||||
|
@ -144,7 +144,6 @@ class SwitchUserListener
|
||||
|
||||
try {
|
||||
$this->provider->loadUserByUsername($nonExistentUsername);
|
||||
throw new \LogicException('AuthenticationException expected');
|
||||
} catch (AuthenticationException $e) {
|
||||
}
|
||||
} catch (AuthenticationException $e) {
|
||||
|
@ -27,7 +27,7 @@ trait ClassResolverTrait
|
||||
*
|
||||
* @param object|string $value
|
||||
*
|
||||
* @throws InvalidArgumentException If the class does not exists
|
||||
* @throws InvalidArgumentException If the class does not exist
|
||||
*/
|
||||
private function getClass($value): string
|
||||
{
|
||||
|
@ -287,7 +287,7 @@ abstract class Constraint
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function __sleep(): array
|
||||
public function __sleep()
|
||||
{
|
||||
// Initialize "groups" option if it is not set
|
||||
$this->groups;
|
||||
|
@ -362,6 +362,10 @@
|
||||
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
|
||||
<target>このパスワードは漏洩している為使用できません。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="94">
|
||||
<source>This value should be between {{ min }} and {{ max }}.</source>
|
||||
<target>{{ min }}以上{{ max }}以下でなければなりません。</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
@ -177,7 +177,8 @@ abstract class ConstraintValidatorTestCase extends TestCase
|
||||
->willReturn($validator);
|
||||
$validator->expects($this->at(2 * $i + 1))
|
||||
->method('validate')
|
||||
->with($value, $this->logicalOr(null, [], $this->isInstanceOf('\Symfony\Component\Validator\Constraints\Valid')), $group);
|
||||
->with($value, $this->logicalOr(null, [], $this->isInstanceOf('\Symfony\Component\Validator\Constraints\Valid')), $group)
|
||||
->willReturn($validator);
|
||||
}
|
||||
|
||||
protected function expectValidateValueAt($i, $propertyPath, $value, $constraints, $group = null)
|
||||
@ -189,7 +190,8 @@ abstract class ConstraintValidatorTestCase extends TestCase
|
||||
->willReturn($contextualValidator);
|
||||
$contextualValidator->expects($this->at(2 * $i + 1))
|
||||
->method('validate')
|
||||
->with($value, $constraints, $group);
|
||||
->with($value, $constraints, $group)
|
||||
->willReturn($contextualValidator);
|
||||
}
|
||||
|
||||
protected function assertNoViolation()
|
||||
|
@ -51,7 +51,7 @@ final class MethodMarkingStore implements MarkingStoreInterface
|
||||
$method = 'get'.ucfirst($this->property);
|
||||
|
||||
if (!method_exists($subject, $method)) {
|
||||
throw new LogicException(sprintf('The method "%s::%s()" does not exists.', \get_class($subject), $method));
|
||||
throw new LogicException(sprintf('The method "%s::%s()" does not exist.', \get_class($subject), $method));
|
||||
}
|
||||
|
||||
$marking = $subject->{$method}();
|
||||
@ -81,7 +81,7 @@ final class MethodMarkingStore implements MarkingStoreInterface
|
||||
$method = 'set'.ucfirst($this->property);
|
||||
|
||||
if (!method_exists($subject, $method)) {
|
||||
throw new LogicException(sprintf('The method "%s::%s()" does not exists.', \get_class($subject), $method));
|
||||
throw new LogicException(sprintf('The method "%s::%s()" does not exist.', \get_class($subject), $method));
|
||||
}
|
||||
|
||||
$subject->{$method}($marking, $context);
|
||||
|
@ -5,6 +5,7 @@ namespace Symfony\Component\Workflow\Tests;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\Workflow\Event\GuardEvent;
|
||||
use Symfony\Component\Workflow\Exception\NotEnabledTransitionException;
|
||||
use Symfony\Component\Workflow\StateMachine;
|
||||
use Symfony\Component\Workflow\TransitionBlocker;
|
||||
|
||||
@ -84,27 +85,52 @@ class StateMachineTest extends TestCase
|
||||
$subject = new Subject();
|
||||
|
||||
// There may be multiple transitions with the same name. Make sure that transitions
|
||||
// that are not enabled by the marking are evaluated.
|
||||
// that are enabled by the marking are evaluated.
|
||||
// see https://github.com/symfony/symfony/issues/28432
|
||||
|
||||
// Test if when you are in place "a"trying transition "t1" then returned
|
||||
// Test if when you are in place "a" and trying to apply "t1" then it returns
|
||||
// blocker list contains guard blocker instead blockedByMarking
|
||||
$subject->setMarking('a');
|
||||
$transitionBlockerList = $net->buildTransitionBlockerList($subject, 't1');
|
||||
$this->assertCount(1, $transitionBlockerList);
|
||||
$blockers = iterator_to_array($transitionBlockerList);
|
||||
|
||||
$this->assertSame('Transition blocker of place a', $blockers[0]->getMessage());
|
||||
$this->assertSame('blocker', $blockers[0]->getCode());
|
||||
|
||||
// Test if when you are in place "d" trying transition "t1" then
|
||||
// returned blocker list contains guard blocker instead blockedByMarking
|
||||
// Test if when you are in place "d" and trying to apply "t1" then
|
||||
// it returns blocker list contains guard blocker instead blockedByMarking
|
||||
$subject->setMarking('d');
|
||||
$transitionBlockerList = $net->buildTransitionBlockerList($subject, 't1');
|
||||
$this->assertCount(1, $transitionBlockerList);
|
||||
$blockers = iterator_to_array($transitionBlockerList);
|
||||
|
||||
$this->assertSame('Transition blocker of place d', $blockers[0]->getMessage());
|
||||
$this->assertSame('blocker', $blockers[0]->getCode());
|
||||
}
|
||||
|
||||
public function testApplyReturnsExpectedReasonOnBranchMerge()
|
||||
{
|
||||
$definition = $this->createComplexStateMachineDefinition();
|
||||
|
||||
$dispatcher = new EventDispatcher();
|
||||
$net = new StateMachine($definition, null, $dispatcher);
|
||||
|
||||
$dispatcher->addListener('workflow.guard', function (GuardEvent $event) {
|
||||
$event->addTransitionBlocker(new TransitionBlocker(sprintf('Transition blocker of place %s', $event->getTransition()->getFroms()[0]), 'blocker'));
|
||||
});
|
||||
|
||||
$subject = new Subject();
|
||||
|
||||
// There may be multiple transitions with the same name. Make sure that all transitions
|
||||
// that are enabled by the marking are evaluated.
|
||||
// see https://github.com/symfony/symfony/issues/34489
|
||||
|
||||
try {
|
||||
$net->apply($subject, 't1');
|
||||
$this->fail();
|
||||
} catch (NotEnabledTransitionException $e) {
|
||||
$blockers = iterator_to_array($e->getTransitionBlockerList());
|
||||
$this->assertSame('Transition blocker of place a', $blockers[0]->getMessage());
|
||||
$this->assertSame('blocker', $blockers[0]->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,25 +154,47 @@ class Workflow implements WorkflowInterface
|
||||
{
|
||||
$marking = $this->getMarking($subject);
|
||||
|
||||
$transitionBlockerList = null;
|
||||
$applied = false;
|
||||
$approvedTransitionQueue = [];
|
||||
$transitionExist = false;
|
||||
$approvedTransitions = [];
|
||||
$bestTransitionBlockerList = null;
|
||||
|
||||
foreach ($this->definition->getTransitions() as $transition) {
|
||||
if ($transition->getName() !== $transitionName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$transitionBlockerList = $this->buildTransitionBlockerListForTransition($subject, $marking, $transition);
|
||||
if (!$transitionBlockerList->isEmpty()) {
|
||||
$transitionExist = true;
|
||||
|
||||
$tmpTransitionBlockerList = $this->buildTransitionBlockerListForTransition($subject, $marking, $transition);
|
||||
|
||||
if ($tmpTransitionBlockerList->isEmpty()) {
|
||||
$approvedTransitions[] = $transition;
|
||||
continue;
|
||||
}
|
||||
$approvedTransitionQueue[] = $transition;
|
||||
|
||||
if (!$bestTransitionBlockerList) {
|
||||
$bestTransitionBlockerList = $tmpTransitionBlockerList;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We prefer to return transitions blocker by something else than
|
||||
// marking. Because it means the marking was OK. Transitions are
|
||||
// deterministic: it's not possible to have many transitions enabled
|
||||
// at the same time that match the same marking with the same name
|
||||
if (!$tmpTransitionBlockerList->has(TransitionBlocker::BLOCKED_BY_MARKING)) {
|
||||
$bestTransitionBlockerList = $tmpTransitionBlockerList;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($approvedTransitionQueue as $transition) {
|
||||
$applied = true;
|
||||
if (!$transitionExist) {
|
||||
throw new UndefinedTransitionException($subject, $transitionName, $this);
|
||||
}
|
||||
|
||||
if (!$approvedTransitions) {
|
||||
throw new NotEnabledTransitionException($subject, $transitionName, $this, $bestTransitionBlockerList);
|
||||
}
|
||||
|
||||
foreach ($approvedTransitions as $transition) {
|
||||
$this->leave($subject, $transition, $marking);
|
||||
|
||||
$context = $this->transition($subject, $transition, $marking, $context);
|
||||
@ -188,14 +210,6 @@ class Workflow implements WorkflowInterface
|
||||
$this->announce($subject, $transition, $marking);
|
||||
}
|
||||
|
||||
if (!$transitionBlockerList) {
|
||||
throw new UndefinedTransitionException($subject, $transitionName, $this);
|
||||
}
|
||||
|
||||
if (!$applied) {
|
||||
throw new NotEnabledTransitionException($subject, $transitionName, $this, $transitionBlockerList);
|
||||
}
|
||||
|
||||
return $marking;
|
||||
}
|
||||
|
||||
|
@ -155,6 +155,27 @@ switch ($vars['REQUEST_URI']) {
|
||||
usleep(500);
|
||||
}
|
||||
exit;
|
||||
|
||||
case '/json':
|
||||
header("Content-Type: application/json");
|
||||
echo json_encode([
|
||||
'documents' => [
|
||||
['id' => '/json/1'],
|
||||
['id' => '/json/2'],
|
||||
['id' => '/json/3'],
|
||||
],
|
||||
]);
|
||||
exit;
|
||||
|
||||
case '/json/1':
|
||||
case '/json/2':
|
||||
case '/json/3':
|
||||
header("Content-Type: application/json");
|
||||
echo json_encode([
|
||||
'title' => $vars['REQUEST_URI'],
|
||||
]);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json', true);
|
||||
|
@ -24,8 +24,6 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
*/
|
||||
abstract class HttpClientTestCase extends TestCase
|
||||
{
|
||||
private static $server;
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
TestHttpServer::start();
|
||||
|
@ -19,31 +19,22 @@ use Symfony\Component\Process\Process;
|
||||
*/
|
||||
class TestHttpServer
|
||||
{
|
||||
private static $server;
|
||||
private static $started;
|
||||
|
||||
public static function start()
|
||||
{
|
||||
if (null !== self::$server) {
|
||||
if (self::$started) {
|
||||
return;
|
||||
}
|
||||
|
||||
$finder = new PhpExecutableFinder();
|
||||
$process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', '127.0.0.1:8057']));
|
||||
$process->setWorkingDirectory(__DIR__.'/Fixtures/web');
|
||||
$process->setTimeout(300);
|
||||
$process->start();
|
||||
|
||||
self::$server = new class() {
|
||||
public $process;
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->process->stop();
|
||||
}
|
||||
};
|
||||
|
||||
self::$server->process = $process;
|
||||
|
||||
register_shutdown_function([$process, 'stop']);
|
||||
sleep('\\' === \DIRECTORY_SEPARATOR ? 10 : 1);
|
||||
|
||||
self::$started = true;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user