Merge branch '4.4' into 5.0

* 4.4:
  [travis] fix CI (ter)
  Revert "[travis][appveyor] don't cache .phpunit"
  silence E_NOTICE triggered since PHP 7.4
  [Form] Removed legacy check in `ValidationListener`
  [HttpClient] fix HTTP/2 support on non-SSL connections - CurlHttpClient only
  Force ping after transport Exception
  do not merge constraints within interfaces
  [Validator] Fixed default group for nested composite constraints
This commit is contained in:
Nicolas Grekas 2020-04-12 11:48:36 +02:00
commit 7e739eeee2
18 changed files with 198 additions and 38 deletions

View File

@ -4,6 +4,7 @@ clone_folder: c:\projects\symfony
cache:
- composer.phar
- .phpunit -> phpunit
init:
- SET PATH=c:\php;%PATH%

View File

@ -35,6 +35,7 @@ matrix:
cache:
directories:
- .phpunit
- php-$MIN_PHP
- ~/php-ext
@ -270,8 +271,10 @@ install:
if [[ !$deps && $PHP = 7.2 ]]; then
phpenv global $PHP
tfold 'composer update' $COMPOSER_UP
tfold 'phpunit install' ./phpunit install
tfold src/Symfony/Component/HttpClient.h2push "docker run -it --rm -v $(pwd):/app -v /usr/local/bin/vulcain:/usr/local/bin/vulcain -w /app php:7.3-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push"
[ -d .phpunit ] && mv .phpunit .phpunit.bak
tfold src/Symfony/Component/HttpClient.h2push "docker run -it --rm -v $(pwd):/app -v $(phpenv which composer):/usr/local/bin/composer -v /usr/local/bin/vulcain:/usr/local/bin/vulcain -w /app php:7.3-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push"
sudo rm .phpunit -rf
[ -d .phpunit.bak ] && mv .phpunit.bak .phpunit
fi
if [[ $PHP != 7.4* && $PHP != $TRAVIS_PHP_VERSION && $TRAVIS_PULL_REQUEST != false ]]; then
@ -280,12 +283,10 @@ install:
fi
([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; cp composer.json composer.bak; composer config platform.ext-mongodb 1.6.0; composer require --dev --no-update mongodb/mongodb ~1.5.0)
if [[ $deps || $PHP != 7.2 ]]; then
tfold 'composer update' $COMPOSER_UP
tfold 'phpunit install' ./phpunit install
fi
tfold 'phpunit install' ./phpunit install
if [[ $deps = high ]]; then
echo "$COMPONENTS" | parallel --gnu "tfold {} 'cd {} && $COMPOSER_UP && $PHPUNIT_X$LEGACY'" || X=1
(cd src/Symfony/Component/HttpFoundation; mv composer.bak composer.json)

View File

@ -1,6 +1,8 @@
#!/usr/bin/env php
<?php
// Cache-Id: 2020-04-10 20:30 UTC
if (!file_exists(__DIR__.'/vendor/symfony/phpunit-bridge/bin/simple-phpunit')) {
echo "Unable to find the `simple-phpunit` script in `vendor/symfony/phpunit-bridge/bin/`.\nPlease run `composer update` before running this command.\n";
exit(1);

View File

@ -50,8 +50,7 @@ class ValidationListener implements EventSubscriberInterface
foreach ($this->validator->validate($form) as $violation) {
// Allow the "invalid" constraint to be put onto
// non-synchronized forms
// ConstraintViolation::getConstraint() must not expect to provide a constraint as long as Symfony\Component\Validator\ExecutionContext exists (before 3.0)
$allowNonSynchronized = (null === $violation->getConstraint() || $violation->getConstraint() instanceof Form) && Form::NOT_SYNCHRONIZED_ERROR === $violation->getCode();
$allowNonSynchronized = $violation->getConstraint() instanceof Form && Form::NOT_SYNCHRONIZED_ERROR === $violation->getCode();
$this->violationMapper->mapViolation($violation, $form, $allowNonSynchronized);
}

View File

@ -141,12 +141,12 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
CURLOPT_CERTINFO => $options['capture_peer_cert_chain'],
];
if (1.0 === (float) $options['http_version']) {
$curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
} elseif (1.1 === (float) $options['http_version'] || 'https:' !== $scheme) {
$curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
} elseif (\defined('CURL_VERSION_HTTP2') && CURL_VERSION_HTTP2 & self::$curlVersion['features']) {
if (\defined('CURL_VERSION_HTTP2') && (CURL_VERSION_HTTP2 & self::$curlVersion['features']) && ('https:' === $scheme || 2.0 === (float) $options['http_version'])) {
$curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
} elseif (1.0 === (float) $options['http_version']) {
$curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
} elseif (1.1 === (float) $options['http_version']) {
$curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
}
if (isset($options['auth_ntlm'])) {

View File

@ -79,7 +79,7 @@ class Logger extends AbstractLogger
}
$formatter = $this->formatter;
fwrite($this->handle, $formatter($level, $message, $context));
@fwrite($this->handle, $formatter($level, $message, $context));
}
private function format(string $level, string $message, array $context): string

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Mailer\Tests\Transport\Smtp;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\TransportException;
use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport;
use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream;
use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
@ -43,6 +44,29 @@ class SmtpTransportTest extends TestCase
$this->assertNotContains("NOOP\r\n", $stream->getCommands());
}
public function testSendPingAfterTransportException(): void
{
$stream = new DummyStream();
$envelope = new Envelope(new Address('sender@example.org'), [new Address('recipient@example.org')]);
$transport = new SmtpTransport($stream);
$transport->send(new RawMessage('Message 1'), $envelope);
$stream->close();
$catch = false;
try {
$transport->send(new RawMessage('Message 2'), $envelope);
} catch (TransportException $exception) {
$catch = true;
}
$this->assertTrue($catch);
$this->assertTrue($stream->isClosed());
$transport->send(new RawMessage('Message 3'), $envelope);
$this->assertFalse($stream->isClosed());
}
public function testSendDoesPingAboveThreshold(): void
{
$stream = new DummyStream();
@ -76,13 +100,23 @@ class DummyStream extends AbstractStream
*/
private $commands;
/**
* @var bool
*/
private $closed = true;
public function initialize(): void
{
$this->closed = false;
$this->nextResponse = '220 localhost';
}
public function write(string $bytes, $debug = true): void
{
if ($this->closed) {
throw new TransportException('Unable to write bytes on the wire.');
}
$this->commands[] = $bytes;
if (0 === strpos($bytes, 'DATA')) {
@ -120,4 +154,14 @@ class DummyStream extends AbstractStream
{
return 'null';
}
public function close(): void
{
$this->closed = true;
}
public function isClosed(): bool
{
return $this->closed;
}
}

View File

@ -206,12 +206,11 @@ class SmtpTransport extends AbstractTransport
$this->stream->flush();
$this->executeCommand("\r\n.\r\n", [250]);
$message->appendDebug($this->stream->getDebug());
$this->lastMessageTime = microtime(true);
} catch (TransportExceptionInterface $e) {
$e->appendDebug($this->stream->getDebug());
$this->lastMessageTime = 0;
throw $e;
} finally {
$this->lastMessageTime = microtime(true);
}
}

