Merge branch '4.4' into 5.0

* 4.4:
  Fix abstract method name in PHP doc block
  Various cleanups
  [HttpClient] fix issues in tests
  Fixes sprintf(): Too few arguments in form transformer
  [Console] Fix QuestionHelper::disableStty()
  [Validator] Use Mime component to determine mime type for file validator
  validate subforms in all validation groups
  Update Hungarian translations
  Add meaningful message when Process is not installed (ProcessHelper)
  [PropertyAccess] Fix TypeError parsing again.
  [TwigBridge] fix fallback html-to-txt body converter
  [Form] add missing Czech validators translation
  [Validator] add missing Czech translations
  never directly validate Existence (Required/Optional) constraints
This commit is contained in:
Nicolas Grekas 2020-05-30 22:12:43 +02:00
commit 527f3f305e
49 changed files with 309 additions and 88 deletions

View File

@ -151,6 +151,7 @@ before_install:
INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini
echo date.timezone = Europe/Paris >> $INI echo date.timezone = Europe/Paris >> $INI
echo memory_limit = -1 >> $INI echo memory_limit = -1 >> $INI
echo default_socket_timeout = 10 >> $INI
echo session.gc_probability = 0 >> $INI echo session.gc_probability = 0 >> $INI
echo opcache.enable_cli = 1 >> $INI echo opcache.enable_cli = 1 >> $INI
echo apc.enable_cli = 1 >> $INI echo apc.enable_cli = 1 >> $INI

View File

@ -102,7 +102,7 @@ EOF
} }
if (!$socket = stream_socket_server($host, $errno, $errstr)) { 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) { foreach ($this->getLogs($socket) as $clientId => $message) {

View File

@ -74,6 +74,6 @@ final class BodyRenderer implements BodyRendererInterface
return $this->converter->convert($html); return $this->converter->convert($html);
} }
return strip_tags($html); return strip_tags(preg_replace('{<(head|style)\b.*?</\1>}i', '', $html));
} }
} }

View File

@ -29,11 +29,12 @@ class BodyRendererTest extends TestCase
public function testRenderHtmlOnly(): void public function testRenderHtmlOnly(): void
{ {
$email = $this->prepareEmail(null, '<b>HTML</b>'); $html = '<head>head</head><b>HTML</b><style type="text/css">css</style>';
$email = $this->prepareEmail(null, $html);
$body = $email->getBody(); $body = $email->getBody();
$this->assertInstanceOf(AlternativePart::class, $body); $this->assertInstanceOf(AlternativePart::class, $body);
$this->assertEquals('HTML', $body->getParts()[0]->bodyToString()); $this->assertEquals('HTML', $body->getParts()[0]->bodyToString());
$this->assertEquals('<b>HTML</b>', $body->getParts()[1]->bodyToString()); $this->assertEquals(str_replace('=', '=3D', $html), $body->getParts()[1]->bodyToString());
} }
public function testRenderHtmlOnlyWithTextSet(): void public function testRenderHtmlOnlyWithTextSet(): void

View File

@ -59,7 +59,7 @@ class JsonManifestVersionStrategy implements VersionStrategyInterface
$this->manifestData = json_decode(file_get_contents($this->manifestPath), true); $this->manifestData = json_decode(file_get_contents($this->manifestPath), true);
if (0 < json_last_error()) { 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());
} }
} }

View File

@ -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 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) { if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput(); $output = $output->getErrorOutput();
} }

View File

