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
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

View File

@ -102,7 +102,7 @@ EOF
}
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) {

View File

@ -74,6 +74,6 @@ final class BodyRenderer implements BodyRendererInterface
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
{
$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();
$this->assertInstanceOf(AlternativePart::class, $body);
$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

View File

@ -59,7 +59,7 @@ class JsonManifestVersionStrategy implements VersionStrategyInterface
$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());
}
}

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
{
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();
}

View File

@ -33,7 +33,7 @@ class QuestionHelper extends Helper
{
private $inputStream;
private static $shell;
private static $stty;
private static $stty = true;
/**
* Asks a question to the user.
@ -107,7 +107,7 @@ class QuestionHelper extends Helper
$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 {
@ -415,7 +415,7 @@ class QuestionHelper extends Helper
return $value;
}
if (Terminal::hasSttyAvailable()) {
if (self::$stty && Terminal::hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
shell_exec('stty -echo');

View File

@ -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 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
$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()
{
// <NEWLINE>

View File

@ -227,7 +227,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface
$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)) {

View File

@ -97,7 +97,7 @@ class Filesystem
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 @@ class Filesystem
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);
}
}
}

View File

@ -63,12 +63,16 @@ class FormValidator extends ConstraintValidator
/** @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 @@ class FormValidator extends ConstraintValidator
// 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 @@ class FormValidator extends ConstraintValidator
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 @@ class FormValidator extends ConstraintValidator
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;

View File

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

View File

@ -1055,7 +1055,7 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac
$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;
@ -1077,7 +1077,7 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac
$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;
@ -1106,7 +1106,7 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac
$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;
@ -1130,7 +1130,7 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac
$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;

View File

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

View File

@ -14,6 +14,10 @@
<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>
</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>
</file>
</xliff>

View File

@ -14,6 +14,10 @@
<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>
</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>
</file>
</xliff>

View File

@ -658,7 +658,8 @@ class FormValidatorTest extends ConstraintValidatorTestCase
$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());
@ -681,7 +682,8 @@ class FormValidatorTest extends ConstraintValidatorTestCase
$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());

View File

@ -54,9 +54,8 @@ class ValidatorExtensionTest extends TestCase
$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 @@ class ValidatorExtensionTest extends TestCase
$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()

View File

@ -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 @@ use Symfony\Contracts\HttpClient\Test\TestHttpServer;
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 @@ class HttpClientDataCollectorTest extends TestCase
public function testItCollectsErrorCount()
{
TestHttpServer::start();
$httpClient1 = $this->httpClientThatHasTracedRequests([
[
'method' => 'GET',
@ -80,7 +85,6 @@ class HttpClientDataCollectorTest extends TestCase
public function testItCollectsErrorCountByClient()
{
TestHttpServer::start();
$httpClient1 = $this->httpClientThatHasTracedRequests([
[
'method' => 'GET',
@ -113,7 +117,6 @@ class HttpClientDataCollectorTest extends TestCase
public function testItCollectsTracesByClient()
{
TestHttpServer::start();
$httpClient1 = $this->httpClientThatHasTracedRequests([
[
'method' => 'GET',
@ -146,7 +149,6 @@ class HttpClientDataCollectorTest extends TestCase
public function testItIsEmptyAfterReset()
{
TestHttpServer::start();
$httpClient1 = $this->httpClientThatHasTracedRequests([
[
'method' => 'GET',

View File

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

View File

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

View File

@ -64,7 +64,7 @@ class ControllerResolver implements ControllerResolverInterface
}
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 @@ class ControllerResolver implements ControllerResolverInterface
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 @@ class ControllerResolver implements ControllerResolverInterface
}
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;

View File

@ -46,7 +46,7 @@ class JsonBundleReader implements BundleReaderInterface
$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;

View File

@ -38,7 +38,7 @@ class EntryManager implements EntryManagerInterface
$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()));
throw new LdapException(sprintf('Could not add entry "%s": ', $entry->getDn()).ldap_error($con));
}
return $this;
@ -52,7 +52,7 @@ class EntryManager implements EntryManagerInterface
$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()));
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();
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();
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();
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();
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);
// 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));
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)) {
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));
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';

View File

@ -65,7 +65,7 @@ class SesHttpTransport extends AbstractHttpTransport
$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);

View File

@ -51,7 +51,7 @@ class MandrillApiTransport extends AbstractApiTransport
$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);

View File

@ -57,7 +57,7 @@ class MandrillHttpTransport extends AbstractHttpTransport
$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);

View File

@ -65,10 +65,10 @@ class MailgunApiTransport extends AbstractApiTransport
$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']);

View File

@ -67,10 +67,10 @@ class MailgunHttpTransport extends AbstractHttpTransport
$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']);

View File

@ -54,7 +54,7 @@ class PostmarkApiTransport extends AbstractApiTransport
$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']);

View File

@ -53,7 +53,7 @@ class SendgridApiTransport extends AbstractApiTransport
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]);

View File

@ -135,7 +135,7 @@ final class SocketStream extends AbstractStream
$streamContext = stream_context_create($options);
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, $this->timeout, STREAM_CLIENT_CONNECT, $streamContext);

View File

@ -70,7 +70,7 @@ final class NexmoTransport extends AbstractTransport
$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);
}
}
}

View File

@ -80,7 +80,7 @@ final class TelegramTransport extends AbstractTransport
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);
}
}
}

View File

@ -70,7 +70,7 @@ final class TwilioTransport extends AbstractTransport
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);
}
}
}

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) {
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]]);

View File

@ -392,9 +392,15 @@ class PropertyAccessor implements PropertyAccessorInterface
try {
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
} catch (\TypeError $e) {
list($trace) = $e->getTrace();
// 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)) {
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);
if (__FILE__ === $trace['file']
&& $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;

View File

@ -155,6 +155,59 @@ class PropertyAccessorTest extends TestCase
$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');

View File

@ -23,7 +23,7 @@ use Symfony\Component\Routing\RouteCompiler;
/**
* 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).
*

View File

@ -94,7 +94,7 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
$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 {

View File

@ -40,7 +40,7 @@ class IntlFormatter implements IntlFormatterInterface
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 @@ class IntlFormatter implements IntlFormatterInterface
}
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;

View File

@ -58,7 +58,7 @@ class XliffFileLoader implements LoaderInterface
$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) {

View File

@ -13,8 +13,10 @@ namespace Symfony\Component\Validator\Constraints;
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 @@ class FileValidator extends ConstraintValidator
}
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) {

View File

@ -334,6 +334,54 @@
<source>This value should be valid JSON.</source>
<target>Tato hodnota musí být validní JSON.</target>
</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>
</file>
</xliff>

View File

@ -19,6 +19,8 @@ use Symfony\Component\Validator\Constraints\IsTrue;
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 @@ class RecursiveValidatorTest extends AbstractTest
$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);
}
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Validator\Validator;
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 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
$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) {

View File

@ -41,7 +41,7 @@ class DumpServer
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);
}
}

View File

@ -19,12 +19,12 @@ use Symfony\Component\Process\Process;
*/
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();
@ -32,9 +32,10 @@ class TestHttpServer
$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;
}
}