View File

@ -88,7 +88,8 @@ abstract class Composite extends Constraint
}
}
$this->groups = array_keys($mergedGroups);
// prevent empty composite constraint to have empty groups
$this->groups = array_keys($mergedGroups) ?: [self::DEFAULT_GROUP];
$this->$compositeOption = $nestedConstraints;
return;

View File

@ -113,34 +113,25 @@ class LazyLoadingMetadataFactory implements MetadataFactoryInterface
private function mergeConstraints(ClassMetadata $metadata)
{
if ($metadata->getReflectionClass()->isInterface()) {
return;
}
// Include constraints from the parent class
if ($parent = $metadata->getReflectionClass()->getParentClass()) {
$metadata->mergeConstraints($this->getMetadataFor($parent->name));
}
$interfaces = $metadata->getReflectionClass()->getInterfaces();
$interfaces = array_filter($interfaces, function (\ReflectionClass $interface) use ($parent, $interfaces) {
$interfaceName = $interface->getName();
if ($parent && $parent->implementsInterface($interfaceName)) {
return false;
}
foreach ($interfaces as $i) {
if ($i !== $interface && $i->implementsInterface($interfaceName)) {
return false;
}
}
return true;
});
// Include constraints from all directly implemented interfaces
foreach ($interfaces as $interface) {
foreach ($metadata->getReflectionClass()->getInterfaces() as $interface) {
if ('Symfony\Component\Validator\GroupSequenceProviderInterface' === $interface->name) {
continue;
}
if ($parent && \in_array($interface->getName(), $parent->getInterfaceNames(), true)) {
continue;
}
$metadata->mergeConstraints($this->getMetadataFor($interface->name));
}
}

View File

@ -100,4 +100,16 @@ class CollectionTest extends TestCase
$this->assertEquals($collection1, $collection2);
}
public function testConstraintHasDefaultGroupWithOptionalValues()
{
$constraint = new Collection([
'foo' => new Required(),
'bar' => new Optional(),
]);
$this->assertEquals(['Default'], $constraint->groups);
$this->assertEquals(['Default'], $constraint->fields['foo']->groups);
$this->assertEquals(['Default'], $constraint->fields['bar']->groups);
}
}