@ -33,7 +33,7 @@ class QuestionHelper extends Helper
{ {
private $inputStream; private $inputStream;
private static $shell; private static $shell;
private static $stty; private static $stty = true;
/** /**
* Asks a question to the user. * Asks a question to the user.
@ -107,7 +107,7 @@ class QuestionHelper extends Helper
$inputStream = $this->inputStream ?: STDIN; $inputStream = $this->inputStream ?: STDIN;
$autocomplete = $question->getAutocompleterCallback(); $autocomplete = $question->getAutocompleterCallback();
if (null === $autocomplete || !Terminal::hasSttyAvailable()) { if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
$ret = false; $ret = false;
if ($question->isHidden()) { if ($question->isHidden()) {
try { try {
@ -415,7 +415,7 @@ class QuestionHelper extends Helper
return $value; return $value;
} }
if (Terminal::hasSttyAvailable()) { if (self::$stty && Terminal::hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g'); $sttyMode = shell_exec('stty -g');
shell_exec('stty -echo'); shell_exec('stty -echo');

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Console\Tests\Helper; namespace Symfony\Component\Console\Tests\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\FormatterHelper; use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Helper\HelperSet;
@ -783,6 +784,35 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
$this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $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);
// <UP ARROW><UP ARROW><NEWLINE><DOWN ARROW><DOWN ARROW><NEWLINE>
// 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() public function testTraversableMultiselectAutocomplete()
{ {
// <NEWLINE> // <NEWLINE>

View File

@ -227,7 +227,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface
$env = json_decode($env, true); $env = json_decode($env, true);
if (JSON_ERROR_NONE !== json_last_error()) { 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)) { if (null !== $env && !\is_array($env)) {

View File

@ -97,7 +97,7 @@ class Filesystem
if (!is_dir($dir)) { 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 // 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) { 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); throw new IOException(sprintf('Failed to create "%s".', $dir), 0, null, $dir);
} }
@ -167,16 +167,16 @@ class Filesystem
if (is_link($file)) { if (is_link($file)) {
// See https://bugs.php.net/52176 // See https://bugs.php.net/52176
if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) { 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)) { } elseif (is_dir($file)) {
$this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)); $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
if (!self::box('rmdir', $file) && file_exists($file)) { 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)) { } 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);
} }
} }
} }

View File

@ -63,12 +63,16 @@ class FormValidator extends ConstraintValidator
/** @var Constraint[] $constraints */ /** @var Constraint[] $constraints */
$constraints = $config->getOption('constraints', []); $constraints = $config->getOption('constraints', []);
$hasChildren = $form->count() > 0;
if ($hasChildren && $form->isRoot()) {
$this->resolvedGroups = new \SplObjectStorage();
}
if ($groups instanceof GroupSequence) { if ($groups instanceof GroupSequence) {
// Validate the data, the form AND nested fields in sequence // Validate the data, the form AND nested fields in sequence
$violationsCount = $this->context->getViolations()->count(); $violationsCount = $this->context->getViolations()->count();
$fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s'; $fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s';
$hasChildren = $form->count() > 0;
$this->resolvedGroups = $hasChildren ? new \SplObjectStorage() : null;
foreach ($groups->groups as $group) { foreach ($groups->groups as $group) {
if ($validateDataGraph) { if ($validateDataGraph) {
@ -86,7 +90,8 @@ class FormValidator extends ConstraintValidator
// sequence recursively, thus some fields could fail // sequence recursively, thus some fields could fail
// in different steps without breaking early enough // in different steps without breaking early enough
$this->resolvedGroups[$field] = (array) $group; $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 @@ class FormValidator extends ConstraintValidator
break; break;
} }
} }
if ($hasChildren) {
// destroy storage at the end of the sequence to avoid memory leaks
$this->resolvedGroups = null;
}
} else { } else {
$fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s';
if ($validateDataGraph) { if ($validateDataGraph) {
$validator->atPath('data')->validate($data, null, $groups); $validator->atPath('data')->validate($data, null, $groups);
} }
@ -131,6 +133,19 @@ class FormValidator extends ConstraintValidator
foreach ($groupedConstraints as $group => $constraint) { foreach ($groupedConstraints as $group => $constraint) {
$validator->atPath('data')->validate($data, $constraint, $group); $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()) { } elseif (!$form->isSynchronized()) {
$childrenSynchronized = true; $childrenSynchronized = true;

View File

@ -13,7 +13,7 @@ namespace Symfony\Component\Form\Extension\Validator;
use Symfony\Component\Form\AbstractExtension; use Symfony\Component\Form\AbstractExtension;
use Symfony\Component\Form\Extension\Validator\Constraints\Form; 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\Mapping\ClassMetadata;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
@ -37,7 +37,7 @@ class ValidatorExtension extends AbstractExtension
/* @var $metadata ClassMetadata */ /* @var $metadata ClassMetadata */
$metadata->addConstraint(new Form()); $metadata->addConstraint(new Form());
$metadata->addPropertyConstraint('children', new Valid()); $metadata->addConstraint(new Traverse(false));
$this->validator = $validator; $this->validator = $validator;
} }

View File

@ -1055,7 +1055,7 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac
$value = $transformer->transform($value); $value = $transformer->transform($value);
} }
} catch (TransformationFailedException $exception) { } 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; return $value;
@ -1077,7 +1077,7 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac
$value = $transformers[$i]->reverseTransform($value); $value = $transformers[$i]->reverseTransform($value);
} }
} catch (TransformationFailedException $exception) { } 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; return $value;
@ -1106,7 +1106,7 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac
$value = $transformer->transform($value); $value = $transformer->transform($value);
} }
} catch (TransformationFailedException $exception) { } 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; return $value;
@ -1130,7 +1130,7 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac
$value = $transformers[$i]->reverseTransform($value); $value = $transformers[$i]->reverseTransform($value);
} }
} catch (TransformationFailedException $exception) { } 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; return $value;

