diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 52531183458b8..f24caa613bcca 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,6 +21,17 @@ jobs: - 5432:5432 env: POSTGRES_PASSWORD: 'password' + ldap: + image: bitnami/openldap + ports: + - 3389:3389 + env: + LDAP_ADMIN_USERNAME: admin + LDAP_ADMIN_PASSWORD: symfony + LDAP_ROOT: dc=symfony,dc=com + LDAP_PORT_NUMBER: 3389 + LDAP_USERS: a + LDAP_PASSWORDS: a redis: image: redis:6.0.0 ports: @@ -35,9 +46,16 @@ jobs: - 7004:7004 - 7005:7005 - 7006:7006 - - 7007:7007 env: - STANDALONE: true + STANDALONE: 1 + redis-sentinel: + image: bitnami/redis-sentinel:6.0 + ports: + - 26379:26379 + env: + REDIS_MASTER_HOST: redis + REDIS_MASTER_SET: redis_sentinel + REDIS_SENTINEL_QUORUM: 1 memcached: image: memcached:1.6.5 ports: @@ -105,11 +123,17 @@ jobs: uses: shivammathur/setup-php@v2 with: coverage: "none" - extensions: "json,couchbase,memcached,mongodb,redis,rdkafka,xsl" + extensions: "json,couchbase,memcached,mongodb,redis,rdkafka,xsl,ldap" ini-values: "memory_limit=-1" php-version: "${{ matrix.php }}" tools: pecl + - name: Load fixtures + uses: docker://bitnami/openldap + with: + entrypoint: /bin/bash + args: -c "(ldapwhoami -h localhost:3389 -D cn=admin,dc=symfony,dc=com -w symfony||sleep 5) && ldapadd -h ldap:3389 -D cn=admin,dc=symfony,dc=com -w symfony -f src/Symfony/Component/Ldap/Tests/Fixtures/data/fixtures.ldif && ldapdelete -h ldap:3389 -D cn=admin,dc=symfony,dc=com -w symfony cn=a,ou=users,dc=symfony,dc=com" + - name: Configure composer run: | COMPOSER_HOME="$(composer config home)" @@ -138,15 +162,19 @@ jobs: echo "::endgroup::" - name: Run tests - run: ./phpunit --group integration + run: ./phpunit --group integration -v env: REDIS_HOST: localhost REDIS_CLUSTER_HOSTS: 'localhost:7000 localhost:7001 localhost:7002 localhost:7003 localhost:7004 localhost:7005' + REDIS_SENTINEL_HOSTS: 'localhost:26379' + REDIS_SENTINEL_SERVICE: redis_sentinel MESSENGER_REDIS_DSN: redis://127.0.0.1:7006/messages MESSENGER_AMQP_DSN: amqp://localhost/%2f/messages MESSENGER_SQS_DSN: "sqs://localhost:9494/messages?sslmode=disable&poll_timeout=0.01" MESSENGER_SQS_FIFO_QUEUE_DSN: "sqs://localhost:9494/messages.fifo?sslmode=disable&poll_timeout=0.01" MEMCACHED_HOST: localhost + LDAP_HOST: localhost + LDAP_PORT: 3389 MONGODB_HOST: localhost KAFKA_BROKER: localhost:9092 POSTGRES_HOST: localhost diff --git a/.travis.yml b/.travis.yml index 8d284a7a121bc..e59103e3a27a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,6 @@ addons: apt_packages: - parallel - language-pack-fr-base - - ldap-utils - - slapd - zookeeperd - libzookeeper-mt-dev @@ -55,11 +53,6 @@ before_install: # General configuration set -e stty cols 120 - mkdir /tmp/slapd - if [ ! -e /tmp/slapd-modules ]; then - [ -d /usr/lib/openldap ] && ln -s /usr/lib/openldap /tmp/slapd-modules || ln -s /usr/lib/ldap /tmp/slapd-modules - fi - slapd -f src/Symfony/Component/Ldap/Tests/Fixtures/conf/slapd.conf -h ldap://localhost:3389 & cp .github/composer-config.json "$(composer config home)/config.json" export PHPUNIT=$(readlink -f ./phpunit) export PHPUNIT_X="$PHPUNIT --exclude-group tty,benchmark,intl-data" @@ -169,13 +162,6 @@ before_install: tfold ext.redis tpecl redis-5.2.3 redis.so $INI "no" done - - | - # Load fixtures - if [[ ! $skip ]]; then - ldapadd -h localhost:3389 -D cn=admin,dc=symfony,dc=com -w symfony -f src/Symfony/Component/Ldap/Tests/Fixtures/data/base.ldif && - ldapadd -h localhost:3389 -D cn=admin,dc=symfony,dc=com -w symfony -f src/Symfony/Component/Ldap/Tests/Fixtures/data/fixtures.ldif - fi - install: - | # Install the phpunit-bridge from a PR if required diff --git a/CHANGELOG-5.2.md b/CHANGELOG-5.2.md index 4708969f2be28..2a83a9f8b5649 100644 --- a/CHANGELOG-5.2.md +++ b/CHANGELOG-5.2.md @@ -7,6 +7,22 @@ in 5.2 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v5.2.0...v5.2.1 +* 5.2.0-RC2 (2020-11-21) + + * bug #39113 [DoctrineBridge] drop binary variants of UID types (nicolas-grekas) + * feature #39111 [Security] Update password upgrader listener to work with the new UserBadge (wouterj) + * bug #39083 [Dotenv] Check if method inheritEnvironmentVariables exists (Chi-teck) + * bug #39094 [Ldap] Fix undefined variable $con (derrabus) + * bug #39091 [Config] Recheck glob brace support after GlobResource was serialized (wouterj) + * bug #39092 Fix critical extension when reseting paged control (jderusse) + * bug #38614 [HttpFoundation] Fix for virtualhosts based on URL path (mvorisek) + * bug #39072 [FrameworkBundle] [Notifier] fix firebase transport factory DI tag type (xabbuh) + * bug #39070 [Validator] Remove IsinValidator's validator dependency (derrabus) + * bug #38387 [Validator] prevent hash collisions caused by reused object hashes (fancyweb, xabbuh) + * bug #38999 [DependencyInjection] autoconfigure behavior describing tags on decorators (xabbuh) + * bug #39058 [DependencyInjection] Fix circular detection with multiple paths (jderusse) + * bug #39059 [Filesystem] fix cleaning up tmp files when dumpFile() fails (nicolas-grekas) + * 5.2.0-RC1 (2020-11-10) * bug #39004 [Messenger] Fix JSON deserialization of ErrorDetailsStamp and normalization of FlattenException::$statusText (Jean85) diff --git a/composer.json b/composer.json index 3034f796f7084..3ca90e56d1d68 100644 --- a/composer.json +++ b/composer.json @@ -175,6 +175,6 @@ ], "minimum-stability": "dev", "extra": { - "branch-version": "5.x" + "branch-version": "5.2" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d2179994d502e..5bf27434011c1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -15,7 +15,7 @@ - + diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 574db63d5ec07..ea6918de35f1c 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 5.2.0 ----- - * added support for symfony/uid as `UlidType`, `UuidType`, `UlidBinaryType` and `UuidBinaryType` as Doctrine types + * added support for symfony/uid as `UlidType` and `UuidType` as Doctrine types * added `UlidGenerator`, `UUidV1Generator`, `UuidV4Generator` and `UuidV6Generator` 5.0.0 diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php index f5fea85d14e9c..e30fe44429670 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php @@ -11,9 +11,7 @@ namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; -use Symfony\Bridge\Doctrine\Types\UlidBinaryType; use Symfony\Bridge\Doctrine\Types\UlidType; -use Symfony\Bridge\Doctrine\Types\UuidBinaryType; use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -40,14 +38,6 @@ public function process(ContainerBuilder $container) $typeDefinition['ulid'] = ['class' => UlidType::class]; } - if (!isset($typeDefinition['uuid_binary'])) { - $typeDefinition['uuid_binary'] = ['class' => UuidBinaryType::class]; - } - - if (!isset($typeDefinition['ulid_binary'])) { - $typeDefinition['ulid_binary'] = ['class' => UlidBinaryType::class]; - } - $container->setParameter('doctrine.dbal.connection_factory.types', $typeDefinition); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidBinaryTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidBinaryTypeTest.php deleted file mode 100644 index 4141dfa55e540..0000000000000 --- a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidBinaryTypeTest.php +++ /dev/null @@ -1,114 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Doctrine\Tests\Types; - -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Types\ConversionException; -use Doctrine\DBAL\Types\Type; -use PHPUnit\Framework\TestCase; -use Symfony\Bridge\Doctrine\Types\UlidBinaryType; -use Symfony\Component\Uid\Ulid; - -class UlidBinaryTypeTest extends TestCase -{ - private const DUMMY_ULID = '01EEDQEK6ZAZE93J8KG5B4MBJC'; - - private $platform; - - /** @var UlidBinaryType */ - private $type; - - public static function setUpBeforeClass(): void - { - Type::addType('ulid_binary', UlidBinaryType::class); - } - - protected function setUp(): void - { - $this->platform = $this->createMock(AbstractPlatform::class); - $this->platform - ->method('getBinaryTypeDeclarationSQL') - ->willReturn('DUMMYBINARY(16)'); - - $this->type = Type::getType('ulid_binary'); - } - - public function testUlidConvertsToDatabaseValue() - { - $uuid = Ulid::fromString(self::DUMMY_ULID); - - $expected = $uuid->toBinary(); - $actual = $this->type->convertToDatabaseValue($uuid, $this->platform); - - $this->assertEquals($expected, $actual); - } - - public function testStringUlidConvertsToDatabaseValue() - { - $expected = Ulid::fromString(self::DUMMY_ULID)->toBinary(); - $actual = $this->type->convertToDatabaseValue(self::DUMMY_ULID, $this->platform); - - $this->assertEquals($expected, $actual); - } - - public function testNotSupportedTypeConversionForDatabaseValue() - { - $this->expectException(ConversionException::class); - - $this->type->convertToDatabaseValue(new \stdClass(), $this->platform); - } - - public function testNullConversionForDatabaseValue() - { - $this->assertNull($this->type->convertToDatabaseValue(null, $this->platform)); - } - - public function testUlidConvertsToPHPValue() - { - $uuid = $this->type->convertToPHPValue(self::DUMMY_ULID, $this->platform); - - $this->assertEquals(self::DUMMY_ULID, $uuid->__toString()); - } - - public function testInvalidUlidConversionForPHPValue() - { - $this->expectException(ConversionException::class); - - $this->type->convertToPHPValue('abcdefg', $this->platform); - } - - public function testNullConversionForPHPValue() - { - $this->assertNull($this->type->convertToPHPValue(null, $this->platform)); - } - - public function testReturnValueIfUlidForPHPValue() - { - $uuid = new Ulid(); - $this->assertSame($uuid, $this->type->convertToPHPValue($uuid, $this->platform)); - } - - public function testGetName() - { - $this->assertEquals('ulid_binary', $this->type->getName()); - } - - public function testGetGuidTypeDeclarationSQL() - { - $this->assertEquals('DUMMYBINARY(16)', $this->type->getSqlDeclaration(['length' => 36], $this->platform)); - } - - public function testRequiresSQLCommentHint() - { - $this->assertTrue($this->type->requiresSQLCommentHint($this->platform)); - } -} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php index f3969bcb4c725..36aace3ed843d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php @@ -37,6 +37,9 @@ public static function setUpBeforeClass(): void protected function setUp(): void { $this->platform = $this->createMock(AbstractPlatform::class); + $this->platform + ->method('hasNativeGuidType') + ->willReturn(true); $this->platform ->method('getGuidTypeDeclarationSQL') ->willReturn('DUMMYVARCHAR()'); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UuidBinaryTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UuidBinaryTypeTest.php deleted file mode 100644 index b44a52578c5d2..0000000000000 --- a/src/Symfony/Bridge/Doctrine/Tests/Types/UuidBinaryTypeTest.php +++ /dev/null @@ -1,123 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Doctrine\Tests\Types; - -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Types\ConversionException; -use Doctrine\DBAL\Types\Type; -use PHPUnit\Framework\TestCase; -use Symfony\Bridge\Doctrine\Types\UuidBinaryType; -use Symfony\Component\Uid\Uuid; - -class UuidBinaryTypeTest extends TestCase -{ - private const DUMMY_UUID = '9f755235-5a2d-4aba-9605-e9962b312e50'; - - private $platform; - - /** @var UuidBinaryType */ - private $type; - - public static function setUpBeforeClass(): void - { - Type::addType('uuid_binary', UuidBinaryType::class); - } - - protected function setUp(): void - { - $this->platform = $this->createMock(AbstractPlatform::class); - $this->platform - ->method('getBinaryTypeDeclarationSQL') - ->willReturn('DUMMYBINARY(16)'); - - $this->type = Type::getType('uuid_binary'); - } - - public function testUuidConvertsToDatabaseValue() - { - $uuid = Uuid::fromString(self::DUMMY_UUID); - - $expected = uuid_parse(self::DUMMY_UUID); - $actual = $this->type->convertToDatabaseValue($uuid, $this->platform); - - $this->assertEquals($expected, $actual); - } - - public function testStringUuidConvertsToDatabaseValue() - { - $uuid = self::DUMMY_UUID; - - $expected = uuid_parse(self::DUMMY_UUID); - $actual = $this->type->convertToDatabaseValue($uuid, $this->platform); - - $this->assertEquals($expected, $actual); - } - - public function testInvalidUuidConversionForDatabaseValue() - { - $this->expectException(ConversionException::class); - - $this->type->convertToDatabaseValue('abcdefg', $this->platform); - } - - public function testNullConversionForDatabaseValue() - { - $this->assertNull($this->type->convertToDatabaseValue(null, $this->platform)); - } - - public function testUuidConvertsToPHPValue() - { - $uuid = $this->type->convertToPHPValue(uuid_parse(self::DUMMY_UUID), $this->platform); - - $this->assertEquals(self::DUMMY_UUID, $uuid->__toString()); - } - - public function testInvalidUuidConversionForPHPValue() - { - $this->expectException(ConversionException::class); - - $this->type->convertToPHPValue('abcdefg', $this->platform); - } - - public function testNotSupportedTypeConversionForDatabaseValue() - { - $this->expectException(ConversionException::class); - - $this->type->convertToDatabaseValue(new \stdClass(), $this->platform); - } - - public function testNullConversionForPHPValue() - { - $this->assertNull($this->type->convertToPHPValue(null, $this->platform)); - } - - public function testReturnValueIfUuidForPHPValue() - { - $uuid = Uuid::v4(); - $this->assertSame($uuid, $this->type->convertToPHPValue($uuid, $this->platform)); - } - - public function testGetName() - { - $this->assertEquals('uuid_binary', $this->type->getName()); - } - - public function testGetGuidTypeDeclarationSQL() - { - $this->assertEquals('DUMMYBINARY(16)', $this->type->getSqlDeclaration(['length' => 36], $this->platform)); - } - - public function testRequiresSQLCommentHint() - { - $this->assertTrue($this->type->requiresSQLCommentHint($this->platform)); - } -} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php index da775ca81573c..45e2ee0d5dc71 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php @@ -37,6 +37,9 @@ public static function setUpBeforeClass(): void protected function setUp(): void { $this->platform = $this->createMock(AbstractPlatform::class); + $this->platform + ->method('hasNativeGuidType') + ->willReturn(true); $this->platform ->method('getGuidTypeDeclarationSQL') ->willReturn('DUMMYVARCHAR()'); diff --git a/src/Symfony/Bridge/Doctrine/Types/AbstractBinaryUidType.php b/src/Symfony/Bridge/Doctrine/Types/AbstractBinaryUidType.php deleted file mode 100644 index 1dbb70abf556a..0000000000000 --- a/src/Symfony/Bridge/Doctrine/Types/AbstractBinaryUidType.php +++ /dev/null @@ -1,86 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Doctrine\Types; - -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Types\ConversionException; -use Doctrine\DBAL\Types\Type; -use Symfony\Component\Uid\AbstractUid; - -abstract class AbstractBinaryUidType extends Type -{ - abstract protected function getUidClass(): string; - - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string - { - return $platform->getBinaryTypeDeclarationSQL([ - 'length' => '16', - 'fixed' => true, - ]); - } - - /** - * {@inheritdoc} - * - * @throws ConversionException - */ - public function convertToPHPValue($value, AbstractPlatform $platform): ?AbstractUid - { - if ($value instanceof AbstractUid || null === $value) { - return $value; - } - - if (!\is_string($value)) { - throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]); - } - - try { - return $this->getUidClass()::fromString($value); - } catch (\InvalidArgumentException $e) { - throw ConversionException::conversionFailed($value, $this->getName(), $e); - } - } - - /** - * {@inheritdoc} - * - * @throws ConversionException - */ - public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string - { - if ($value instanceof AbstractUid) { - return $value->toBinary(); - } - - if (null === $value || '' === $value) { - return null; - } - - if (!\is_string($value)) { - throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]); - } - - try { - return $this->getUidClass()::fromString($value)->toBinary(); - } catch (\InvalidArgumentException $e) { - throw ConversionException::conversionFailed($value, $this->getName()); - } - } - - /** - * {@inheritdoc} - */ - public function requiresSQLCommentHint(AbstractPlatform $platform): bool - { - return true; - } -} diff --git a/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php b/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php index 4b57bc6b852f6..ec0c6ef6f8078 100644 --- a/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php +++ b/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php @@ -25,7 +25,14 @@ abstract protected function getUidClass(): string; */ public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - return $platform->getGuidTypeDeclarationSQL($column); + if ($platform->hasNativeGuidType()) { + return $platform->getGuidTypeDeclarationSQL($column); + } + + return $platform->getBinaryTypeDeclarationSQL([ + 'length' => '16', + 'fixed' => true, + ]); } /** @@ -57,8 +64,10 @@ public function convertToPHPValue($value, AbstractPlatform $platform): ?Abstract */ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string { + $toString = $platform->hasNativeGuidType() ? 'toRfc4122' : 'toBinary'; + if ($value instanceof AbstractUid) { - return $value->toRfc4122(); + return $value->$toString(); } if (null === $value || '' === $value) { @@ -70,7 +79,7 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?str } try { - return $this->getUidClass()::fromString($value)->toRfc4122(); + return $this->getUidClass()::fromString($value)->$toString(); } catch (\InvalidArgumentException $e) { throw ConversionException::conversionFailed($value, $this->getName()); } diff --git a/src/Symfony/Bridge/Doctrine/Types/UlidBinaryType.php b/src/Symfony/Bridge/Doctrine/Types/UlidBinaryType.php deleted file mode 100644 index 34077d24494e0..0000000000000 --- a/src/Symfony/Bridge/Doctrine/Types/UlidBinaryType.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Doctrine\Types; - -use Symfony\Component\Uid\Ulid; - -final class UlidBinaryType extends AbstractBinaryUidType -{ - public function getName(): string - { - return 'ulid_binary'; - } - - protected function getUidClass(): string - { - return Ulid::class; - } -} diff --git a/src/Symfony/Bridge/Doctrine/Types/UuidBinaryType.php b/src/Symfony/Bridge/Doctrine/Types/UuidBinaryType.php deleted file mode 100644 index 9e161a8ccba76..0000000000000 --- a/src/Symfony/Bridge/Doctrine/Types/UuidBinaryType.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Doctrine\Types; - -use Symfony\Component\Uid\Uuid; - -final class UuidBinaryType extends AbstractBinaryUidType -{ - public function getName(): string - { - return 'uuid_binary'; - } - - protected function getUidClass(): string - { - return Uuid::class; - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/SutNotFindTest.php b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/SutNotFoundTest.php similarity index 100% rename from src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/SutNotFindTest.php rename to src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/SutNotFoundTest.php diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php index 3362aab9fb5eb..684ff36581776 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php @@ -76,7 +76,7 @@ public function & __get($name) $targetObject = $this->valueHolder%s; - $backtrace = debug_backtrace(false); + $backtrace = debug_backtrace(false%S); trigger_error( sprintf( 'Undefined property: %s::$%s in %s on line %s', @@ -115,8 +115,7 @@ public function __unset($name) $targetObject = $this->valueHolder%s; unset($targetObject->$name); -return; - } +%a } public function __clone() { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ea5e139e3fe64..a599a684af7cd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -533,6 +533,14 @@ public function load(array $configs, ContainerBuilder $container) $container->registerForAutoconfiguration(RouteLoaderInterface::class) ->addTag('routing.route_loader'); + + $container->setParameter('container.behavior_describing_tags', [ + 'container.service_locator', + 'container.service_subscriber', + 'kernel.event_subscriber', + 'kernel.locale_aware', + 'kernel.reset', + ]); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mime_type.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mime_type.php index 0d874a07ed321..a7e9bbd912746 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mime_type.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mime_type.php @@ -11,11 +11,16 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\Mime\MimeTypeGuesserInterface; use Symfony\Component\Mime\MimeTypes; +use Symfony\Component\Mime\MimeTypesInterface; return static function (ContainerConfigurator $container) { $container->services() ->set('mime_types', MimeTypes::class) ->call('setDefault', [service('mime_types')]) + + ->alias(MimeTypesInterface::class, 'mime_types') + ->alias(MimeTypeGuesserInterface::class, 'mime_types') ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index cff7f0d0c91dc..be40329cc9ebc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -73,7 +73,7 @@ ->set('notifier.transport_factory.firebase', FirebaseTransportFactory::class) ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') + ->tag('chatter.transport_factory') ->set('notifier.transport_factory.freemobile', FreeMobileTransportFactory::class) ->parent('notifier.transport_factory.abstract') diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index b902d3b636a2f..e9d141d528757 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1622,6 +1622,20 @@ public function testHttpClientMockResponseFactory() $this->assertSame('my_response_factory', (string) $argument); } + public function testRegisterParameterCollectingBehaviorDescribingTags() + { + $container = $this->createContainerFromFile('default_config'); + + $this->assertTrue($container->hasParameter('container.behavior_describing_tags')); + $this->assertEquals([ + 'container.service_locator', + 'container.service_subscriber', + 'kernel.event_subscriber', + 'kernel.locale_aware', + 'kernel.reset', + ], $container->getParameter('container.behavior_describing_tags')); + } + protected function createContainer(array $data = []) { return new ContainerBuilder(new EnvPlaceholderParameterBag(array_merge([ diff --git a/src/Symfony/Component/BrowserKit/CHANGELOG.md b/src/Symfony/Component/BrowserKit/CHANGELOG.md index 323166a3d6cc5..8506ad8efe73c 100644 --- a/src/Symfony/Component/BrowserKit/CHANGELOG.md +++ b/src/Symfony/Component/BrowserKit/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.2.0 +----- + + * [BC BREAK] Request parameters are now casted to string in `Request::__construct()`. + 4.3.0 ----- diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php index 09f563036bc2f..82b9f08b65474 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php @@ -37,7 +37,7 @@ public static function setUpBeforeClass(): void public function testInvalidDSNHasBothClusterAndSentinel() { $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException'); - $this->expectExceptionMessage('Invalid Redis DSN: cannot use both redis_cluster and redis_sentinel at the same time'); + $this->expectExceptionMessage('Cannot use both "redis_cluster" and "redis_sentinel" at the same time:'); $dsn = 'redis:?host[redis1]&host[redis2]&host[redis3]&redis_cluster=1&redis_sentinel=mymaster'; RedisAdapter::createConnection($dsn); } diff --git a/src/Symfony/Component/Cache/Tests/DataCollector/CacheDataCollectorTest.php b/src/Symfony/Component/Cache/Tests/DataCollector/CacheDataCollectorTest.php new file mode 100644 index 0000000000000..f704bbfe0e49f --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/DataCollector/CacheDataCollectorTest.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Marshaller; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Cache\Adapter\TraceableAdapter; +use Symfony\Component\Cache\DataCollector\CacheDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class CacheDataCollectorTest extends TestCase +{ + private const INSTANCE_NAME = 'test'; + + public function testEmptyDataCollector() + { + $statistics = $this->getCacheDataCollectorStatisticsFromEvents([]); + + $this->assertEquals($statistics[self::INSTANCE_NAME]['calls'], 0, 'calls'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['reads'], 0, 'reads'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['hits'], 0, 'hits'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['misses'], 0, 'misses'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['writes'], 0, 'writes'); + } + + public function testOneEventDataCollector() + { + $traceableAdapterEvent = new \stdClass(); + $traceableAdapterEvent->name = 'getItem'; + $traceableAdapterEvent->start = 0; + $traceableAdapterEvent->end = 0; + $traceableAdapterEvent->hits = 0; + + $statistics = $this->getCacheDataCollectorStatisticsFromEvents([$traceableAdapterEvent]); + + $this->assertEquals($statistics[self::INSTANCE_NAME]['calls'], 1, 'calls'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['reads'], 1, 'reads'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['hits'], 0, 'hits'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['misses'], 1, 'misses'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['writes'], 0, 'writes'); + } + + public function testHitedEventDataCollector() + { + $traceableAdapterEvent = new \stdClass(); + $traceableAdapterEvent->name = 'hasItem'; + $traceableAdapterEvent->start = 0; + $traceableAdapterEvent->end = 0; + $traceableAdapterEvent->hits = 1; + $traceableAdapterEvent->misses = 0; + $traceableAdapterEvent->result = ['foo' => false]; + + $statistics = $this->getCacheDataCollectorStatisticsFromEvents([$traceableAdapterEvent]); + + $this->assertEquals($statistics[self::INSTANCE_NAME]['calls'], 1, 'calls'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['reads'], 1, 'reads'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['hits'], 1, 'hits'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['misses'], 0, 'misses'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['writes'], 0, 'writes'); + } + + public function testSavedEventDataCollector() + { + $traceableAdapterEvent = new \stdClass(); + $traceableAdapterEvent->name = 'save'; + $traceableAdapterEvent->start = 0; + $traceableAdapterEvent->end = 0; + + $statistics = $this->getCacheDataCollectorStatisticsFromEvents([$traceableAdapterEvent]); + + $this->assertEquals($statistics[self::INSTANCE_NAME]['calls'], 1, 'calls'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['reads'], 0, 'reads'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['hits'], 0, 'hits'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['misses'], 0, 'misses'); + $this->assertEquals($statistics[self::INSTANCE_NAME]['writes'], 1, 'writes'); + } + + private function getCacheDataCollectorStatisticsFromEvents(array $traceableAdapterEvents) + { + $traceableAdapterMock = $this->createMock(TraceableAdapter::class); + $traceableAdapterMock->method('getCalls')->willReturn($traceableAdapterEvents); + + $cacheDataCollector = new CacheDataCollector(); + $cacheDataCollector->addInstance(self::INSTANCE_NAME, $traceableAdapterMock); + $cacheDataCollector->collect(new Request(), new Response()); + + return $cacheDataCollector->getStatistics(); + } +} diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json index 600f63e1d2e24..b6a41417a8339 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -38,6 +38,7 @@ "symfony/config": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", "symfony/filesystem": "^4.4|^5.0", + "symfony/http-kernel": "^4.4|^5.0", "symfony/messenger": "^4.4|^5.0", "symfony/var-dumper": "^4.4|^5.0" }, diff --git a/src/Symfony/Component/Config/Resource/GlobResource.php b/src/Symfony/Component/Config/Resource/GlobResource.php index 21777e648eb45..c77e837c245f9 100644 --- a/src/Symfony/Component/Config/Resource/GlobResource.php +++ b/src/Symfony/Component/Config/Resource/GlobResource.php @@ -94,6 +94,14 @@ public function __sleep(): array return ['prefix', 'pattern', 'recursive', 'hash', 'forExclusion', 'excludedPrefixes']; } + /** + * @internal + */ + public function __wakeup(): void + { + $this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0; + } + public function getIterator(): \Traversable { if (!file_exists($this->prefix) || (!$this->recursive && '' === $this->pattern)) { diff --git a/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php index 2b6609d740c86..a30fbe8c4339b 100644 --- a/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php @@ -194,4 +194,17 @@ public function testUnbalancedBraceFallback() $this->assertSame([], array_keys(iterator_to_array($resource))); } + + public function testSerializeUnserialize() + { + $dir = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'; + $resource = new GlobResource($dir, '/Resource', true); + + $newResource = unserialize(serialize($resource)); + + $p = new \ReflectionProperty($resource, 'globBrace'); + $p->setAccessible(true); + + $this->assertEquals($p->getValue($resource), $p->getValue($newResource)); + } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php index 60d059fb29445..4aa33aad1ac26 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php @@ -35,12 +35,22 @@ public function process(ContainerBuilder $container) } } + $tagsToKeep = []; + + if ($container->hasParameter('container.behavior_describing_tags')) { + $tagsToKeep = $container->getParameter('container.behavior_describing_tags'); + } + foreach ($container->getDefinitions() as $id => $definition) { - $container->setDefinition($id, $this->processDefinition($container, $id, $definition)); + $container->setDefinition($id, $this->processDefinition($container, $id, $definition, $tagsToKeep)); + } + + if ($container->hasParameter('container.behavior_describing_tags')) { + $container->getParameterBag()->remove('container.behavior_describing_tags'); } } - private function processDefinition(ContainerBuilder $container, string $id, Definition $definition): Definition + private function processDefinition(ContainerBuilder $container, string $id, Definition $definition, array $tagsToKeep): Definition { $instanceofConditionals = $definition->getInstanceofConditionals(); $autoconfiguredInstanceof = $definition->isAutoconfigured() ? $container->getAutoconfiguredInstanceof() : []; @@ -114,10 +124,10 @@ private function processDefinition(ContainerBuilder $container, string $id, Defi } // Don't add tags to service decorators - if (null === $definition->getDecoratedService()) { - $i = \count($instanceofTags); - while (0 <= --$i) { - foreach ($instanceofTags[$i] as $k => $v) { + $i = \count($instanceofTags); + while (0 <= --$i) { + foreach ($instanceofTags[$i] as $k => $v) { + if (null === $definition->getDecoratedService() || \in_array($k, $tagsToKeep, true)) { foreach ($v as $v) { if ($definition->hasTag($k) && \in_array($v, $definition->getTag($k))) { continue; diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 7b0ea433033f6..9e71d955a227e 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -433,14 +433,13 @@ private function analyzeReferences() $this->singleUsePrivateIds = array_diff_key($this->singleUsePrivateIds, $this->circularReferences); } - private function collectCircularReferences(string $sourceId, array $edges, array &$checkedNodes, array $path = [], bool $byConstructor = true): void + private function collectCircularReferences(string $sourceId, array $edges, array &$checkedNodes, array &$loops = [], array $path = [], bool $byConstructor = true): void { $path[$sourceId] = $byConstructor; $checkedNodes[$sourceId] = true; foreach ($edges as $edge) { $node = $edge->getDestNode(); $id = $node->getId(); - if (!($definition = $node->getValue()) instanceof Definition || $sourceId === $id || ($edge->isLazy() && ($this->proxyDumper ?? $this->getProxyDumper())->isProxyCandidate($definition)) || $edge->isWeak()) { continue; } @@ -448,9 +447,12 @@ private function collectCircularReferences(string $sourceId, array $edges, array if (isset($path[$id])) { $loop = null; $loopByConstructor = $edge->isReferencedByConstructor(); + $pathInLoop = [$id, []]; foreach ($path as $k => $pathByConstructor) { if (null !== $loop) { $loop[] = $k; + $pathInLoop[1][$k] = $pathByConstructor; + $loops[$k][] = &$pathInLoop; $loopByConstructor = $loopByConstructor && $pathByConstructor; } elseif ($k === $id) { $loop = []; @@ -458,7 +460,39 @@ private function collectCircularReferences(string $sourceId, array $edges, array } $this->addCircularReferences($id, $loop, $loopByConstructor); } elseif (!isset($checkedNodes[$id])) { - $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $path, $edge->isReferencedByConstructor()); + $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor()); + } elseif (isset($loops[$id])) { + // we already had detected loops for this edge + // let's check if we have a common ancestor in one of the detected loops + foreach ($loops[$id] as [$first, $loopPath]) { + if (!isset($path[$first])) { + continue; + } + // We have a common ancestor, let's fill the current path + $fillPath = null; + foreach ($loopPath as $k => $pathByConstructor) { + if (null !== $fillPath) { + $fillPath[$k] = $pathByConstructor; + } elseif ($k === $id) { + $fillPath = $path; + $fillPath[$k] = $pathByConstructor; + } + } + + // we can now build the loop + $loop = null; + $loopByConstructor = $edge->isReferencedByConstructor(); + foreach ($fillPath as $k => $pathByConstructor) { + if (null !== $loop) { + $loop[] = $k; + $loopByConstructor = $loopByConstructor && $pathByConstructor; + } elseif ($k === $first) { + $loop = []; + } + } + $this->addCircularReferences($first, $loop, true); + break; + } } } unset($path[$sourceId]); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php index 3acfbed776f1f..99aa65b13869b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php @@ -12,12 +12,16 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\ResourceCheckerInterface; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Contracts\Service\ResetInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; class ResolveInstanceofConditionalsPassTest extends TestCase { @@ -325,4 +329,60 @@ public function testDecoratorsAreNotAutomaticallyTagged() $this->assertSame(['manual' => [[]]], $container->getDefinition('decorator')->getTags()); } + + public function testDecoratorsKeepBehaviorDescribingTags() + { + $container = new ContainerBuilder(); + + $container->setParameter('container.behavior_describing_tags', [ + 'container.service_subscriber', + 'kernel.reset', + ]); + + $container->register('decorator', DecoratorWithBehavior::class) + ->setAutoconfigured(true) + ->setDecoratedService('decorated') + ; + + $container->registerForAutoconfiguration(ResourceCheckerInterface::class) + ->addTag('config_cache.resource_checker') + ; + $container->registerForAutoconfiguration(ServiceSubscriberInterface::class) + ->addTag('container.service_subscriber') + ; + $container->registerForAutoconfiguration(ResetInterface::class) + ->addTag('kernel.reset', ['method' => 'reset']) + ; + + (new ResolveInstanceofConditionalsPass())->process($container); + + $this->assertEquals([ + 'container.service_subscriber' => [0 => []], + 'kernel.reset' => [ + [ + 'method' => 'reset', + ], + ], + ], $container->getDefinition('decorator')->getTags()); + $this->assertFalse($container->hasParameter('container.behavior_describing_tags')); + } +} + +class DecoratorWithBehavior implements ResetInterface, ResourceCheckerInterface, ServiceSubscriberInterface +{ + public function reset() + { + } + + public function supports(ResourceInterface $metadata) + { + } + + public function isFresh(ResourceInterface $resource, $timestamp) + { + } + + public static function getSubscribedServices() + { + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index df6f06653edcb..005ea3230ad40 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1395,6 +1395,9 @@ public function testAlmostCircular($visibility) $container = include __DIR__.'/Fixtures/containers/container_almost_circular.php'; $container->compile(); + $pA = $container->get('pA'); + $this->assertEquals(new \stdClass(), $pA); + $logger = $container->get('monolog.logger'); $this->assertEquals(new \stdClass(), $logger->handler); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 3b77b7ed6eb2d..e68b4ceb228c3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -1054,6 +1054,9 @@ public function testAlmostCircular($visibility) $container = new $container(); + $pA = $container->get('pA'); + $this->assertEquals(new \stdClass(), $pA); + $logger = $container->get('monolog.logger'); $this->assertEquals(new \stdClass(), $logger->handler); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php index 96c714493e8f3..6a0b2da766cdd 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php @@ -9,6 +9,21 @@ $public = 'public' === $visibility; $container = new ContainerBuilder(); +// multiple path detection + +$container->register('pA', 'stdClass')->setPublic(true) + ->addArgument(new Reference('pB')) + ->addArgument(new Reference('pC')); + +$container->register('pB', 'stdClass')->setPublic($public) + ->setProperty('d', new Reference('pD')); +$container->register('pC', 'stdClass')->setPublic($public) + ->setLazy(true) + ->setProperty('d', new Reference('pD')); + +$container->register('pD', 'stdClass')->setPublic($public) + ->addArgument(new Reference('pA')); + // monolog-like + handler that require monolog $container->register('monolog.logger', 'stdClass')->setPublic(true) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index 8bc0146fb13f7..6d9985af3265b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -37,6 +37,7 @@ public function __construct() 'manager2' => 'getManager2Service', 'manager3' => 'getManager3Service', 'monolog.logger' => 'getMonolog_LoggerService', + 'pA' => 'getPAService', 'root' => 'getRootService', 'subscriber' => 'getSubscriberService', ]; @@ -84,6 +85,9 @@ public function getRemovedIds(): array 'manager4' => true, 'monolog.logger_2' => true, 'multiuse1' => true, + 'pB' => true, + 'pC' => true, + 'pD' => true, 'subscriber2' => true, ]; } @@ -371,6 +375,28 @@ protected function getMonolog_LoggerService() return $instance; } + /** + * Gets the public 'pA' shared service. + * + * @return \stdClass + */ + protected function getPAService() + { + $a = new \stdClass(); + + $b = ($this->privates['pC'] ?? $this->getPCService()); + + if (isset($this->services['pA'])) { + return $this->services['pA']; + } + + $this->services['pA'] = $instance = new \stdClass($a, $b); + + $a->d = ($this->privates['pD'] ?? $this->getPDService()); + + return $instance; + } + /** * Gets the public 'root' shared service. * @@ -478,4 +504,34 @@ protected function getManager4Service($lazyLoad = true) return $instance; } + + /** + * Gets the private 'pC' shared service. + * + * @return \stdClass + */ + protected function getPCService($lazyLoad = true) + { + $this->privates['pC'] = $instance = new \stdClass(); + + $instance->d = ($this->privates['pD'] ?? $this->getPDService()); + + return $instance; + } + + /** + * Gets the private 'pD' shared service. + * + * @return \stdClass + */ + protected function getPDService() + { + $a = ($this->services['pA'] ?? $this->getPAService()); + + if (isset($this->privates['pD'])) { + return $this->privates['pD']; + } + + return $this->privates['pD'] = new \stdClass($a); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index 4540459203d00..2e5d5281e0b79 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -50,6 +50,10 @@ public function __construct() 'manager3' => 'getManager3Service', 'monolog.logger' => 'getMonolog_LoggerService', 'monolog.logger_2' => 'getMonolog_Logger2Service', + 'pA' => 'getPAService', + 'pB' => 'getPBService', + 'pC' => 'getPCService', + 'pD' => 'getPDService', 'root' => 'getRootService', 'subscriber' => 'getSubscriberService', ]; @@ -559,6 +563,71 @@ protected function getMonolog_Logger2Service() return $instance; } + /** + * Gets the public 'pA' shared service. + * + * @return \stdClass + */ + protected function getPAService() + { + $a = ($this->services['pB'] ?? $this->getPBService()); + + if (isset($this->services['pA'])) { + return $this->services['pA']; + } + $b = ($this->services['pC'] ?? $this->getPCService()); + + if (isset($this->services['pA'])) { + return $this->services['pA']; + } + + return $this->services['pA'] = new \stdClass($a, $b); + } + + /** + * Gets the public 'pB' shared service. + * + * @return \stdClass + */ + protected function getPBService() + { + $this->services['pB'] = $instance = new \stdClass(); + + $instance->d = ($this->services['pD'] ?? $this->getPDService()); + + return $instance; + } + + /** + * Gets the public 'pC' shared service. + * + * @return \stdClass + */ + protected function getPCService($lazyLoad = true) + { + $this->services['pC'] = $instance = new \stdClass(); + + $instance->d = ($this->services['pD'] ?? $this->getPDService()); + + return $instance; + } + + /** + * Gets the public 'pD' shared service. + * + * @return \stdClass + */ + protected function getPDService() + { + $a = ($this->services['pA'] ?? $this->getPAService()); + + if (isset($this->services['pD'])) { + return $this->services['pD']; + } + + return $this->services['pD'] = new \stdClass($a); + } + /** * Gets the public 'root' shared service. * diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index 8cb2ba82c278c..21aa729091a7b 100644 --- a/src/Symfony/Component/Dotenv/Dotenv.php +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -456,7 +456,7 @@ private function resolveCommands(string $value, array $loadedVars): string $process = method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline('echo '.$matches[0]) : new Process('echo '.$matches[0]); - if (!method_exists(Process::class, 'fromShellCommandline')) { + if (!method_exists(Process::class, 'fromShellCommandline') && method_exists(Process::class, 'inheritEnvironmentVariables')) { // Symfony 3.4 does not inherit env vars by default: $process->inheritEnvironmentVariables(); } diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index d9609f5484c2d..a1c53d88bb4b5 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -658,13 +658,17 @@ public function dumpFile(string $filename, $content) // when the filesystem supports chmod. $tmpFile = $this->tempnam($dir, basename($filename)); - if (false === @file_put_contents($tmpFile, $content)) { - throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename); - } + try { + if (false === @file_put_contents($tmpFile, $content)) { + throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename); + } - @chmod($tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask()); + @chmod($tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask()); - $this->rename($tmpFile, $filename, true); + $this->rename($tmpFile, $filename, true); + } finally { + @unlink($tmpFile); + } } /** diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index 68542d7b5c97d..4158f88f85aa9 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -1720,6 +1720,18 @@ public function testAppendToFileCreateTheFileIfNotExists() $this->assertStringEqualsFile($filename, 'bar'); } + public function testDumpRemovesTmpFilesOnFailure() + { + $expected = scandir(__DIR__, \SCANDIR_SORT_ASCENDING); + + try { + $this->filesystem->dumpFile(__DIR__.'/Fixtures', 'bar'); + $this->fail('IOException expected.'); + } catch (IOException $e) { + $this->assertSame($expected, scandir(__DIR__, \SCANDIR_SORT_ASCENDING)); + } + } + public function testDumpKeepsExistingPermissionsWhenOverwritingAnExistingFile() { $this->markAsSkippedIfChmodIsMissing(); diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 2b13a73ceb42c..10cd62aacd1ba 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -655,7 +655,7 @@ public function append(iterable $iterator) } /** - * Check if the any results were found. + * Check if any results were found. * * @return bool */ diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index 1c63ecf5f4586..43eaeee4c829c 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -25,7 +25,6 @@ class FormValidator extends ConstraintValidator { private $resolvedGroups; - private $fieldFormConstraints; /** * {@inheritdoc} @@ -68,7 +67,6 @@ public function validate($form, Constraint $formConstraint) if ($hasChildren && $form->isRoot()) { $this->resolvedGroups = new \SplObjectStorage(); - $this->fieldFormConstraints = []; } if ($groups instanceof GroupSequence) { @@ -93,7 +91,6 @@ public function validate($form, Constraint $formConstraint) $this->resolvedGroups[$field] = (array) $group; $fieldFormConstraint = new Form(); $fieldFormConstraint->groups = $group; - $this->fieldFormConstraints[] = $fieldFormConstraint; $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath()); $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint, $group); } @@ -139,10 +136,8 @@ public function validate($form, Constraint $formConstraint) foreach ($form->all() as $field) { if ($field->isSubmitted()) { $this->resolvedGroups[$field] = $groups; - $fieldFormConstraint = new Form(); - $this->fieldFormConstraints[] = $fieldFormConstraint; $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath()); - $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint); + $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $formConstraint); } } } @@ -150,7 +145,6 @@ public function validate($form, Constraint $formConstraint) if ($hasChildren && $form->isRoot()) { // destroy storage to avoid memory leaks $this->resolvedGroups = new \SplObjectStorage(); - $this->fieldFormConstraints = []; } } elseif (!$form->isSynchronized()) { $childrenSynchronized = true; @@ -159,11 +153,8 @@ public function validate($form, Constraint $formConstraint) foreach ($form as $child) { if (!$child->isSynchronized()) { $childrenSynchronized = false; - - $fieldFormConstraint = new Form(); - $this->fieldFormConstraints[] = $fieldFormConstraint; $this->context->setNode($this->context->getValue(), $child, $this->context->getMetadata(), $this->context->getPropertyPath()); - $validator->atPath(sprintf('children[%s]', $child->getName()))->validate($child, $fieldFormConstraint); + $validator->atPath(sprintf('children[%s]', $child->getName()))->validate($child, $formConstraint); } } diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index b17ef9804901d..8d9d8f9623588 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -29,7 +29,7 @@ }, "require-dev": { "doctrine/collections": "~1.0", - "symfony/validator": "^4.4.12|^5.1.6", + "symfony/validator": "^4.4.17|^5.1.9", "symfony/dependency-injection": "^4.4|^5.0", "symfony/expression-language": "^4.4|^5.0", "symfony/config": "^4.4|^5.0", diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 34cd4924656ff..7df9a962be21e 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -595,7 +595,7 @@ public function overrideGlobals() public static function setTrustedProxies(array $proxies, int $trustedHeaderSet) { if (self::HEADER_X_FORWARDED_ALL === $trustedHeaderSet) { - trigger_deprecation('symfony/http-fundation', '5.2', 'The "HEADER_X_FORWARDED_ALL" constant is deprecated, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead.'); + trigger_deprecation('symfony/http-foundation', '5.2', 'The "HEADER_X_FORWARDED_ALL" constant is deprecated, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead.'); } self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) { if ('REMOTE_ADDR' !== $proxy) { @@ -1907,9 +1907,15 @@ protected function prepareBaseUrl() } $basename = basename($baseUrl); - if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { - // no match whatsoever; set it blank - return ''; + if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri).'/', '/'.$basename.'/')) { + // strip autoindex filename, for virtualhost based on URL path + $baseUrl = \dirname($baseUrl).'/'; + + $basename = basename($baseUrl); + if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri).'/', '/'.$basename.'/')) { + // no match whatsoever; set it blank + return ''; + } } // If using mod_rewrite or ISAPI_Rewrite strip the script filename diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 6b01446c2f611..0b9cfefa27699 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1785,6 +1785,36 @@ public function getBaseUrlData() '/foo', '/bar+baz', ], + [ + '/sub/foo/bar', + [ + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo/app.php', + 'SCRIPT_NAME' => '/foo/app.php', + 'PHP_SELF' => '/foo/app.php', + ], + '/sub/foo', + '/bar', + ], + [ + '/sub/foo/app.php/bar', + [ + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo/app.php', + 'SCRIPT_NAME' => '/foo/app.php', + 'PHP_SELF' => '/foo/app.php', + ], + '/sub/foo/app.php', + '/bar', + ], + [ + '/sub/foo/bar/baz', + [ + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo/app2.phpx', + 'SCRIPT_NAME' => '/foo/app2.phpx', + 'PHP_SELF' => '/foo/app2.phpx', + ], + '/sub/foo', + '/bar/baz', + ], ]; } @@ -2473,7 +2503,7 @@ public function preferSafeContentData() */ public function testXForwarededAllConstantDeprecated() { - $this->expectDeprecation('Since symfony/http-fundation 5.2: The "HEADER_X_FORWARDED_ALL" constant is deprecated, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead.'); + $this->expectDeprecation('Since symfony/http-foundation 5.2: The "HEADER_X_FORWARDED_ALL" constant is deprecated, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead.'); Request::setTrustedProxies([], Request::HEADER_X_FORWARDED_ALL); } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 5f604808da955..aa069bb7e952e 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -74,12 +74,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.2.0-RC1'; + const VERSION = '5.2.0-RC2'; const VERSION_ID = 50200; const MAJOR_VERSION = 5; const MINOR_VERSION = 2; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'RC1'; + const EXTRA_VERSION = 'RC2'; const END_OF_MAINTENANCE = '07/2021'; const END_OF_LIFE = '07/2021'; diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php index fd67af368b740..62179ba154fcd 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php @@ -239,6 +239,7 @@ public function testResponseIsExpirableButNotValidateableWhenMasterResponseCombi } /** + * @group time-sensitive * @dataProvider cacheControlMergingProvider */ public function testCacheControlMerging(array $expects, array $master, array $surrogates) diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php index 33d3bb957f808..7aa9789e98b95 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php @@ -151,8 +151,9 @@ public function applyOperations(string $dn, iterable $operations): void $operationsMapped[] = $modification->toArray(); } - if (!@ldap_modify_batch($this->getConnectionResource(), $dn, $operationsMapped)) { - throw new UpdateOperationException(sprintf('Error executing UpdateOperation on "%s": ', $dn).ldap_error($this->getConnectionResource()), ldap_errno($con)); + $con = $this->getConnectionResource(); + if (!@ldap_modify_batch($con, $dn, $operationsMapped)) { + throw new UpdateOperationException(sprintf('Error executing UpdateOperation on "%s": ', $dn).ldap_error($con), ldap_errno($con)); } } diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php index 6af544fb5b5c6..497c8313156ba 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php @@ -100,7 +100,7 @@ public function execute() $cookie = ''; do { if ($pageControl) { - $this->controlPagedResult($con, $pageSize, $cookie); + $this->controlPagedResult($con, $pageSize, true, $cookie); } $sizeLimit = $itemsLeft; if ($pageSize > 0 && $sizeLimit >= $pageSize) { @@ -174,7 +174,7 @@ public function getResources(): array private function resetPagination() { $con = $this->connection->getResource(); - $this->controlPagedResult($con, 0, ''); + $this->controlPagedResult($con, 0, false, ''); $this->serverctrls = []; // This is a workaround for a bit of a bug in the above invocation @@ -204,15 +204,15 @@ private function resetPagination() * * @param resource $con */ - private function controlPagedResult($con, int $pageSize, string $cookie): bool + private function controlPagedResult($con, int $pageSize, bool $critical, string $cookie): bool { if (\PHP_VERSION_ID < 70300) { - return ldap_control_paged_result($con, $pageSize, true, $cookie); + return ldap_control_paged_result($con, $pageSize, $critical, $cookie); } $this->serverctrls = [ [ 'oid' => \LDAP_CONTROL_PAGEDRESULTS, - 'isCritical' => true, + 'isCritical' => $critical, 'value' => [ 'size' => $pageSize, 'cookie' => $cookie, diff --git a/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/AdapterTest.php b/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/AdapterTest.php index 0c2253ee00142..566ba215dc6d8 100644 --- a/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/AdapterTest.php +++ b/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/AdapterTest.php @@ -22,15 +22,10 @@ /** * @requires extension ldap + * @group integration */ class AdapterTest extends LdapTestCase { - private const PAGINATION_REQUIRED_CONFIG = [ - 'options' => [ - 'protocol_version' => 3, - ], - ]; - public function testLdapEscape() { $ldap = new Adapter(); @@ -122,7 +117,7 @@ public function testLdapQueryScopeOneLevel() public function testLdapPagination() { - $ldap = new Adapter(array_merge($this->getLdapConfig(), static::PAGINATION_REQUIRED_CONFIG)); + $ldap = new Adapter($this->getLdapConfig()); $ldap->getConnection()->bind('cn=admin,dc=symfony,dc=com', 'symfony'); $entries = $this->setupTestUsers($ldap); @@ -153,7 +148,7 @@ public function testLdapPagination() $this->assertEquals(\count($fully_paged_query->getResources()), 1); $this->assertEquals(\count($paged_query->getResources()), 5); - if (\PHP_MAJOR_VERSION > 7 || (\PHP_MAJOR_VERSION == 7 && \PHP_MINOR_VERSION >= 2)) { + if (\PHP_VERSION_ID >= 70200) { // This last query is to ensure that we haven't botched the state of our connection // by not resetting pagination properly. extldap <= PHP 7.1 do not implement the necessary // bits to work around an implementation flaw, so we simply can't guarantee this to work there. @@ -205,7 +200,7 @@ private function destroyEntries($ldap, $entries) public function testLdapPaginationLimits() { - $ldap = new Adapter(array_merge($this->getLdapConfig(), static::PAGINATION_REQUIRED_CONFIG)); + $ldap = new Adapter($this->getLdapConfig()); $ldap->getConnection()->bind('cn=admin,dc=symfony,dc=com', 'symfony'); $entries = $this->setupTestUsers($ldap); diff --git a/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php b/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php index e23e8018547bf..bfdd7d5342ff5 100644 --- a/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php +++ b/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/LdapManagerTest.php @@ -15,7 +15,6 @@ use Symfony\Component\Ldap\Adapter\ExtLdap\Collection; use Symfony\Component\Ldap\Adapter\ExtLdap\UpdateOperation; use Symfony\Component\Ldap\Entry; -use Symfony\Component\Ldap\Exception\AlreadyExistsException; use Symfony\Component\Ldap\Exception\LdapException; use Symfony\Component\Ldap\Exception\NotBoundException; use Symfony\Component\Ldap\Exception\UpdateOperationException; @@ -23,6 +22,7 @@ /** * @requires extension ldap + * @group integration */ class LdapManagerTest extends LdapTestCase { @@ -82,7 +82,7 @@ public function testLdapAddInvalidEntry() */ public function testLdapAddDouble() { - $this->expectException(AlreadyExistsException::class); + $this->expectException(LdapException::class); $this->executeSearchQuery(1); $entry = new Entry('cn=Elsa Amrouche,dc=symfony,dc=com', [ @@ -94,7 +94,11 @@ public function testLdapAddDouble() $em = $this->adapter->getEntryManager(); $em->add($entry); - $em->add($entry); + try { + $em->add($entry); + } finally { + $em->remove($entry); + } } /** @@ -210,11 +214,12 @@ public function testLdapRenameWithoutRemovingOldRdn() $newEntry = $result[0]; $originalCN = $entry->getAttribute('cn')[0]; - $this->assertStringContainsString($originalCN, $newEntry->getAttribute('cn')); - - $entryManager->rename($newEntry, 'cn='.$originalCN); - - $this->executeSearchQuery(1); + try { + $this->assertContains($originalCN, $newEntry->getAttribute('cn')); + $this->assertContains('Kevin', $newEntry->getAttribute('cn')); + } finally { + $entryManager->rename($newEntry, 'cn='.$originalCN); + } } public function testLdapAddRemoveAttributeValues() @@ -372,7 +377,7 @@ public function testLdapMove() $result = $this->executeSearchQuery(1); $entry = $result[0]; - $this->assertNotContains('ou=Ldap', $entry->getDn()); + $this->assertStringNotContainsString('ou=Ldap', $entry->getDn()); $entryManager = $this->adapter->getEntryManager(); $entryManager->move($entry, 'ou=Ldap,ou=Components,dc=symfony,dc=com'); @@ -380,5 +385,8 @@ public function testLdapMove() $result = $this->executeSearchQuery(1); $movedEntry = $result[0]; $this->assertStringContainsString('ou=Ldap', $movedEntry->getDn()); + + // Move back entry + $entryManager->move($movedEntry, 'dc=symfony,dc=com'); } } diff --git a/src/Symfony/Component/Ldap/Tests/Fixtures/conf/slapd.conf b/src/Symfony/Component/Ldap/Tests/Fixtures/conf/slapd.conf deleted file mode 100644 index 24eebcb2dbe33..0000000000000 --- a/src/Symfony/Component/Ldap/Tests/Fixtures/conf/slapd.conf +++ /dev/null @@ -1,18 +0,0 @@ -# See slapd.conf(5) for details on configuration options. -include /etc/ldap/schema/core.schema -include /etc/ldap/schema/cosine.schema -include /etc/ldap/schema/inetorgperson.schema -include /etc/ldap/schema/nis.schema - -pidfile /tmp/slapd/slapd.pid -argsfile /tmp/slapd/slapd.args - -modulepath /tmp/slapd-modules -moduleload back_hdb - -database hdb -directory /tmp/slapd - -suffix "dc=symfony,dc=com" -rootdn "cn=admin,dc=symfony,dc=com" -rootpw {SSHA}btWUi971ytYpVMbZLkaQ2A6ETh3VA0lL diff --git a/src/Symfony/Component/Ldap/Tests/Fixtures/data/base.ldif b/src/Symfony/Component/Ldap/Tests/Fixtures/data/base.ldif deleted file mode 100644 index 25abb296c9a6c..0000000000000 --- a/src/Symfony/Component/Ldap/Tests/Fixtures/data/base.ldif +++ /dev/null @@ -1,4 +0,0 @@ -dn: dc=symfony,dc=com -objectClass: dcObject -objectClass: organizationalUnit -ou: Organization diff --git a/src/Symfony/Component/Ldap/Tests/LdapTestCase.php b/src/Symfony/Component/Ldap/Tests/LdapTestCase.php index 9a1424a62e8f7..606065e491e36 100644 --- a/src/Symfony/Component/Ldap/Tests/LdapTestCase.php +++ b/src/Symfony/Component/Ldap/Tests/LdapTestCase.php @@ -9,6 +9,7 @@ class LdapTestCase extends TestCase protected function getLdapConfig() { $h = @ldap_connect(getenv('LDAP_HOST'), getenv('LDAP_PORT')); + @ldap_set_option($h, LDAP_OPT_PROTOCOL_VERSION, 3); if (!$h || !@ldap_bind($h)) { $this->markTestSkipped('No server is listening on LDAP_HOST:LDAP_PORT'); diff --git a/src/Symfony/Component/Ldap/phpunit.xml.dist b/src/Symfony/Component/Ldap/phpunit.xml.dist index e9861f822170b..a679848078856 100644 --- a/src/Symfony/Component/Ldap/phpunit.xml.dist +++ b/src/Symfony/Component/Ldap/phpunit.xml.dist @@ -10,7 +10,7 @@ > - + diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index b6e7ca04dcd68..566625a7c1f7c 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -15,6 +15,7 @@ CHANGELOG * Added `LoginThrottlingListener`. * Added `LoginLinkAuthenticator`. * Moved methods `supports()` and `authenticate()` from `AbstractListener` to `FirewallListenerInterface`. + * [BC break] `PasswordUpgradeBadge::getPasswordUpgrader()` changed its return type to return null or a `PasswordUpgraderInterface` implementation. 5.1.0 ----- diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php index 5d88513af7ca0..cfffe9d307b78 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php @@ -30,10 +30,10 @@ class PasswordUpgradeBadge implements BadgeInterface private $passwordUpgrader; /** - * @param string $plaintextPassword The presented password, used in the rehash - * @param PasswordUpgraderInterface $passwordUpgrader The password upgrader, usually the UserProvider + * @param string $plaintextPassword The presented password, used in the rehash + * @param PasswordUpgraderInterface|null $passwordUpgrader The password upgrader, defaults to the UserProvider if null */ - public function __construct(string $plaintextPassword, PasswordUpgraderInterface $passwordUpgrader) + public function __construct(string $plaintextPassword, ?PasswordUpgraderInterface $passwordUpgrader = null) { $this->plaintextPassword = $plaintextPassword; $this->passwordUpgrader = $passwordUpgrader; @@ -51,7 +51,7 @@ public function getAndErasePlaintextPassword(): string return $password; } - public function getPasswordUpgrader(): PasswordUpgraderInterface + public function getPasswordUpgrader(): ?PasswordUpgraderInterface { return $this->passwordUpgrader; } diff --git a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php index 140552b3f3097..d667b5826eaca 100644 --- a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php @@ -13,7 +13,9 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use Symfony\Component\Security\Http\Event\LoginSuccessEvent; @@ -53,7 +55,19 @@ public function onLoginSuccess(LoginSuccessEvent $event): void return; } - $badge->getPasswordUpgrader()->upgradePassword($user, $passwordEncoder->encodePassword($plaintextPassword, $user->getSalt())); + $passwordUpgrader = $badge->getPasswordUpgrader(); + if (null === $passwordUpgrader) { + /** @var UserBadge $userBadge */ + $userBadge = $passport->getBadge(UserBadge::class); + $userLoader = $userBadge->getUserLoader(); + if (\is_array($userLoader) && $userLoader[0] instanceof PasswordUpgraderInterface) { + $passwordUpgrader = $userLoader[0]; + } else { + return; + } + } + + $passwordUpgrader->upgradePassword($user, $passwordEncoder->encodePassword($plaintextPassword, $user->getSalt())); } public static function getSubscribedEvents(): array diff --git a/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php b/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php index cb0d8fcdae114..5b862e6c0a755 100644 --- a/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php @@ -16,6 +16,9 @@ use Symfony\Component\Security\Http\Event\CheckPassportEvent; /** + * Configures the user provider as user loader, if no user load + * has been explicitly set. + * * @author Wouter de Jong * * @final diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php index bf90aa3be6d5e..42607ca853328 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php @@ -18,6 +18,7 @@ use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; @@ -34,9 +35,14 @@ class PasswordMigratingListenerTest extends TestCase protected function setUp(): void { + $this->user = $this->createMock(UserInterface::class); + $this->user->expects($this->any())->method('getPassword')->willReturn('old-encoded-password'); + $encoder = $this->createMock(PasswordEncoderInterface::class); + $encoder->expects($this->any())->method('needsRehash')->willReturn(true); + $encoder->expects($this->any())->method('encodePassword')->with('pa$$word', null)->willReturn('new-encoded-password'); $this->encoderFactory = $this->createMock(EncoderFactoryInterface::class); + $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->user)->willReturn($encoder); $this->listener = new PasswordMigratingListener($this->encoderFactory); - $this->user = $this->createMock(UserInterface::class); } /** @@ -61,16 +67,8 @@ public function provideUnsupportedEvents() yield [$this->createEvent($this->createMock(PassportInterface::class))]; } - public function testUpgrade() + public function testUpgradeWithUpgrader() { - $encoder = $this->createMock(PasswordEncoderInterface::class); - $encoder->expects($this->any())->method('needsRehash')->willReturn(true); - $encoder->expects($this->any())->method('encodePassword')->with('pa$$word', null)->willReturn('new-encoded-password'); - - $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->user)->willReturn($encoder); - - $this->user->expects($this->any())->method('getPassword')->willReturn('old-encoded-password'); - $passwordUpgrader = $this->createPasswordUpgrader(); $passwordUpgrader->expects($this->once()) ->method('upgradePassword') @@ -81,6 +79,20 @@ public function testUpgrade() $this->listener->onLoginSuccess($event); } + public function testUpgradeWithoutUpgrader() + { + $userLoader = $this->createMock(MigratingUserProvider::class); + $userLoader->expects($this->any())->method('loadUserByUsername')->willReturn($this->user); + + $userLoader->expects($this->once()) + ->method('upgradePassword') + ->with($this->user, 'new-encoded-password') + ; + + $event = $this->createEvent(new SelfValidatingPassport(new UserBadge('test', [$userLoader, 'loadUserByUsername']), [new PasswordUpgradeBadge('pa$$word')])); + $this->listener->onLoginSuccess($event); + } + private function createPasswordUpgrader() { return $this->createMock(PasswordUpgraderInterface::class); @@ -91,3 +103,7 @@ private function createEvent(PassportInterface $passport) return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->createMock(TokenInterface::class), new Request(), null, 'main'); } } + +abstract class MigratingUserProvider implements UserProviderInterface, PasswordUpgraderInterface +{ +} diff --git a/src/Symfony/Component/Validator/Constraints/IsinValidator.php b/src/Symfony/Component/Validator/Constraints/IsinValidator.php index 9ae31acb14669..d5e4d9dbc9736 100644 --- a/src/Symfony/Component/Validator/Constraints/IsinValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IsinValidator.php @@ -15,7 +15,6 @@ use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; -use Symfony\Component\Validator\Validator\ValidatorInterface; /** * @author Laurent Masforné @@ -24,16 +23,6 @@ */ class IsinValidator extends ConstraintValidator { - /** - * @var ValidatorInterface - */ - private $validator; - - public function __construct(ValidatorInterface $validator) - { - $this->validator = $validator; - } - /** * {@inheritdoc} */ @@ -87,6 +76,6 @@ private function isCorrectChecksum(string $input): bool } $number = implode('', $characters); - return 0 === $this->validator->validate($number, new Luhn())->count(); + return 0 === $this->context->getValidator()->validate($number, new Luhn())->count(); } } diff --git a/src/Symfony/Component/Validator/Context/ExecutionContext.php b/src/Symfony/Component/Validator/Context/ExecutionContext.php index 74ed391034f15..4560793eb5da8 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContext.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContext.php @@ -128,6 +128,7 @@ class ExecutionContext implements ExecutionContextInterface * @var array */ private $initializedObjects; + private $cachedObjectsRefs; /** * @param mixed $root The root value of the validated object graph @@ -141,6 +142,7 @@ public function __construct(ValidatorInterface $validator, $root, TranslatorInte $this->translator = $translator; $this->translationDomain = $translationDomain; $this->violations = new ConstraintViolationList(); + $this->cachedObjectsRefs = new \SplObjectStorage(); } /** @@ -346,4 +348,20 @@ public function isObjectInitialized(string $cacheKey): bool { return isset($this->initializedObjects[$cacheKey]); } + + /** + * @internal + * + * @param object $object + * + * @return string + */ + public function generateCacheKey($object) + { + if (!isset($this->cachedObjectsRefs[$object])) { + $this->cachedObjectsRefs[$object] = spl_object_hash($object); + } + + return $this->cachedObjectsRefs[$object]; + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IsinValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IsinValidatorTest.php index 0822fb5ad65f9..0a219401bcbe4 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/IsinValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/IsinValidatorTest.php @@ -4,16 +4,14 @@ use Symfony\Component\Validator\Constraints\Isin; use Symfony\Component\Validator\Constraints\IsinValidator; +use Symfony\Component\Validator\Constraints\Luhn; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; -use Symfony\Component\Validator\ValidatorBuilder; class IsinValidatorTest extends ConstraintValidatorTestCase { protected function createValidator() { - $validatorBuilder = new ValidatorBuilder(); - - return new IsinValidator($validatorBuilder->getValidator()); + return new IsinValidator(); } public function testNullIsValid() @@ -36,6 +34,7 @@ public function testEmptyStringIsValid() public function testValidIsin($isin) { $this->validator->validate($isin, new Isin()); + $this->expectViolationsAt(0, $isin, new Luhn()); $this->assertNoViolation(); } @@ -103,6 +102,7 @@ public function getIsinWithInvalidPattern() */ public function testIsinWithValidFormatButIncorrectChecksum($isin) { + $this->expectViolationsAt(0, $isin, new Luhn()); $this->assertViolationRaised($isin, Isin::INVALID_CHECKSUM_ERROR); } diff --git a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php index f543ddf7d6c86..6866a88084d90 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php @@ -13,12 +13,15 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Translation\IdentityTranslator; +use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\Cascade; use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\Constraints\Expression; use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\IsFalse; +use Symfony\Component\Validator\Constraints\IsNull; use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; @@ -28,6 +31,7 @@ use Symfony\Component\Validator\Constraints\Traverse; use Symfony\Component\Validator\Constraints\Type; use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\ConstraintViolationInterface; use Symfony\Component\Validator\Context\ExecutionContextFactory; @@ -2330,4 +2334,66 @@ public function testValidateWithExplicitCascade() CascadingEntity::$staticChild = null; } + + public function testValidatedConstraintsHashesDoNotCollide() + { + $metadata = new ClassMetadata(Entity::class); + $metadata->addPropertyConstraint('initialized', new NotNull(['groups' => 'should_pass'])); + $metadata->addPropertyConstraint('initialized', new IsNull(['groups' => 'should_fail'])); + + $this->metadataFactory->addMetadata($metadata); + + $entity = new Entity(); + $entity->data = new \stdClass(); + + $this->assertCount(2, $this->validator->validate($entity, new TestConstraintHashesDoNotCollide())); + } + + public function testValidatedConstraintsHashesDoNotCollideWithSameConstraintValidatingDifferentProperties() + { + $value = new \stdClass(); + + $entity = new Entity(); + $entity->firstName = $value; + $entity->setLastName($value); + + $validator = $this->validator->startContext($entity); + + $constraint = new IsNull(); + $validator->atPath('firstName') + ->validate($entity->firstName, $constraint); + $validator->atPath('lastName') + ->validate($entity->getLastName(), $constraint); + + $this->assertCount(2, $validator->getViolations()); + } +} + +final class TestConstraintHashesDoNotCollide extends Constraint +{ +} + +final class TestConstraintHashesDoNotCollideValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$value instanceof Entity) { + throw new \LogicException(); + } + + $this->context->getValidator() + ->inContext($this->context) + ->atPath('data') + ->validate($value, new NotNull()) + ->validate($value, new NotNull()) + ->validate($value, new IsFalse()); + + $this->context->getValidator() + ->inContext($this->context) + ->validate($value, null, new GroupSequence(['should_pass'])) + ->validate($value, null, new GroupSequence(['should_fail'])); + } } diff --git a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php index eaa1309fd0ff3..b1f1384ffbd16 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php @@ -108,7 +108,7 @@ public function validate($value, $constraints = null, $groups = null) $this->validateGenericNode( $value, $previousObject, - \is_object($value) ? spl_object_hash($value) : null, + \is_object($value) ? $this->generateCacheKey($value) : null, $metadata, $this->defaultPropertyPath, $groups, @@ -176,7 +176,7 @@ public function validateProperty($object, string $propertyName, $groups = null) $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; - $cacheKey = spl_object_hash($object); + $cacheKey = $this->generateCacheKey($object); $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName); $previousValue = $this->context->getValue(); @@ -224,7 +224,7 @@ public function validatePropertyValue($objectOrClass, string $propertyName, $val if (\is_object($objectOrClass)) { $object = $objectOrClass; $class = \get_class($object); - $cacheKey = spl_object_hash($objectOrClass); + $cacheKey = $this->generateCacheKey($objectOrClass); $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName); } else { // $objectOrClass contains a class name @@ -311,7 +311,7 @@ private function validateObject($object, string $propertyPath, array $groups, in $this->validateClassNode( $object, - spl_object_hash($object), + $this->generateCacheKey($object), $classMetadata, $propertyPath, $groups, @@ -425,7 +425,7 @@ private function validateClassNode(object $object, ?string $cacheKey, ClassMetad $defaultOverridden = false; // Use the object hash for group sequences - $groupHash = \is_object($group) ? spl_object_hash($group) : $group; + $groupHash = \is_object($group) ? $this->generateCacheKey($group, true) : $group; if ($context->isGroupValidated($cacheKey, $groupHash)) { // Skip this group when validating the properties and when @@ -730,7 +730,7 @@ private function validateInGroup($value, ?string $cacheKey, MetadataInterface $m // Prevent duplicate validation of constraints, in the case // that constraints belong to multiple validated groups if (null !== $cacheKey) { - $constraintHash = spl_object_hash($constraint); + $constraintHash = $this->generateCacheKey($constraint, true); // instanceof Valid: In case of using a Valid constraint with many groups // it makes a reference object get validated by each group if ($constraint instanceof Composite || $constraint instanceof Valid) { @@ -762,4 +762,22 @@ private function validateInGroup($value, ?string $cacheKey, MetadataInterface $m } } } + + /** + * @param object $object + */ + private function generateCacheKey($object, bool $dependsOnPropertyPath = false): string + { + if ($this->context instanceof ExecutionContext) { + $cacheKey = $this->context->generateCacheKey($object); + } else { + $cacheKey = spl_object_hash($object); + } + + if ($dependsOnPropertyPath) { + $cacheKey .= $this->context->getPropertyPath(); + } + + return $cacheKey; + } } diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 690c20298365d..f98fcd69795c8 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -1190,27 +1190,6 @@ private function parseQuotedString(string $yaml): ?string } return $value; - - for ($i = 1; isset($yaml[$i]) && $quotation !== $yaml[$i]; ++$i) { - } - - // quoted single line string - if (isset($yaml[$i]) && $quotation === $yaml[$i]) { - return $yaml; - } - - $lines = [$yaml]; - - while ($this->moveToNextLine()) { - for ($i = 1; isset($this->currentLine[$i]) && $quotation !== $this->currentLine[$i]; ++$i) { - } - - $lines[] = trim($this->currentLine); - - if (isset($this->currentLine[$i]) && $quotation === $this->currentLine[$i]) { - break; - } - } } private function lexInlineMapping(string $yaml): string