View File

@ -143,6 +143,29 @@ abstract class CollectionValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
public function testExtraFieldsDisallowedWithOptionalValues()
{
$constraint = new Optional();
$data = $this->prepareTestData([
'baz' => 6,
]);
$this->validator->validate($data, new Collection([
'fields' => [
'foo' => $constraint,
],
'extraFieldsMessage' => 'myMessage',
]));
$this->buildViolation('myMessage')
->setParameter('{{ field }}', '"baz"')
->atPath('property.path[baz]')
->setInvalidValue(6)
->setCode(Collection::NO_SUCH_FIELD_ERROR)
->assertRaised();
}
// bug fix
public function testNullNotConsideredExtraField()
{

View File

@ -19,7 +19,7 @@ use Symfony\Component\Validator\Constraints\Valid;
class ConcreteComposite extends Composite
{
public $constraints;
public $constraints = [];
protected function getCompositeOption(): string
{
@ -37,6 +37,30 @@ class ConcreteComposite extends Composite
*/
class CompositeTest extends TestCase
{
public function testConstraintHasDefaultGroup()
{
$constraint = new ConcreteComposite([
new NotNull(),
new NotBlank(),
]);
$this->assertEquals(['Default'], $constraint->groups);
$this->assertEquals(['Default'], $constraint->constraints[0]->groups);
$this->assertEquals(['Default'], $constraint->constraints[1]->groups);
}
public function testNestedCompositeConstraintHasDefaultGroup()
{
$constraint = new ConcreteComposite([
new ConcreteComposite(),
new ConcreteComposite(),
]);
$this->assertEquals(['Default'], $constraint->groups);
$this->assertEquals(['Default'], $constraint->constraints[0]->groups);
$this->assertEquals(['Default'], $constraint->constraints[1]->groups);
}
public function testMergeNestedGroupsIfNoExplicitParentGroup()
{
$constraint = new ConcreteComposite([

View File

@ -0,0 +1,13 @@
<?php
namespace Symfony\Component\Validator\Tests\Fixtures;
abstract class AbstractPropertyGetter implements PropertyGetterInterface
{
private $property;
public function getProperty()
{
return $this->property;
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\Validator\Tests\Fixtures;
interface ChildGetterInterface extends PropertyGetterInterface
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace Symfony\Component\Validator\Tests\Fixtures;
/**
* This class has two paths to PropertyGetterInterface:
* PropertyGetterInterface <- AbstractPropertyGetter <- PropertyGetter
* PropertyGetterInterface <- ChildGetterInterface <- PropertyGetter
*/
class PropertyGetter extends AbstractPropertyGetter implements ChildGetterInterface
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace Symfony\Component\Validator\Tests\Fixtures;
interface PropertyGetterInterface
{
public function getProperty();
}

View File

@ -15,10 +15,13 @@ use PHPUnit\Framework\TestCase;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\PropertyGetter;
use Symfony\Component\Validator\Tests\Fixtures\PropertyGetterInterface;
class LazyLoadingMetadataFactoryTest extends TestCase
{
@ -70,7 +73,6 @@ class LazyLoadingMetadataFactoryTest extends TestCase
new ConstraintA(['groups' => [
'Default',
'EntityParentInterface',
'EntityInterfaceB',
'Entity',
]]),
];
@ -150,6 +152,15 @@ class LazyLoadingMetadataFactoryTest extends TestCase
$this->assertContains('EntityStaticCar', $groups);
$this->assertContains('EntityStaticVehicle', $groups);
}
public function testMultipathInterfaceConstraint()
{
$factory = new LazyLoadingMetadataFactory(new PropertyGetterInterfaceConstraintLoader());
$metadata = $factory->getMetadataFor(PropertyGetter::class);
$constraints = $metadata->getPropertyMetadata('property');
$this->assertCount(1, $constraints);
}
}
class TestLoader implements LoaderInterface
@ -161,3 +172,15 @@ class TestLoader implements LoaderInterface
return true;
}
}
class PropertyGetterInterfaceConstraintLoader implements LoaderInterface
{
public function loadClassMetadata(ClassMetadata $metadata)
{
if (PropertyGetterInterface::class === $metadata->getClassName()) {
$metadata->addGetterConstraint('property', new NotBlank());
}
return true;
}
}