View File

@ -6,8 +6,8 @@
<class name="Symfony\Component\Form\Form"> <class name="Symfony\Component\Form\Form">
<constraint name="Symfony\Component\Form\Extension\Validator\Constraints\Form" /> <constraint name="Symfony\Component\Form\Extension\Validator\Constraints\Form" />
<property name="children"> <constraint name="Symfony\Component\Validator\Constraints\Traverse">
<constraint name="Valid" /> <option name="traverse">false</option>
</property> </constraint>
</class> </class>
</constraint-mapping> </constraint-mapping>

View File

@ -14,6 +14,10 @@
<source>The CSRF token is invalid. Please try to resubmit the form.</source> <source>The CSRF token is invalid. Please try to resubmit the form.</source>
<target>CSRF token je neplatný. Zkuste prosím znovu odeslat formulář.</target> <target>CSRF token je neplatný. Zkuste prosím znovu odeslat formulář.</target>
</trans-unit> </trans-unit>
<trans-unit id="99">
<source>This value is not a valid HTML5 color.</source>
<target>Tato hodnota není platná HTML5 barva.</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -14,6 +14,10 @@
<source>The CSRF token is invalid. Please try to resubmit the form.</source> <source>The CSRF token is invalid. Please try to resubmit the form.</source>
<target>Érvénytelen CSRF token. Kérem, próbálja újra elküldeni az űrlapot.</target> <target>Érvénytelen CSRF token. Kérem, próbálja újra elküldeni az űrlapot.</target>
</trans-unit> </trans-unit>
<trans-unit id="99">
<source>This value is not a valid HTML5 color.</source>
<target>Ez az érték nem egy érvényes HTML5 szín.</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -658,7 +658,8 @@ class FormValidatorTest extends ConstraintValidatorTestCase
$this->assertTrue($form->isSubmitted()); $this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized()); $this->assertTrue($form->isSynchronized());
$this->expectNoValidate();
$this->expectValidateValueAt(0, 'children[child]', $form->get('child'), new Form());
$this->validator->validate($form, new Form()); $this->validator->validate($form, new Form());
@ -681,7 +682,8 @@ class FormValidatorTest extends ConstraintValidatorTestCase
$this->assertTrue($form->isSubmitted()); $this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized()); $this->assertTrue($form->isSynchronized());
$this->expectNoValidate();
$this->expectValidateValueAt(0, 'children[child]', $form->get('child'), new Form());
$this->validator->validate($form, new Form()); $this->validator->validate($form, new Form());

View File

