diff --git a/.travis.yml b/.travis.yml index 73eec7a9ebd53..24155a5c0c36f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -151,6 +151,7 @@ before_install: INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini echo date.timezone = Europe/Paris >> $INI echo memory_limit = -1 >> $INI + echo default_socket_timeout = 10 >> $INI echo session.gc_probability = 0 >> $INI echo opcache.enable_cli = 1 >> $INI echo apc.enable_cli = 1 >> $INI diff --git a/CHANGELOG-5.1.md b/CHANGELOG-5.1.md index 5f1269de038e9..7d1409a1ec728 100644 --- a/CHANGELOG-5.1.md +++ b/CHANGELOG-5.1.md @@ -7,6 +7,23 @@ in 5.1 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/v5.1.0...v5.1.1 +* 5.1.0 (2020-05-31) + + * bug #37009 [Validator] use "allowedVariables" to configure the ExpressionLanguageSyntax constraint (xabbuh) + * bug #37008 [Security] Fixed AbstractToken::hasUserChanged() (wouterj) + * bug #36894 [Validator] never directly validate Existence (Required/Optional) constraints (xabbuh) + * bug #37007 [Console] Fix QuestionHelper::disableStty() (chalasr) + * bug #36865 [Form] validate subforms in all validation groups (xabbuh) + * bug #36907 Fixes sprintf(): Too few arguments in form transformer (pedrocasado) + * bug #36868 [Validator] Use Mime component to determine mime type for file validator (pierredup) + * bug #37000 Add meaningful message when using ProcessHelper and Process is not installed (l-vo) + * bug #36990 [Messenger] Change the default notify timeout value for PostgreSQL (fabpot) + * bug #36995 [TwigBridge] fix fallback html-to-txt body converter (nicolas-grekas) + * bug #36993 [ErrorHandler] fix setting $trace to null in FatalError (nicolas-grekas) + * bug #36987 Handle fetch mode deprecation of DBAL 2.11. (derrabus) + * bug #36984 [SecurityBundle] Fixed version constraint on security-core and security-guard (wouterj) + * bug #36974 [Security] Fixed handling of CSRF logout error (wouterj) + * 5.1.0-RC2 (2020-05-26) * bug #36966 Fix extra SQL support in Doctrine migrations (fabpot) diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index e58803b397c83..eda901d0541e0 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -63,7 +63,7 @@ public function loadTokenBySeries(string $series) $paramValues = ['series' => $series]; $paramTypes = ['series' => \PDO::PARAM_STR]; $stmt = $this->conn->executeQuery($sql, $paramValues, $paramTypes); - $row = $stmt->fetch(\PDO::FETCH_ASSOC); + $row = method_exists($stmt, 'fetchAssociative') ? $stmt->fetchAssociative() : $stmt->fetch(\PDO::FETCH_ASSOC); if ($row) { return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['last_used'])); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php new file mode 100644 index 0000000000000..df696ff7ff292 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php @@ -0,0 +1,88 @@ += 80000) { + self::markTestSkipped('Doctrine DBAL 2.x is incompatible with PHP 8.'); + } + } + + public function testCreateNewToken() + { + $provider = $this->bootstrapProvider(); + + $token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51')); + $provider->createNewToken($token); + + $this->assertEquals($provider->loadTokenBySeries('someSeries'), $token); + } + + public function testLoadTokenBySeriesThrowsNotFoundException() + { + $provider = $this->bootstrapProvider(); + + $this->expectException(TokenNotFoundException::class); + $provider->loadTokenBySeries('someSeries'); + } + + public function testUpdateToken() + { + $provider = $this->bootstrapProvider(); + + $token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51')); + $provider->createNewToken($token); + $provider->updateToken('someSeries', 'newValue', $lastUsed = new \DateTime('2014-06-26T22:03:46')); + $token = $provider->loadTokenBySeries('someSeries'); + + $this->assertEquals('newValue', $token->getTokenValue()); + $this->assertEquals($token->getLastUsed(), $lastUsed); + } + + public function testDeleteToken() + { + $provider = $this->bootstrapProvider(); + $token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51')); + $provider->createNewToken($token); + $provider->deleteTokenBySeries('someSeries'); + + $this->expectException(TokenNotFoundException::class); + + $provider->loadTokenBySeries('someSeries'); + } + + /** + * @return DoctrineTokenProvider + */ + private function bootstrapProvider() + { + $connection = DriverManager::getConnection([ + 'driver' => 'pdo_sqlite', + 'url' => 'sqlite:///:memory:', + ]); + $connection->executeUpdate(<<< 'SQL' + CREATE TABLE rememberme_token ( + series char(88) UNIQUE PRIMARY KEY NOT NULL, + value char(88) NOT NULL, + lastUsed datetime NOT NULL, + class varchar(100) NOT NULL, + username varchar(200) NOT NULL + ); +SQL + ); + + return new DoctrineTokenProvider($connection); + } +} diff --git a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php index eb1a4dc257677..f04db50c8ced7 100644 --- a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php +++ b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php @@ -102,7 +102,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!$socket = stream_socket_server($host, $errno, $errstr)) { - throw new RuntimeException(sprintf('Server start failed on "%s": '.$errstr.' '.$errno, $host)); + throw new RuntimeException(sprintf('Server start failed on "%s": ', $host).$errstr.' '.$errno); } foreach ($this->getLogs($socket) as $clientId => $message) { diff --git a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php index 14535f232a0a4..510cfcdd5b740 100644 --- a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php +++ b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php @@ -74,6 +74,6 @@ private function convertHtmlToText(string $html): string return $this->converter->convert($html); } - return strip_tags($html); + return strip_tags(preg_replace('{<(head|style)\b.*?}i', '', $html)); } } diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php index 6eeade3a737af..175a8e1978066 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php @@ -29,11 +29,12 @@ public function testRenderTextOnly(): void public function testRenderHtmlOnly(): void { - $email = $this->prepareEmail(null, 'HTML'); + $html = 'headHTML'; + $email = $this->prepareEmail(null, $html); $body = $email->getBody(); $this->assertInstanceOf(AlternativePart::class, $body); $this->assertEquals('HTML', $body->getParts()[0]->bodyToString()); - $this->assertEquals('HTML', $body->getParts()[1]->bodyToString()); + $this->assertEquals(str_replace('=', '=3D', $html), $body->getParts()[1]->bodyToString()); } public function testRenderHtmlOnlyWithTextSet(): void diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 3be54e720ac1a..c1603160dd0cd 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -23,9 +23,9 @@ "symfony/event-dispatcher": "^5.1", "symfony/http-kernel": "^5.0", "symfony/polyfill-php80": "^1.15", - "symfony/security-core": "^4.4|^5.0", + "symfony/security-core": "^5.1", "symfony/security-csrf": "^4.4|^5.0", - "symfony/security-guard": "^4.4|^5.0", + "symfony/security-guard": "^5.1", "symfony/security-http": "^5.1" }, "require-dev": { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.js b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.js index 6e693a9dc57c7..588a9d22ed350 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.js +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.js @@ -94,7 +94,7 @@ class TimelineEngine { createLabel(name, duration, memory, period) { const label = this.renderer.createText(name, period.start * this.scale, this.labelY, 'timeline-label'); - const sublabel = this.renderer.createTspan(` ${duration} ms / ${memory} Mb`, 'timeline-sublabel'); + const sublabel = this.renderer.createTspan(` ${duration} ms / ${memory} MiB`, 'timeline-sublabel'); label.appendChild(sublabel); diff --git a/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php index e48f6f22410a7..c4de580d0b514 100644 --- a/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php +++ b/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php @@ -59,7 +59,7 @@ private function getManifestPath(string $path): ?string $this->manifestData = json_decode(file_get_contents($this->manifestPath), true); if (0 < json_last_error()) { - throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": '.json_last_error_msg(), $this->manifestPath)); + throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": ', $this->manifestPath).json_last_error_msg()); } } diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php index f9cee34931a8b..a042ad8a2c1d2 100644 --- a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php @@ -219,7 +219,13 @@ protected function doFetch(array $ids) } $stmt->execute(); - while ($row = $stmt->fetch(\PDO::FETCH_NUM)) { + if (method_exists($stmt, 'iterateNumeric')) { + $stmt = $stmt->iterateNumeric(); + } else { + $stmt->setFetchMode(\PDO::FETCH_NUM); + } + + foreach ($stmt as $row) { if (null === $row[1]) { $expired[] = $row[0]; } else { @@ -251,7 +257,7 @@ protected function doHave(string $id) $stmt->bindValue(':time', time(), \PDO::PARAM_INT); $stmt->execute(); - return (bool) $stmt->fetchColumn(); + return (bool) (method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn()); } /** diff --git a/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php b/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php index d9c3b91568429..05a88e086c3cf 100644 --- a/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php +++ b/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php @@ -24,11 +24,11 @@ protected function isPruned($cache, string $name): bool $getPdoConn = $o->getMethod('getConnection'); $getPdoConn->setAccessible(true); - /** @var \Doctrine\DBAL\Statement $select */ + /** @var \Doctrine\DBAL\Statement|\PDOStatement $select */ $select = $getPdoConn->invoke($cache)->prepare('SELECT 1 FROM cache_items WHERE item_id LIKE :id'); $select->bindValue(':id', sprintf('%%%s', $name)); $select->execute(); - return 0 === \count($select->fetchAll(\PDO::FETCH_COLUMN)); + return 1 !== (int) (method_exists($select, 'fetchOne') ? $select->fetchOne() : $select->fetch(\PDO::FETCH_COLUMN)); } } diff --git a/src/Symfony/Component/Console/Helper/ProcessHelper.php b/src/Symfony/Component/Console/Helper/ProcessHelper.php index 01989681572aa..f82c16bae84a0 100644 --- a/src/Symfony/Component/Console/Helper/ProcessHelper.php +++ b/src/Symfony/Component/Console/Helper/ProcessHelper.php @@ -36,6 +36,10 @@ class ProcessHelper extends Helper */ public function run(OutputInterface $output, $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process { + if (!class_exists(Process::class)) { + throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".'); + } + if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index a4bd54a97e624..7dad15620632b 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -35,7 +35,7 @@ class QuestionHelper extends Helper { private $inputStream; private static $shell; - private static $stty; + private static $stty = true; /** * Asks a question to the user. @@ -109,7 +109,7 @@ private function doAsk(OutputInterface $output, Question $question) $inputStream = $this->inputStream ?: STDIN; $autocomplete = $question->getAutocompleterCallback(); - if (null === $autocomplete || !Terminal::hasSttyAvailable()) { + if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { $ret = false; if ($question->isHidden()) { try { @@ -416,7 +416,7 @@ private function getHiddenResponse(OutputInterface $output, $inputStream, bool $ return $value; } - if (Terminal::hasSttyAvailable()) { + if (self::$stty && Terminal::hasSttyAvailable()) { $sttyMode = shell_exec('stty -g'); shell_exec('stty -echo'); diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 8164f743bc390..9afad24357d07 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Console\Tests\Helper; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\FormatterHelper; use Symfony\Component\Console\Helper\HelperSet; @@ -783,6 +784,35 @@ public function testTraversableAutocomplete() $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } + public function testDisableSttby() + { + if (!Terminal::hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('invalid'); + + QuestionHelper::disableStty(); + $dialog = new QuestionHelper(); + $dialog->setHelperSet(new HelperSet([new FormatterHelper()])); + + $question = new ChoiceQuestion('Please select a bundle', [1 => 'AcmeDemoBundle', 4 => 'AsseticBundle']); + $question->setMaxAttempts(1); + + // + // Gives `AcmeDemoBundle` with stty + $inputStream = $this->getInputStream("\033[A\033[A\n\033[B\033[B\n"); + + try { + $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question); + } finally { + $reflection = new \ReflectionProperty(QuestionHelper::class, 'stty'); + $reflection->setAccessible(true); + $reflection->setValue(null, true); + } + } + public function testTraversableMultiselectAutocomplete() { // diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index eae626f0e6a5c..b16bad18dff4e 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -227,7 +227,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv) $env = json_decode($env, true); if (JSON_ERROR_NONE !== json_last_error()) { - throw new RuntimeException(sprintf('Invalid JSON in env var "%s": '.json_last_error_msg(), $name)); + throw new RuntimeException(sprintf('Invalid JSON in env var "%s": ', $name).json_last_error_msg()); } if (null !== $env && !\is_array($env)) { diff --git a/src/Symfony/Component/Dotenv/README.md b/src/Symfony/Component/Dotenv/README.md index 10bfff14ba78d..855b8c02a1df6 100644 --- a/src/Symfony/Component/Dotenv/README.md +++ b/src/Symfony/Component/Dotenv/README.md @@ -4,10 +4,32 @@ Dotenv Component Symfony Dotenv parses `.env` files to make environment variables stored in them accessible via `$_SERVER` or `$_ENV`. +Getting Started +--------------- + +``` +$ composer require symfony/dotenv +``` + +```php +use Symfony\Component\Dotenv\Dotenv; + +$dotenv = new Dotenv(); +$dotenv->load(__DIR__.'/.env'); + +// you can also load several files +$dotenv->load(__DIR__.'/.env', __DIR__.'/.env.dev'); + +// overwrites existing env variables +$dotenv->overload(__DIR__.'/.env'); + +// loads .env, .env.local, and .env.$APP_ENV.local or .env.$APP_ENV +$dotenv->loadEnv(__DIR__.'/.env'); +``` + Resources --------- - * [Documentation](https://symfony.com/doc/current/components/dotenv.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) diff --git a/src/Symfony/Component/ErrorHandler/Error/FatalError.php b/src/Symfony/Component/ErrorHandler/Error/FatalError.php index 68172d876cb51..98490b5accc41 100644 --- a/src/Symfony/Component/ErrorHandler/Error/FatalError.php +++ b/src/Symfony/Component/ErrorHandler/Error/FatalError.php @@ -72,9 +72,11 @@ public function __construct(string $message, int $code, array $error, int $trace 'line' => $error['line'], 'trace' => $trace, ] as $property => $value) { - $refl = new \ReflectionProperty(\Error::class, $property); - $refl->setAccessible(true); - $refl->setValue($this, $value); + if (null !== $value) { + $refl = new \ReflectionProperty(\Error::class, $property); + $refl->setAccessible(true); + $refl->setValue($this, $value); + } } } diff --git a/src/Symfony/Component/ErrorHandler/README.md b/src/Symfony/Component/ErrorHandler/README.md index 17e1cfd751d06..d14ccfd7b9c58 100644 --- a/src/Symfony/Component/ErrorHandler/README.md +++ b/src/Symfony/Component/ErrorHandler/README.md @@ -3,6 +3,35 @@ ErrorHandler Component The ErrorHandler component provides tools to manage errors and ease debugging PHP code. +Getting Started +--------------- + +``` +$ composer require symfony/error-handler +``` + +```php +use Symfony\Component\ErrorHandler\Debug; +use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Component\ErrorHandler\DebugClassLoader; + +Debug::enable(); + +// or enable only one feature +//ErrorHandler::register(); +//DebugClassLoader::enable(); + +$data = ErrorHandler::call(static function () use ($filename, $datetimeFormat) { + // if any code executed inside this anonymous function fails, a PHP exception + // will be thrown, even if the code uses the '@' PHP silence operator + $data = json_decode(file_get_contents($filename), true); + $data['read_at'] = date($datetimeFormat); + file_put_contents($filename, json_encode($data)); + + return $data; +}); +``` + Resources --------- diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index a5539fa5eb26f..b6df1bd967737 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -97,7 +97,7 @@ public function mkdir($dirs, int $mode = 0777) if (!is_dir($dir)) { // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one if (self::$lastError) { - throw new IOException(sprintf('Failed to create "%s": '.self::$lastError, $dir), 0, null, $dir); + throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir); } throw new IOException(sprintf('Failed to create "%s".', $dir), 0, null, $dir); } @@ -167,16 +167,16 @@ public function remove($files) if (is_link($file)) { // See https://bugs.php.net/52176 if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) { - throw new IOException(sprintf('Failed to remove symlink "%s": '.self::$lastError, $file)); + throw new IOException(sprintf('Failed to remove symlink "%s": ', $file).self::$lastError); } } elseif (is_dir($file)) { $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)); if (!self::box('rmdir', $file) && file_exists($file)) { - throw new IOException(sprintf('Failed to remove directory "%s": '.self::$lastError, $file)); + throw new IOException(sprintf('Failed to remove directory "%s": ', $file).self::$lastError); } } elseif (!self::box('unlink', $file) && file_exists($file)) { - throw new IOException(sprintf('Failed to remove file "%s": '.self::$lastError, $file)); + throw new IOException(sprintf('Failed to remove file "%s": ', $file).self::$lastError); } } } diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index 9454adb0686cb..bd7f553cdedc2 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -63,12 +63,16 @@ public function validate($form, Constraint $formConstraint) /** @var Constraint[] $constraints */ $constraints = $config->getOption('constraints', []); + $hasChildren = $form->count() > 0; + + if ($hasChildren && $form->isRoot()) { + $this->resolvedGroups = new \SplObjectStorage(); + } + if ($groups instanceof GroupSequence) { // Validate the data, the form AND nested fields in sequence $violationsCount = $this->context->getViolations()->count(); $fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s'; - $hasChildren = $form->count() > 0; - $this->resolvedGroups = $hasChildren ? new \SplObjectStorage() : null; foreach ($groups->groups as $group) { if ($validateDataGraph) { @@ -86,7 +90,8 @@ public function validate($form, Constraint $formConstraint) // sequence recursively, thus some fields could fail // in different steps without breaking early enough $this->resolvedGroups[$field] = (array) $group; - $validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $formConstraint); + $fieldFormConstraint = new Form(); + $validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint); } } @@ -94,12 +99,9 @@ public function validate($form, Constraint $formConstraint) break; } } - - if ($hasChildren) { - // destroy storage at the end of the sequence to avoid memory leaks - $this->resolvedGroups = null; - } } else { + $fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s'; + if ($validateDataGraph) { $validator->atPath('data')->validate($data, null, $groups); } @@ -131,6 +133,19 @@ public function validate($form, Constraint $formConstraint) foreach ($groupedConstraints as $group => $constraint) { $validator->atPath('data')->validate($data, $constraint, $group); } + + foreach ($form->all() as $field) { + if ($field->isSubmitted()) { + $this->resolvedGroups[$field] = $groups; + $fieldFormConstraint = new Form(); + $validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint); + } + } + } + + if ($hasChildren && $form->isRoot()) { + // destroy storage to avoid memory leaks + $this->resolvedGroups = new \SplObjectStorage(); } } elseif (!$form->isSynchronized()) { $childrenSynchronized = true; diff --git a/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php index a5e38859c0888..ac2d61238feb9 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php @@ -13,7 +13,7 @@ use Symfony\Component\Form\AbstractExtension; use Symfony\Component\Form\Extension\Validator\Constraints\Form; -use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\Constraints\Traverse; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -37,7 +37,7 @@ public function __construct(ValidatorInterface $validator) /* @var $metadata ClassMetadata */ $metadata->addConstraint(new Form()); - $metadata->addPropertyConstraint('children', new Valid()); + $metadata->addConstraint(new Traverse(false)); $this->validator = $validator; } diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index a14b0ce05c9db..e22415c95efdb 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -1063,7 +1063,7 @@ private function modelToNorm($value) $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException(sprintf('Unable to transform data for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + throw new TransformationFailedException(sprintf('Unable to transform data for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; @@ -1085,7 +1085,7 @@ private function normToModel($value) $value = $transformers[$i]->reverseTransform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; @@ -1114,7 +1114,7 @@ private function normToView($value) $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException(sprintf('Unable to transform value for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + throw new TransformationFailedException(sprintf('Unable to transform value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; @@ -1138,7 +1138,7 @@ private function viewToNorm($value) $value = $transformers[$i]->reverseTransform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; diff --git a/src/Symfony/Component/Form/Resources/config/validation.xml b/src/Symfony/Component/Form/Resources/config/validation.xml index b2b935442d467..918f101f4266a 100644 --- a/src/Symfony/Component/Form/Resources/config/validation.xml +++ b/src/Symfony/Component/Form/Resources/config/validation.xml @@ -6,8 +6,8 @@ - - - + + + diff --git a/src/Symfony/Component/Form/Resources/translations/validators.cs.xlf b/src/Symfony/Component/Form/Resources/translations/validators.cs.xlf index 776da49481816..44d597db980ec 100644 --- a/src/Symfony/Component/Form/Resources/translations/validators.cs.xlf +++ b/src/Symfony/Component/Form/Resources/translations/validators.cs.xlf @@ -14,6 +14,10 @@ The CSRF token is invalid. Please try to resubmit the form. CSRF token je neplatný. Zkuste prosím znovu odeslat formulář. + + This value is not a valid HTML5 color. + Tato hodnota není platná HTML5 barva. + diff --git a/src/Symfony/Component/Form/Resources/translations/validators.hu.xlf b/src/Symfony/Component/Form/Resources/translations/validators.hu.xlf index 374cfaaea34ac..bc7f6055e4845 100644 --- a/src/Symfony/Component/Form/Resources/translations/validators.hu.xlf +++ b/src/Symfony/Component/Form/Resources/translations/validators.hu.xlf @@ -14,6 +14,10 @@ The CSRF token is invalid. Please try to resubmit the form. Érvénytelen CSRF token. Kérem, próbálja újra elküldeni az űrlapot. + + This value is not a valid HTML5 color. + Ez az érték nem egy érvényes HTML5 szín. + diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index ea216673f0c10..b6e0cfa164993 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -658,7 +658,8 @@ public function testViolationIfExtraData() $this->assertTrue($form->isSubmitted()); $this->assertTrue($form->isSynchronized()); - $this->expectNoValidate(); + + $this->expectValidateValueAt(0, 'children[child]', $form->get('child'), new Form()); $this->validator->validate($form, new Form()); @@ -682,7 +683,8 @@ public function testViolationFormatIfMultipleExtraFields() $this->assertTrue($form->isSubmitted()); $this->assertTrue($form->isSynchronized()); - $this->expectNoValidate(); + + $this->expectValidateValueAt(0, 'children[child]', $form->get('child'), new Form()); $this->validator->validate($form, new Form()); diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php index 383b7556d51b8..4324541cb7ffc 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php @@ -54,9 +54,8 @@ public function test2Dot5ValidationApi() $this->assertInstanceOf(FormConstraint::class, $metadata->getConstraints()[0]); $this->assertSame(CascadingStrategy::NONE, $metadata->cascadingStrategy); - $this->assertSame(TraversalStrategy::IMPLICIT, $metadata->traversalStrategy); - $this->assertSame(CascadingStrategy::CASCADE, $metadata->getPropertyMetadata('children')[0]->cascadingStrategy); - $this->assertSame(TraversalStrategy::IMPLICIT, $metadata->getPropertyMetadata('children')[0]->traversalStrategy); + $this->assertSame(TraversalStrategy::NONE, $metadata->traversalStrategy); + $this->assertCount(0, $metadata->getPropertyMetadata('children')); } public function testDataConstraintsInvalidateFormEvenIfFieldIsNotSubmitted() @@ -142,6 +141,33 @@ public function testFieldsValidateInSequenceWithNestedGroupsArray() $this->assertInstanceOf(Length::class, $errors[1]->getCause()->getConstraint()); } + public function testConstraintsInDifferentGroupsOnSingleField() + { + $form = $this->createForm(FormType::class, null, [ + 'validation_groups' => new GroupSequence(['group1', 'group2']), + ]) + ->add('foo', TextType::class, [ + 'constraints' => [ + new NotBlank([ + 'groups' => ['group1'], + ]), + new Length([ + 'groups' => ['group2'], + 'max' => 3, + ]), + ], + ]); + $form->submit([ + 'foo' => 'test@example.com', + ]); + + $errors = $form->getErrors(true); + + $this->assertFalse($form->isValid()); + $this->assertCount(1, $errors); + $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); + } + private function createForm($type, $data = null, array $options = []) { $validator = Validation::createValidatorBuilder() diff --git a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php index d173c172501a5..76bbbe7c57c65 100755 --- a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php +++ b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php @@ -9,6 +9,8 @@ * file that was distributed with this source code. */ +namespace Symfony\Component\HttpClient\Tests\DataCollector; + use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector; use Symfony\Component\HttpClient\NativeHttpClient; @@ -19,9 +21,13 @@ class HttpClientDataCollectorTest extends TestCase { - public function testItCollectsRequestCount() + public static function setUpBeforeClass(): void { TestHttpServer::start(); + } + + public function testItCollectsRequestCount() + { $httpClient1 = $this->httpClientThatHasTracedRequests([ [ 'method' => 'GET', @@ -50,7 +56,6 @@ public function testItCollectsRequestCount() public function testItCollectsErrorCount() { - TestHttpServer::start(); $httpClient1 = $this->httpClientThatHasTracedRequests([ [ 'method' => 'GET', @@ -80,7 +85,6 @@ public function testItCollectsErrorCount() public function testItCollectsErrorCountByClient() { - TestHttpServer::start(); $httpClient1 = $this->httpClientThatHasTracedRequests([ [ 'method' => 'GET', @@ -113,7 +117,6 @@ public function testItCollectsErrorCountByClient() public function testItCollectsTracesByClient() { - TestHttpServer::start(); $httpClient1 = $this->httpClientThatHasTracedRequests([ [ 'method' => 'GET', @@ -146,7 +149,6 @@ public function testItCollectsTracesByClient() public function testItIsEmptyAfterReset() { - TestHttpServer::start(); $httpClient1 = $this->httpClientThatHasTracedRequests([ [ 'method' => 'GET', @@ -170,7 +172,7 @@ private function httpClientThatHasTracedRequests($tracedRequests): TraceableHttp foreach ($tracedRequests as $request) { $response = $httpClient->request($request['method'], $request['url'], $request['options'] ?? []); - $response->getContent(false); // To avoid exception in ResponseTrait::doDestruct + $response->getContent(false); // disables exceptions from destructors } return $httpClient; diff --git a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php index 66a8cef6e7c52..6a8cadbbc88a8 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php @@ -22,8 +22,6 @@ class HttplugClientTest extends TestCase { - private static $server; - public static function setUpBeforeClass(): void { TestHttpServer::start(); diff --git a/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php b/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php index 42e627b590e1a..1ef36fc5bd09e 100644 --- a/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php @@ -21,8 +21,6 @@ class Psr18ClientTest extends TestCase { - private static $server; - public static function setUpBeforeClass(): void { TestHttpServer::start(); diff --git a/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php index 8c0ec0d48cc5a..9b6752f7b62cd 100755 --- a/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php @@ -21,6 +21,11 @@ class TraceableHttpClientTest extends TestCase { + public static function setUpBeforeClass(): void + { + TestHttpServer::start(); + } + public function testItTracesRequest() { $httpClient = $this->getMockBuilder(HttpClientInterface::class)->getMock(); @@ -85,8 +90,6 @@ public function testItResetsTraces() public function testStream() { - TestHttpServer::start(); - $sut = new TraceableHttpClient(new NativeHttpClient()); $response = $sut->request('GET', 'http://localhost:8057/chunked'); $chunks = []; diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php index 0f2f6d980a486..cffe45904dcf5 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php @@ -64,7 +64,7 @@ public function getController(Request $request) } if (!\is_callable($controller)) { - throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: '.$this->getControllerError($controller), $request->getPathInfo())); + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller)); } return $controller; @@ -72,7 +72,7 @@ public function getController(Request $request) if (\is_object($controller)) { if (!\is_callable($controller)) { - throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: '.$this->getControllerError($controller), $request->getPathInfo())); + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller)); } return $controller; @@ -89,7 +89,7 @@ public function getController(Request $request) } if (!\is_callable($callable)) { - throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: '.$this->getControllerError($callable), $request->getPathInfo())); + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($callable)); } return $callable; diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index b06cb82389faa..0df231dca9334 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.1.0-RC2'; + const VERSION = '5.1.0'; const VERSION_ID = 50100; const MAJOR_VERSION = 5; const MINOR_VERSION = 1; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'RC2'; + const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '01/2021'; const END_OF_LIFE = '01/2021'; diff --git a/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php b/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php index 566fced840564..36b578d84c5ae 100644 --- a/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php +++ b/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php @@ -46,7 +46,7 @@ public function read(string $path, string $locale) $data = json_decode(file_get_contents($fileName), true); if (null === $data) { - throw new RuntimeException(sprintf('The resource bundle "%s" contains invalid JSON: '.json_last_error_msg(), $fileName)); + throw new RuntimeException(sprintf('The resource bundle "%s" contains invalid JSON: ', $fileName).json_last_error_msg()); } return $data; diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php index 674ae0957ab92..33d3bb957f808 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php @@ -38,7 +38,7 @@ public function add(Entry $entry) $con = $this->getConnectionResource(); if (!@ldap_add($con, $entry->getDn(), $entry->getAttributes())) { - throw new LdapException(sprintf('Could not add entry "%s": '.ldap_error($con), $entry->getDn()), ldap_errno($con)); + throw new LdapException(sprintf('Could not add entry "%s": ', $entry->getDn()).ldap_error($con), ldap_errno($con)); } return $this; @@ -52,7 +52,7 @@ public function update(Entry $entry) $con = $this->getConnectionResource(); if (!@ldap_modify($con, $entry->getDn(), $entry->getAttributes())) { - throw new LdapException(sprintf('Could not update entry "%s": '.ldap_error($con), $entry->getDn()), ldap_errno($con)); + throw new LdapException(sprintf('Could not update entry "%s": ', $entry->getDn()).ldap_error($con), ldap_errno($con)); } } @@ -64,7 +64,7 @@ public function remove(Entry $entry) $con = $this->getConnectionResource(); if (!@ldap_delete($con, $entry->getDn())) { - throw new LdapException(sprintf('Could not remove entry "%s": '.ldap_error($con), $entry->getDn()), ldap_errno($con)); + throw new LdapException(sprintf('Could not remove entry "%s": ', $entry->getDn()).ldap_error($con), ldap_errno($con)); } } @@ -79,7 +79,7 @@ public function addAttributeValues(Entry $entry, string $attribute, array $value $con = $this->getConnectionResource(); if (!@ldap_mod_add($con, $entry->getDn(), [$attribute => $values])) { - throw new LdapException(sprintf('Could not add values to entry "%s", attribute %s: '.ldap_error($con), $entry->getDn(), $attribute), ldap_errno($con)); + throw new LdapException(sprintf('Could not add values to entry "%s", attribute %s: ', $entry->getDn(), $attribute).ldap_error($con), ldap_errno($con)); } } @@ -94,7 +94,7 @@ public function removeAttributeValues(Entry $entry, string $attribute, array $va $con = $this->getConnectionResource(); if (!@ldap_mod_del($con, $entry->getDn(), [$attribute => $values])) { - throw new LdapException(sprintf('Could not remove values from entry "%s", attribute %s: '.ldap_error($con), $entry->getDn(), $attribute), ldap_errno($con)); + throw new LdapException(sprintf('Could not remove values from entry "%s", attribute %s: ', $entry->getDn(), $attribute).ldap_error($con), ldap_errno($con)); } } @@ -106,7 +106,7 @@ public function rename(Entry $entry, string $newRdn, bool $removeOldRdn = true) $con = $this->getConnectionResource(); if (!@ldap_rename($con, $entry->getDn(), $newRdn, null, $removeOldRdn)) { - throw new LdapException(sprintf('Could not rename entry "%s" to "%s": '.ldap_error($con), $entry->getDn(), $newRdn), ldap_errno($con)); + throw new LdapException(sprintf('Could not rename entry "%s" to "%s": ', $entry->getDn(), $newRdn).ldap_error($con), ldap_errno($con)); } } @@ -122,7 +122,7 @@ public function move(Entry $entry, string $newParent) $rdn = $this->parseRdnFromEntry($entry); // deleteOldRdn does not matter here, since the Rdn will not be changing in the move. if (!@ldap_rename($con, $entry->getDn(), $rdn, $newParent, true)) { - throw new LdapException(sprintf('Could not move entry "%s" to "%s": '.ldap_error($con), $entry->getDn(), $newParent), ldap_errno($con)); + throw new LdapException(sprintf('Could not move entry "%s" to "%s": ', $entry->getDn(), $newParent).ldap_error($con), ldap_errno($con)); } } @@ -152,7 +152,7 @@ public function applyOperations(string $dn, iterable $operations): void } if (!@ldap_modify_batch($this->getConnectionResource(), $dn, $operationsMapped)) { - throw new UpdateOperationException(sprintf('Error executing UpdateOperation on "%s": '.ldap_error($this->getConnectionResource()), $dn), ldap_errno($con)); + throw new UpdateOperationException(sprintf('Error executing UpdateOperation on "%s": ', $dn).ldap_error($this->getConnectionResource()), ldap_errno($con)); } } diff --git a/src/Symfony/Component/Lock/Store/PdoStore.php b/src/Symfony/Component/Lock/Store/PdoStore.php index a58d1b285dfde..b792d4d9ca979 100644 --- a/src/Symfony/Component/Lock/Store/PdoStore.php +++ b/src/Symfony/Component/Lock/Store/PdoStore.php @@ -193,7 +193,7 @@ public function exists(Key $key) $stmt->bindValue(':token', $this->getUniqueToken($key)); $stmt->execute(); - return (bool) $stmt->fetchColumn(); + return (bool) (method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn()); } /** diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php index dc174e3cd12c1..0a7fde06ce6d6 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php @@ -67,7 +67,7 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e $result = new \SimpleXMLElement($response->getContent(false)); if (200 !== $response->getStatusCode()) { - throw new HttpTransportException(sprintf('Unable to send an email: '.$result->Error->Message.' (code %d).', $result->Error->Code), $response); + throw new HttpTransportException('Unable to send an email: '.$result->Error->Message.sprintf(' (code %d).', $result->Error->Code), $response); } $property = $payload['Action'].'Result'; diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php index 2b4dd651e2e03..e3fefd4583a5e 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php @@ -67,7 +67,7 @@ protected function doSendHttp(SentMessage $message): ResponseInterface $result = new \SimpleXMLElement($response->getContent(false)); if (200 !== $response->getStatusCode()) { - throw new HttpTransportException(sprintf('Unable to send an email: '.$result->Error->Message.' (code %d).', $result->Error->Code), $response); + throw new HttpTransportException('Unable to send an email: '.$result->Error->Message.sprintf(' (code %d).', $result->Error->Code), $response); } $message->setMessageId($result->SendRawEmailResult->MessageId); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php index 82508e6481672..50be4e02cb2a4 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php @@ -53,7 +53,7 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('error' === ($result['status'] ?? false)) { - throw new HttpTransportException(sprintf('Unable to send an email: '.$result['message'].' (code %d).', $result['code']), $response); + throw new HttpTransportException('Unable to send an email: '.$result['message'].sprintf(' (code %d).', $result['code']), $response); } throw new HttpTransportException(sprintf('Unable to send an email (code %d).', $result['code']), $response); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php index e67b17ecb9055..2bb23c5af23dc 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php @@ -59,7 +59,7 @@ protected function doSendHttp(SentMessage $message): ResponseInterface $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('error' === ($result['status'] ?? false)) { - throw new HttpTransportException(sprintf('Unable to send an email: '.$result['message'].' (code %d).', $result['code']), $response); + throw new HttpTransportException('Unable to send an email: '.$result['message'].sprintf(' (code %d).', $result['code']), $response); } throw new HttpTransportException(sprintf('Unable to send an email (code %d).', $result['code']), $response); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php index 1f6dabffbde07..636a218de15a6 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php @@ -67,10 +67,10 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('application/json' === $response->getHeaders(false)['content-type'][0]) { - throw new HttpTransportException(sprintf('Unable to send an email: '.$result['message'].' (code %d).', $response->getStatusCode()), $response); + throw new HttpTransportException('Unable to send an email: '.$result['message'].sprintf(' (code %d).', $response->getStatusCode()), $response); } - throw new HttpTransportException(sprintf('Unable to send an email: '.$response->getContent(false).' (code %d).', $response->getStatusCode()), $response); + throw new HttpTransportException('Unable to send an email: '.$response->getContent(false).sprintf(' (code %d).', $response->getStatusCode()), $response); } $sentMessage->setMessageId($result['id']); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php index 9e143b34b3cfc..150c5c8245666 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php @@ -69,10 +69,10 @@ protected function doSendHttp(SentMessage $message): ResponseInterface $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('application/json' === $response->getHeaders(false)['content-type'][0]) { - throw new HttpTransportException(sprintf('Unable to send an email: '.$result['message'].' (code %d).', $response->getStatusCode()), $response); + throw new HttpTransportException('Unable to send an email: '.$result['message'].sprintf(' (code %d).', $response->getStatusCode()), $response); } - throw new HttpTransportException(sprintf('Unable to send an email: '.$response->getContent(false).' (code %d).', $response->getStatusCode()), $response); + throw new HttpTransportException('Unable to send an email: '.$response->getContent(false).sprintf(' (code %d).', $response->getStatusCode()), $response); } $message->setMessageId($result['id']); diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php index 61b8fad3d8fff..2b9930a308627 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php @@ -56,7 +56,7 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { - throw new HttpTransportException(sprintf('Unable to send an email: '.$result['Message'].' (code %d).', $result['ErrorCode']), $response); + throw new HttpTransportException('Unable to send an email: '.$result['Message'].sprintf(' (code %d).', $result['ErrorCode']), $response); } $sentMessage->setMessageId($result['MessageID']); diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php index aee333ddb16cf..28eb76d25bf6b 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php @@ -53,7 +53,7 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e if (202 !== $response->getStatusCode()) { $errors = $response->toArray(false); - throw new HttpTransportException(sprintf('Unable to send an email: '.implode('; ', array_column($errors['errors'], 'message')).' (code %d).', $response->getStatusCode()), $response); + throw new HttpTransportException('Unable to send an email: '.implode('; ', array_column($errors['errors'], 'message')).sprintf(' (code %d).', $response->getStatusCode()), $response); } $sentMessage->setMessageId($response->getHeaders(false)['x-message-id'][0]); diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php index 3c131051f8371..41e8c368e1e54 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php @@ -136,7 +136,7 @@ public function initialize(): void $timeout = $this->getTimeout(); set_error_handler(function ($type, $msg) { - throw new TransportException(sprintf('Connection could not be established with host "%s": '.$msg, $this->url)); + throw new TransportException(sprintf('Connection could not be established with host "%s": ', $this->url).$msg); }); try { $this->stream = stream_socket_client($this->url, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $streamContext); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php index 27a7e6b9e0de6..ef94e60f44312 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php @@ -12,7 +12,8 @@ namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport; use Doctrine\DBAL\DBALException; -use Doctrine\DBAL\Driver\Statement; +use Doctrine\DBAL\Driver\ResultStatement; +use Doctrine\DBAL\ForwardCompatibility\Driver\ResultStatement as ForwardCompatibleResultStatement; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Schema\AbstractSchemaManager; @@ -143,11 +144,16 @@ private function getQueryBuilderMock() return $queryBuilder; } - private function getStatementMock($expectedResult): Statement + private function getStatementMock($expectedResult): ResultStatement { - $stmt = $this->createMock(Statement::class); + $mockedInterface = interface_exists(ForwardCompatibleResultStatement::class) + ? ForwardCompatibleResultStatement::class + : ResultStatement::class; + + $stmt = $this->createMock($mockedInterface); + $stmt->expects($this->once()) - ->method('fetch') + ->method(method_exists($mockedInterface, 'fetchAssociative') ? 'fetchAssociative' : 'fetch') ->willReturn($expectedResult); return $stmt; @@ -309,9 +315,12 @@ public function testFindAll() 'headers' => json_encode(['type' => DummyMessage::class]), ]; - $stmt = $this->createMock(Statement::class); + $mockedInterface = interface_exists(ForwardCompatibleResultStatement::class) + ? ForwardCompatibleResultStatement::class + : ResultStatement::class; + $stmt = $this->createMock($mockedInterface); $stmt->expects($this->once()) - ->method('fetchAll') + ->method(method_exists($mockedInterface, 'fetchAllAssociative') ? 'fetchAllAssociative' : 'fetchAll') ->willReturn([$message1, $message2]); $driverConnection diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php index 19035be25db16..a16800464fc1d 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php @@ -64,15 +64,14 @@ public function testSendWithDelay() { $this->connection->send('{"message": "Hi i am delayed"}', ['type' => DummyMessage::class], 600000); - $available_at = $this->driverConnection->createQueryBuilder() + $stmt = $this->driverConnection->createQueryBuilder() ->select('m.available_at') ->from('messenger_messages', 'm') ->where('m.body = :body') ->setParameter(':body', '{"message": "Hi i am delayed"}') - ->execute() - ->fetchColumn(); + ->execute(); - $available_at = new \DateTime($available_at); + $available_at = new \DateTime(method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn()); $now = new \DateTime(); $now->modify('+60 seconds'); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php index f99452ef3c85a..90017123e168c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -170,11 +170,12 @@ public function get(): ?array ->setMaxResults(1); // use SELECT ... FOR UPDATE to lock table - $doctrineEnvelope = $this->executeQuery( + $stmt = $this->executeQuery( $query->getSQL().' '.$this->driverConnection->getDatabasePlatform()->getWriteLockSQL(), $query->getParameters(), $query->getParameterTypes() - )->fetch(); + ); + $doctrineEnvelope = method_exists($stmt, 'fetchAssociative') ? $stmt->fetchAssociative() : $stmt->fetch(); if (false === $doctrineEnvelope) { $this->driverConnection->commit(); @@ -264,7 +265,9 @@ public function getMessageCount(): int ->select('COUNT(m.id) as message_count') ->setMaxResults(1); - return $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes())->fetchColumn(); + $stmt = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes()); + + return method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn(); } public function findAll(int $limit = null): array @@ -274,7 +277,8 @@ public function findAll(int $limit = null): array $queryBuilder->setMaxResults($limit); } - $data = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes())->fetchAll(); + $stmt = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes()); + $data = method_exists($stmt, 'fetchAllAssociative') ? $stmt->fetchAllAssociative() : $stmt->fetchAll(); return array_map(function ($doctrineEnvelope) { return $this->decodeEnvelopeHeaders($doctrineEnvelope); @@ -286,9 +290,8 @@ public function find($id): ?array $queryBuilder = $this->createQueryBuilder() ->where('m.id = ?'); - $data = $this->executeQuery($queryBuilder->getSQL(), [ - $id, - ])->fetch(); + $stmt = $this->executeQuery($queryBuilder->getSQL(), [$id]); + $data = method_exists($stmt, 'fetchAssociative') ? $stmt->fetchAssociative() : $stmt->fetch(); return false === $data ? null : $this->decodeEnvelopeHeaders($data); } diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/PostgreSqlConnection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/PostgreSqlConnection.php index dfffdac4b7162..225f0b880111f 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/PostgreSqlConnection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/PostgreSqlConnection.php @@ -24,11 +24,11 @@ final class PostgreSqlConnection extends Connection { /** * * use_notify: Set to false to disable the use of LISTEN/NOTIFY. Default: true - * * check_delayed_interval: The interval to check for delayed messages, in milliseconds. Set to 0 to disable checks. Default: 1000 + * * check_delayed_interval: The interval to check for delayed messages, in milliseconds. Set to 0 to disable checks. Default: 60000 (1 minute) * * get_notify_timeout: The length of time to wait for a response when calling PDO::pgsqlGetNotify, in milliseconds. Default: 0. */ protected const DEFAULT_OPTIONS = parent::DEFAULT_OPTIONS + [ - 'check_delayed_interval' => 1000, + 'check_delayed_interval' => 60000, 'get_notify_timeout' => 0, ]; @@ -75,6 +75,8 @@ public function get(): ?array // delayed messages (microtime(true) * 1000 - $this->queueEmptiedAt < $this->configuration['check_delayed_interval']) ) { + usleep(1000); + return null; } diff --git a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php index e50c6f18377fa..56b72ee012204 100644 --- a/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Nexmo/NexmoTransport.php @@ -70,7 +70,7 @@ protected function doSend(MessageInterface $message): void $result = $response->toArray(false); foreach ($result['messages'] as $msg) { if ($msg['status'] ?? false) { - throw new TransportException(sprintf('Unable to send the SMS: '.$msg['error-text'].' (%s).', $msg['status']), $response); + throw new TransportException('Unable to send the SMS: '.$msg['error-text'].sprintf(' (code %s).', $msg['status']), $response); } } } diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php index 7cd637f57994d..5e2710ac7a5d1 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php @@ -80,7 +80,7 @@ protected function doSend(MessageInterface $message): void if (200 !== $response->getStatusCode()) { $result = $response->toArray(false); - throw new TransportException(sprintf('Unable to post the Telegram message: '.$result['description'].' (%s).', $result['error_code']), $response); + throw new TransportException('Unable to post the Telegram message: '.$result['description'].sprintf(' (code %s).', $result['error_code']), $response); } } } diff --git a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php index b09a7277a5787..92a31fbc0a140 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Twilio/TwilioTransport.php @@ -70,7 +70,7 @@ protected function doSend(MessageInterface $message): void if (201 !== $response->getStatusCode()) { $error = $response->toArray(false); - throw new TransportException(sprintf('Unable to send the SMS: '.$error['message'].' (see %s).', $error['more_info']), $response); + throw new TransportException('Unable to send the SMS: '.$error['message'].sprintf(' (see %s).', $error['more_info']), $response); } } } diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index 33fb5f9eefc1f..e1efb9ccc80eb 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -1608,7 +1608,7 @@ private function replacePlaceholders(string $commandline, array $env) { return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) { if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) { - throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": '.$commandline, $matches[1])); + throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline); } return $this->escapeArgument($env[$matches[1]]); diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 1fb342f0fa50c..f5e64651bab7e 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -404,8 +404,15 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid try { $result[self::VALUE] = $object->$name(); } catch (\TypeError $e) { - if (preg_match((sprintf('/^Return value of %s::%s\(\) must be of (?:the )?type (\w+), null returned$/', preg_quote(\get_class($object)), $name)), $e->getMessage(), $matches)) { - throw new UninitializedPropertyException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Did you forget to initialize a property or to make the return type nullable using "?%3$s"?', \get_class($object), $name, $matches[1]), 0, $e); + list($trace) = $e->getTrace(); + + // handle uninitialized properties in PHP >= 7 + if (__FILE__ === $trace['file'] + && $name === $trace['function'] + && $object instanceof $trace['class'] + && preg_match((sprintf('/Return value (?:of .*::\w+\(\) )?must be of (?:the )?type (\w+), null returned$/')), $e->getMessage(), $matches) + ) { + throw new UninitializedPropertyException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Did you forget to initialize a property or to make the return type nullable using "?%3$s"?', false === strpos(\get_class($object), "@anonymous\0") ? \get_class($object) : (get_parent_class($object) ?: key(class_implements($object)) ?: 'class').'@anonymous', $name, $matches[1]), 0, $e); } throw $e; diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index 34f0fc4c442d6..89e02f334d867 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -153,6 +153,59 @@ public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetter() $this->propertyAccessor->getValue(new UninitializedPrivateProperty(), 'uninitialized'); } + /** + * @requires PHP 7 + */ + public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetterOfAnonymousClass() + { + $this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException'); + $this->expectExceptionMessage('The method "class@anonymous::getUninitialized()" returned "null", but expected type "array". Did you forget to initialize a property or to make the return type nullable using "?array"?'); + + $object = eval('return new class() { + private $uninitialized; + + public function getUninitialized(): array + { + return $this->uninitialized; + } + };'); + + $this->propertyAccessor->getValue($object, 'uninitialized'); + } + + /** + * @requires PHP 7 + */ + public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetterOfAnonymousStdClass() + { + $this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException'); + $this->expectExceptionMessage('The method "stdClass@anonymous::getUninitialized()" returned "null", but expected type "array". Did you forget to initialize a property or to make the return type nullable using "?array"?'); + + $object = eval('return new class() extends \stdClass { + private $uninitialized; + + public function getUninitialized(): array + { + return $this->uninitialized; + } + };'); + + $this->propertyAccessor->getValue($object, 'uninitialized'); + } + + /** + * @requires PHP 7 + */ + public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetterOfAnonymousChildClass() + { + $this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException'); + $this->expectExceptionMessage('The method "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty@anonymous::getUninitialized()" returned "null", but expected type "array". Did you forget to initialize a property or to make the return type nullable using "?array"?'); + + $object = eval('return new class() extends \Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty {};'); + + $this->propertyAccessor->getValue($object, 'uninitialized'); + } + public function testGetValueThrowsExceptionIfNotArrayAccess() { $this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException'); diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php index 8800e8985406d..89d0bfa31727e 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php @@ -22,7 +22,7 @@ /** * AnnotationClassLoader loads routing information from a PHP class and its methods. * - * You need to define an implementation for the getRouteDefaults() method. Most of the + * You need to define an implementation for the configureRoute() method. Most of the * time, this method should define some PHP callable to be called for the route * (a controller in MVC speak). * diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php index 94c37aab4d6e6..9106334b99e8b 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php @@ -270,10 +270,13 @@ private function hasUserChanged(UserInterface $user): bool return true; } - $currentUserRoles = array_map('strval', (array) $this->user->getRoles()); $userRoles = array_map('strval', (array) $user->getRoles()); - if (\count($userRoles) !== \count($currentUserRoles) || \count($userRoles) !== \count(array_intersect($userRoles, $currentUserRoles))) { + if ($this instanceof SwitchUserToken) { + $userRoles[] = 'ROLE_PREVIOUS_ADMIN'; + } + + if (\count($userRoles) !== \count($this->getRoleNames()) || \count($userRoles) !== \count(array_intersect($userRoles, $this->getRoleNames()))) { return true; } diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php index 769646c1a3cbb..2cd992737d890 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php @@ -152,13 +152,28 @@ public function getUserChanges() */ public function testSetUserDoesNotSetAuthenticatedToFalseWhenUserDoesNotChange($user) { - $token = new ConcreteToken(['ROLE_FOO']); + $token = new ConcreteToken(); + $token->setAuthenticated(true); + $this->assertTrue($token->isAuthenticated()); + + $token->setUser($user); + $this->assertTrue($token->isAuthenticated()); + + $token->setUser($user); + $this->assertTrue($token->isAuthenticated()); + } + + public function testIsUserChangedWhenSerializing() + { + $token = new ConcreteToken(['ROLE_ADMIN']); $token->setAuthenticated(true); $this->assertTrue($token->isAuthenticated()); + $user = new SerializableUser('wouter', ['ROLE_ADMIN']); $token->setUser($user); $this->assertTrue($token->isAuthenticated()); + $token = unserialize(serialize($token)); $token->setUser($user); $this->assertTrue($token->isAuthenticated()); } @@ -179,6 +194,56 @@ public function __toString(): string } } +class SerializableUser implements UserInterface, \Serializable +{ + private $roles; + private $name; + + public function __construct($name, array $roles = []) + { + $this->name = $name; + $this->roles = $roles; + } + + public function getUsername() + { + return $this->name; + } + + public function getPassword() + { + return '***'; + } + + public function getRoles() + { + if (empty($this->roles)) { + return ['ROLE_USER']; + } + + return $this->roles; + } + + public function eraseCredentials() + { + } + + public function getSalt() + { + return null; + } + + public function serialize() + { + return serialize($this->name); + } + + public function unserialize($serialized) + { + $this->name = unserialize($serialized); + } +} + class ConcreteToken extends AbstractToken { private $credentials = 'credentials_value'; diff --git a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php index 52366fb5487c6..dde4c01c499a9 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php @@ -111,7 +111,7 @@ public function onKernelException(ExceptionEvent $event) } if ($exception instanceof LogoutException) { - $this->handleLogoutException($exception); + $this->handleLogoutException($event, $exception); return; } @@ -183,10 +183,12 @@ private function handleAccessDeniedException(ExceptionEvent $event, AccessDenied } } - private function handleLogoutException(LogoutException $exception): void + private function handleLogoutException(ExceptionEvent $event, LogoutException $exception): void { + $event->setThrowable(new AccessDeniedHttpException($exception->getMessage(), $exception)); + if (null !== $this->logger) { - $this->logger->info('A LogoutException was thrown.', ['exception' => $exception]); + $this->logger->info('A LogoutException was thrown; wrapping with AccessDeniedHttpException', ['exception' => $exception]); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php index 3713ec7b25edc..7328394b486d2 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php @@ -21,6 +21,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\LogoutException; use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use Symfony\Component\Security\Http\Firewall\ExceptionListener; @@ -157,6 +158,17 @@ public function testAccessDeniedExceptionNotFullFledged(\Exception $exception, \ $this->assertSame(null === $eventException ? $exception : $eventException, $event->getThrowable()->getPrevious()); } + public function testLogoutException() + { + $event = $this->createEvent(new LogoutException('Invalid CSRF.')); + + $listener = $this->createExceptionListener(); + $listener->onKernelException($event); + + $this->assertEquals('Invalid CSRF.', $event->getThrowable()->getMessage()); + $this->assertEquals(403, $event->getThrowable()->getStatusCode()); + } + public function getAccessDeniedExceptionProvider() { return [ diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php index d37358ce23cb4..c12e23470a445 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php @@ -94,7 +94,7 @@ public function denormalize($data, string $type, string $format = null, array $c $dateTimeErrors = \DateTime::class === $type ? \DateTime::getLastErrors() : \DateTimeImmutable::getLastErrors(); - throw new NotNormalizableValueException(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors:.'."\n".'%s', $data, $dateTimeFormat, $dateTimeErrors['error_count'], implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])))); + throw new NotNormalizableValueException(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors']))); } try { diff --git a/src/Symfony/Component/Templating/README.md b/src/Symfony/Component/Templating/README.md index 2b8ecad873964..a4798fdaafdef 100644 --- a/src/Symfony/Component/Templating/README.md +++ b/src/Symfony/Component/Templating/README.md @@ -9,10 +9,33 @@ for changes. It also provides a concrete template engine implementation using PHP with additional tools for escaping and separating templates into blocks and layouts. +Getting Started +--------------- + +``` +$ composer require symfony/templating +``` + +```php +use Symfony\Component\Templating\Loader\FilesystemLoader; +use Symfony\Component\Templating\PhpEngine; +use Symfony\Component\Templating\Helper\SlotsHelper; +use Symfony\Component\Templating\TemplateNameParser; + +$filesystemLoader = new FilesystemLoader(__DIR__.'/views/%name%'); + +$templating = new PhpEngine(new TemplateNameParser(), $filesystemLoader); +$templating->set(new SlotsHelper()); + +echo $templating->render('hello.php', ['firstname' => 'Fabien']); + +// hello.php +Hello, escape($firstname) ?>! +``` + Resources --------- - * [Documentation](https://symfony.com/doc/current/components/templating.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) diff --git a/src/Symfony/Component/Translation/Formatter/IntlFormatter.php b/src/Symfony/Component/Translation/Formatter/IntlFormatter.php index 9101a63aa2ee9..f7f1c36a2a127 100644 --- a/src/Symfony/Component/Translation/Formatter/IntlFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/IntlFormatter.php @@ -40,7 +40,7 @@ public function formatIntl(string $message, string $locale, array $parameters = try { $this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message); } catch (\IntlException $e) { - throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): '.intl_get_error_message(), intl_get_error_code()), 0, $e); + throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): ', intl_get_error_code()).intl_get_error_message(), 0, $e); } } @@ -52,7 +52,7 @@ public function formatIntl(string $message, string $locale, array $parameters = } if (false === $message = $formatter->format($parameters)) { - throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): '.$formatter->getErrorMessage(), $formatter->getErrorCode())); + throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): ', $formatter->getErrorCode()).$formatter->getErrorMessage()); } return $message; diff --git a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php index 291e8c3ebbe3b..dc7e936860cff 100644 --- a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -63,7 +63,7 @@ private function extract($resource, MessageCatalogue $catalogue, string $domain) $xliffVersion = XliffUtils::getVersionNumber($dom); if ($errors = XliffUtils::validateSchema($dom)) { - throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: '.XliffUtils::getErrorsAsString($errors), $resource)); + throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors)); } if ('1.2' === $xliffVersion) { diff --git a/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntax.php b/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntax.php index eab003854dc72..50790fba054a4 100644 --- a/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntax.php +++ b/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntax.php @@ -29,8 +29,7 @@ class ExpressionLanguageSyntax extends Constraint public $message = 'This value should be a valid expression.'; public $service; - public $validateNames = true; - public $names = []; + public $allowedVariables = null; /** * {@inheritdoc} diff --git a/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntaxValidator.php b/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntaxValidator.php index e7104452e7678..4b67da2c2be9c 100644 --- a/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntaxValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntaxValidator.php @@ -16,6 +16,7 @@ use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; /** * @author Andrey Sevastianov @@ -39,7 +40,7 @@ public function validate($expression, Constraint $constraint): void } if (!\is_string($expression)) { - throw new UnexpectedTypeException($expression, 'string'); + throw new UnexpectedValueException($expression, 'string'); } if (null === $this->expressionLanguage) { @@ -47,7 +48,7 @@ public function validate($expression, Constraint $constraint): void } try { - $this->expressionLanguage->lint($expression, ($constraint->validateNames ? ($constraint->names ?? []) : null)); + $this->expressionLanguage->lint($expression, $constraint->allowedVariables); } catch (SyntaxError $exception) { $this->context->buildViolation($constraint->message) ->setParameter('{{ syntax_error }}', $this->formatValue($exception->getMessage())) diff --git a/src/Symfony/Component/Validator/Constraints/FileValidator.php b/src/Symfony/Component/Validator/Constraints/FileValidator.php index 52e937944188b..31218c2020fa8 100644 --- a/src/Symfony/Component/Validator/Constraints/FileValidator.php +++ b/src/Symfony/Component/Validator/Constraints/FileValidator.php @@ -13,8 +13,10 @@ use Symfony\Component\HttpFoundation\File\File as FileObject; use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\Mime\MimeTypes; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\LogicException; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; @@ -170,12 +172,17 @@ public function validate($value, Constraint $constraint) } if ($constraint->mimeTypes) { - if (!$value instanceof FileObject) { - $value = new FileObject($value); + if ($value instanceof FileObject) { + $mime = $value->getMimeType(); + } elseif (class_exists(MimeTypes::class)) { + $mime = MimeTypes::getDefault()->guessMimeType($path); + } elseif (!class_exists(FileObject::class)) { + throw new LogicException('You cannot validate the mime-type of files as the Mime component is not installed. Try running "composer require symfony/mime".'); + } else { + $mime = (new FileObject($value))->getMimeType(); } $mimeTypes = (array) $constraint->mimeTypes; - $mime = $value->getMimeType(); foreach ($mimeTypes as $mimeType) { if ($mimeType === $mime) { diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf index e637d09aa9faa..ce32f1368de64 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf @@ -334,6 +334,54 @@ This value should be valid JSON. Tato hodnota musí být validní JSON. + + This collection should contain only unique elements. + Tato kolekce musí obsahovat pouze unikátní prvky. + + + This value should be positive. + Tato hodnota musí být kladná. + + + This value should be either positive or zero. + Tato hodnota musí být buď kladná nebo nula. + + + This value should be negative. + Tato hodnota musí být záporná. + + + This value should be either negative or zero. + Tato hodnota musí být buď záporná nebo nula. + + + This value is not a valid timezone. + Tato časová zóna neexistuje. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Zadané heslo bylo součástí úniku dat, takže ho není možné použít. Použijte prosím jiné heslo. + + + This value should be between {{ min }} and {{ max }}. + Hodnota musí být mezi {{ min }} a {{ max }}. + + + This value is not a valid hostname. + Tato hodnota není platný hostname. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Počet prvků v této kolekci musí být násobek {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Tato hodnota musí splňovat alespoň jedno z následujících omezení: + + + Each element of this collection should satisfy its own set of constraints. + Každý prvek v této kolekci musí splňovat svá vlastní omezení. + diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxValidatorTest.php similarity index 94% rename from src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxTest.php rename to src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxValidatorTest.php index dc80288c38544..7db835b333f52 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxValidatorTest.php @@ -18,7 +18,7 @@ use Symfony\Component\Validator\Constraints\ExpressionLanguageSyntaxValidator; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; -class ExpressionLanguageSyntaxTest extends ConstraintValidatorTestCase +class ExpressionLanguageSyntaxValidatorTest extends ConstraintValidatorTestCase { /** * @var \PHPUnit\Framework\MockObject\MockObject|ExpressionLanguage @@ -45,6 +45,7 @@ public function testExpressionValid(): void $this->validator->validate($this->value, new ExpressionLanguageSyntax([ 'message' => 'myMessage', + 'allowedVariables' => [], ])); $this->assertNoViolation(); @@ -58,7 +59,6 @@ public function testExpressionWithoutNames(): void $this->validator->validate($this->value, new ExpressionLanguageSyntax([ 'message' => 'myMessage', - 'validateNames' => false, ])); $this->assertNoViolation(); @@ -73,6 +73,7 @@ public function testExpressionIsNotValid(): void $this->validator->validate($this->value, new ExpressionLanguageSyntax([ 'message' => 'myMessage', + 'allowedVariables' => [], ])); $this->buildViolation('myMessage') diff --git a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php index 3d41539de2e68..ef69a5bc0032c 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php @@ -19,6 +19,8 @@ use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; +use Symfony\Component\Validator\Constraints\Optional; +use Symfony\Component\Validator\Constraints\Required; use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\Context\ExecutionContextFactory; use Symfony\Component\Validator\Mapping\ClassMetadata; @@ -159,4 +161,18 @@ public function testAllConstraintValidateAllGroupsForNestedConstraints() $this->assertInstanceOf(Length::class, $violations->get(1)->getConstraint()); $this->assertInstanceOf(Length::class, $violations->get(2)->getConstraint()); } + + public function testRequiredConstraintIsIgnored() + { + $violations = $this->validator->validate([], new Required()); + + $this->assertCount(0, $violations); + } + + public function testOptionalConstraintIsIgnored() + { + $violations = $this->validator->validate([], new Optional()); + + $this->assertCount(0, $violations); + } } diff --git a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php index fd2e658afa588..75d8e50175655 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php @@ -13,6 +13,7 @@ use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Composite; +use Symfony\Component\Validator\Constraints\Existence; use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; @@ -718,6 +719,10 @@ private function validateInGroup($value, ?string $cacheKey, MetadataInterface $m $context->setGroup($group); foreach ($metadata->findConstraints($group) as $constraint) { + if ($constraint instanceof Existence) { + continue; + } + // Prevent duplicate validation of constraints, in the case // that constraints belong to multiple validated groups if (null !== $cacheKey) { diff --git a/src/Symfony/Component/VarDumper/Server/DumpServer.php b/src/Symfony/Component/VarDumper/Server/DumpServer.php index e9e93f137a182..3e6343e350258 100644 --- a/src/Symfony/Component/VarDumper/Server/DumpServer.php +++ b/src/Symfony/Component/VarDumper/Server/DumpServer.php @@ -41,7 +41,7 @@ public function __construct(string $host, LoggerInterface $logger = null) public function start(): void { if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) { - throw new \RuntimeException(sprintf('Server start failed on "%s": '.$errstr.' '.$errno, $this->host)); + throw new \RuntimeException(sprintf('Server start failed on "%s": ', $this->host).$errstr.' '.$errno); } } diff --git a/src/Symfony/Component/WebLink/README.md b/src/Symfony/Component/WebLink/README.md index d246e50754318..7c6abd3969a5c 100644 --- a/src/Symfony/Component/WebLink/README.md +++ b/src/Symfony/Component/WebLink/README.md @@ -8,10 +8,30 @@ This component implements the [HTML5's Links](https://www.w3.org/TR/html5/links. and [Resource Hints](https://www.w3.org/TR/resource-hints/) W3C's specifications. It can also be used with extensions defined in the [HTML5 link type extensions wiki](http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions). +Getting Started +--------------- + +``` +$ composer require symfony/web-link +``` + +```php +use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Component\WebLink\HttpHeaderSerializer; +use Symfony\Component\WebLink\Link; + +$linkProvider = (new GenericLinkProvider()) + ->withLink(new Link('preload', '/bootstrap.min.css')); + +header('Link: '.(new HttpHeaderSerializer())->serialize($linkProvider->getLinks())); + +echo 'Hello'; +``` + Resources --------- - * [Documentation](https://symfony.com/doc/current/components/web_link.html) + * [Documentation](https://symfony.com/doc/current/web_link.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) diff --git a/src/Symfony/Contracts/Deprecation/composer.json b/src/Symfony/Contracts/Deprecation/composer.json index de77c26ae8b0a..c8ace825c325b 100644 --- a/src/Symfony/Contracts/Deprecation/composer.json +++ b/src/Symfony/Contracts/Deprecation/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "^7.1" + "php": ">=7.1" }, "autoload": { "files": [ diff --git a/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php b/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php index 16e9dad29939d..5dae27c912a09 100644 --- a/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php +++ b/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php @@ -16,12 +16,12 @@ class TestHttpServer { - private static $started; + private static $process; public static function start() { - if (self::$started) { - return; + if (self::$process) { + self::$process->stop(); } $finder = new PhpExecutableFinder(); @@ -29,9 +29,10 @@ public static function start() $process->setWorkingDirectory(__DIR__.'/Fixtures/web'); $process->start(); - register_shutdown_function([$process, 'stop']); - sleep('\\' === \DIRECTORY_SEPARATOR ? 10 : 1); + do { + usleep(50000); + } while (!@fopen('http://127.0.0.1:8057/', 'r')); - self::$started = true; + self::$process = $process; } } 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