Merge branch '4.3' into 4.4
* 4.3: [FWBundle] Remove unused parameter [Intl] [Workflow] fixes English grammar typos [Filesystem] [Serializer] fixes English grammar typo [Messenger] Adding exception to amqp transport in case amqp ext is not installed [Monolog Bridge] Fixed accessing static property as non static. Improve Symfony description Add DateTimeZoneNormalizer into Dependency Injection [Messenger] Error when specified default bus is not among the configured [Validator] Add Japanese translation [Workflow] Apply the same logic of precedence between the apply() and the buildTransitionBlockerList() method Remove some unused methods parameters Avoid empty \"If-Modified-Since\" header in validation request [Security] Fix SwitchUser is broken when the User Provider always returns a valid user Fix error message according to the new regex compatibility with DoctrineBundle 2 [Validator] ConstraintValidatorTestCase: add missing return value to mocked validate method calls
This commit is contained in:
commit
cde2538849
@ -2,7 +2,7 @@
|
|||||||
<img src="https://symfony.com/logos/symfony_black_02.svg">
|
<img src="https://symfony.com/logos/symfony_black_02.svg">
|
||||||
</a></p>
|
</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
|
**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
|
BlaBlaCar.com and Spotify.com) and most of the [popular PHP projects][2] (including
|
||||||
Drupal and Magento).
|
Drupal and Magento).
|
||||||
|
@ -41,7 +41,7 @@ class ChromePhpHandler extends BaseChromePhpHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!preg_match(static::USER_AGENT_REGEX, $event->getRequest()->headers->get('User-Agent'))) {
|
if (!preg_match(static::USER_AGENT_REGEX, $event->getRequest()->headers->get('User-Agent'))) {
|
||||||
$this->sendHeaders = false;
|
self::$sendHeaders = false;
|
||||||
$this->headers = [];
|
$this->headers = [];
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -59,7 +59,7 @@ class ChromePhpHandler extends BaseChromePhpHandler
|
|||||||
*/
|
*/
|
||||||
protected function sendHeader($header, $content)
|
protected function sendHeader($header, $content)
|
||||||
{
|
{
|
||||||
if (!$this->sendHeaders) {
|
if (!self::$sendHeaders) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ use Symfony\Component\Asset\Package;
|
|||||||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
||||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||||
|
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||||
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||||
use Symfony\Component\Form\Form;
|
use Symfony\Component\Form\Form;
|
||||||
use Symfony\Component\HttpClient\HttpClient;
|
use Symfony\Component\HttpClient\HttpClient;
|
||||||
@ -1173,6 +1174,10 @@ class Configuration implements ConfigurationInterface
|
|||||||
->ifTrue(function ($v) { return isset($v['buses']) && \count($v['buses']) > 1 && null === $v['default_bus']; })
|
->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.')
|
->thenInvalid('You must specify the "default_bus" if you define more than one bus.')
|
||||||
->end()
|
->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()
|
->children()
|
||||||
->arrayNode('routing')
|
->arrayNode('routing')
|
||||||
->normalizeKeys(false)
|
->normalizeKeys(false)
|
||||||
|
@ -304,7 +304,7 @@ class FrameworkExtension extends Extension
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->messengerConfigEnabled = $this->isConfigEnabled($container, $config['messenger'])) {
|
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 {
|
} else {
|
||||||
$container->removeDefinition('console.command.messenger_consume_messages');
|
$container->removeDefinition('console.command.messenger_consume_messages');
|
||||||
$container->removeDefinition('console.command.messenger_debug');
|
$container->removeDefinition('console.command.messenger_debug');
|
||||||
@ -1696,7 +1696,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)) {
|
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".');
|
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" />
|
<tag name="serializer.normalizer" priority="-915" />
|
||||||
</service>
|
</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">
|
<service id="serializer.normalizer.dateinterval" class="Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer">
|
||||||
<!-- Run before serializer.normalizer.object -->
|
<!-- Run before serializer.normalizer.object -->
|
||||||
<tag name="serializer.normalizer" priority="-915" />
|
<tag name="serializer.normalizer" priority="-915" />
|
||||||
|
@ -329,6 +329,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()
|
protected static function getBundleDefaultConfig()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -121,7 +121,7 @@ EOF
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->displayLog($input, $output, $clientId, $record);
|
$this->displayLog($output, $clientId, $record);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -150,7 +150,7 @@ EOF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function displayLog(InputInterface $input, OutputInterface $output, int $clientId, array $record)
|
private function displayLog(OutputInterface $output, int $clientId, array $record)
|
||||||
{
|
{
|
||||||
if (isset($record['log_id'])) {
|
if (isset($record['log_id'])) {
|
||||||
$clientId = unpack('H*', $record['log_id'])[1];
|
$clientId = unpack('H*', $record['log_id'])[1];
|
||||||
|
@ -797,7 +797,7 @@ class FilesystemTest extends FilesystemTestCase
|
|||||||
$file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
|
$file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
|
||||||
$link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
|
$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->filesystem->symlink($file, $link);
|
||||||
|
|
||||||
$this->assertTrue(is_link($link));
|
$this->assertTrue(is_link($link));
|
||||||
|
@ -63,7 +63,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
|
|||||||
|
|
||||||
if (preg_match('/^([^a-zA-Z0-9_].*)?(.*[^a-zA-Z0-9_\-:].*)?$/D', $name, $matches)) {
|
if (preg_match('/^([^a-zA-Z0-9_].*)?(.*[^a-zA-Z0-9_\-:].*)?$/D', $name, $matches)) {
|
||||||
if (isset($matches[1])) {
|
if (isset($matches[1])) {
|
||||||
@trigger_error(sprintf('Using names for buttons that do not start with a lowercase letter, a digit, or an underscore is deprecated since Symfony 4.3 and will throw an exception in 5.0 ("%s" given).', $name), E_USER_DEPRECATED);
|
@trigger_error(sprintf('Using names for buttons that do not start with a letter, a digit, or an underscore is deprecated since Symfony 4.3 and will throw an exception in 5.0 ("%s" given).', $name), E_USER_DEPRECATED);
|
||||||
}
|
}
|
||||||
if (isset($matches[2])) {
|
if (isset($matches[2])) {
|
||||||
@trigger_error(sprintf('Using names for buttons that do not contain only letters, digits, underscores ("_"), hyphens ("-") and colons (":") ("%s" given) is deprecated since Symfony 4.3 and will throw an exception in 5.0.', $name), E_USER_DEPRECATED);
|
@trigger_error(sprintf('Using names for buttons that do not contain only letters, digits, underscores ("_"), hyphens ("-") and colons (":") ("%s" given) is deprecated since Symfony 4.3 and will throw an exception in 5.0.', $name), E_USER_DEPRECATED);
|
||||||
|
@ -377,7 +377,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add our cached last-modified validator
|
// 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.
|
// Add our cached etag validator to the environment.
|
||||||
// We keep the etags from the client to handle the case when the client
|
// We keep the etags from the client to handle the case when the client
|
||||||
|
@ -857,6 +857,7 @@ class HttpCacheTest extends HttpCacheTestCase
|
|||||||
public function testValidatesCachedResponsesWithETagAndNoFreshnessInformation()
|
public function testValidatesCachedResponsesWithETagAndNoFreshnessInformation()
|
||||||
{
|
{
|
||||||
$this->setNextResponse(200, [], 'Hello World', function ($request, $response) {
|
$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('Cache-Control', 'public');
|
||||||
$response->headers->set('ETag', '"12345"');
|
$response->headers->set('ETag', '"12345"');
|
||||||
if ($response->getETag() == $request->headers->get('IF_NONE_MATCH')) {
|
if ($response->getETag() == $request->headers->get('IF_NONE_MATCH')) {
|
||||||
|
@ -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
|
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
|
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
|
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
|
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
|
public static function getName(string $script, string $displayLocale = null): string
|
||||||
{
|
{
|
||||||
|
@ -28,6 +28,9 @@ class AmqpTransportFactoryTest extends TestCase
|
|||||||
$this->assertFalse($factory->supports('invalid-dsn', []));
|
$this->assertFalse($factory->supports('invalid-dsn', []));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires extension amqp
|
||||||
|
*/
|
||||||
public function testItCreatesTheTransport()
|
public function testItCreatesTheTransport()
|
||||||
{
|
{
|
||||||
$factory = new AmqpTransportFactory();
|
$factory = new AmqpTransportFactory();
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\Messenger\Transport\AmqpExt;
|
namespace Symfony\Component\Messenger\Transport\AmqpExt;
|
||||||
|
|
||||||
use Symfony\Component\Messenger\Exception\InvalidArgumentException;
|
use Symfony\Component\Messenger\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Messenger\Exception\LogicException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An AMQP connection.
|
* An AMQP connection.
|
||||||
@ -58,6 +59,10 @@ class Connection
|
|||||||
|
|
||||||
public function __construct(array $connectionOptions, array $exchangeOptions, array $queuesOptions, AmqpFactory $amqpFactory = null)
|
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([
|
$this->connectionOptions = array_replace_recursive([
|
||||||
'delay' => [
|
'delay' => [
|
||||||
'exchange_name' => 'delays',
|
'exchange_name' => 'delays',
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\Messenger\Transport\Doctrine;
|
namespace Symfony\Component\Messenger\Transport\Doctrine;
|
||||||
|
|
||||||
use Doctrine\Common\Persistence\ConnectionRegistry;
|
use Doctrine\Common\Persistence\ConnectionRegistry;
|
||||||
|
use Symfony\Bridge\Doctrine\RegistryInterface;
|
||||||
use Symfony\Component\Messenger\Exception\TransportException;
|
use Symfony\Component\Messenger\Exception\TransportException;
|
||||||
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
||||||
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
|
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
|
||||||
@ -24,8 +25,12 @@ class DoctrineTransportFactory implements TransportFactoryInterface
|
|||||||
{
|
{
|
||||||
private $registry;
|
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;
|
$this->registry = $registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +148,6 @@ class SwitchUserListener implements ListenerInterface
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$this->provider->loadUserByUsername($nonExistentUsername);
|
$this->provider->loadUserByUsername($nonExistentUsername);
|
||||||
throw new \LogicException('AuthenticationException expected');
|
|
||||||
} catch (AuthenticationException $e) {
|
} catch (AuthenticationException $e) {
|
||||||
}
|
}
|
||||||
} catch (AuthenticationException $e) {
|
} catch (AuthenticationException $e) {
|
||||||
|
@ -27,7 +27,7 @@ trait ClassResolverTrait
|
|||||||
*
|
*
|
||||||
* @param object|string $value
|
* @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
|
private function getClass($value): string
|
||||||
{
|
{
|
||||||
|
@ -362,6 +362,10 @@
|
|||||||
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
|
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
|
||||||
<target>このパスワードは漏洩している為使用できません。</target>
|
<target>このパスワードは漏洩している為使用できません。</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="94">
|
||||||
|
<source>This value should be between {{ min }} and {{ max }}.</source>
|
||||||
|
<target>{{ min }}以上{{ max }}以下でなければなりません。</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -179,7 +179,8 @@ abstract class ConstraintValidatorTestCase extends TestCase
|
|||||||
->willReturn($validator);
|
->willReturn($validator);
|
||||||
$validator->expects($this->at(2 * $i + 1))
|
$validator->expects($this->at(2 * $i + 1))
|
||||||
->method('validate')
|
->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)
|
protected function expectValidateValueAt($i, $propertyPath, $value, $constraints, $group = null)
|
||||||
@ -191,7 +192,8 @@ abstract class ConstraintValidatorTestCase extends TestCase
|
|||||||
->willReturn($contextualValidator);
|
->willReturn($contextualValidator);
|
||||||
$contextualValidator->expects($this->at(2 * $i + 1))
|
$contextualValidator->expects($this->at(2 * $i + 1))
|
||||||
->method('validate')
|
->method('validate')
|
||||||
->with($value, $constraints, $group);
|
->with($value, $constraints, $group)
|
||||||
|
->willReturn($contextualValidator);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function assertNoViolation()
|
protected function assertNoViolation()
|
||||||
|
@ -51,7 +51,7 @@ final class MethodMarkingStore implements MarkingStoreInterface
|
|||||||
$method = 'get'.ucfirst($this->property);
|
$method = 'get'.ucfirst($this->property);
|
||||||
|
|
||||||
if (!method_exists($subject, $method)) {
|
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}();
|
$marking = $subject->{$method}();
|
||||||
@ -81,7 +81,7 @@ final class MethodMarkingStore implements MarkingStoreInterface
|
|||||||
$method = 'set'.ucfirst($this->property);
|
$method = 'set'.ucfirst($this->property);
|
||||||
|
|
||||||
if (!method_exists($subject, $method)) {
|
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);
|
$subject->{$method}($marking, $context);
|
||||||
|
@ -5,6 +5,7 @@ namespace Symfony\Component\Workflow\Tests;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
use Symfony\Component\Workflow\Event\GuardEvent;
|
use Symfony\Component\Workflow\Event\GuardEvent;
|
||||||
|
use Symfony\Component\Workflow\Exception\NotEnabledTransitionException;
|
||||||
use Symfony\Component\Workflow\StateMachine;
|
use Symfony\Component\Workflow\StateMachine;
|
||||||
use Symfony\Component\Workflow\TransitionBlocker;
|
use Symfony\Component\Workflow\TransitionBlocker;
|
||||||
|
|
||||||
@ -84,27 +85,52 @@ class StateMachineTest extends TestCase
|
|||||||
$subject = new Subject();
|
$subject = new Subject();
|
||||||
|
|
||||||
// There may be multiple transitions with the same name. Make sure that transitions
|
// 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
|
// 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
|
// blocker list contains guard blocker instead blockedByMarking
|
||||||
$subject->setMarking('a');
|
$subject->setMarking('a');
|
||||||
$transitionBlockerList = $net->buildTransitionBlockerList($subject, 't1');
|
$transitionBlockerList = $net->buildTransitionBlockerList($subject, 't1');
|
||||||
$this->assertCount(1, $transitionBlockerList);
|
$this->assertCount(1, $transitionBlockerList);
|
||||||
$blockers = iterator_to_array($transitionBlockerList);
|
$blockers = iterator_to_array($transitionBlockerList);
|
||||||
|
|
||||||
$this->assertSame('Transition blocker of place a', $blockers[0]->getMessage());
|
$this->assertSame('Transition blocker of place a', $blockers[0]->getMessage());
|
||||||
$this->assertSame('blocker', $blockers[0]->getCode());
|
$this->assertSame('blocker', $blockers[0]->getCode());
|
||||||
|
|
||||||
// Test if when you are in place "d" trying transition "t1" then
|
// Test if when you are in place "d" and trying to apply "t1" then
|
||||||
// returned blocker list contains guard blocker instead blockedByMarking
|
// it returns blocker list contains guard blocker instead blockedByMarking
|
||||||
$subject->setMarking('d');
|
$subject->setMarking('d');
|
||||||
$transitionBlockerList = $net->buildTransitionBlockerList($subject, 't1');
|
$transitionBlockerList = $net->buildTransitionBlockerList($subject, 't1');
|
||||||
$this->assertCount(1, $transitionBlockerList);
|
$this->assertCount(1, $transitionBlockerList);
|
||||||
$blockers = iterator_to_array($transitionBlockerList);
|
$blockers = iterator_to_array($transitionBlockerList);
|
||||||
|
|
||||||
$this->assertSame('Transition blocker of place d', $blockers[0]->getMessage());
|
$this->assertSame('Transition blocker of place d', $blockers[0]->getMessage());
|
||||||
$this->assertSame('blocker', $blockers[0]->getCode());
|
$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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,25 +159,47 @@ class Workflow implements WorkflowInterface
|
|||||||
|
|
||||||
$marking = $this->getMarking($subject);
|
$marking = $this->getMarking($subject);
|
||||||
|
|
||||||
$transitionBlockerList = null;
|
$transitionExist = false;
|
||||||
$applied = false;
|
$approvedTransitions = [];
|
||||||
$approvedTransitionQueue = [];
|
$bestTransitionBlockerList = null;
|
||||||
|
|
||||||
foreach ($this->definition->getTransitions() as $transition) {
|
foreach ($this->definition->getTransitions() as $transition) {
|
||||||
if ($transition->getName() !== $transitionName) {
|
if ($transition->getName() !== $transitionName) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$transitionBlockerList = $this->buildTransitionBlockerListForTransition($subject, $marking, $transition);
|
$transitionExist = true;
|
||||||
if (!$transitionBlockerList->isEmpty()) {
|
|
||||||
|
$tmpTransitionBlockerList = $this->buildTransitionBlockerListForTransition($subject, $marking, $transition);
|
||||||
|
|
||||||
|
if ($tmpTransitionBlockerList->isEmpty()) {
|
||||||
|
$approvedTransitions[] = $transition;
|
||||||
continue;
|
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) {
|
if (!$transitionExist) {
|
||||||
$applied = true;
|
throw new UndefinedTransitionException($subject, $transitionName, $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$approvedTransitions) {
|
||||||
|
throw new NotEnabledTransitionException($subject, $transitionName, $this, $bestTransitionBlockerList);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($approvedTransitions as $transition) {
|
||||||
$this->leave($subject, $transition, $marking);
|
$this->leave($subject, $transition, $marking);
|
||||||
|
|
||||||
$context = $this->transition($subject, $transition, $marking, $context);
|
$context = $this->transition($subject, $transition, $marking, $context);
|
||||||
@ -193,14 +215,6 @@ class Workflow implements WorkflowInterface
|
|||||||
$this->announce($subject, $transition, $marking);
|
$this->announce($subject, $transition, $marking);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$transitionBlockerList) {
|
|
||||||
throw new UndefinedTransitionException($subject, $transitionName, $this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$applied) {
|
|
||||||
throw new NotEnabledTransitionException($subject, $transitionName, $this, $transitionBlockerList);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $marking;
|
return $marking;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user