diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 0e8c7cc123143..5b77bf4d6539b 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -33,6 +33,7 @@ jobs: mode: low-deps - php: '8.3' - php: '8.4' + - php: '8.5' #mode: experimental fail-fast: false diff --git a/CHANGELOG-7.2.md b/CHANGELOG-7.2.md index 0e61bef1eaa74..11ad8ff6b9959 100644 --- a/CHANGELOG-7.2.md +++ b/CHANGELOG-7.2.md @@ -7,6 +7,18 @@ in 7.2 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/v7.2.0...v7.2.1 +* 7.2.1 (2024-12-11) + + * bug #59145 [TypeInfo] Make `Type::nullable` method no-op on every nullable type (mtarld) + * bug #59122 [Notifier] fix desktop channel bus abstract arg (raphael-geffroy) + * bug #59124 [FrameworkBundle] fix: notifier push channel bus abstract arg (raphael-geffroy) + * bug #59069 [Console] Fix division by 0 error (Rindula) + * bug #59086 [FrameworkBundle] Make uri_signer lazy and improve error when kernel.secret is empty (nicolas-grekas) + * bug #59099 [sendgrid-mailer] Fix null check on region (AUDUL) + * bug #59070 [PropertyInfo] evaluate access flags for properties with asymmetric visibility (xabbuh) + * bug #59062 [HttpClient] Always set CURLOPT_CUSTOMREQUEST to the correct HTTP method in CurlHttpClient (KurtThiemann) + * bug #59059 [TwigBridge] generate conflict-free variable names (xabbuh) + * 7.2.0 (2024-11-29) * bug #59023 [HttpClient] Fix streaming and redirecting with NoPrivateNetworkHttpClient (nicolas-grekas) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociatedEntityDto.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociatedEntityDto.php new file mode 100644 index 0000000000000..d6f82f8214846 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociatedEntityDto.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +class AssociatedEntityDto +{ + public $singleId; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderPostgresTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderPostgresTest.php index e0c897ce23232..53cbbb07a211c 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderPostgresTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderPostgresTest.php @@ -10,6 +10,7 @@ /** * @requires extension pdo_pgsql + * * @group integration */ class DoctrineTokenProviderPostgresTest extends DoctrineTokenProviderTest diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 451046f2ec150..c5a5592185085 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -21,6 +21,7 @@ use Doctrine\Persistence\ObjectManager; use PHPUnit\Framework\MockObject\MockObject; use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociatedEntityDto; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity2; use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; @@ -609,6 +610,40 @@ public function testAssociatedEntityWithNull() $this->assertNoViolation(); } + public function testAssociatedEntityReferencedByPrimaryKey() + { + $this->registry = $this->createRegistryMock($this->em); + $this->registry->expects($this->any()) + ->method('getManagerForClass') + ->willReturn($this->em); + $this->validator = $this->createValidator(); + $this->validator->initialize($this->context); + + $entity = new SingleIntIdEntity(1, 'foo'); + $associated = new AssociationEntity(); + $associated->single = $entity; + + $this->em->persist($entity); + $this->em->persist($associated); + $this->em->flush(); + + $dto = new AssociatedEntityDto(); + $dto->singleId = 1; + + $this->validator->validate($dto, new UniqueEntity( + fields: ['singleId' => 'single'], + entityClass: AssociationEntity::class, + )); + + $this->buildViolation('This value is already used.') + ->atPath('property.path.single') + ->setParameter('{{ value }}', 1) + ->setInvalidValue(1) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) + ->setCause([$associated]) + ->assertRaised(); + } + public function testValidateUniquenessWithArrayValue() { $repository = $this->createRepositoryMock(SingleIntIdEntity::class); diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 9a0ecc9d97781..3b8196fae410e 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -21,7 +21,8 @@ use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; use Twig\Node\Expression\NameExpression; -use Twig\Node\Expression\Variable\LocalVariable; +use Twig\Node\Expression\Variable\AssignContextVariable; +use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\ModuleNode; use Twig\Node\Node; use Twig\Node\Nodes; @@ -34,7 +35,6 @@ final class TranslationDefaultDomainNodeVisitor implements NodeVisitorInterface { private Scope $scope; - private int $nestingLevel = 0; public function __construct() { @@ -48,22 +48,25 @@ public function enterNode(Node $node, Environment $env): Node } if ($node instanceof TransDefaultDomainNode) { - ++$this->nestingLevel; - if ($node->getNode('expr') instanceof ConstantExpression) { $this->scope->set('domain', $node->getNode('expr')); return $node; } + if (null === $templateName = $node->getTemplateName()) { + throw new \LogicException('Cannot traverse a node without a template name.'); + } + + $var = '__internal_trans_default_domain'.hash('xxh128', $templateName); + if (class_exists(Nodes::class)) { - $name = new LocalVariable(null, $node->getTemplateLine()); - $this->scope->set('domain', $name); + $name = new AssignContextVariable($var, $node->getTemplateLine()); + $this->scope->set('domain', new ContextVariable($var, $node->getTemplateLine())); return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine()); } - $var = '__internal_trans_default_domain_'.$this->nestingLevel; $name = new AssignNameExpression($var, $node->getTemplateLine()); $this->scope->set('domain', new NameExpression($var, $node->getTemplateLine())); @@ -105,8 +108,6 @@ public function enterNode(Node $node, Environment $env): Node public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof TransDefaultDomainNode) { - --$this->nestingLevel; - return null; } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php index 96f707cdfdf2c..f6dd5f623baee 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php @@ -206,6 +206,68 @@ public function testDefaultTranslationDomainWithNamedArguments() $this->assertEquals('foo (custom)foo (foo)foo (custom)foo (custom)foo (fr)foo (custom)foo (fr)', trim($template->render([]))); } + public function testDefaultTranslationDomainWithExpression() + { + $templates = [ + 'index' => ' + {%- extends "base" %} + + {%- trans_default_domain custom_domain %} + + {%- block content %} + {{- "foo"|trans }} + {%- endblock %} + ', + + 'base' => ' + {%- block content "" %} + ', + ]; + + $translator = new Translator('en'); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', ['foo' => 'foo (messages)'], 'en'); + $translator->addResource('array', ['foo' => 'foo (custom)'], 'en', 'custom'); + $translator->addResource('array', ['foo' => 'foo (foo)'], 'en', 'foo'); + + $template = $this->getTemplate($templates, $translator); + + $this->assertEquals('foo (foo)', trim($template->render(['custom_domain' => 'foo']))); + } + + public function testDefaultTranslationDomainWithExpressionAndInheritance() + { + $templates = [ + 'index' => ' + {%- extends "base" %} + + {%- trans_default_domain foo_domain %} + + {%- block content %} + {{- "foo"|trans }} + {%- endblock %} + ', + + 'base' => ' + {%- trans_default_domain custom_domain %} + + {{- "foo"|trans }} + {%- block content "" %} + {{- "foo"|trans }} + ', + ]; + + $translator = new Translator('en'); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', ['foo' => 'foo (messages)'], 'en'); + $translator->addResource('array', ['foo' => 'foo (custom)'], 'en', 'custom'); + $translator->addResource('array', ['foo' => 'foo (foo)'], 'en', 'foo'); + + $template = $this->getTemplate($templates, $translator); + + $this->assertEquals('foo (custom)foo (foo)foo (custom)', trim($template->render(['foo_domain' => 'foo', 'custom_domain' => 'custom']))); + } + private function getTemplate($template, ?TranslatorInterface $translator = null): TemplateWrapper { $translator ??= new Translator('en'); diff --git a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php index dc419f5dad227..ab9113acf5c57 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -21,6 +21,7 @@ use Twig\Node\Expression\ConditionalExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\Ternary\ConditionalTernary; use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\Node; use Twig\Node\Nodes; @@ -275,32 +276,32 @@ public function testCompileLabelWithLabelAndAttributes() public function testCompileLabelWithLabelThatEvaluatesToNull() { + if (class_exists(ConditionalTernary::class)) { + $conditional = new ConditionalTernary( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 + ); + } else { + $conditional = new ConditionalExpression( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 + ); + } + if (class_exists(Nodes::class)) { - $arguments = new Nodes([ - new ContextVariable('form', 0), - new ConditionalExpression( - // if - new ConstantExpression(true, 0), - // then - new ConstantExpression(null, 0), - // else - new ConstantExpression(null, 0), - 0 - ), - ]); + $arguments = new Nodes([new ContextVariable('form', 0), $conditional]); } else { - $arguments = new Node([ - new NameExpression('form', 0), - new ConditionalExpression( - // if - new ConstantExpression(true, 0), - // then - new ConstantExpression(null, 0), - // else - new ConstantExpression(null, 0), - 0 - ), - ]); + $arguments = new Node([new NameExpression('form', 0), $conditional]); } $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); @@ -322,18 +323,32 @@ public function testCompileLabelWithLabelThatEvaluatesToNull() public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() { + if (class_exists(ConditionalTernary::class)) { + $conditional = new ConditionalTernary( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 + ); + } else { + $conditional = new ConditionalExpression( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 + ); + } + if (class_exists(Nodes::class)) { $arguments = new Nodes([ new ContextVariable('form', 0), - new ConditionalExpression( - // if - new ConstantExpression(true, 0), - // then - new ConstantExpression(null, 0), - // else - new ConstantExpression(null, 0), - 0 - ), + $conditional, new ArrayExpression([ new ConstantExpression('foo', 0), new ConstantExpression('bar', 0), @@ -344,12 +359,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() } else { $arguments = new Node([ new NameExpression('form', 0), - new ConditionalExpression( - new ConstantExpression(true, 0), - new ConstantExpression(null, 0), - new ConstantExpression(null, 0), - 0 - ), + $conditional, new ArrayExpression([ new ConstantExpression('foo', 0), new ConstantExpression('bar', 0), diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php index f4281cd21eef8..4dc86130a8cc5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php @@ -86,7 +86,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ['Timezone', date_default_timezone_get().' ('.(new \DateTimeImmutable())->format(\DateTimeInterface::W3C).')'], ['OPcache', \extension_loaded('Zend OPcache') ? (filter_var(\ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN) ? 'Enabled' : 'Not enabled') : 'Not installed'], ['APCu', \extension_loaded('apcu') ? (filter_var(\ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? 'Enabled' : 'Not enabled') : 'Not installed'], - ['Xdebug', \extension_loaded('xdebug') ? ($xdebugMode && $xdebugMode !== 'off' ? 'Enabled (' . $xdebugMode . ')' : 'Not enabled') : 'Not installed'], + ['Xdebug', \extension_loaded('xdebug') ? ($xdebugMode && 'off' !== $xdebugMode ? 'Enabled (' . $xdebugMode . ')' : 'Not enabled') : 'Not installed'], ]; $io->table([], $rows); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 26cae1f306c8f..a6d5956033d5e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -310,10 +310,17 @@ public function load(array $configs, ContainerBuilder $container): void } } + $emptySecretHint = '"framework.secret" option'; if (isset($config['secret'])) { $container->setParameter('kernel.secret', $config['secret']); + $usedEnvs = []; + $container->resolveEnvPlaceholders($config['secret'], null, $usedEnvs); + + if ($usedEnvs) { + $emptySecretHint = \sprintf('"%s" env var%s', implode('", "', $usedEnvs), 1 === \count($usedEnvs) ? '' : 's'); + } } - $container->parameterCannotBeEmpty('kernel.secret', 'A non-empty value for the parameter "kernel.secret" is required. Did you forget to configure the "framework.secret" option?'); + $container->parameterCannotBeEmpty('kernel.secret', 'A non-empty value for the parameter "kernel.secret" is required. Did you forget to configure the '.$emptySecretHint.'?'); $container->setParameter('kernel.http_method_override', $config['http_method_override']); $container->setParameter('kernel.trust_x_sendfile_type_header', $config['trust_x_sendfile_type_header']); @@ -2777,7 +2784,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ $container->removeDefinition('notifier.channel.email'); } - foreach (['texter', 'chatter', 'notifier.channel.chat', 'notifier.channel.email', 'notifier.channel.sms'] as $serviceId) { + foreach (['texter', 'chatter', 'notifier.channel.chat', 'notifier.channel.email', 'notifier.channel.sms', 'notifier.channel.push', 'notifier.channel.desktop'] as $serviceId) { if (!$container->hasDefinition($serviceId)) { continue; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php index 04fa4ce510a2b..28900ad10d7bd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php @@ -75,11 +75,17 @@ ->tag('notifier.channel', ['channel' => 'email']) ->set('notifier.channel.push', PushChannel::class) - ->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->args([ + service('texter.transports'), + abstract_arg('message bus'), + ]) ->tag('notifier.channel', ['channel' => 'push']) ->set('notifier.channel.desktop', DesktopChannel::class) - ->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->args([ + service('texter.transports'), + abstract_arg('message bus'), + ]) ->tag('notifier.channel', ['channel' => 'desktop']) ->set('notifier.monolog_handler', NotifierHandler::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php index 53856f356d056..6abe1b6d8f1a2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php @@ -157,6 +157,7 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] ->args([ new Parameter('kernel.secret'), ]) + ->lazy() ->alias(UriSigner::class, 'uri_signer') ->set('config_cache_factory', ResourceCheckerConfigCacheFactory::class) diff --git a/src/Symfony/Component/Cache/Traits/Relay/MoveTrait.php b/src/Symfony/Component/Cache/Traits/Relay/MoveTrait.php index 1f1b84c009399..18086f61d68c5 100644 --- a/src/Symfony/Component/Cache/Traits/Relay/MoveTrait.php +++ b/src/Symfony/Component/Cache/Traits/Relay/MoveTrait.php @@ -33,12 +33,12 @@ public function lmove($srckey, $dstkey, $srcpos, $dstpos): mixed */ trait MoveTrait { - public function blmove($srckey, $dstkey, $srcpos, $dstpos, $timeout): \Relay\Relay|false|null|string + public function blmove($srckey, $dstkey, $srcpos, $dstpos, $timeout): \Relay\Relay|false|string|null { return $this->initializeLazyObject()->blmove(...\func_get_args()); } - public function lmove($srckey, $dstkey, $srcpos, $dstpos): \Relay\Relay|false|null|string + public function lmove($srckey, $dstkey, $srcpos, $dstpos): \Relay\Relay|false|string|null { return $this->initializeLazyObject()->lmove(...\func_get_args()); } diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index 2dac1dfa237b7..49e34d8d8c9e8 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -229,7 +229,7 @@ public function getEstimated(): float public function getRemaining(): float { - if (!$this->step) { + if (0 === $this->step || $this->step === $this->startingStep) { return 0; } diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 4693535051124..4f6e6cb96cf32 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -79,6 +79,7 @@ protected function tearDown(): void pcntl_signal(\SIGTERM, \SIG_DFL); pcntl_signal(\SIGUSR1, \SIG_DFL); pcntl_signal(\SIGUSR2, \SIG_DFL); + pcntl_signal(\SIGALRM, \SIG_DFL); } } diff --git a/src/Symfony/Component/Console/Tests/ConsoleEventsTest.php b/src/Symfony/Component/Console/Tests/ConsoleEventsTest.php index 9c04d2706e8da..408f8c0d35c58 100644 --- a/src/Symfony/Component/Console/Tests/ConsoleEventsTest.php +++ b/src/Symfony/Component/Console/Tests/ConsoleEventsTest.php @@ -39,6 +39,7 @@ protected function tearDown(): void pcntl_signal(\SIGTERM, \SIG_DFL); pcntl_signal(\SIGUSR1, \SIG_DFL); pcntl_signal(\SIGUSR2, \SIG_DFL); + pcntl_signal(\SIGALRM, \SIG_DFL); } } diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index 88b3e9228a2f2..3d1bfa48fce27 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -110,6 +110,16 @@ public function testRegularTimeEstimation() ); } + public function testRegularTimeRemainingWithDifferentStartAtAndCustomDisplay() + { + $this->expectNotToPerformAssertions(); + + ProgressBar::setFormatDefinition('custom', ' %current%/%max% [%bar%] %percent:3s%% %remaining% %estimated%'); + $bar = new ProgressBar($this->getOutputStream(), 1_200, 0); + $bar->setFormat('custom'); + $bar->start(1_200, 600); + } + public function testResumedTimeEstimation() { $bar = new ProgressBar($output = $this->getOutputStream(), 1_200, 0); diff --git a/src/Symfony/Component/Console/Tests/SignalRegistry/SignalRegistryTest.php b/src/Symfony/Component/Console/Tests/SignalRegistry/SignalRegistryTest.php index f997f7c1d8cee..92d500f9ee4e5 100644 --- a/src/Symfony/Component/Console/Tests/SignalRegistry/SignalRegistryTest.php +++ b/src/Symfony/Component/Console/Tests/SignalRegistry/SignalRegistryTest.php @@ -27,6 +27,7 @@ protected function tearDown(): void pcntl_signal(\SIGTERM, \SIG_DFL); pcntl_signal(\SIGUSR1, \SIG_DFL); pcntl_signal(\SIGUSR2, \SIG_DFL); + pcntl_signal(\SIGALRM, \SIG_DFL); } public function testOneCallbackForASignalSignalIsHandled() diff --git a/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt b/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt index cfb10d03dafdd..81becafd8e350 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt +++ b/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt @@ -24,7 +24,7 @@ var_dump([ $eHandler[0]->setExceptionHandler('print_r'); if (true) { - class Broken implements \JsonSerializable + class Broken implements \Iterator { } } @@ -37,14 +37,14 @@ array(1) { } object(Symfony\Component\ErrorHandler\Error\FatalError)#%d (%d) { ["message":protected]=> - string(186) "Error: Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)" + string(209) "Error: Class Symfony\Component\ErrorHandler\Broken contains 5 abstract methods and must therefore be declared abstract or implement the remaining methods (Iterator::current, Iterator::next, Iterator::key, ...)" %a ["error":"Symfony\Component\ErrorHandler\Error\FatalError":private]=> array(4) { ["type"]=> int(1) ["message"]=> - string(179) "Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)" + string(202) "Class Symfony\Component\ErrorHandler\Broken contains 5 abstract methods and must therefore be declared abstract or implement the remaining methods (Iterator::current, Iterator::next, Iterator::key, ...)" ["file"]=> string(%d) "%s" ["line"]=> diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index 88feb64caef81..67a6c1dddd5d8 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -197,13 +197,12 @@ public function request(string $method, string $url, array $options = []): Respo $curlopts[\CURLOPT_RESOLVE] = $resolve; } + $curlopts[\CURLOPT_CUSTOMREQUEST] = $method; if ('POST' === $method) { // Use CURLOPT_POST to have browser-like POST-to-GET redirects for 301, 302 and 303 $curlopts[\CURLOPT_POST] = true; } elseif ('HEAD' === $method) { $curlopts[\CURLOPT_NOBODY] = true; - } else { - $curlopts[\CURLOPT_CUSTOMREQUEST] = $method; } if ('\\' !== \DIRECTORY_SEPARATOR && $options['timeout'] < 1) { diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php index 55c424011de84..119e201b204e1 100644 --- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php +++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php @@ -316,7 +316,7 @@ private static function perform(ClientState $multi, ?array &$responses = null): } $multi->handlesActivity[$id][] = null; - $multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) || (curl_error($ch) === 'OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 0' && -1.0 === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) && \in_array('close', array_map('strtolower', $responses[$id]->headers['connection']), true)) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).\sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL))); + $multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) || ('OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 0' === curl_error($ch) && -1.0 === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) && \in_array('close', array_map('strtolower', $responses[$id]->headers['connection']), true)) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).\sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL))); } } finally { $multi->performing = false; diff --git a/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php index 1a30f16c1ff0e..a18b253203092 100644 --- a/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php @@ -17,6 +17,7 @@ /** * @requires extension curl + * * @group dns-sensitive */ class CurlHttpClientTest extends HttpClientTestCase diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php index 5ec2ae442e10f..d3efa896db8ac 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php @@ -656,4 +656,26 @@ public function testDefaultContentType() $this->assertSame(['abc' => 'def', 'content-type' => 'application/json', 'REQUEST_METHOD' => 'POST'], $response->toArray()); } + + public function testHeadRequestWithClosureBody() + { + $p = TestHttpServer::start(8067); + + try { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('HEAD', 'http://localhost:8057/head', [ + 'body' => fn () => '', + ]); + $headers = $response->getHeaders(); + } finally { + $p->stop(); + } + + $this->assertArrayHasKey('x-request-vars', $headers); + + $vars = json_decode($headers['x-request-vars'][0], true); + $this->assertIsArray($vars); + $this->assertSame('HEAD', $vars['REQUEST_METHOD']); + } } diff --git a/src/Symfony/Component/HttpClient/Tests/NoPrivateNetworkHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/NoPrivateNetworkHttpClientTest.php index 181b7f42be28d..c160dff77492a 100644 --- a/src/Symfony/Component/HttpClient/Tests/NoPrivateNetworkHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/NoPrivateNetworkHttpClientTest.php @@ -68,6 +68,7 @@ public static function getExcludeHostData(): iterable /** * @dataProvider getExcludeIpData + * * @group dns-sensitive */ public function testExcludeByIp(string $ipAddr, $subnets, bool $mustThrow) @@ -105,6 +106,7 @@ public function testExcludeByIp(string $ipAddr, $subnets, bool $mustThrow) /** * @dataProvider getExcludeHostData + * * @group dns-sensitive */ public function testExcludeByHost(string $ipAddr, $subnets, bool $mustThrow) diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json index f294e7a577902..7ca008fd01f13 100644 --- a/src/Symfony/Component/HttpClient/composer.json +++ b/src/Symfony/Component/HttpClient/composer.json @@ -25,7 +25,7 @@ "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "~3.4.3|^3.5.1", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", "symfony/service-contracts": "^2.5|^3" }, "require-dev": { diff --git a/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php index 747a495036d17..8713dcf1e55d9 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php @@ -57,7 +57,7 @@ public function collect(Request $request, Response $response, ?\Throwable $excep 'php_intl_locale' => class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', 'php_timezone' => date_default_timezone_get(), 'xdebug_enabled' => \extension_loaded('xdebug'), - 'xdebug_status' => \extension_loaded('xdebug') ? ($xdebugMode && $xdebugMode !== 'off' ? 'Enabled (' . $xdebugMode . ')' : 'Not enabled') : 'Not installed', + 'xdebug_status' => \extension_loaded('xdebug') ? ($xdebugMode && 'off' !== $xdebugMode ? 'Enabled (' . $xdebugMode . ')' : 'Not enabled') : 'Not installed', 'apcu_enabled' => \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL), 'apcu_status' => \extension_loaded('apcu') ? (filter_var(\ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? 'Enabled' : 'Not enabled') : 'Not installed', 'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL), diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index f37b506b2202c..82e6dbfbd0c79 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -73,11 +73,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '7.2.0'; - public const VERSION_ID = 70200; + public const VERSION = '7.2.1'; + public const VERSION_ID = 70201; public const MAJOR_VERSION = 7; public const MINOR_VERSION = 2; - public const RELEASE_VERSION = 0; + public const RELEASE_VERSION = 1; public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '07/2025'; diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridSmtpTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridSmtpTransportTest.php new file mode 100644 index 0000000000000..77e5135c55cc4 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridSmtpTransportTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Sendgrid\Tests\Transport; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridSmtpTransport; + +class SendgridSmtpTransportTest extends TestCase +{ + /** + * @dataProvider getTransportData + */ + public function testToString(SendgridSmtpTransport $transport, string $expected) + { + $this->assertSame($expected, (string) $transport); + } + + public static function getTransportData() + { + return [ + [ + new SendgridSmtpTransport('KEY'), + 'smtps://smtp.sendgrid.net', + ], + [ + new SendgridSmtpTransport('KEY', null, null, 'eu'), + 'smtps://smtp.eu.sendgrid.net', + ], + ]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridSmtpTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridSmtpTransport.php index d939359ff8bc0..c7b67685dab9f 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridSmtpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridSmtpTransport.php @@ -22,7 +22,7 @@ class SendgridSmtpTransport extends EsmtpTransport { public function __construct(#[\SensitiveParameter] string $key, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null, private ?string $region = null) { - parent::__construct('null' !== $region ? \sprintf('smtp.%s.sendgrid.net', $region) : 'smtp.sendgrid.net', 465, true, $dispatcher, $logger); + parent::__construct(null !== $region ? \sprintf('smtp.%s.sendgrid.net', $region) : 'smtp.sendgrid.net', 465, true, $dispatcher, $logger); $this->setUsername('apikey'); $this->setPassword($key); diff --git a/src/Symfony/Component/Messenger/Attribute/AsMessage.php b/src/Symfony/Component/Messenger/Attribute/AsMessage.php index bc60ec032bff9..ee46bd5491fe1 100644 --- a/src/Symfony/Component/Messenger/Attribute/AsMessage.php +++ b/src/Symfony/Component/Messenger/Attribute/AsMessage.php @@ -23,7 +23,7 @@ public function __construct( /** * Name of the transports to which the message should be routed. */ - public null|string|array $transport = null, + public string|array|null $transport = null, ) { } } diff --git a/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php b/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php index f2d6e27066356..4c7b6080bdf46 100644 --- a/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php +++ b/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php @@ -24,7 +24,7 @@ final class FormDataPart extends AbstractMultipartPart { /** - * @param array $fields + * @param array $fields */ public function __construct( private array $fields = [], diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index be1a471d9d53a..340d3684dd3e4 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -714,8 +714,12 @@ private function isAllowedProperty(string $class, string $property, bool $writeA return false; } - if (\PHP_VERSION_ID >= 80400 && ($reflectionProperty->isProtectedSet() || $reflectionProperty->isPrivateSet())) { - return false; + if (\PHP_VERSION_ID >= 80400 && $reflectionProperty->isProtectedSet()) { + return (bool) ($this->propertyReflectionFlags & \ReflectionProperty::IS_PROTECTED); + } + + if (\PHP_VERSION_ID >= 80400 && $reflectionProperty->isPrivateSet()) { + return (bool) ($this->propertyReflectionFlags & \ReflectionProperty::IS_PRIVATE); } if (\PHP_VERSION_ID >= 80400 &&$reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) { diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index 13e9db752b2f2..972091d3031f0 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -703,6 +703,51 @@ public function testAsymmetricVisibility() $this->assertFalse($this->extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate')); } + /** + * @requires PHP 8.4 + */ + public function testAsymmetricVisibilityAllowPublicOnly() + { + $extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC); + + $this->assertTrue($extractor->isReadable(AsymmetricVisibility::class, 'publicPrivate')); + $this->assertTrue($extractor->isReadable(AsymmetricVisibility::class, 'publicProtected')); + $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'protectedPrivate')); + $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicPrivate')); + $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicProtected')); + $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate')); + } + + /** + * @requires PHP 8.4 + */ + public function testAsymmetricVisibilityAllowProtectedOnly() + { + $extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PROTECTED); + + $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicPrivate')); + $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicProtected')); + $this->assertTrue($extractor->isReadable(AsymmetricVisibility::class, 'protectedPrivate')); + $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicPrivate')); + $this->assertTrue($extractor->isWritable(AsymmetricVisibility::class, 'publicProtected')); + $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate')); + } + + /** + * @requires PHP 8.4 + */ + public function testAsymmetricVisibilityAllowPrivateOnly() + { + $extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PRIVATE); + + $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicPrivate')); + $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicProtected')); + $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'protectedPrivate')); + $this->assertTrue($extractor->isWritable(AsymmetricVisibility::class, 'publicPrivate')); + $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicProtected')); + $this->assertTrue($extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate')); + } + /** * @requires PHP 8.4 */ @@ -718,6 +763,7 @@ public function testVirtualProperties() /** * @dataProvider provideAsymmetricVisibilityMutator + * * @requires PHP 8.4 */ public function testAsymmetricVisibilityMutator(string $property, string $readVisibility, string $writeVisibility) @@ -743,6 +789,7 @@ public static function provideAsymmetricVisibilityMutator(): iterable /** * @dataProvider provideVirtualPropertiesMutator + * * @requires PHP 8.4 */ public function testVirtualPropertiesMutator(string $property, string $readVisibility, string $writeVisibility) diff --git a/src/Symfony/Component/Scheduler/Tests/RecurringMessageTest.php b/src/Symfony/Component/Scheduler/Tests/RecurringMessageTest.php index d668b7d03b02b..1954d4f470402 100644 --- a/src/Symfony/Component/Scheduler/Tests/RecurringMessageTest.php +++ b/src/Symfony/Component/Scheduler/Tests/RecurringMessageTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Scheduler\Tests; use PHPUnit\Framework\TestCase; -use Random\Randomizer; use Symfony\Component\Scheduler\Exception\InvalidArgumentException; use Symfony\Component\Scheduler\RecurringMessage; @@ -22,13 +21,8 @@ public function testCanCreateHashedCronMessage() { $object = new DummyStringableMessage(); - if (class_exists(Randomizer::class)) { - $this->assertSame('30 0 * * *', (string) RecurringMessage::cron('#midnight', $object)->getTrigger()); - $this->assertSame('30 0 * * 3', (string) RecurringMessage::cron('#weekly', $object)->getTrigger()); - } else { - $this->assertSame('36 0 * * *', (string) RecurringMessage::cron('#midnight', $object)->getTrigger()); - $this->assertSame('36 0 * * 6', (string) RecurringMessage::cron('#weekly', $object)->getTrigger()); - } + $this->assertSame('30 0 * * *', (string) RecurringMessage::cron('#midnight', $object)->getTrigger()); + $this->assertSame('30 0 * * 3', (string) RecurringMessage::cron('#weekly', $object)->getTrigger()); } public function testHashedCronContextIsRequiredIfMessageIsNotStringable() diff --git a/src/Symfony/Component/Scheduler/Tests/Trigger/CronExpressionTriggerTest.php b/src/Symfony/Component/Scheduler/Tests/Trigger/CronExpressionTriggerTest.php index cf12a7ceccf52..a700372d4765c 100644 --- a/src/Symfony/Component/Scheduler/Tests/Trigger/CronExpressionTriggerTest.php +++ b/src/Symfony/Component/Scheduler/Tests/Trigger/CronExpressionTriggerTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Scheduler\Tests\Trigger; use PHPUnit\Framework\TestCase; -use Random\Randomizer; use Symfony\Component\Scheduler\Trigger\CronExpressionTrigger; class CronExpressionTriggerTest extends TestCase @@ -33,54 +32,28 @@ public function testHashedExpressionParsing(string $input, string $expected) public static function hashedExpressionProvider(): array { - if (class_exists(Randomizer::class)) { - return [ - ['# * * * *', '30 * * * *'], - ['# # * * *', '30 0 * * *'], - ['# # # * *', '30 0 25 * *'], - ['# # # # *', '30 0 25 10 *'], - ['# # # # #', '30 0 25 10 5'], - ['# # 1,15 1-11 *', '30 0 1,15 1-11 *'], - ['# # 1,15 * *', '30 0 1,15 * *'], - ['#hourly', '30 * * * *'], - ['#daily', '30 0 * * *'], - ['#weekly', '30 0 * * 3'], - ['#weekly@midnight', '30 0 * * 3'], - ['#monthly', '30 0 25 * *'], - ['#monthly@midnight', '30 0 25 * *'], - ['#yearly', '30 0 25 10 *'], - ['#yearly@midnight', '30 0 25 10 *'], - ['#annually', '30 0 25 10 *'], - ['#annually@midnight', '30 0 25 10 *'], - ['#midnight', '30 0 * * *'], - ['#(1-15) * * * *', '1 * * * *'], - ['#(1-15) * * * #(3-5)', '1 * * * 3'], - ['#(1-15) * # * #(3-5)', '1 * 17 * 5'], - ]; - } - return [ - ['# * * * *', '36 * * * *'], - ['# # * * *', '36 0 * * *'], - ['# # # * *', '36 0 14 * *'], - ['# # # # *', '36 0 14 3 *'], - ['# # # # #', '36 0 14 3 5'], - ['# # 1,15 1-11 *', '36 0 1,15 1-11 *'], - ['# # 1,15 * *', '36 0 1,15 * *'], - ['#hourly', '36 * * * *'], - ['#daily', '36 0 * * *'], - ['#weekly', '36 0 * * 6'], - ['#weekly@midnight', '36 0 * * 6'], - ['#monthly', '36 0 14 * *'], - ['#monthly@midnight', '36 0 14 * *'], - ['#yearly', '36 0 14 3 *'], - ['#yearly@midnight', '36 0 14 3 *'], - ['#annually', '36 0 14 3 *'], - ['#annually@midnight', '36 0 14 3 *'], - ['#midnight', '36 0 * * *'], - ['#(1-15) * * * *', '7 * * * *'], - ['#(1-15) * * * #(3-5)', '7 * * * 3'], - ['#(1-15) * # * #(3-5)', '7 * 1 * 5'], + ['# * * * *', '30 * * * *'], + ['# # * * *', '30 0 * * *'], + ['# # # * *', '30 0 25 * *'], + ['# # # # *', '30 0 25 10 *'], + ['# # # # #', '30 0 25 10 5'], + ['# # 1,15 1-11 *', '30 0 1,15 1-11 *'], + ['# # 1,15 * *', '30 0 1,15 * *'], + ['#hourly', '30 * * * *'], + ['#daily', '30 0 * * *'], + ['#weekly', '30 0 * * 3'], + ['#weekly@midnight', '30 0 * * 3'], + ['#monthly', '30 0 25 * *'], + ['#monthly@midnight', '30 0 25 * *'], + ['#yearly', '30 0 25 10 *'], + ['#yearly@midnight', '30 0 25 10 *'], + ['#annually', '30 0 25 10 *'], + ['#annually@midnight', '30 0 25 10 *'], + ['#midnight', '30 0 * * *'], + ['#(1-15) * * * *', '1 * * * *'], + ['#(1-15) * * * #(3-5)', '1 * * * 3'], + ['#(1-15) * # * #(3-5)', '1 * 17 * 5'], ]; } diff --git a/src/Symfony/Component/Security/Http/CHANGELOG.md b/src/Symfony/Component/Security/Http/CHANGELOG.md index 9cb40fc926f4a..e9985ad13192b 100644 --- a/src/Symfony/Component/Security/Http/CHANGELOG.md +++ b/src/Symfony/Component/Security/Http/CHANGELOG.md @@ -14,7 +14,7 @@ CHANGELOG * Add `#[IsCsrfTokenValid]` attribute * Add CAS 2.0 access token handler - * Make empty username or empty password on form login attempts return Bad Request (400) + * Make empty username or empty password on form login attempts throw `BadCredentialsException` 7.0 --- diff --git a/src/Symfony/Component/TypeInfo/README.md b/src/Symfony/Component/TypeInfo/README.md index b654e271a1c6b..d9b3a2e5bbf2f 100644 --- a/src/Symfony/Component/TypeInfo/README.md +++ b/src/Symfony/Component/TypeInfo/README.md @@ -15,6 +15,7 @@ composer require phpstan/phpdoc-parser # to support raw string resolving resolve('bool'); // returns a "bool" Type instance // Types can be instantiated thanks to static factories $type = Type::list(Type::nullable(Type::bool())); -// Type instances have several helper methods -$type->getBaseType(); // returns an "array" Type instance -$type->getCollectionKeyType(); // returns an "int" Type instance -$type->getCollectionValueType()->isNullable(); // returns true +// Type classes have their specific methods +Type::object(FooClass::class)->getClassName(); +Type::enum(FooEnum::class, Type::int())->getBackingType(); +Type::list(Type::int())->isList(); + +// Every type can be cast to string +(string) Type::generic(Type::object(Collection::class), Type::int()) // returns "Collection" + +// You can check that a type (or one of its wrapped/composed parts) is identified by one of some identifiers. +$type->isIdentifiedBy(Foo::class, Bar::class); +$type->isIdentifiedBy(TypeIdentifier::OBJECT); +$type->isIdentifiedBy('float'); + +// You can also check that a type satifies specific conditions +$type->isSatisfiedBy(fn (Type $type): bool => !$type->isNullable() && $type->isIdentifiedBy(TypeIdentifier::INT)); ``` Resources diff --git a/src/Symfony/Component/TypeInfo/Tests/TypeFactoryTest.php b/src/Symfony/Component/TypeInfo/Tests/TypeFactoryTest.php index 9e8796d09b3c4..60a0ded22c648 100644 --- a/src/Symfony/Component/TypeInfo/Tests/TypeFactoryTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/TypeFactoryTest.php @@ -195,6 +195,7 @@ public function testCreateNullable() { $this->assertEquals(new NullableType(new BuiltinType(TypeIdentifier::INT)), Type::nullable(Type::int())); $this->assertEquals(new NullableType(new BuiltinType(TypeIdentifier::INT)), Type::nullable(Type::nullable(Type::int()))); + $this->assertEquals(new BuiltinType(TypeIdentifier::MIXED), Type::nullable(Type::mixed())); $this->assertEquals( new NullableType(new UnionType(new BuiltinType(TypeIdentifier::INT), new BuiltinType(TypeIdentifier::STRING))), diff --git a/src/Symfony/Component/TypeInfo/Type/UnionType.php b/src/Symfony/Component/TypeInfo/Type/UnionType.php index fd11214fc306f..91597e2089e3b 100644 --- a/src/Symfony/Component/TypeInfo/Type/UnionType.php +++ b/src/Symfony/Component/TypeInfo/Type/UnionType.php @@ -45,7 +45,7 @@ public function __construct(Type ...$types) } if ($type instanceof BuiltinType) { - if ($type->getTypeIdentifier() === TypeIdentifier::NULL && !is_a(static::class, NullableType::class, allow_string: true)) { + if (TypeIdentifier::NULL === $type->getTypeIdentifier() && !is_a(static::class, NullableType::class, allow_string: true)) { throw new InvalidArgumentException(\sprintf('Cannot create union with "null", please use "%s" instead.', NullableType::class)); } diff --git a/src/Symfony/Component/TypeInfo/TypeFactoryTrait.php b/src/Symfony/Component/TypeInfo/TypeFactoryTrait.php index a1d7f8c43b461..d32a97276057c 100644 --- a/src/Symfony/Component/TypeInfo/TypeFactoryTrait.php +++ b/src/Symfony/Component/TypeInfo/TypeFactoryTrait.php @@ -330,11 +330,11 @@ public static function intersection(Type ...$types): IntersectionType * * @param T $type * - * @return ($type is NullableType ? T : NullableType) + * @return T|NullableType */ - public static function nullable(Type $type): NullableType + public static function nullable(Type $type): Type { - if ($type instanceof NullableType) { + if ($type->isNullable()) { return $type; } diff --git a/src/Symfony/Component/TypeInfo/TypeResolver/StringTypeResolver.php b/src/Symfony/Component/TypeInfo/TypeResolver/StringTypeResolver.php index 1a8cdfac570a4..4809e9b83bf7e 100644 --- a/src/Symfony/Component/TypeInfo/TypeResolver/StringTypeResolver.php +++ b/src/Symfony/Component/TypeInfo/TypeResolver/StringTypeResolver.php @@ -215,7 +215,7 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Typ }; } - if ($type instanceof BuiltinType && $type->getTypeIdentifier() !== TypeIdentifier::ARRAY && $type->getTypeIdentifier() !== TypeIdentifier::ITERABLE) { + if ($type instanceof BuiltinType && TypeIdentifier::ARRAY !== $type->getTypeIdentifier() && TypeIdentifier::ITERABLE !== $type->getTypeIdentifier()) { return $type; } diff --git a/src/Symfony/Component/TypeInfo/composer.json b/src/Symfony/Component/TypeInfo/composer.json index a10b6d8785d0e..a246dd94f45fd 100644 --- a/src/Symfony/Component/TypeInfo/composer.json +++ b/src/Symfony/Component/TypeInfo/composer.json @@ -29,12 +29,7 @@ "psr/container": "^1.1|^2.0" }, "require-dev": { - "phpstan/phpdoc-parser": "^1.0|^2.0", - "symfony/dependency-injection": "^6.4|^7.0" - }, - "conflict": { - "phpstan/phpdoc-parser": "<1.0", - "symfony/dependency-injection": "<6.4" + "phpstan/phpdoc-parser": "^1.0|^2.0" }, "autoload": { "psr-4": { "Symfony\\Component\\TypeInfo\\": "" }, diff --git a/src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php b/src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php index a75001785ae2d..59033d55a0350 100644 --- a/src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php +++ b/src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php @@ -42,6 +42,7 @@ exit; case '/head': + header('X-Request-Vars: '.json_encode($vars, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); header('Content-Length: '.strlen($json), true); break; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy