Skip to content

Commit

Permalink
Merge branch '4.4' into 5.0
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nicolas-grekas committed May 30, 2020
2 parents 8bbf62e + 143718c commit 527f3f3
Show file tree
Hide file tree
Showing 49 changed files with 309 additions and 88 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
}

if (!$socket = stream_socket_server($host, $errno, $errstr)) {
throw new RuntimeException(sprintf('Server start failed on "%s": '.$errstr.' '.$errno, $host));
throw new RuntimeException(sprintf('Server start failed on "%s": ', $host).$errstr.' '.$errno);
}

foreach ($this->getLogs($socket) as $clientId => $message) {
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bridge/Twig/Mime/BodyRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,6 @@ private function convertHtmlToText(string $html): string
return $this->converter->convert($html);
}

return strip_tags($html);
return strip_tags(preg_replace('{<(head|style)\b.*?</\1>}i', '', $html));
}
}
5 changes: 3 additions & 2 deletions src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ public function testRenderTextOnly(): 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();
$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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private function getManifestPath(string $path): ?string

$this->manifestData = json_decode(file_get_contents($this->manifestPath), true);
if (0 < json_last_error()) {
throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": '.json_last_error_msg(), $this->manifestPath));
throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": ', $this->manifestPath).json_last_error_msg());
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/Symfony/Component/Console/Helper/ProcessHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/Console/Helper/QuestionHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -107,7 +107,7 @@ private function doAsk(OutputInterface $output, Question $question)
$inputStream = $this->inputStream ?: STDIN;
$autocomplete = $question->getAutocompleterCallback();

if (null === $autocomplete || !Terminal::hasSttyAvailable()) {
if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
$ret = false;
if ($question->isHidden()) {
try {
Expand Down Expand Up @@ -415,7 +415,7 @@ private function getHiddenResponse(OutputInterface $output, $inputStream, bool $
return $value;
}

if (Terminal::hasSttyAvailable()) {
if (self::$stty && Terminal::hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');

shell_exec('stty -echo');
Expand Down
30 changes: 30 additions & 0 deletions src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -783,6 +784,35 @@ public function testTraversableAutocomplete()
$this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
}

public function testDisableSttby()
{
if (!Terminal::hasSttyAvailable()) {
$this->markTestSkipped('`stty` is required to test autocomplete functionality');
}

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('invalid');

QuestionHelper::disableStty();
$dialog = new QuestionHelper();
$dialog->setHelperSet(new HelperSet([new FormatterHelper()]));

$question = new ChoiceQuestion('Please select a bundle', [1 => 'AcmeDemoBundle', 4 => 'AsseticBundle']);
$question->setMaxAttempts(1);

// <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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv)
$env = json_decode($env, true);

if (JSON_ERROR_NONE !== json_last_error()) {
throw new RuntimeException(sprintf('Invalid JSON in env var "%s": '.json_last_error_msg(), $name));
throw new RuntimeException(sprintf('Invalid JSON in env var "%s": ', $name).json_last_error_msg());
}

if (null !== $env && !\is_array($env)) {
Expand Down
8 changes: 4 additions & 4 deletions src/Symfony/Component/Filesystem/Filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public function mkdir($dirs, int $mode = 0777)
if (!is_dir($dir)) {
// The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
if (self::$lastError) {
throw new IOException(sprintf('Failed to create "%s": '.self::$lastError, $dir), 0, null, $dir);
throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir);
}
throw new IOException(sprintf('Failed to create "%s".', $dir), 0, null, $dir);
}
Expand Down Expand Up @@ -167,16 +167,16 @@ public function remove($files)
if (is_link($file)) {
// See https://bugs.php.net/52176
if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove symlink "%s": '.self::$lastError, $file));
throw new IOException(sprintf('Failed to remove symlink "%s": ', $file).self::$lastError);
}
} elseif (is_dir($file)) {
$this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));

if (!self::box('rmdir', $file) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove directory "%s": '.self::$lastError, $file));
throw new IOException(sprintf('Failed to remove directory "%s": ', $file).self::$lastError);
}
} elseif (!self::box('unlink', $file) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove file "%s": '.self::$lastError, $file));
throw new IOException(sprintf('Failed to remove file "%s": ', $file).self::$lastError);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,16 @@ public function validate($form, Constraint $formConstraint)
/** @var Constraint[] $constraints */
$constraints = $config->getOption('constraints', []);

$hasChildren = $form->count() > 0;

if ($hasChildren && $form->isRoot()) {
$this->resolvedGroups = new \SplObjectStorage();
}

if ($groups instanceof GroupSequence) {
// Validate the data, the form AND nested fields in sequence
$violationsCount = $this->context->getViolations()->count();
$fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s';
$hasChildren = $form->count() > 0;
$this->resolvedGroups = $hasChildren ? new \SplObjectStorage() : null;

foreach ($groups->groups as $group) {
if ($validateDataGraph) {
Expand All @@ -86,20 +90,18 @@ public function validate($form, Constraint $formConstraint)
// sequence recursively, thus some fields could fail
// in different steps without breaking early enough
$this->resolvedGroups[$field] = (array) $group;
$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $formConstraint);
$fieldFormConstraint = new Form();
$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint);
}
}

if ($violationsCount < $this->context->getViolations()->count()) {
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);
}
Expand Down Expand Up @@ -131,6 +133,19 @@ public function validate($form, Constraint $formConstraint)
foreach ($groupedConstraints as $group => $constraint) {
$validator->atPath('data')->validate($data, $constraint, $group);
}

foreach ($form->all() as $field) {
if ($field->isSubmitted()) {
$this->resolvedGroups[$field] = $groups;
$fieldFormConstraint = new Form();
$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint);
}
}
}

if ($hasChildren && $form->isRoot()) {
// destroy storage to avoid memory leaks
$this->resolvedGroups = new \SplObjectStorage();
}
} elseif (!$form->isSynchronized()) {
$childrenSynchronized = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

use Symfony\Component\Form\AbstractExtension;
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Constraints\Traverse;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Validator\ValidatorInterface;

Expand All @@ -37,7 +37,7 @@ public function __construct(ValidatorInterface $validator)

/* @var $metadata ClassMetadata */
$metadata->addConstraint(new Form());
$metadata->addPropertyConstraint('children', new Valid());
$metadata->addConstraint(new Traverse(false));

$this->validator = $validator;
}
Expand Down
8 changes: 4 additions & 4 deletions src/Symfony/Component/Form/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -1055,7 +1055,7 @@ private function modelToNorm($value)
$value = $transformer->transform($value);
}
} catch (TransformationFailedException $exception) {
throw new TransformationFailedException(sprintf('Unable to transform data for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
throw new TransformationFailedException(sprintf('Unable to transform data for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
}

return $value;
Expand All @@ -1077,7 +1077,7 @@ private function normToModel($value)
$value = $transformers[$i]->reverseTransform($value);
}
} catch (TransformationFailedException $exception) {
throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
}

return $value;
Expand Down Expand Up @@ -1106,7 +1106,7 @@ private function normToView($value)
$value = $transformer->transform($value);
}
} catch (TransformationFailedException $exception) {
throw new TransformationFailedException(sprintf('Unable to transform value for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
throw new TransformationFailedException(sprintf('Unable to transform value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
}

return $value;
Expand All @@ -1130,7 +1130,7 @@ private function viewToNorm($value)
$value = $transformers[$i]->reverseTransform($value);
}
} catch (TransformationFailedException $exception) {
throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": ', $this->getPropertyPath()).$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
}

return $value;
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/Form/Resources/config/validation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,8 @@ public function testViolationIfExtraData()

$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized());
$this->expectNoValidate();

$this->expectValidateValueAt(0, 'children[child]', $form->get('child'), new Form());

$this->validator->validate($form, new Form());

Expand All @@ -681,7 +682,8 @@ public function testViolationFormatIfMultipleExtraFields()

$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized());
$this->expectNoValidate();

$this->expectValidateValueAt(0, 'children[child]', $form->get('child'), new Form());

$this->validator->validate($form, new Form());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ public function test2Dot5ValidationApi()
$this->assertInstanceOf(FormConstraint::class, $metadata->getConstraints()[0]);

$this->assertSame(CascadingStrategy::NONE, $metadata->cascadingStrategy);
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->traversalStrategy);
$this->assertSame(CascadingStrategy::CASCADE, $metadata->getPropertyMetadata('children')[0]->cascadingStrategy);
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->getPropertyMetadata('children')[0]->traversalStrategy);
$this->assertSame(TraversalStrategy::NONE, $metadata->traversalStrategy);
$this->assertCount(0, $metadata->getPropertyMetadata('children'));
}

public function testDataConstraintsInvalidateFormEvenIfFieldIsNotSubmitted()
Expand Down Expand Up @@ -142,6 +141,33 @@ public function testFieldsValidateInSequenceWithNestedGroupsArray()
$this->assertInstanceOf(Length::class, $errors[1]->getCause()->getConstraint());
}

public function testConstraintsInDifferentGroupsOnSingleField()
{
$form = $this->createForm(FormType::class, null, [
'validation_groups' => new GroupSequence(['group1', 'group2']),
])
->add('foo', TextType::class, [
'constraints' => [
new NotBlank([
'groups' => ['group1'],
]),
new Length([
'groups' => ['group2'],
'max' => 3,
]),
],
]);
$form->submit([
'foo' => 'test@example.com',
]);

$errors = $form->getErrors(true);

$this->assertFalse($form->isValid());
$this->assertCount(1, $errors);
$this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
}

private function createForm($type, $data = null, array $options = [])
{
$validator = Validation::createValidatorBuilder()
Expand Down

0 comments on commit 527f3f3

Please sign in to comment.