@ -54,9 +54,8 @@ class ValidatorExtensionTest extends TestCase
$this->assertInstanceOf(FormConstraint::class, $metadata->getConstraints()[0]); $this->assertInstanceOf(FormConstraint::class, $metadata->getConstraints()[0]);
$this->assertSame(CascadingStrategy::NONE, $metadata->cascadingStrategy); $this->assertSame(CascadingStrategy::NONE, $metadata->cascadingStrategy);
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->traversalStrategy); $this->assertSame(TraversalStrategy::NONE, $metadata->traversalStrategy);
$this->assertSame(CascadingStrategy::CASCADE, $metadata->getPropertyMetadata('children')[0]->cascadingStrategy); $this->assertCount(0, $metadata->getPropertyMetadata('children'));
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->getPropertyMetadata('children')[0]->traversalStrategy);
} }
public function testDataConstraintsInvalidateFormEvenIfFieldIsNotSubmitted() public function testDataConstraintsInvalidateFormEvenIfFieldIsNotSubmitted()
@ -142,6 +141,33 @@ class ValidatorExtensionTest extends TestCase
$this->assertInstanceOf(Length::class, $errors[1]->getCause()->getConstraint()); $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 = []) private function createForm($type, $data = null, array $options = [])
{ {
$validator = Validation::createValidatorBuilder() $validator = Validation::createValidatorBuilder()

View File

@ -9,6 +9,8 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Symfony\Component\HttpClient\Tests\DataCollector;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector; use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector;
use Symfony\Component\HttpClient\NativeHttpClient; use Symfony\Component\HttpClient\NativeHttpClient;
@ -19,9 +21,13 @@ use Symfony\Contracts\HttpClient\Test\TestHttpServer;
class HttpClientDataCollectorTest extends TestCase class HttpClientDataCollectorTest extends TestCase
{ {
public function testItCollectsRequestCount() public static function setUpBeforeClass(): void
{ {
TestHttpServer::start(); TestHttpServer::start();
}
public function testItCollectsRequestCount()
{
$httpClient1 = $this->httpClientThatHasTracedRequests([ $httpClient1 = $this->httpClientThatHasTracedRequests([
[ [
'method' => 'GET', 'method' => 'GET',
@ -50,7 +56,6 @@ class HttpClientDataCollectorTest extends TestCase
public function testItCollectsErrorCount() public function testItCollectsErrorCount()
{ {
TestHttpServer::start();
$httpClient1 = $this->httpClientThatHasTracedRequests([ $httpClient1 = $this->httpClientThatHasTracedRequests([
[ [
'method' => 'GET', 'method' => 'GET',
@ -80,7 +85,6 @@ class HttpClientDataCollectorTest extends TestCase
public function testItCollectsErrorCountByClient() public function testItCollectsErrorCountByClient()
{ {
TestHttpServer::start();
$httpClient1 = $this->httpClientThatHasTracedRequests([ $httpClient1 = $this->httpClientThatHasTracedRequests([
[ [
'method' => 'GET', 'method' => 'GET',
@ -113,7 +117,6 @@ class HttpClientDataCollectorTest extends TestCase
public function testItCollectsTracesByClient() public function testItCollectsTracesByClient()
{ {
TestHttpServer::start();
$httpClient1 = $this->httpClientThatHasTracedRequests([ $httpClient1 = $this->httpClientThatHasTracedRequests([
[ [
'method' => 'GET', 'method' => 'GET',
@ -146,7 +149,6 @@ class HttpClientDataCollectorTest extends TestCase
public function testItIsEmptyAfterReset() public function testItIsEmptyAfterReset()
{ {
TestHttpServer::start();
$httpClient1 = $this->httpClientThatHasTracedRequests([ $httpClient1 = $this->httpClientThatHasTracedRequests([
[ [
'method' => 'GET', 'method' => 'GET',

View File

@ -22,8 +22,6 @@ use Symfony\Contracts\HttpClient\Test\TestHttpServer;
class HttplugClientTest extends TestCase class HttplugClientTest extends TestCase
{ {
private static $server;
public static function setUpBeforeClass(): void public static function setUpBeforeClass(): void
{ {
TestHttpServer::start(); TestHttpServer::start();

View File

@ -21,8 +21,6 @@ use Symfony\Contracts\HttpClient\Test\TestHttpServer;
class Psr18ClientTest extends TestCase class Psr18ClientTest extends TestCase
{ {
private static $server;
public static function setUpBeforeClass(): void public static function setUpBeforeClass(): void
{ {
TestHttpServer::start(); TestHttpServer::start();

View File

@ -64,7 +64,7 @@ class ControllerResolver implements ControllerResolverInterface
} }
if (!\is_callable($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; return $controller;
@ -72,7 +72,7 @@ class ControllerResolver implements ControllerResolverInterface
if (\is_object($controller)) { if (\is_object($controller)) {
if (!\is_callable($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; return $controller;
@ -89,7 +89,7 @@ class ControllerResolver implements ControllerResolverInterface
} }
if (!\is_callable($callable)) { 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; return $callable;

View File

@ -46,7 +46,7 @@ class JsonBundleReader implements BundleReaderInterface
$data = json_decode(file_get_contents($fileName), true); $data = json_decode(file_get_contents($fileName), true);
if (null === $data) { 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; return $data;

View File

@ -38,7 +38,7 @@ class EntryManager implements EntryManagerInterface
$con = $this->getConnectionResource(); $con = $this->getConnectionResource();
if (!@ldap_add($con, $entry->getDn(), $entry->getAttributes())) { if (!@ldap_add($con, $entry->getDn(), $entry->getAttributes())) {
throw new LdapException(sprintf('Could not add entry "%s": '.ldap_error($con), $entry->getDn())); throw new LdapException(sprintf('Could not add entry "%s": ', $entry->getDn()).ldap_error($con));
} }
return $this; return $this;
@ -52,7 +52,7 @@ class EntryManager implements EntryManagerInterface
$con = $this->getConnectionResource(); $con = $this->getConnectionResource();
if (!@ldap_modify($con, $entry->getDn(), $entry->getAttributes())) { if (!@ldap_modify($con, $entry->getDn(), $entry->getAttributes())) {
throw new LdapException(sprintf('Could not update entry "%s": '.ldap_error($con), $entry->getDn())); throw new LdapException(sprintf('Could not update entry "%s": ', $entry->getDn()).ldap_error($con));
} }
} }
@ -64,7 +64,7 @@ class EntryManager implements EntryManagerInterface
$con = $this->getConnectionResource(); $con = $this->getConnectionResource();
if (!@ldap_delete($con, $entry->getDn())) { if (!@ldap_delete($con, $entry->getDn())) {
throw new LdapException(sprintf('Could not remove entry "%s": '.ldap_error($con), $entry->getDn())); throw new LdapException(sprintf('Could not remove entry "%s": ', $entry->getDn()).ldap_error($con));
} }
} }
@ -79,7 +79,7 @@ class EntryManager implements EntryManagerInterface
$con = $this->getConnectionResource(); $con = $this->getConnectionResource();
if (!@ldap_mod_add($con, $entry->getDn(), [$attribute => $values])) { 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)); throw new LdapException(sprintf('Could not add values to entry "%s", attribute %s: ', $entry->getDn(), $attribute).ldap_error($con));
} }
} }
@ -94,7 +94,7 @@ class EntryManager implements EntryManagerInterface
$con = $this->getConnectionResource(); $con = $this->getConnectionResource();
if (!@ldap_mod_del($con, $entry->getDn(), [$attribute => $values])) { 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)); throw new LdapException(sprintf('Could not remove values from entry "%s", attribute %s: ', $entry->getDn(), $attribute).ldap_error($con));
} }
} }
@ -106,7 +106,7 @@ class EntryManager implements EntryManagerInterface
$con = $this->getConnectionResource(); $con = $this->getConnectionResource();
if (!@ldap_rename($con, $entry->getDn(), $newRdn, null, $removeOldRdn)) { 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)); throw new LdapException(sprintf('Could not rename entry "%s" to "%s": ', $entry->getDn(), $newRdn).ldap_error($con));
} }
} }
@ -122,7 +122,7 @@ class EntryManager implements EntryManagerInterface
$rdn = $this->parseRdnFromEntry($entry); $rdn = $this->parseRdnFromEntry($entry);
// deleteOldRdn does not matter here, since the Rdn will not be changing in the move. // deleteOldRdn does not matter here, since the Rdn will not be changing in the move.
if (!@ldap_rename($con, $entry->getDn(), $rdn, $newParent, true)) { 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)); throw new LdapException(sprintf('Could not move entry "%s" to "%s": ', $entry->getDn(), $newParent).ldap_error($con));
} }
} }
@ -152,7 +152,7 @@ class EntryManager implements EntryManagerInterface
} }
if (!@ldap_modify_batch($this->getConnectionResource(), $dn, $operationsMapped)) { if (!@ldap_modify_batch($this->getConnectionResource(), $dn, $operationsMapped)) {
throw new UpdateOperationException(sprintf('Error executing UpdateOperation on "%s": '.ldap_error($this->getConnectionResource()), $dn)); throw new UpdateOperationException(sprintf('Error executing UpdateOperation on "%s": ', $dn).ldap_error($this->getConnectionResource()));
} }
} }

View File

@ -65,7 +65,7 @@ class SesApiTransport extends AbstractApiTransport
$result = new \SimpleXMLElement($response->getContent(false)); $result = new \SimpleXMLElement($response->getContent(false));
if (200 !== $response->getStatusCode()) { 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'; $property = $payload['Action'].'Result';

View File

@ -65,7 +65,7 @@ class SesHttpTransport extends AbstractHttpTransport
$result = new \SimpleXMLElement($response->getContent(false)); $result = new \SimpleXMLElement($response->getContent(false));
if (200 !== $response->getStatusCode()) { 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); $message->setMessageId($result->SendRawEmailResult->MessageId);

View File

@ -51,7 +51,7 @@ class MandrillApiTransport extends AbstractApiTransport
$result = $response->toArray(false); $result = $response->toArray(false);
if (200 !== $response->getStatusCode()) { if (200 !== $response->getStatusCode()) {
if ('error' === ($result['status'] ?? false)) { 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); throw new HttpTransportException(sprintf('Unable to send an email (code %d).', $result['code']), $response);

View File

@ -57,7 +57,7 @@ class MandrillHttpTransport extends AbstractHttpTransport
$result = $response->toArray(false); $result = $response->toArray(false);
if (200 !== $response->getStatusCode()) { if (200 !== $response->getStatusCode()) {
if ('error' === ($result['status'] ?? false)) { 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); throw new HttpTransportException(sprintf('Unable to send an email (code %d).', $result['code']), $response);

View File

@ -65,10 +65,10 @@ class MailgunApiTransport extends AbstractApiTransport
$result = $response->toArray(false); $result = $response->toArray(false);
if (200 !== $response->getStatusCode()) { if (200 !== $response->getStatusCode()) {
if ('application/json' === $response->getHeaders(false)['content-type'][0]) { 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']); $sentMessage->setMessageId($result['id']);

View File

@ -67,10 +67,10 @@ class MailgunHttpTransport extends AbstractHttpTransport
$result = $response->toArray(false); $result = $response->toArray(false);
if (200 !== $response->getStatusCode()) { if (200 !== $response->getStatusCode()) {
if ('application/json' === $response->getHeaders(false)['content-type'][0]) { 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']); $message->setMessageId($result['id']);

View File

@ -54,7 +54,7 @@ class PostmarkApiTransport extends AbstractApiTransport
$result = $response->toArray(false); $result = $response->toArray(false);
if (200 !== $response->getStatusCode()) { 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']); $sentMessage->setMessageId($result['MessageID']);

View File

@ -53,7 +53,7 @@ class SendgridApiTransport extends AbstractApiTransport
if (202 !== $response->getStatusCode()) { if (202 !== $response->getStatusCode()) {
$errors = $response->toArray(false); $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]); $sentMessage->setMessageId($response->getHeaders(false)['x-message-id'][0]);

View File

@ -135,7 +135,7 @@ final class SocketStream extends AbstractStream
$streamContext = stream_context_create($options); $streamContext = stream_context_create($options);
set_error_handler(function ($type, $msg) { 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 { try {
$this->stream = stream_socket_client($this->url, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $streamContext); $this->stream = stream_socket_client($this->url, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $streamContext);

View File

@ -70,7 +70,7 @@ final class NexmoTransport extends AbstractTransport
$result = $response->toArray(false); $result = $response->toArray(false);
foreach ($result['messages'] as $msg) { foreach ($result['messages'] as $msg) {
if ($msg['status'] ?? false) { 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);
} }
} }
} }

View File

@ -80,7 +80,7 @@ final class TelegramTransport extends AbstractTransport
if (200 !== $response->getStatusCode()) { if (200 !== $response->getStatusCode()) {
$result = $response->toArray(false); $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);
} }
} }
} }

View File

@ -70,7 +70,7 @@ final class TwilioTransport extends AbstractTransport
if (201 !== $response->getStatusCode()) { if (201 !== $response->getStatusCode()) {
$error = $response->toArray(false); $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);
} }
} }
} }

View File

@ -1596,7 +1596,7 @@ class Process implements \IteratorAggregate
{ {
return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $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]]) { 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]]); return $this->escapeArgument($env[$matches[1]]);

View File

@ -392,9 +392,15 @@ class PropertyAccessor implements PropertyAccessorInterface
try { try {
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}(); $result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
} catch (\TypeError $e) { } catch (\TypeError $e) {
list($trace) = $e->getTrace();
// handle uninitialized properties in PHP >= 7 // handle uninitialized properties in PHP >= 7
if (preg_match((sprintf('/^Return value of %s::%s\(\) must be of (?:the )?type (\w+), null returned$/', preg_quote(\get_class($object)), $access[self::ACCESS_NAME])), $e->getMessage(), $matches)) { if (__FILE__ === $trace['file']
throw new AccessException(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), $access[self::ACCESS_NAME], $matches[1]), 0, $e); && $access[self::ACCESS_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 AccessException(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) ?: 'class').'@anonymous', $access[self::ACCESS_NAME], $matches[1]), 0, $e);
} }
throw $e; throw $e;

View File

@ -155,6 +155,59 @@ class PropertyAccessorTest extends TestCase
$this->propertyAccessor->getValue(new UninitializedPrivateProperty(), 'uninitialized'); $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() public function testGetValueThrowsExceptionIfNotArrayAccess()
{ {
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException'); $this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException');

View File

@ -23,7 +23,7 @@ use Symfony\Component\Routing\RouteCompiler;
/** /**
* AnnotationClassLoader loads routing information from a PHP class and its methods. * 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 * time, this method should define some PHP callable to be called for the route
* (a controller in MVC speak). * (a controller in MVC speak).
* *

View File

@ -94,7 +94,7 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
$dateTimeErrors = \DateTime::class === $type ? \DateTime::getLastErrors() : \DateTimeImmutable::getLastErrors(); $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 { try {

View File

@ -40,7 +40,7 @@ class IntlFormatter implements IntlFormatterInterface
try { try {
$this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message); $this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message);
} catch (\IntlException $e) { } 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 @@ class IntlFormatter implements IntlFormatterInterface
} }
if (false === $message = $formatter->format($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; return $message;

View File

@ -58,7 +58,7 @@ class XliffFileLoader implements LoaderInterface
$xliffVersion = XliffUtils::getVersionNumber($dom); $xliffVersion = XliffUtils::getVersionNumber($dom);
if ($errors = XliffUtils::validateSchema($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) { if ('1.2' === $xliffVersion) {

View File

@ -13,8 +13,10 @@ namespace Symfony\Component\Validator\Constraints;
use Symfony\Component\HttpFoundation\File\File as FileObject; use Symfony\Component\HttpFoundation\File\File as FileObject;
use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\LogicException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException; use Symfony\Component\Validator\Exception\UnexpectedValueException;
@ -170,12 +172,17 @@ class FileValidator extends ConstraintValidator
} }
if ($constraint->mimeTypes) { if ($constraint->mimeTypes) {
if (!$value instanceof FileObject) { if ($value instanceof FileObject) {
$value = new FileObject($value); $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; $mimeTypes = (array) $constraint->mimeTypes;
$mime = $value->getMimeType();
foreach ($mimeTypes as $mimeType) { foreach ($mimeTypes as $mimeType) {
if ($mimeType === $mime) { if ($mimeType === $mime) {

View File

@ -334,6 +334,54 @@
<source>This value should be valid JSON.</source> <source>This value should be valid JSON.</source>
<target>Tato hodnota musí být validní JSON.</target> <target>Tato hodnota musí být validní JSON.</target>
</trans-unit> </trans-unit>
<trans-unit id="87">
<source>This collection should contain only unique elements.</source>
<target>Tato kolekce musí obsahovat pouze unikátní prvky.</target>
</trans-unit>
<trans-unit id="88">
<source>This value should be positive.</source>
<target>Tato hodnota musí být kladná.</target>
</trans-unit>
<trans-unit id="89">
<source>This value should be either positive or zero.</source>
<target>Tato hodnota musí být buď kladná nebo nula.</target>
</trans-unit>
<trans-unit id="90">
<source>This value should be negative.</source>
<target>Tato hodnota musí být záporná.</target>
</trans-unit>
<trans-unit id="91">
<source>This value should be either negative or zero.</source>
<target>Tato hodnota musí být buď záporná nebo nula.</target>
</trans-unit>
<trans-unit id="92">
<source>This value is not a valid timezone.</source>
<target>Tato časová zóna neexistuje.</target>
</trans-unit>
<trans-unit id="93">
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
<target>Zadané heslo bylo součástí úniku dat, takže ho není možné použít. Použijte prosím jiné heslo.</target>
</trans-unit>
<trans-unit id="94">
<source>This value should be between {{ min }} and {{ max }}.</source>
<target>Hodnota musí být mezi {{ min }} a {{ max }}.</target>
</trans-unit>
<trans-unit id="95">
<source>This value is not a valid hostname.</source>
<target>Tato hodnota není platný hostname.</target>
</trans-unit>
<trans-unit id="96">
<source>The number of elements in this collection should be a multiple of {{ compared_value }}.</source>
<target>Počet prvků v této kolekci musí být násobek {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="97">
<source>This value should satisfy at least one of the following constraints:</source>
<target>Tato hodnota musí splňovat alespoň jedno z následujících omezení:</target>
</trans-unit>
<trans-unit id="98">
<source>Each element of this collection should satisfy its own set of constraints.</source>
<target>Každý prvek v této kolekci musí splňovat svá vlastní omezení.</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -19,6 +19,8 @@ use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull; 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\ConstraintValidatorFactory;
use Symfony\Component\Validator\Context\ExecutionContextFactory; use Symfony\Component\Validator\Context\ExecutionContextFactory;
use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\ClassMetadata;
@ -159,4 +161,18 @@ class RecursiveValidatorTest extends AbstractTest
$this->assertInstanceOf(Length::class, $violations->get(1)->getConstraint()); $this->assertInstanceOf(Length::class, $violations->get(1)->getConstraint());
$this->assertInstanceOf(Length::class, $violations->get(2)->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);
}
} }

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Validator\Validator;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Composite; use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Constraints\Existence;
use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
@ -718,6 +719,10 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
$context->setGroup($group); $context->setGroup($group);
foreach ($metadata->findConstraints($group) as $constraint) { foreach ($metadata->findConstraints($group) as $constraint) {
if ($constraint instanceof Existence) {
continue;
}
// Prevent duplicate validation of constraints, in the case // Prevent duplicate validation of constraints, in the case
// that constraints belong to multiple validated groups // that constraints belong to multiple validated groups
if (null !== $cacheKey) { if (null !== $cacheKey) {

View File

@ -41,7 +41,7 @@ class DumpServer
public function start(): void public function start(): void
{ {
if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) { 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);
} }
} }

View File

@ -19,12 +19,12 @@ use Symfony\Component\Process\Process;
*/ */
class TestHttpServer class TestHttpServer
{ {
private static $started; private static $process;
public static function start() public static function start()
{ {
if (self::$started) { if (self::$process) {
return; self::$process->stop();
} }
$finder = new PhpExecutableFinder(); $finder = new PhpExecutableFinder();
@ -32,9 +32,10 @@ class TestHttpServer
$process->setWorkingDirectory(__DIR__.'/Fixtures/web'); $process->setWorkingDirectory(__DIR__.'/Fixtures/web');
$process->start(); $process->start();
register_shutdown_function([$process, 'stop']); do {
sleep('\\' === \DIRECTORY_SEPARATOR ? 10 : 1); usleep(50000);
} while (!@fopen('http://127.0.0.1:8057/', 'r'));
self::$started = true; self::$process = $process;
} }
} }