diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000000..64eca4a5c6f72 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,3 @@ +# Apply php-cs-fixer fix --rules nullable_type_declaration_for_default_null_value +f4118e110a46de3ffb799e7d79bf15128d1646ea +9519b54417c09c49496a4a6be238e63be9a73465 diff --git a/.gitattributes b/.gitattributes index d30fb22a3bdbb..d1570aff1cd79 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,5 @@ /src/Symfony/Component/Messenger/Bridge export-ignore /src/Symfony/Component/Notifier/Bridge export-ignore /src/Symfony/Component/Runtime export-ignore +/src/Symfony/Component/Translation/Bridge export-ignore +/src/Symfony/Component/Intl/Resources/data/*/* linguist-generated=true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d925c20a4f318..2acf5f0f6cde1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -38,10 +38,10 @@ # Serializer /src/Symfony/Component/Serializer/ @dunglas # Security -/src/Symfony/Bridge/Doctrine/Security/ @wouterj @chalasr -/src/Symfony/Bundle/SecurityBundle/ @wouterj @chalasr -/src/Symfony/Component/Security/ @wouterj @chalasr -/src/Symfony/Component/Ldap/Security/ @wouterj @chalasr +/src/Symfony/Bridge/Doctrine/Security/ @chalasr +/src/Symfony/Bundle/SecurityBundle/ @chalasr +/src/Symfony/Component/Security/ @chalasr +/src/Symfony/Component/Ldap/Security/ @chalasr # Scheduler /src/Symfony/Component/Scheduler/ @kbond # TwigBundle diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 72ea7cfa3e9c0..00a24cbcfc13c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,14 +1,14 @@ | Q | A | ------------- | --- -| Branch? | 6.3 for features / 5.4 or 6.2 for bug fixes +| Branch? | 7.1 for features / 5.4, 6.4, or 7.0 for bug fixes | Bug fix? | yes/no | New feature? | yes/no | Deprecations? | yes/no -| Tickets | Fix #... +| Issues | Fix #... | License | MIT -| Doc PR | symfony/symfony-docs#... + + + + + + + + + + diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 93d7ac92b4ce1..03bda678cca76 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Deprecate `DoctrineDbalCacheAdapterSchemaSubscriber` in favor of `DoctrineDbalCacheAdapterSchemaListener` * Deprecate `MessengerTransportDoctrineSchemaSubscriber` in favor of `MessengerTransportDoctrineSchemaListener` * Deprecate `RememberMeTokenProviderDoctrineSchemaSubscriber` in favor of `RememberMeTokenProviderDoctrineSchemaListener` + * Add optional parameter `$isSameDatabase` to `DoctrineTokenProvider::configureSchema()` 6.2 --- diff --git a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php index 42cb71bfca07c..10b1de236f71e 100644 --- a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php +++ b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php @@ -31,6 +31,7 @@ class ContainerAwareEventManager extends EventManager private array $listeners = []; private array $initialized = []; private bool $initializedSubscribers = false; + private array $initializedHashMapping = []; private array $methods = []; private ContainerInterface $container; @@ -43,7 +44,7 @@ public function __construct(ContainerInterface $container, array $listeners = [] $this->listeners = $listeners; } - public function dispatchEvent($eventName, EventArgs $eventArgs = null): void + public function dispatchEvent($eventName, ?EventArgs $eventArgs = null): void { if (!$this->initializedSubscribers) { $this->initializeSubscribers(); @@ -63,9 +64,6 @@ public function dispatchEvent($eventName, EventArgs $eventArgs = null): void } } - /** - * @return object[][] - */ public function getListeners($event = null): array { if (null === $event) { @@ -122,6 +120,7 @@ public function addEventListener($events, $listener): void if (\is_string($listener)) { unset($this->initialized[$event]); + unset($this->initializedHashMapping[$event][$hash]); } else { $this->methods[$event][$hash] = $this->getMethod($listener, $event); } @@ -137,6 +136,11 @@ public function removeEventListener($events, $listener): void $hash = $this->getHash($listener); foreach ((array) $events as $event) { + if (isset($this->initializedHashMapping[$event][$hash])) { + $hash = $this->initializedHashMapping[$event][$hash]; + unset($this->initializedHashMapping[$event][$hash]); + } + // Check if we actually have this listener associated if (isset($this->listeners[$event][$hash])) { unset($this->listeners[$event][$hash]); @@ -169,13 +173,25 @@ public function removeEventSubscriber(EventSubscriber $subscriber): void private function initializeListeners(string $eventName): void { $this->initialized[$eventName] = true; + + // We'll refill the whole array in order to keep the same order + $listeners = []; foreach ($this->listeners[$eventName] as $hash => $listener) { if (\is_string($listener)) { - $this->listeners[$eventName][$hash] = $listener = $this->container->get($listener); + $listener = $this->container->get($listener); + $newHash = $this->getHash($listener); + + $this->initializedHashMapping[$eventName][$hash] = $newHash; + + $listeners[$newHash] = $listener; - $this->methods[$eventName][$hash] = $this->getMethod($listener, $eventName); + $this->methods[$eventName][$newHash] = $this->getMethod($listener, $eventName); + } else { + $listeners[$hash] = $listener; } } + + $this->listeners[$eventName] = $listeners; } private function initializeSubscribers(): void @@ -191,8 +207,8 @@ private function initializeSubscribers(): void if (\is_string($listener)) { $listener = $this->container->get($listener); } - // throw new \InvalidArgumentException(sprintf('Using Doctrine subscriber "%s" is not allowed, declare it as a listener instead.', \is_object($listener) ? $listener::class : $listener)); - trigger_deprecation('symfony/doctrine-bridge', '6.3', 'Using Doctrine subscribers as services is deprecated, declare listeners instead'); + // throw new \InvalidArgumentException(sprintf('Using Doctrine subscriber "%s" is not allowed. Register it as a listener instead, using e.g. the #[AsDoctrineListener] or #[AsDocumentListener] attribute.', \is_object($listener) ? $listener::class : $listener)); + trigger_deprecation('symfony/doctrine-bridge', '6.3', 'Registering "%s" as a Doctrine subscriber is deprecated. Register it as a listener instead, using e.g. the #[AsDoctrineListener] or #[AsDocumentListener] attribute.', \is_object($listener) ? get_debug_type($listener) : $listener); parent::addEventSubscriber($listener); } } diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index ada5fcbd49804..cedff599f5351 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -58,7 +58,7 @@ public function addLogger(string $name, DebugStack $logger) /** * @return void */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, ?\Throwable $exception = null) { $this->data = [ 'queries' => $this->collectQueries(), diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php index 1ce0ffd40cd9b..7cbe341b75c12 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php @@ -141,7 +141,7 @@ protected function setMappingDriverConfig(array $mappingConfig, string $mappingN * * Returns false when autodetection failed, an array of the completed information otherwise. */ - protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, ContainerBuilder $container, string $bundleDir = null): array|false + protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, ContainerBuilder $container, ?string $bundleDir = null): array|false { $bundleClassDir = \dirname($bundle->getFileName()); $bundleDir ??= $bundleClassDir; @@ -451,7 +451,7 @@ abstract protected function getMappingObjectDefaultName(): string; /** * Relative path from the bundle root to the directory where mapping files reside. */ - abstract protected function getMappingResourceConfigDirectory(string $bundleDir = null): string; + abstract protected function getMappingResourceConfigDirectory(?string $bundleDir = null): string; /** * Extension used by the mapping files. diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index 16a37b524acc6..d920fbcc8bead 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -75,7 +75,7 @@ private function addTaggedServices(ContainerBuilder $container): array $listenerTag = $this->tagPrefix.'.event_listener'; $subscriberTag = $this->tagPrefix.'.event_subscriber'; $listenerRefs = []; - $taggedServices = $this->findAndSortTags([$subscriberTag, $listenerTag], $container); + $taggedServices = $this->findAndSortTags($subscriberTag, $listenerTag, $container); $managerDefs = []; foreach ($taggedServices as $taggedSubscriber) { @@ -106,7 +106,7 @@ private function addTaggedServices(ContainerBuilder $container): array $refs = $managerDef->getArguments()[1] ?? []; $listenerRefs[$con][$id] = new Reference($id); if ($subscriberTag === $tagName) { - trigger_deprecation('symfony/doctrine-bridge', '6.3', 'Using Doctrine subscribers as services is deprecated, declare listeners instead'); + trigger_deprecation('symfony/doctrine-bridge', '6.3', 'Registering "%s" as a Doctrine subscriber is deprecated. Register it as a listener instead, using e.g. the #[%s] attribute.', $id, str_starts_with($this->tagPrefix, 'doctrine_mongodb') ? 'AsDocumentListener' : 'AsDoctrineListener'); $refs[] = $id; } else { $refs[] = [[$tag['event']], $id]; @@ -144,12 +144,17 @@ private function getEventManagerDef(ContainerBuilder $container, string $name): * @see https://bugs.php.net/53710 * @see https://bugs.php.net/60926 */ - private function findAndSortTags(array $tagNames, ContainerBuilder $container): array + private function findAndSortTags(string $subscriberTag, string $listenerTag, ContainerBuilder $container): array { $sortedTags = []; - - foreach ($tagNames as $tagName) { - foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $tags) { + $taggedIds = [ + $subscriberTag => $container->findTaggedServiceIds($subscriberTag, true), + $listenerTag => $container->findTaggedServiceIds($listenerTag, true), + ]; + $taggedIds[$subscriberTag] = array_diff_key($taggedIds[$subscriberTag], $taggedIds[$listenerTag]); + + foreach ($taggedIds as $tagName => $serviceIds) { + foreach ($serviceIds as $serviceId => $tags) { foreach ($tags as $attributes) { $priority = $attributes['priority'] ?? 0; $sortedTags[$priority][] = [$tagName, $serviceId, $attributes]; @@ -157,11 +162,8 @@ private function findAndSortTags(array $tagNames, ContainerBuilder $container): } } - if ($sortedTags) { - krsort($sortedTags); - $sortedTags = array_merge(...$sortedTags); - } + krsort($sortedTags); - return $sortedTags; + return array_merge(...$sortedTags); } } diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php index 52cc766ac9c5d..9531de627d9b8 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php @@ -36,12 +36,12 @@ class DoctrineChoiceLoader extends AbstractChoiceLoader * * @param string $class The class name of the loaded objects */ - public function __construct(ObjectManager $manager, string $class, IdReader $idReader = null, EntityLoaderInterface $objectLoader = null) + public function __construct(ObjectManager $manager, string $class, ?IdReader $idReader = null, ?EntityLoaderInterface $objectLoader = null) { $classMetadata = $manager->getClassMetadata($class); if ($idReader && !$idReader->isSingleId()) { - throw new \InvalidArgumentException(sprintf('The second argument "$idReader" of "%s" must be null when the query cannot be optimized because of composite id fields.', __METHOD__)); + throw new \InvalidArgumentException(sprintf('The "$idReader" argument of "%s" must be null when the query cannot be optimized because of composite id fields.', __METHOD__)); } $this->manager = $manager; @@ -57,9 +57,6 @@ protected function loadChoices(): iterable : $this->manager->getRepository($this->class)->findAll(); } - /** - * @internal to be remove in Symfony 6 - */ protected function doLoadValuesForChoices(array $choices): array { // Optimize performance for single-field identifiers. We already diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php index 15a685bbc9bef..cf99efde61f61 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php @@ -74,7 +74,7 @@ public function isIntId(): bool * * This method assumes that the object has a single-column ID. */ - public function getIdValue(object $object = null): string + public function getIdValue(?object $object = null): string { if (!$object) { return ''; diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index c8b341f0edddb..e3a4c021f0ce2 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; @@ -70,13 +71,13 @@ public function getEntitiesByIds(string $identifier, array $values): array $entity = current($qb->getRootEntities()); $metadata = $qb->getEntityManager()->getClassMetadata($entity); if (\in_array($type = $metadata->getTypeOfField($identifier), ['integer', 'bigint', 'smallint'])) { - $parameterType = Connection::PARAM_INT_ARRAY; + $parameterType = class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY; // Filter out non-integer values (e.g. ""). If we don't, some // databases such as PostgreSQL fail. $values = array_values(array_filter($values, fn ($v) => (string) $v === (string) (int) $v || ctype_digit($v))); } elseif (\in_array($type, ['ulid', 'uuid', 'guid'])) { - $parameterType = Connection::PARAM_STR_ARRAY; + $parameterType = class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY; // Like above, but we just filter out empty strings. $values = array_values(array_filter($values, fn ($v) => '' !== (string) $v)); @@ -95,7 +96,7 @@ public function getEntitiesByIds(string $identifier, array $values): array unset($value); } } else { - $parameterType = Connection::PARAM_STR_ARRAY; + $parameterType = class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY; } if (!$values) { return []; diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 1847e50eeef28..3fd13ce951567 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -60,7 +60,7 @@ public function guessType(string $class, string $property): ?TypeGuess } return match ($metadata->getTypeOfField($property)) { - Types::ARRAY, + 'array', // DBAL < 4 Types::SIMPLE_ARRAY => new TypeGuess(CollectionType::class, [], Guess::MEDIUM_CONFIDENCE), Types::BOOLEAN => new TypeGuess(CheckboxType::class, [], Guess::HIGH_CONFIDENCE), Types::DATETIME_MUTABLE, diff --git a/src/Symfony/Bridge/Doctrine/IdGenerator/UlidGenerator.php b/src/Symfony/Bridge/Doctrine/IdGenerator/UlidGenerator.php index 95573309f9e4b..f84b67a344eb0 100644 --- a/src/Symfony/Bridge/Doctrine/IdGenerator/UlidGenerator.php +++ b/src/Symfony/Bridge/Doctrine/IdGenerator/UlidGenerator.php @@ -21,7 +21,7 @@ final class UlidGenerator extends AbstractIdGenerator { private ?UlidFactory $factory; - public function __construct(UlidFactory $factory = null) + public function __construct(?UlidFactory $factory = null) { $this->factory = $factory; } diff --git a/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php b/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php index 8c366fd80d734..e326e3524e00e 100644 --- a/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php +++ b/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php @@ -26,7 +26,7 @@ final class UuidGenerator extends AbstractIdGenerator private UuidFactory|NameBasedUuidFactory|RandomBasedUuidFactory|TimeBasedUuidFactory $factory; private ?string $entityGetter = null; - public function __construct(UuidFactory $factory = null) + public function __construct(?UuidFactory $factory = null) { $this->protoFactory = $this->factory = $factory ?? new UuidFactory(); } @@ -52,7 +52,7 @@ public function generateId(EntityManagerInterface $em, $entity): Uuid return $this->factory->create(); } - public function nameBased(string $entityGetter, Uuid|string $namespace = null): static + public function nameBased(string $entityGetter, Uuid|string|null $namespace = null): static { $clone = clone $this; $clone->factory = $clone->protoFactory->nameBased($namespace); @@ -70,7 +70,7 @@ public function randomBased(): static return $clone; } - public function timeBased(Uuid|string $node = null): static + public function timeBased(Uuid|string|null $node = null): static { $clone = clone $this; $clone->factory = $clone->protoFactory->timeBased($node); diff --git a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php index b2369e95d601a..235e3f7de6cbd 100644 --- a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php +++ b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php @@ -26,13 +26,13 @@ class DbalLogger implements SQLLogger protected $logger; protected $stopwatch; - public function __construct(LoggerInterface $logger = null, Stopwatch $stopwatch = null) + public function __construct(?LoggerInterface $logger = null, ?Stopwatch $stopwatch = null) { $this->logger = $logger; $this->stopwatch = $stopwatch; } - public function startQuery($sql, array $params = null, array $types = null): void + public function startQuery($sql, ?array $params = null, ?array $types = null): void { $this->stopwatch?->start('doctrine', 'doctrine'); diff --git a/src/Symfony/Bridge/Doctrine/Messenger/AbstractDoctrineMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/AbstractDoctrineMiddleware.php index 9fbf2deb963e3..83413c37871f6 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/AbstractDoctrineMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/AbstractDoctrineMiddleware.php @@ -28,7 +28,7 @@ abstract class AbstractDoctrineMiddleware implements MiddlewareInterface protected $managerRegistry; protected $entityManagerName; - public function __construct(ManagerRegistry $managerRegistry, string $entityManagerName = null) + public function __construct(ManagerRegistry $managerRegistry, ?string $entityManagerName = null) { $this->managerRegistry = $managerRegistry; $this->entityManagerName = $entityManagerName; diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php index 246f0090e58ef..2ef3bbbb92815 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php @@ -26,8 +26,10 @@ class DoctrineOpenTransactionLoggerMiddleware extends AbstractDoctrineMiddleware { private $logger; + /** @var bool */ + private $isHandling = false; - public function __construct(ManagerRegistry $managerRegistry, string $entityManagerName = null, LoggerInterface $logger = null) + public function __construct(ManagerRegistry $managerRegistry, ?string $entityManagerName = null, ?LoggerInterface $logger = null) { parent::__construct($managerRegistry, $entityManagerName); @@ -36,6 +38,12 @@ public function __construct(ManagerRegistry $managerRegistry, string $entityMana protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope { + if ($this->isHandling) { + return $stack->next()->handle($envelope, $stack); + } + + $this->isHandling = true; + try { return $stack->next()->handle($envelope, $stack); } finally { @@ -44,6 +52,7 @@ protected function handleForManager(EntityManagerInterface $entityManager, Envel 'message' => $envelope->getMessage(), ]); } + $this->isHandling = false; } } } diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php index 1831715039d2e..08e1269b61e9c 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Doctrine\Messenger; +use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception as DBALException; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\Envelope; @@ -38,14 +39,23 @@ private function pingConnection(EntityManagerInterface $entityManager): void $connection = $entityManager->getConnection(); try { - $connection->executeQuery($connection->getDatabasePlatform()->getDummySelectSQL()); + $this->executeDummySql($connection); } catch (DBALException) { $connection->close(); - $connection->connect(); + // Attempt to reestablish the lazy connection by sending another query. + $this->executeDummySql($connection); } if (!$entityManager->isOpen()) { $this->managerRegistry->resetManager($this->entityManagerName); } } + + /** + * @throws DBALException + */ + private function executeDummySql(Connection $connection): void + { + $connection->executeQuery($connection->getDatabasePlatform()->getDummySelectSQL()); + } } diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php index f55a0d220a874..a0d642dd7d250 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php @@ -14,18 +14,16 @@ use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; use Doctrine\DBAL\Driver\Result; -use Doctrine\DBAL\Driver\Statement as DriverStatement; use Symfony\Component\Stopwatch\Stopwatch; /** * @author Laurent VOULLEMIER + * @author Alexander M. Turek * * @internal */ final class Connection extends AbstractConnectionMiddleware { - private int $nestingLevel = 0; - public function __construct( ConnectionInterface $connection, private DebugDataHolder $debugDataHolder, @@ -35,7 +33,7 @@ public function __construct( parent::__construct($connection); } - public function prepare(string $sql): DriverStatement + public function prepare(string $sql): Statement { return new Statement( parent::prepare($sql), @@ -54,13 +52,11 @@ public function query(string $sql): Result $query->start(); try { - $result = parent::query($sql); + return parent::query($sql); } finally { $query->stop(); $this->stopwatch?->stop('doctrine'); } - - return $result; } public function exec(string $sql): int @@ -80,63 +76,51 @@ public function exec(string $sql): int return $affectedRows; } - public function beginTransaction(): bool + public function beginTransaction(): void { - $query = null; - if (1 === ++$this->nestingLevel) { - $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"START TRANSACTION"')); - } + $query = new Query('"START TRANSACTION"'); + $this->debugDataHolder->addQuery($this->connectionName, $query); $this->stopwatch?->start('doctrine', 'doctrine'); - $query?->start(); + $query->start(); try { - $ret = parent::beginTransaction(); + parent::beginTransaction(); } finally { - $query?->stop(); + $query->stop(); $this->stopwatch?->stop('doctrine'); } - - return $ret; } - public function commit(): bool + public function commit(): void { - $query = null; - if (1 === $this->nestingLevel--) { - $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"COMMIT"')); - } + $query = new Query('"COMMIT"'); + $this->debugDataHolder->addQuery($this->connectionName, $query); $this->stopwatch?->start('doctrine', 'doctrine'); - $query?->start(); + $query->start(); try { - $ret = parent::commit(); + parent::commit(); } finally { - $query?->stop(); + $query->stop(); $this->stopwatch?->stop('doctrine'); } - - return $ret; } - public function rollBack(): bool + public function rollBack(): void { - $query = null; - if (1 === $this->nestingLevel--) { - $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"ROLLBACK"')); - } + $query = new Query('"ROLLBACK"'); + $this->debugDataHolder->addQuery($this->connectionName, $query); $this->stopwatch?->start('doctrine', 'doctrine'); - $query?->start(); + $query->start(); try { - $ret = parent::rollBack(); + parent::rollBack(); } finally { - $query?->stop(); + $query->stop(); $this->stopwatch?->stop('doctrine'); } - - return $ret; } } diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Connection.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Connection.php new file mode 100644 index 0000000000000..e3bec4d611780 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Connection.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug\DBAL3; + +use Doctrine\DBAL\Driver\Connection as ConnectionInterface; +use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; +use Doctrine\DBAL\Driver\Result; +use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; +use Symfony\Bridge\Doctrine\Middleware\Debug\Query; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Connection extends AbstractConnectionMiddleware +{ + private int $nestingLevel = 0; + + public function __construct( + ConnectionInterface $connection, + private DebugDataHolder $debugDataHolder, + private ?Stopwatch $stopwatch, + private string $connectionName, + ) { + parent::__construct($connection); + } + + public function prepare(string $sql): Statement + { + return new Statement( + parent::prepare($sql), + $this->debugDataHolder, + $this->connectionName, + $sql, + $this->stopwatch, + ); + } + + public function query(string $sql): Result + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + return parent::query($sql); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function exec(string $sql): int + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + return parent::exec($sql); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function beginTransaction(): bool + { + $query = null; + if (1 === ++$this->nestingLevel) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"START TRANSACTION"')); + } + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query?->start(); + + try { + return parent::beginTransaction(); + } finally { + $query?->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function commit(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"COMMIT"')); + } + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query?->start(); + + try { + return parent::commit(); + } finally { + $query?->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function rollBack(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"ROLLBACK"')); + } + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query?->start(); + + try { + return parent::rollBack(); + } finally { + $query?->stop(); + $this->stopwatch?->stop('doctrine'); + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Statement.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Statement.php new file mode 100644 index 0000000000000..53b117eaba3e5 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Statement.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug\DBAL3; + +use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; +use Doctrine\DBAL\Driver\Result as ResultInterface; +use Doctrine\DBAL\Driver\Statement as StatementInterface; +use Doctrine\DBAL\ParameterType; +use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; +use Symfony\Bridge\Doctrine\Middleware\Debug\Query; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Statement extends AbstractStatementMiddleware +{ + private Query $query; + + public function __construct( + StatementInterface $statement, + private DebugDataHolder $debugDataHolder, + private string $connectionName, + string $sql, + private ?Stopwatch $stopwatch = null, + ) { + $this->query = new Query($sql); + + parent::__construct($statement); + } + + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool + { + $this->query->setParam($param, $variable, $type); + + return parent::bindParam($param, $variable, $type, ...\array_slice(\func_get_args(), 3)); + } + + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + $this->query->setValue($param, $value, $type); + + return parent::bindValue($param, $value, $type); + } + + public function execute($params = null): ResultInterface + { + if (null !== $params) { + $this->query->setValues($params); + } + + // clone to prevent variables by reference to change + $this->debugDataHolder->addQuery($this->connectionName, $query = clone $this->query); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + return parent::execute($params); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php index c66bed90d08fd..ea1ecfbd60b05 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Middleware\Debug; use Doctrine\DBAL\Driver as DriverInterface; +use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use Symfony\Component\Stopwatch\Stopwatch; @@ -24,20 +25,31 @@ final class Driver extends AbstractDriverMiddleware { public function __construct( DriverInterface $driver, - private DebugDataHolder $debugDataHolder, - private ?Stopwatch $stopwatch, - private string $connectionName, + private readonly DebugDataHolder $debugDataHolder, + private readonly ?Stopwatch $stopwatch, + private readonly string $connectionName, ) { parent::__construct($driver); } - public function connect(array $params): Connection + public function connect(array $params): ConnectionInterface { + $connection = parent::connect($params); + + if ('void' !== (string) (new \ReflectionMethod(DriverInterface\Connection::class, 'commit'))->getReturnType()) { + return new DBAL3\Connection( + $connection, + $this->debugDataHolder, + $this->stopwatch, + $this->connectionName + ); + } + return new Connection( - parent::connect($params), + $connection, $this->debugDataHolder, $this->stopwatch, - $this->connectionName, + $this->connectionName ); } } diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php index 9813d712132f4..7bbc8dbceddee 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php @@ -21,13 +21,15 @@ class Query { private array $params = []; + + /** @var array */ private array $types = []; private ?float $start = null; private ?float $duration = null; public function __construct( - private string $sql, + private readonly string $sql, ) { } @@ -43,7 +45,7 @@ public function stop(): void } } - public function setParam(string|int $param, null|string|int|float|bool &$variable, int $type): void + public function setParam(string|int $param, mixed &$variable, ParameterType|int $type): void { // Numeric indexes start at 0 in profiler $idx = \is_int($param) ? $param - 1 : $param; @@ -52,7 +54,7 @@ public function setParam(string|int $param, null|string|int|float|bool &$variabl $this->types[$idx] = $type; } - public function setValue(string|int $param, mixed $value, int $type): void + public function setValue(string|int $param, mixed $value, ParameterType|int $type): void { // Numeric indexes start at 0 in profiler $idx = \is_int($param) ? $param - 1 : $param; @@ -85,7 +87,7 @@ public function getParams(): array } /** - * @return array + * @return array */ public function getTypes(): array { diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php index 67cf2f5e3119e..3f4ba10fc2138 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php @@ -19,6 +19,7 @@ /** * @author Laurent VOULLEMIER + * @author Alexander M. Turek * * @internal */ @@ -38,26 +39,15 @@ public function __construct( $this->query = new Query($sql); } - public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool - { - $this->query->setParam($param, $variable, $type); - - return parent::bindParam($param, $variable, $type, ...\array_slice(\func_get_args(), 3)); - } - - public function bindValue($param, $value, $type = ParameterType::STRING): bool + public function bindValue(int|string $param, mixed $value, ParameterType $type): void { $this->query->setValue($param, $value, $type); - return parent::bindValue($param, $value, $type); + parent::bindValue($param, $value, $type); } - public function execute($params = null): ResultInterface + public function execute(): ResultInterface { - if (null !== $params) { - $this->query->setValues($params); - } - // clone to prevent variables by reference to change $this->debugDataHolder->addQuery($this->connectionName, $query = clone $this->query); @@ -65,12 +55,10 @@ public function execute($params = null): ResultInterface $query->start(); try { - $result = parent::execute($params); + return parent::execute(); } finally { $query->stop(); $this->stopwatch?->stop('doctrine'); } - - return $result; } } diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index c567fc37fd835..34b0e55c64c52 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -14,9 +14,8 @@ use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; -use Doctrine\ORM\Mapping\Embedded; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Doctrine\Persistence\Mapping\MappingException; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; @@ -46,7 +45,7 @@ public function getProperties(string $class, array $context = []): ?array $properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames()); - if ($metadata instanceof ClassMetadataInfo && class_exists(Embedded::class) && $metadata->embeddedClasses) { + if ($metadata instanceof ClassMetadata && $metadata->embeddedClasses) { $properties = array_filter($properties, fn ($property) => !str_contains($property, '.')); $properties = array_merge($properties, array_keys($metadata->embeddedClasses)); @@ -65,7 +64,7 @@ public function getTypes(string $class, string $property, array $context = []): $class = $metadata->getAssociationTargetClass($property); if ($metadata->isSingleValuedAssociation($property)) { - if ($metadata instanceof ClassMetadataInfo) { + if ($metadata instanceof ClassMetadata) { $associationMapping = $metadata->getAssociationMapping($property); $nullable = $this->isAssociationNullable($associationMapping); @@ -78,11 +77,10 @@ public function getTypes(string $class, string $property, array $context = []): $collectionKeyType = Type::BUILTIN_TYPE_INT; - if ($metadata instanceof ClassMetadataInfo) { + if ($metadata instanceof ClassMetadata) { $associationMapping = $metadata->getAssociationMapping($property); if (isset($associationMapping['indexBy'])) { - /** @var ClassMetadataInfo $subMetadata */ $subMetadata = $this->entityManager->getClassMetadata($associationMapping['targetEntity']); // Check if indexBy value is a property @@ -94,7 +92,6 @@ public function getTypes(string $class, string $property, array $context = []): // Maybe the column name is the association join column? $associationMapping = $subMetadata->getAssociationMapping($fieldName); - /** @var ClassMetadataInfo $subMetadata */ $indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($fieldName); $subMetadata = $this->entityManager->getClassMetadata($associationMapping['targetEntity']); @@ -122,7 +119,7 @@ public function getTypes(string $class, string $property, array $context = []): )]; } - if ($metadata instanceof ClassMetadataInfo && class_exists(Embedded::class) && isset($metadata->embeddedClasses[$property])) { + if ($metadata instanceof ClassMetadata && isset($metadata->embeddedClasses[$property])) { return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $metadata->embeddedClasses[$property]['class'])]; } @@ -133,7 +130,7 @@ public function getTypes(string $class, string $property, array $context = []): return null; } - $nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property); + $nullable = $metadata instanceof ClassMetadata && $metadata->isNullable($property); $enumType = null; if (null !== $enumClass = $metadata->getFieldMapping($property)['enumType'] ?? null) { $enumType = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $enumClass); @@ -162,8 +159,8 @@ public function getTypes(string $class, string $property, array $context = []): break; case Type::BUILTIN_TYPE_ARRAY: switch ($typeOfField) { - case Types::ARRAY: - case 'json_array': + case 'array': // DBAL < 4 + case 'json_array': // DBAL < 3 // return null if $enumType is set, because we can't determine if collectionKeyType is string or int if ($enumType) { return null; @@ -219,9 +216,11 @@ private function getMetadata(string $class): ?ClassMetadata /** * Determines whether an association is nullable. * + * @param array|AssociationMapping $associationMapping + * * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246 */ - private function isAssociationNullable(array $associationMapping): bool + private function isAssociationNullable(array|AssociationMapping $associationMapping): bool { if (isset($associationMapping['id']) && $associationMapping['id']) { return false; @@ -258,7 +257,7 @@ private function getPhpType(string $doctrineType): ?string Types::BOOLEAN => Type::BUILTIN_TYPE_BOOL, Types::BLOB, Types::BINARY => Type::BUILTIN_TYPE_RESOURCE, - Types::OBJECT, + 'object', // DBAL < 4 Types::DATE_MUTABLE, Types::DATETIME_MUTABLE, Types::DATETIMETZ_MUTABLE, @@ -269,9 +268,9 @@ private function getPhpType(string $doctrineType): ?string Types::DATETIMETZ_IMMUTABLE, Types::TIME_IMMUTABLE, Types::DATEINTERVAL => Type::BUILTIN_TYPE_OBJECT, - Types::ARRAY, - Types::SIMPLE_ARRAY, - 'json_array' => Type::BUILTIN_TYPE_ARRAY, + 'array', // DBAL < 4 + 'json_array', // DBAL < 3 + Types::SIMPLE_ARRAY => Type::BUILTIN_TYPE_ARRAY, default => null, }; } diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/LockStoreSchemaListener.php b/src/Symfony/Bridge/Doctrine/SchemaListener/LockStoreSchemaListener.php index 0902b376d8968..5ab591d318225 100644 --- a/src/Symfony/Bridge/Doctrine/SchemaListener/LockStoreSchemaListener.php +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/LockStoreSchemaListener.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\SchemaListener; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\PersistingStoreInterface; use Symfony\Component\Lock\Store\DoctrineDbalStore; @@ -28,12 +29,20 @@ public function postGenerateSchema(GenerateSchemaEventArgs $event): void { $connection = $event->getEntityManager()->getConnection(); - foreach ($this->stores as $store) { - if (!$store instanceof DoctrineDbalStore) { - continue; + $storesIterator = new \ArrayIterator($this->stores); + while ($storesIterator->valid()) { + try { + $store = $storesIterator->current(); + if (!$store instanceof DoctrineDbalStore) { + continue; + } + + $store->configureSchema($event->getSchema(), $this->getIsSameDatabaseChecker($connection)); + } catch (InvalidArgumentException) { + // no-op } - $store->configureSchema($event->getSchema(), $this->getIsSameDatabaseChecker($connection)); + $storesIterator->next(); } } } diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index 4c7d14f6894e1..f9bc4102bf1b2 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php @@ -15,6 +15,7 @@ use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectRepository; +use Doctrine\Persistence\Proxy; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; @@ -38,7 +39,7 @@ class EntityUserProvider implements UserProviderInterface, PasswordUpgraderInter private string $class; private ?string $property; - public function __construct(ManagerRegistry $registry, string $classOrAlias, string $property = null, string $managerName = null) + public function __construct(ManagerRegistry $registry, string $classOrAlias, ?string $property = null, ?string $managerName = null) { $this->registry = $registry; $this->managerName = $managerName; @@ -97,6 +98,10 @@ public function refreshUser(UserInterface $user): UserInterface } } + if ($refreshedUser instanceof Proxy && !$refreshedUser->__isInitialized()) { + $refreshedUser->__load(); + } + return $refreshedUser; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php b/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php index 883af01280532..c17370ec74494 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php @@ -387,12 +387,12 @@ public function testAlreadyResolved() $this->assertSame([], $resolver->resolve($request, $argument)); } - private function createArgument(string $class = null, MapEntity $entity = null, string $name = 'arg', bool $isNullable = false): ArgumentMetadata + private function createArgument(?string $class = null, ?MapEntity $entity = null, string $name = 'arg', bool $isNullable = false): ArgumentMetadata { return new ArgumentMetadata($name, $class ?? \stdClass::class, false, false, null, $isNullable, $entity ? [$entity] : []); } - private function createRegistry(ObjectManager $manager = null): ManagerRegistry&MockObject + private function createRegistry(?ObjectManager $manager = null): ManagerRegistry&MockObject { $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php index f215f4c774034..88315b51c2465 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php @@ -50,7 +50,7 @@ public function testDispatchEventRespectOrderWithSubscribers() $this->container->set('sub1', $subscriber1 = new MySubscriber(['foo'])); $this->container->set('sub2', $subscriber2 = new MySubscriber(['foo'])); - $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Registering "Symfony\Bridge\Doctrine\Tests\MySubscriber" as a Doctrine subscriber is deprecated. Register it as a listener instead, using e.g. the #[AsDoctrineListener] or #[AsDocumentListener] attribute.'); $this->assertSame([$subscriber1, $subscriber2], array_values($this->evm->getListeners('foo'))); } @@ -92,7 +92,7 @@ public function testDispatchEventWithSubscribers() $this->assertSame(0, $subscriber1->calledSubscribedEventsCount); $this->container->set('lazy1', $listener1 = new MyListener()); - $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Registering "Symfony\Bridge\Doctrine\Tests\MySubscriber" as a Doctrine subscriber is deprecated. Register it as a listener instead, using e.g. the #[AsDoctrineListener] or #[AsDocumentListener] attribute.'); $this->evm->addEventListener('foo', 'lazy1'); $this->evm->addEventListener('foo', $listener2 = new MyListener()); $this->evm->addEventSubscriber($subscriber2 = new MySubscriber(['bar'])); @@ -177,7 +177,7 @@ public function testAddEventListenerAndSubscriberAfterDispatchEvent() $this->assertSame(0, $subscriber1->calledSubscribedEventsCount); $this->container->set('lazy1', $listener1 = new MyListener()); - $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Registering "Symfony\Bridge\Doctrine\Tests\MySubscriber" as a Doctrine subscriber is deprecated. Register it as a listener instead, using e.g. the #[AsDoctrineListener] or #[AsDocumentListener] attribute.'); $this->evm->addEventListener('foo', 'lazy1'); $this->assertSame(1, $subscriber1->calledSubscribedEventsCount); @@ -238,7 +238,7 @@ public function testGetListenersForEventWhenSubscribersArePresent() $this->container->set('lazy', $listener1 = new MyListener()); $this->container->set('lazy2', $subscriber1 = new MySubscriber(['foo'])); - $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Registering "Symfony\Bridge\Doctrine\Tests\MySubscriber" as a Doctrine subscriber is deprecated. Register it as a listener instead, using e.g. the #[AsDoctrineListener] or #[AsDocumentListener] attribute.'); $this->evm->addEventListener('foo', 'lazy'); $this->evm->addEventListener('foo', $listener2 = new MyListener()); @@ -281,6 +281,21 @@ public function testRemoveEventListener() $this->assertSame([], $this->evm->getListeners('foo')); } + public function testRemoveAllEventListener() + { + $this->container->set('lazy', new MyListener()); + $this->evm->addEventListener('foo', 'lazy'); + $this->evm->addEventListener('foo', new MyListener()); + + foreach ($this->evm->getAllListeners() as $event => $listeners) { + foreach ($listeners as $listener) { + $this->evm->removeEventListener($event, $listener); + } + } + + $this->assertSame([], $this->evm->getListeners('foo')); + } + public function testRemoveEventListenerAfterDispatchEvent() { $this->container->set('lazy', $listener1 = new MyListener()); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php index 64bee1203b781..690c5aa6f9b8d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php @@ -32,6 +32,13 @@ class DoctrineDataCollectorWithDebugStackTest extends TestCase { use DoctrineDataCollectorTestTrait; + public static function setUpBeforeClass(): void + { + if (!class_exists(DebugStack::class)) { + self::markTestSkipped('This test requires DBAL < 4.'); + } + } + public function testReset() { $queries = [ diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php index 02cd5acf0365d..254953c9d6a2a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php @@ -238,7 +238,7 @@ public function testProcessEventSubscribersWithMultipleConnections() ]) ; - $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Registering "d" as a Doctrine subscriber is deprecated. Register it as a listener instead, using e.g. the #[AsDoctrineListener] attribute.'); $this->process($container); $eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager'); @@ -322,7 +322,7 @@ public function testProcessEventSubscribersWithPriorities() ]) ; - $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Registering "d" as a Doctrine subscriber is deprecated. Register it as a listener instead, using e.g. the #[AsDoctrineListener] attribute.'); $this->process($container); $eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager'); @@ -416,7 +416,7 @@ public function testProcessEventSubscribersAndListenersWithPriorities() ]) ; - $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Registering "d" as a Doctrine subscriber is deprecated. Register it as a listener instead, using e.g. the #[AsDoctrineListener] attribute.'); $this->process($container); $eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager'); @@ -454,6 +454,44 @@ public function testProcessEventSubscribersAndListenersWithPriorities() ); } + public function testSubscribersAreSkippedIfListenerDefinedForSameDefinition() + { + $container = $this->createBuilder(); + + $container + ->register('a', 'stdClass') + ->setPublic(false) + ->addTag('doctrine.event_listener', [ + 'event' => 'bar', + 'priority' => 3, + ]) + ; + $container + ->register('b', 'stdClass') + ->setPublic(false) + ->addTag('doctrine.event_listener', [ + 'event' => 'bar', + ]) + ->addTag('doctrine.event_listener', [ + 'event' => 'foo', + 'priority' => -5, + ]) + ->addTag('doctrine.event_subscriber') + ; + $this->process($container); + + $eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager'); + + $this->assertEquals( + [ + [['bar'], 'a'], + [['bar'], 'b'], + [['foo'], 'b'], + ], + $eventManagerDef->getArgument(1) + ); + } + public function testProcessNoTaggedServices() { $container = $this->createBuilder(true); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php index d816ee2303a01..e025637f6cd8d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -185,14 +185,14 @@ public function testMappingTypeDetection() // The ordinary fixtures contain annotation $mappingType = $method->invoke($this->extension, __DIR__.'/../Fixtures', $container); - $this->assertSame($mappingType, 'annotation'); + $this->assertSame($mappingType, 'attribute'); // In the attribute folder, attributes are used $mappingType = $method->invoke($this->extension, __DIR__.'/../Fixtures/Attribute', $container); $this->assertSame($mappingType, 'attribute'); } - public static function providerBasicDrivers() + public static function providerBasicDrivers(): array { return [ ['doctrine.orm.cache.apc.class', ['type' => 'apc']], @@ -271,11 +271,11 @@ public function testUnrecognizedCacheDriverException() $this->invokeLoadCacheDriver($objectManager, $container, $cacheName); } - public static function providerBundles() + public static function providerBundles(): iterable { - yield ['AnnotationsBundle', 'annotation', '/Entity']; - yield ['AnnotationsOneLineBundle', 'annotation', '/Entity']; - yield ['FullEmbeddableAnnotationsBundle', 'annotation', '/Entity']; + yield ['AnnotationsBundle', 'attribute', '/Entity']; + yield ['AnnotationsOneLineBundle', 'attribute', '/Entity']; + yield ['FullEmbeddableAnnotationsBundle', 'attribute', '/Entity']; yield ['AttributesBundle', 'attribute', '/Entity']; yield ['FullEmbeddableAttributesBundle', 'attribute', '/Entity']; yield ['XmlBundle', 'xml', '/Resources/config/doctrine']; @@ -284,7 +284,7 @@ public static function providerBundles() yield ['SrcXmlBundle', 'xml', '/Resources/config/doctrine']; - yield ['NewAnnotationsBundle', 'annotation', \DIRECTORY_SEPARATOR.'src/Entity']; + yield ['NewAnnotationsBundle', 'attribute', \DIRECTORY_SEPARATOR.'src/Entity']; yield ['NewXmlBundle', 'xml', '/config/doctrine']; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php b/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php index be18506ba96ae..8bc291c7a06f7 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php @@ -12,10 +12,15 @@ namespace Symfony\Bridge\Doctrine\Tests; use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; +use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\ORM\Mapping\Driver\XmlDriver; +use Doctrine\ORM\ORMSetup; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator; use PHPUnit\Framework\TestCase; @@ -30,7 +35,7 @@ final class DoctrineTestHelper /** * Returns an entity manager for testing. */ - public static function createTestEntityManager(Configuration $config = null): EntityManager + public static function createTestEntityManager(?Configuration $config = null): EntityManager { if (!\extension_loaded('pdo_sqlite')) { TestCase::markTestSkipped('Extension pdo_sqlite is required.'); @@ -41,17 +46,35 @@ public static function createTestEntityManager(Configuration $config = null): En 'memory' => true, ]; - return EntityManager::create($params, $config ?? self::createTestConfiguration()); + $config ??= self::createTestConfiguration(); + + if (!(new \ReflectionMethod(EntityManager::class, '__construct'))->isPublic()) { + return EntityManager::create($params, $config); + } + + $eventManager = new EventManager(); + + return new EntityManager(DriverManager::getConnection($params, $config, $eventManager), $config, $eventManager); } public static function createTestConfiguration(): Configuration { - $config = new Configuration(); + $config = ORMSetup::createConfiguration(true); $config->setEntityNamespaces(['SymfonyTestsDoctrine' => 'Symfony\Bridge\Doctrine\Tests\Fixtures']); $config->setAutoGenerateProxyClasses(true); $config->setProxyDir(sys_get_temp_dir()); $config->setProxyNamespace('SymfonyTests\Doctrine'); - $config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader())); + if (class_exists(AttributeDriver::class)) { + $config->setMetadataDriverImpl(new AttributeDriver([__DIR__.'/../Tests/Fixtures' => 'Symfony\Bridge\Doctrine\Tests\Fixtures'], true)); + } else { + $config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader(), null, true)); + } + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + if (method_exists($config, 'setLazyGhostObjectEnabled')) { + $config->setLazyGhostObjectEnabled(true); + } return $config; } @@ -65,7 +88,9 @@ public static function createTestConfigurationWithXmlLoader(): Configuration new XmlDriver( new SymfonyFileLocator( [__DIR__.'/../Tests/Resources/orm' => 'Symfony\\Bridge\\Doctrine\\Tests\\Fixtures'], '.orm.xml' - ) + ), + '.orm.xml', + true ), 'Symfony\\Bridge\\Doctrine\\Tests\\Fixtures' ); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php index c6d689a96a68c..94be71ec9f153 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php @@ -16,6 +16,7 @@ /** * @ORM\Entity */ +#[ORM\Entity] class AssociationEntity { /** @@ -23,6 +24,7 @@ class AssociationEntity * @ORM\Id @ORM\GeneratedValue * @ORM\Column(type="integer") */ + #[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')] private $id; /** @@ -30,6 +32,7 @@ class AssociationEntity * * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity */ + #[ORM\ManyToOne(targetEntity: SingleIntIdEntity::class)] public $single; /** @@ -41,5 +44,8 @@ class AssociationEntity * * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity */ + #[ORM\ManyToOne(targetEntity: CompositeIntIdEntity::class)] + #[ORM\JoinColumn(name: 'composite_id1', referencedColumnName: 'id1')] + #[ORM\JoinColumn(name: 'composite_id2', referencedColumnName: 'id2')] public $composite; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity2.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity2.php index a0a76124583a0..df4894e76ecbc 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity2.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity2.php @@ -16,6 +16,7 @@ /** * @ORM\Entity */ +#[ORM\Entity] class AssociationEntity2 { /** @@ -23,6 +24,7 @@ class AssociationEntity2 * @ORM\Id @ORM\GeneratedValue * @ORM\Column(type="integer") */ + #[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')] private $id; /** @@ -30,6 +32,7 @@ class AssociationEntity2 * * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity */ + #[ORM\ManyToOne(targetEntity: SingleIntIdNoToStringEntity::class)] public $single; /** @@ -41,5 +44,8 @@ class AssociationEntity2 * * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity */ + #[ORM\ManyToOne(targetEntity: CompositeIntIdEntity::class)] + #[ORM\JoinColumn(name: 'composite_id1', referencedColumnName: 'id1')] + #[ORM\JoinColumn(name: 'composite_id2', referencedColumnName: 'id2')] public $composite; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsBundle/Entity/Person.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsBundle/Entity/Person.php index 0d7cc91362da3..4f33525506493 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsBundle/Entity/Person.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsBundle/Entity/Person.php @@ -18,12 +18,15 @@ /** * @Entity */ +#[Entity] class Person { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsOneLineBundle/Entity/Person.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsOneLineBundle/Entity/Person.php index b55fe6f86503b..0e77dbffc6dff 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsOneLineBundle/Entity/Person.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsOneLineBundle/Entity/Person.php @@ -16,12 +16,15 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class Person { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AttributesBundle/AnnotatedEntity/Person.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AttributesBundle/AnnotatedEntity/Person.php index 0ec41bb096861..ae6fec848d1f2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AttributesBundle/AnnotatedEntity/Person.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AttributesBundle/AnnotatedEntity/Person.php @@ -18,12 +18,15 @@ /** * @Entity */ +#[Entity] class Person { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAnnotationsBundle/Entity/Address.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAnnotationsBundle/Entity/Address.php index d311a3f1ad1a1..bad7402e51c95 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAnnotationsBundle/Entity/Address.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAnnotationsBundle/Entity/Address.php @@ -13,21 +13,24 @@ use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Embeddable; -use Doctrine\ORM\Mapping\Id; /** * @Embeddable */ +#[Embeddable] class Address { /** @Column(type="string") */ + #[Column(type: 'string')] public $street; /** @Column(type="string") */ + #[Column(type: 'string')] public $zipCode; /** @Column(type="string") */ + #[Column(type: 'string')] public $city; public function __construct($street, $zipCode, $city) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAttributesBundle/Entity/Address.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAttributesBundle/Entity/Address.php index c0c58d6a21ce2..e257442ae18e6 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAttributesBundle/Entity/Address.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAttributesBundle/Entity/Address.php @@ -13,7 +13,6 @@ use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Embeddable; -use Doctrine\ORM\Mapping\Id; #[Embeddable] class Address diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/NewAnnotationsBundle/src/Entity/Person.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/NewAnnotationsBundle/src/Entity/Person.php index e94a24e1a95c7..9ab508e30523f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/NewAnnotationsBundle/src/Entity/Person.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/NewAnnotationsBundle/src/Entity/Person.php @@ -18,12 +18,15 @@ /** * @Entity */ +#[Entity] class Person { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php index 7c64cc20ad7e1..40ff64d9488b3 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php @@ -16,15 +16,19 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class CompositeIntIdEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id1; /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id2; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id1, $id2, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php index 73a6419e2f0c0..67f3c4f96c07a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php @@ -18,6 +18,7 @@ * * @ORM\Entity */ +#[ORM\Entity] class CompositeObjectNoToStringIdEntity { /** @@ -27,6 +28,9 @@ class CompositeObjectNoToStringIdEntity * @ORM\ManyToOne(targetEntity="SingleIntIdNoToStringEntity", cascade={"persist"}) * @ORM\JoinColumn(name="object_one_id") */ + #[ORM\Id] + #[ORM\ManyToOne(targetEntity: SingleIntIdNoToStringEntity::class, cascade: ['persist'])] + #[ORM\JoinColumn(name: 'object_one_id')] protected $objectOne; /** @@ -36,6 +40,9 @@ class CompositeObjectNoToStringIdEntity * @ORM\ManyToOne(targetEntity="SingleIntIdNoToStringEntity", cascade={"persist"}) * @ORM\JoinColumn(name="object_two_id") */ + #[ORM\Id] + #[ORM\ManyToOne(targetEntity: SingleIntIdNoToStringEntity::class, cascade: ['persist'])] + #[ORM\JoinColumn(name: 'object_two_id')] protected $objectTwo; public function __construct(SingleIntIdNoToStringEntity $objectOne, SingleIntIdNoToStringEntity $objectTwo) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php index d6e8d2cd2aafa..95e687f6bfa50 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php @@ -16,15 +16,19 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class CompositeStringIdEntity { /** @Id @Column(type="string") */ + #[Id, Column(type: 'string')] protected $id1; /** @Id @Column(type="string") */ + #[Id, Column(type: 'string')] protected $id2; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id1, $id2, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEmbed.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEmbed.php index fc16f1cc135bc..5912ae6b1f9ea 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEmbed.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEmbed.php @@ -16,15 +16,18 @@ /** * @ORM\Embeddable */ +#[ORM\Embeddable] class DoctrineLoaderEmbed { /** * @ORM\Column(length=25) */ + #[ORM\Column(length: 25)] public $embeddedMaxLength; /** * @ORM\Embedded(class=DoctrineLoaderNestedEmbed::class) */ + #[ORM\Embedded(class: DoctrineLoaderNestedEmbed::class)] public $nestedEmbedded; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php index d6aee2d18b0b1..c32a9ef49d472 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php @@ -21,58 +21,70 @@ * * @author Kévin Dunglas */ +#[ORM\Entity, UniqueEntity(fields: ["alreadyMappedUnique"])] class DoctrineLoaderEntity extends DoctrineLoaderParentEntity { /** * @ORM\Id * @ORM\Column */ + #[ORM\Id, ORM\Column] public $id; /** * @ORM\Column(length=20) */ + #[ORM\Column(length: 20)] public $maxLength; /** * @ORM\Column(length=20) * @Assert\Length(min=5) */ + #[ORM\Column(length: 20), Assert\Length(min: 5)] public $mergedMaxLength; /** * @ORM\Column(length=20) * @Assert\Length(min=1, max=10) */ + #[ORM\Column(length: 20), Assert\Length(min: 1, max: 10)] public $alreadyMappedMaxLength; /** * @ORM\Column(unique=true) */ + #[ORM\Column(unique: true)] public $unique; /** * @ORM\Column(unique=true) */ + #[ORM\Column(unique: true)] public $alreadyMappedUnique; /** * @ORM\Embedded(class=DoctrineLoaderEmbed::class) */ + #[ORM\Embedded(class: DoctrineLoaderEmbed::class)] public $embedded; /** @ORM\Column(type="text", nullable=true, length=1000) */ + #[ORM\Column(type: 'text', nullable: true, length: 1000)] public $textField; /** @ORM\Id @ORM\Column(type="guid", length=50) */ + #[ORM\Id, ORM\Column(type: 'guid', length: 50)] protected $guidField; /** @ORM\Column(type="simple_array", length=100) */ + #[ORM\Column(type: 'simple_array', length: 100)] public $simpleArrayField = []; /** * @ORM\Column(length=10) * @Assert\DisableAutoMapping */ + #[ORM\Column(length: 10), Assert\DisableAutoMapping] public $noAutoMapping; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEnum.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEnum.php index 8ac883e89c4a2..4ba7211456902 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEnum.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEnum.php @@ -12,25 +12,31 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt; +use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString; /** * @ORM\Entity */ +#[ORM\Entity] class DoctrineLoaderEnum { /** * @ORM\Id * @ORM\Column */ + #[ORM\Id, ORM\Column] public $id; /** * @ORM\Column(type="string", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString", length=1) */ + #[ORM\Column(type: 'string', enumType: EnumString::class, length: 1)] public $enumString; /** * @ORM\Column(type="integer", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt") */ + #[ORM\Column(type: 'integer', enumType: EnumInt::class)] public $enumInt; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNestedEmbed.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNestedEmbed.php index fbf41555a1316..9d9424f0ece0d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNestedEmbed.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNestedEmbed.php @@ -16,10 +16,12 @@ /** * @ORM\Embeddable() */ +#[ORM\Embeddable] class DoctrineLoaderNestedEmbed { /** * @ORM\Column(length=27) */ + #[ORM\Column(length: 27)] public $nestedEmbeddedMaxLength; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php index 0914411431201..515ec30bc897f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php @@ -20,22 +20,26 @@ * * @author Kévin Dunglas */ +#[ORM\Entity, Assert\DisableAutoMapping] class DoctrineLoaderNoAutoMappingEntity { /** * @ORM\Id * @ORM\Column */ + #[ORM\Id, ORM\Column] public $id; /** * @ORM\Column(length=20, unique=true) */ + #[ORM\Column(length: 20, unique: true)] public $maxLength; /** * @Assert\EnableAutoMapping * @ORM\Column(length=20) */ + #[Assert\EnableAutoMapping, ORM\Column(length: 20)] public $autoMappingExplicitlyEnabled; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderParentEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderParentEntity.php index 7ec0263559c71..d7d832e6af23a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderParentEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderParentEntity.php @@ -16,16 +16,19 @@ /** * @ORM\MappedSuperclass */ +#[ORM\MappedSuperclass] class DoctrineLoaderParentEntity { /** * @ORM\Column(length=35) */ + #[ORM\Column(length: 35)] public $publicParentMaxLength; /** * @ORM\Column(length=30) */ + #[ORM\Column(length: 30)] private $privateParentMaxLength; public function getPrivateParentMaxLength() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php index 3559568787bcd..2c8ac68ce17c2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php @@ -16,15 +16,19 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class DoubleNameEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name2; public function __construct($id, $name, $name2) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNullableNameEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNullableNameEntity.php index 20ef14fd1b578..0c37651412ab4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNullableNameEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNullableNameEntity.php @@ -16,15 +16,19 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class DoubleNullableNameEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name2; public function __construct($id, $name, $name2) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php index f8000dbfd9814..cdc82cf94e34a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php @@ -16,6 +16,7 @@ /** * @ORM\Embeddable */ +#[ORM\Embeddable] class Identifier { /** @@ -24,5 +25,6 @@ class Identifier * @ORM\Id * @ORM\Column(type="integer") */ + #[ORM\Id, ORM\Column(type: 'integer')] protected $value; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php index 6d7b2670962c7..c2cec427ad77e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php @@ -12,10 +12,12 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Tests\Fixtures\Embeddable\Identifier; /** * @ORM\Entity */ +#[ORM\Entity] class EmbeddedIdentifierEntity { /** @@ -23,5 +25,6 @@ class EmbeddedIdentifierEntity * * @ORM\Embedded(class="Symfony\Bridge\Doctrine\Tests\Fixtures\Embeddable\Identifier") */ + #[ORM\Embedded(class: Identifier::class)] protected $id; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Employee.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Employee.php index 24f08b00d781a..b9888adc8beae 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Employee.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Employee.php @@ -14,6 +14,7 @@ use Doctrine\ORM\Mapping\Entity; /** @Entity */ +#[Entity] class Employee extends Person { } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php index 730a9b2b1dbf8..d803ca2310d8b 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php @@ -16,15 +16,19 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class GroupableEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $groupName; public function __construct($id, $name, $groupName) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GuidIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GuidIdEntity.php index 0d447ffc1e62c..d575659595010 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GuidIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GuidIdEntity.php @@ -16,9 +16,11 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class GuidIdEntity { /** @Id @Column(type="guid") */ + #[Id, Column(type: 'guid')] protected $id; public function __construct($id) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/LegacyQueryMock.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/LegacyQueryMock.php new file mode 100644 index 0000000000000..5ec46f606a8d9 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/LegacyQueryMock.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\DBAL\Result; +use Doctrine\ORM\AbstractQuery; + +class LegacyQueryMock extends AbstractQuery +{ + public function __construct() + { + } + + /** + * @return array|string + */ + public function getSQL() + { + } + + /** + * @return Result|int + */ + protected function _doExecute() + { + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Person.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Person.php index b90a54ac02c61..35f3836e0d901 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Person.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Person.php @@ -24,12 +24,15 @@ * @DiscriminatorColumn(name="discr", type="string") * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) */ +#[Entity, InheritanceType('SINGLE_TABLE'), DiscriminatorColumn(name: 'discr', type: 'string'), DiscriminatorMap(['person' => 'Person', 'employee' => 'Employee'])] class Person { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php index bed8bb9a51d01..b4a1ede9f0a2a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php @@ -17,12 +17,15 @@ use Doctrine\ORM\Mapping\OneToOne; /** @Entity */ +#[Entity] class SingleAssociationToIntIdEntity { /** @Id @OneToOne(targetEntity="SingleIntIdNoToStringEntity", cascade={"ALL"}) */ + #[Id, OneToOne(targetEntity: SingleIntIdNoToStringEntity::class, cascade: ['ALL'])] protected $entity; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; public function __construct(SingleIntIdNoToStringEntity $entity, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php index 612566b45d94b..85c1c0cc20ea6 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php @@ -11,20 +11,25 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class SingleIntIdEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; - /** @Column(type="array", nullable=true) */ + /** @Column(type="json", nullable=true) */ + #[Column(type: Types::JSON, nullable: true)] public $phoneNumbers = []; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php index c94815ca02cad..8887c5b5a3d6c 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php @@ -16,12 +16,15 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class SingleIntIdNoToStringEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php index 8f4c3663d2148..8c774009ba530 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php @@ -16,12 +16,15 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class SingleIntIdStringWrapperNameEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string_wrapper", nullable=true) */ + #[Column(type: 'string_wrapper', nullable: true)] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php index 128801a02c922..b3e952d9a8b33 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php @@ -17,6 +17,7 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class SingleStringCastableIdEntity { /** @@ -24,9 +25,11 @@ class SingleStringCastableIdEntity * @Column(type="string") * @GeneratedValue(strategy="NONE") */ + #[Id, Column(type: 'string'), GeneratedValue(strategy: 'NONE')] protected $id; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php index 83f7a9f9ab39d..ce3a5526a5b15 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php @@ -16,12 +16,15 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class SingleStringIdEntity { /** @Id @Column(type="string") */ + #[Id, Column(type: 'string')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php index 89edafa67bc39..0d99be2769423 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php @@ -16,12 +16,12 @@ class StringWrapperType extends StringType { - public function convertToDatabaseValue($value, AbstractPlatform $platform): mixed + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string { return $value instanceof StringWrapper ? $value->getString() : null; } - public function convertToPHPValue($value, AbstractPlatform $platform): mixed + public function convertToPHPValue($value, AbstractPlatform $platform): StringWrapper { return new StringWrapper($value); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php index 3ee909fe4bfc5..238a08511ebe9 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php @@ -16,9 +16,11 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class UlidIdEntity { /** @Id @Column(type="ulid") */ + #[Id, Column(type: 'ulid')] protected $id; public function __construct($id) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php index 02d31078451b8..5604c33370092 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php @@ -18,15 +18,19 @@ use Symfony\Component\Security\Core\User\UserInterface; /** @Entity */ +#[Entity] class User implements UserInterface, PasswordAuthenticatedUserInterface { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id1; /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id2; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id1, $id2, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UuidIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UuidIdEntity.php index 46084ab292d49..60271f7d15ab0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UuidIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UuidIdEntity.php @@ -16,9 +16,11 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class UuidIdEntity { /** @Id @Column(type="uuid") */ + #[Id, Column(type: 'uuid')] protected $id; public function __construct($id) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php index 7205799b8fa7a..465175e1c0dc4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php @@ -416,7 +416,7 @@ public function testLoadChoicesForValuesLoadsOnlyChoicesIfValueIsIdReader() public function testPassingIdReaderWithoutSingleIdEntity() { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The second argument "$idReader" of "Symfony\\Bridge\\Doctrine\\Form\\ChoiceList\\DoctrineChoiceLoader::__construct" must be null when the query cannot be optimized because of composite id fields.'); + $this->expectExceptionMessage('The "$idReader" argument of "Symfony\\Bridge\\Doctrine\\Form\\ChoiceList\\DoctrineChoiceLoader::__construct" must be null when the query cannot be optimized because of composite id fields.'); $idReader = $this->createMock(IdReader::class); $idReader->expects($this->once()) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php index c1cef742e3d28..67f600f5d145e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php @@ -13,13 +13,17 @@ use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\GuidType; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\AbstractQuery; +use Doctrine\ORM\Query; +use Doctrine\ORM\QueryBuilder; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Tests\Fixtures\EmbeddedIdentifierEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\LegacyQueryMock; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity; use Symfony\Bridge\Doctrine\Types\UlidType; @@ -46,13 +50,11 @@ public function testIdentifierTypeIsIntegerArray() $this->checkIdentifierType(SingleIntIdEntity::class, class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY); } - protected function checkIdentifierType($classname, $expectedType) + protected function checkIdentifierType(string $classname, $expectedType) { $em = DoctrineTestHelper::createTestEntityManager(); - $query = $this->getMockBuilder(QueryMock::class) - ->onlyMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) - ->getMock(); + $query = $this->getQueryMock(); $query ->method('getResult') @@ -63,7 +65,7 @@ protected function checkIdentifierType($classname, $expectedType) ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', [1, 2], $expectedType) ->willReturn($query); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -83,9 +85,7 @@ public function testFilterNonIntegerValues() { $em = DoctrineTestHelper::createTestEntityManager(); - $query = $this->getMockBuilder(QueryMock::class) - ->onlyMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) - ->getMock(); + $query = $this->getQueryMock(); $query ->method('getResult') @@ -96,7 +96,7 @@ public function testFilterNonIntegerValues() ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', [1, 2, 3, '9223372036854775808'], class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY) ->willReturn($query); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -119,9 +119,7 @@ public function testFilterEmptyUuids($entityClass) { $em = DoctrineTestHelper::createTestEntityManager(); - $query = $this->getMockBuilder(QueryMock::class) - ->onlyMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) - ->getMock(); + $query = $this->getQueryMock(); $query ->method('getResult') @@ -132,7 +130,7 @@ public function testFilterEmptyUuids($entityClass) ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', ['71c5fd46-3f16-4abb-bad7-90ac1e654a2d', 'b98e8e11-2897-44df-ad24-d2627eb7f499'], class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY) ->willReturn($query); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -164,9 +162,7 @@ public function testFilterUid($entityClass) $em = DoctrineTestHelper::createTestEntityManager(); - $query = $this->getMockBuilder(QueryMock::class) - ->onlyMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) - ->getMock(); + $query = $this->getQueryMock(); $query ->method('getResult') @@ -177,7 +173,7 @@ public function testFilterUid($entityClass) ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', [Uuid::fromString('71c5fd46-3f16-4abb-bad7-90ac1e654a2d')->toBinary(), Uuid::fromString('b98e8e11-2897-44df-ad24-d2627eb7f499')->toBinary()], class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY) ->willReturn($query); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -209,7 +205,7 @@ public function testUidThrowProperException($entityClass) $em = DoctrineTestHelper::createTestEntityManager(); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -232,9 +228,7 @@ public function testEmbeddedIdentifierName() { $em = DoctrineTestHelper::createTestEntityManager(); - $query = $this->getMockBuilder(QueryMock::class) - ->onlyMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) - ->getMock(); + $query = $this->getQueryMock(); $query ->method('getResult') @@ -245,7 +239,7 @@ public function testEmbeddedIdentifierName() ->with('ORMQueryBuilderLoader_getEntitiesByIds_id_value', [1, 2, 3], class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY) ->willReturn($query); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -254,13 +248,13 @@ public function testEmbeddedIdentifierName() ->willReturn($query); $qb->select('e') - ->from('Symfony\Bridge\Doctrine\Tests\Fixtures\EmbeddedIdentifierEntity', 'e'); + ->from(EmbeddedIdentifierEntity::class, 'e'); $loader = new ORMQueryBuilderLoader($qb); $loader->getEntitiesByIds('id.value', [1, '', 2, 3, 'foo']); } - public static function provideGuidEntityClasses() + public static function provideGuidEntityClasses(): array { return [ ['Symfony\Bridge\Doctrine\Tests\Fixtures\GuidIdEntity'], @@ -268,26 +262,21 @@ public static function provideGuidEntityClasses() ]; } - public static function provideUidEntityClasses() + public static function provideUidEntityClasses(): array { return [ ['Symfony\Bridge\Doctrine\Tests\Fixtures\UuidIdEntity'], ['Symfony\Bridge\Doctrine\Tests\Fixtures\UlidIdEntity'], ]; } -} - -class QueryMock extends AbstractQuery -{ - public function __construct() - { - } - public function getSQL(): array|string + /** + * @return (LegacyQueryMock&MockObject)|(Query&MockObject) + */ + private function getQueryMock(): AbstractQuery { - } + $class = ((new \ReflectionClass(Query::class))->isFinal()) ? LegacyQueryMock::class : Query::class; - protected function _doExecute(): Result|int - { + return $this->createMock($class); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php index f211f291f873a..930ee9994879e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php @@ -13,6 +13,8 @@ use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\JoinColumnMapping; +use Doctrine\ORM\Mapping\ManyToOneAssociationMapping; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectManager; use PHPUnit\Framework\TestCase; @@ -69,33 +71,49 @@ public function testRequiredGuesserSimpleFieldNullable() public function testRequiredGuesserOneToOneNullable() { - $classMetadata = $this->createMock(ClassMetadata::class); - $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->willReturn(true); + $classMetadata = new ClassMetadata('Acme\Entity\Foo'); - $mapping = ['joinColumns' => [[]]]; - $classMetadata->expects($this->once())->method('getAssociationMapping')->with('field')->willReturn($mapping); + if (class_exists(ManyToOneAssociationMapping::class)) { + $associationMapping = new ManyToOneAssociationMapping('field', 'Acme\Entity\Foo', 'Acme\Entity\Bar'); + $associationMapping->joinColumns[] = new JoinColumnMapping('field', 'field'); + } else { + $associationMapping = ['joinColumns' => [[]]]; + } + $classMetadata->associationMappings['field'] = $associationMapping; $this->assertEquals(new ValueGuess(false, Guess::HIGH_CONFIDENCE), $this->getGuesser($classMetadata)->guessRequired('TestEntity', 'field')); } public function testRequiredGuesserOneToOneExplicitNullable() { - $classMetadata = $this->createMock(ClassMetadata::class); - $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->willReturn(true); - - $mapping = ['joinColumns' => [['nullable' => true]]]; - $classMetadata->expects($this->once())->method('getAssociationMapping')->with('field')->willReturn($mapping); + $classMetadata = new ClassMetadata('Acme\Entity\Foo'); + + if (class_exists(ManyToOneAssociationMapping::class)) { + $associationMapping = new ManyToOneAssociationMapping('field', 'Acme\Entity\Foo', 'Acme\Entity\Bar'); + $joinColumnMapping = new JoinColumnMapping('field', 'field'); + $joinColumnMapping->nullable = true; + $associationMapping->joinColumns[] = $joinColumnMapping; + } else { + $associationMapping = ['joinColumns' => [['nullable' => true]]]; + } + $classMetadata->associationMappings['field'] = $associationMapping; $this->assertEquals(new ValueGuess(false, Guess::HIGH_CONFIDENCE), $this->getGuesser($classMetadata)->guessRequired('TestEntity', 'field')); } public function testRequiredGuesserOneToOneNotNullable() { - $classMetadata = $this->createMock(ClassMetadata::class); - $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->willReturn(true); - - $mapping = ['joinColumns' => [['nullable' => false]]]; - $classMetadata->expects($this->once())->method('getAssociationMapping')->with('field')->willReturn($mapping); + $classMetadata = new ClassMetadata('Acme\Entity\Foo'); + + if (class_exists(ManyToOneAssociationMapping::class)) { + $associationMapping = new ManyToOneAssociationMapping('field', 'Acme\Entity\Foo', 'Acme\Entity\Bar'); + $joinColumnMapping = new JoinColumnMapping('field', 'field'); + $joinColumnMapping->nullable = false; + $associationMapping->joinColumns[] = $joinColumnMapping; + } else { + $associationMapping = ['joinColumns' => [['nullable' => false]]]; + } + $classMetadata->associationMappings['field'] = $associationMapping; $this->assertEquals(new ValueGuess(true, Guess::HIGH_CONFIDENCE), $this->getGuesser($classMetadata)->guessRequired('TestEntity', 'field')); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 0f7066609f6fb..0d76afe0d0d70 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -158,7 +158,7 @@ public function testChoiceTranslationDomainIsDisabledByDefault($expanded) } } - public static function choiceTranslationDomainProvider() + public static function choiceTranslationDomainProvider(): array { return [ [false], @@ -238,8 +238,6 @@ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => fn () => new \stdClass(), ]); - - $field->submit('2'); } public function testConfigureQueryBuilderWithClosureReturningNullUseDefault() @@ -783,7 +781,7 @@ public function testOverrideChoicesValuesWithCallable() 'em' => 'default', 'class' => self::ITEM_GROUP_CLASS, 'choice_label' => 'name', - 'choice_value' => function (GroupableEntity $entity = null) { + 'choice_value' => function (?GroupableEntity $entity = null) { if (null === $entity) { return ''; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php b/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php index 2e9ed80e3115a..b43bb93d7dd52 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Logger; +use Doctrine\DBAL\Logging\SQLLogger; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Symfony\Bridge\Doctrine\Logger\DbalLogger; @@ -20,6 +21,13 @@ */ class DbalLoggerTest extends TestCase { + public static function setUpBeforeClass(): void + { + if (!class_exists(SQLLogger::class)) { + self::markTestSkipped('This test requires DBAL < 4.'); + } + } + /** * @dataProvider getLogFixtures */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php index 6c7bf67bc08af..1a9ce2c693a51 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php @@ -13,8 +13,11 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Result; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ManagerRegistry; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Bridge\Doctrine\Messenger\DoctrinePingConnectionMiddleware; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; @@ -23,11 +26,11 @@ class DoctrinePingConnectionMiddlewareTest extends MiddlewareTestCase { - private $connection; - private $entityManager; - private $managerRegistry; - private $middleware; - private $entityManagerName = 'default'; + private Connection&MockObject $connection; + private EntityManagerInterface&MockObject $entityManager; + private ManagerRegistry&MockObject $managerRegistry; + private DoctrinePingConnectionMiddleware $middleware; + private string $entityManagerName = 'default'; protected function setUp(): void { @@ -47,16 +50,24 @@ protected function setUp(): void public function testMiddlewarePingOk() { - $this->connection->expects($this->once()) - ->method('getDatabasePlatform') - ->will($this->throwException(new DBALException())); + $this->connection->method('getDatabasePlatform') + ->willReturn($this->mockPlatform()); + + $this->connection->expects($this->exactly(2)) + ->method('executeQuery') + ->willReturnCallback(function () { + static $counter = 0; + + if (1 === ++$counter) { + throw $this->createMock(DBALException::class); + } + + return $this->createMock(Result::class); + }); $this->connection->expects($this->once()) ->method('close') ; - $this->connection->expects($this->once()) - ->method('connect') - ; $envelope = new Envelope(new \stdClass(), [ new ConsumedByWorkerStamp(), @@ -66,9 +77,8 @@ public function testMiddlewarePingOk() public function testMiddlewarePingResetEntityManager() { - $this->connection->expects($this->once()) - ->method('getDatabasePlatform') - ->will($this->throwException(new DBALException())); + $this->connection->method('getDatabasePlatform') + ->willReturn($this->mockPlatform()); $this->entityManager->expects($this->once()) ->method('isOpen') @@ -112,11 +122,16 @@ public function testMiddlewareNoPingInNonWorkerContext() $this->connection->expects($this->never()) ->method('close') ; - $this->connection->expects($this->never()) - ->method('connect') - ; $envelope = new Envelope(new \stdClass()); $this->middleware->handle($envelope, $this->getStackMock()); } + + private function mockPlatform(): AbstractPlatform&MockObject + { + $platform = $this->createMock(AbstractPlatform::class); + $platform->method('getDummySelectSQL')->willReturn('SELECT 1'); + + return $platform; + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php index e605afec5a630..5350efebb7b95 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php @@ -11,14 +11,16 @@ namespace Symfony\Bridge\Doctrine\Tests\Middleware\Debug; -use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Statement; use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\ORMSetup; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; use Symfony\Bridge\Doctrine\Middleware\Debug\Middleware; @@ -49,14 +51,20 @@ private function init(bool $withStopwatch = true): void { $this->stopwatch = $withStopwatch ? new Stopwatch() : null; - $configuration = new Configuration(); + $config = ORMSetup::createConfiguration(true); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + if (method_exists($config, 'setLazyGhostObjectEnabled')) { + $config->setLazyGhostObjectEnabled(true); + } $this->debugDataHolder = new DebugDataHolder(); - $configuration->setMiddlewares([new Middleware($this->debugDataHolder, $this->stopwatch)]); + $config->setMiddlewares([new Middleware($this->debugDataHolder, $this->stopwatch)]); $this->conn = DriverManager::getConnection([ 'driver' => 'pdo_sqlite', 'memory' => true, - ], $configuration); + ], $config); $this->conn->executeQuery(<<init(); - $product = 'product1'; - $price = 12.5; - $stock = 5; + $sql = <<getResourceFromString('mydata'); - $stmt = $this->conn->prepare('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)'); - $stmt->bindParam(1, $product); - $stmt->bindParam(2, $price); - $stmt->bindParam(3, $stock, ParameterType::INTEGER); + $stmt = $this->conn->prepare($sql); + $stmt->bindValue(1, 'product1'); + $stmt->bindValue(2, '12.5'); + $stmt->bindValue(3, 5, ParameterType::INTEGER); + $stmt->bindValue(4, $res, ParameterType::BINARY); $executeMethod($stmt); // Debug data should not be affected by these changes $debug = $this->debugDataHolder->getData()['default'] ?? []; $this->assertCount(2, $debug); - $this->assertSame('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)', $debug[1]['sql']); - $this->assertSame(['product1', '12.5', 5], $debug[1]['params']); - $this->assertSame([ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER], $debug[1]['types']); + $this->assertSame($sql, $debug[1]['sql']); + $this->assertSame(['product1', '12.5', 5, $expectedRes], $debug[1]['params']); + $this->assertSame([ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER, ParameterType::BINARY], $debug[1]['types']); $this->assertGreaterThan(0, $debug[1]['executionMS']); } @@ -180,6 +192,10 @@ public function testTransaction(callable $endTransactionMethod, string $expected { $this->init(); + if (\defined('Doctrine\DBAL\Connection::PARAM_STR_ARRAY')) { + // DBAL < 4 + $this->conn->setNestTransactionsWithSavepoints(true); + } $this->conn->beginTransaction(); $this->conn->beginTransaction(); $this->conn->executeStatement('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)'); @@ -190,39 +206,39 @@ public function testTransaction(callable $endTransactionMethod, string $expected $endTransactionMethod($this->conn); $debug = $this->debugDataHolder->getData()['default'] ?? []; - $this->assertCount(7, $debug); + $this->assertCount(9, $debug); $this->assertSame('"START TRANSACTION"', $debug[1]['sql']); $this->assertGreaterThan(0, $debug[1]['executionMS']); - $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)', $debug[2]['sql']); + $this->assertSame(method_exists(QueryBuilder::class, 'resetOrderBy') ? 'SAVEPOINT DOCTRINE_2' : 'SAVEPOINT DOCTRINE2_SAVEPOINT_2', $debug[2]['sql']); $this->assertGreaterThan(0, $debug[2]['executionMS']); - $this->assertSame($expectedEndTransactionDebug, $debug[3]['sql']); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)', $debug[3]['sql']); $this->assertGreaterThan(0, $debug[3]['executionMS']); - $this->assertSame('"START TRANSACTION"', $debug[4]['sql']); + $this->assertSame(('"ROLLBACK"' === $expectedEndTransactionDebug ? 'ROLLBACK TO' : 'RELEASE').' '.(method_exists(QueryBuilder::class, 'resetOrderBy') ? 'SAVEPOINT DOCTRINE_2' : 'SAVEPOINT DOCTRINE2_SAVEPOINT_2'), $debug[4]['sql']); $this->assertGreaterThan(0, $debug[4]['executionMS']); - $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product2", 15.5, 12)', $debug[5]['sql']); + $this->assertSame($expectedEndTransactionDebug, $debug[5]['sql']); $this->assertGreaterThan(0, $debug[5]['executionMS']); - $this->assertSame($expectedEndTransactionDebug, $debug[6]['sql']); + $this->assertSame('"START TRANSACTION"', $debug[6]['sql']); $this->assertGreaterThan(0, $debug[6]['executionMS']); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product2", 15.5, 12)', $debug[7]['sql']); + $this->assertGreaterThan(0, $debug[7]['executionMS']); + $this->assertSame($expectedEndTransactionDebug, $debug[8]['sql']); + $this->assertGreaterThan(0, $debug[8]['executionMS']); } public static function provideExecuteAndEndTransactionMethods(): array { return [ 'commit and exec' => [ - static fn (Connection $conn, string $sql) => $conn->executeStatement($sql), - static fn (Connection $conn) => $conn->commit(), + static fn (Connection $conn, string $sql): int|string => $conn->executeStatement($sql), + static fn (Connection $conn): ?bool => $conn->commit(), ], 'rollback and query' => [ - static fn (Connection $conn, string $sql) => $conn->executeQuery($sql), - static fn (Connection $conn) => $conn->rollBack(), + static fn (Connection $conn, string $sql): Result => $conn->executeQuery($sql), + static fn (Connection $conn): ?bool => $conn->rollBack(), ], 'prepared statement' => [ - static function (Connection $conn, string $sql): Result { - return $conn->prepare($sql)->executeQuery(); - }, - static function (Connection $conn): bool { - return $conn->commit(); - }, + static fn (Connection $conn, string $sql): Result => $conn->prepare($sql)->executeQuery(), + static fn (Connection $conn): ?bool => $conn->commit(), ], ]; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index 1ddd8e5ea59f8..9b63eb99b3e53 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -12,16 +12,22 @@ namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo; use Doctrine\Common\Collections\Collection; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Types\Type as DBALType; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\ORM\ORMSetup; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy; +use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEmbeddable; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEnum; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineGeneratedValue; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation; +use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString; use Symfony\Component\PropertyInfo\Type; @@ -31,10 +37,24 @@ */ class DoctrineExtractorTest extends TestCase { - private function createExtractor() + private function createExtractor(): DoctrineExtractor { - $config = ORMSetup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true); - $entityManager = EntityManager::create(['driver' => 'pdo_sqlite'], $config); + $config = ORMSetup::createConfiguration(true); + $config->setMetadataDriverImpl(new AttributeDriver([__DIR__.'/../Tests/Fixtures' => 'Symfony\Bridge\Doctrine\Tests\Fixtures'], true)); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + if (method_exists($config, 'setLazyGhostObjectEnabled')) { + $config->setLazyGhostObjectEnabled(true); + } + + if (!(new \ReflectionMethod(EntityManager::class, '__construct'))->isPublic()) { + $entityManager = EntityManager::create(['driver' => 'pdo_sqlite'], $config); + } else { + $eventManager = new EventManager(); + $entityManager = new EntityManager(DriverManager::getConnection(['driver' => 'pdo_sqlite'], $config, $eventManager), $config, $eventManager); + } if (!DBALType::hasType('foo')) { DBALType::addType('foo', 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineFooType'); @@ -98,7 +118,7 @@ public function testTestGetPropertiesWithEmbedded() /** * @dataProvider typesProvider */ - public function testExtract($property, array $type = null) + public function testExtract(string $property, ?array $type = null) { $this->assertEquals($type, $this->createExtractor()->getTypes(DoctrineDummy::class, $property, [])); } @@ -108,11 +128,11 @@ public function testExtractWithEmbedded() $expectedTypes = [new Type( Type::BUILTIN_TYPE_OBJECT, false, - 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEmbeddable' + DoctrineEmbeddable::class )]; $actualTypes = $this->createExtractor()->getTypes( - 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded', + DoctrineWithEmbedded::class, 'embedded', [] ); @@ -132,9 +152,9 @@ public function testExtractEnum() $this->assertNull($this->createExtractor()->getTypes(DoctrineEnum::class, 'enumCustom', [])); } - public static function typesProvider() + public static function typesProvider(): array { - $provider = [ + return [ ['id', [new Type(Type::BUILTIN_TYPE_INT)]], ['guid', [new Type(Type::BUILTIN_TYPE_STRING)]], ['bigint', [new Type(Type::BUILTIN_TYPE_STRING)]], @@ -217,8 +237,6 @@ public static function typesProvider() )]], ['json', null], ]; - - return $provider; } public function testGetPropertiesCatchException() diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php index 8190540e27566..902850c5828ba 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php @@ -23,102 +23,122 @@ * * @author Kévin Dunglas */ +#[Entity] class DoctrineDummy { /** * @Id * @Column(type="smallint") */ + #[Id, Column(type: 'smallint')] public $id; /** * @ManyToOne(targetEntity="DoctrineRelation") */ + #[ManyToOne(targetEntity: DoctrineRelation::class)] public $foo; /** * @ManyToMany(targetEntity="DoctrineRelation") */ + #[ManyToMany(targetEntity: DoctrineRelation::class)] public $bar; /** * @ManyToMany(targetEntity="DoctrineRelation", indexBy="rguid") */ + #[ManyToMany(targetEntity: DoctrineRelation::class, indexBy: 'rguid')] protected $indexedRguid; /** * @ManyToMany(targetEntity="DoctrineRelation", indexBy="rguid_column") */ + #[ManyToMany(targetEntity: DoctrineRelation::class, indexBy: 'rguid_column')] protected $indexedBar; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="foo", indexBy="foo") */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'foo', indexBy: 'foo')] protected $indexedFoo; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="baz", indexBy="baz_id") */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'baz', indexBy: 'baz_id')] protected $indexedBaz; /** * @Column(type="guid") */ + #[Column(type: 'guid')] protected $guid; /** * @Column(type="time") */ + #[Column(type: 'time')] private $time; /** * @Column(type="time_immutable") */ + #[Column(type: 'time_immutable')] private $timeImmutable; /** * @Column(type="dateinterval") */ + #[Column(type: 'dateinterval')] private $dateInterval; /** * @Column(type="json_array") */ + #[Column(type: 'json_array')] private $jsonArray; /** * @Column(type="simple_array") */ + #[Column(type: 'simple_array')] private $simpleArray; /** * @Column(type="float") */ + #[Column(type: 'float')] private $float; /** * @Column(type="decimal", precision=10, scale=2) */ + #[Column(type: 'decimal', precision: 10, scale: 2)] private $decimal; /** * @Column(type="boolean") */ + #[Column(type: 'boolean')] private $bool; /** * @Column(type="binary") */ + #[Column(type: 'binary')] private $binary; /** * @Column(type="custom_foo") */ + #[Column(type: 'custom_foo')] private $customFoo; /** * @Column(type="bigint") */ + #[Column(type: 'bigint')] private $bigint; public $notMapped; @@ -126,25 +146,30 @@ class DoctrineDummy /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="dt", indexBy="dt") */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'dt', indexBy: 'dt')] protected $indexedByDt; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="customType", indexBy="customType") */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'customType', indexBy: 'customType')] private $indexedByCustomType; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="buzField", indexBy="buzField") */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'buzField', indexBy: 'buzField')] protected $indexedBuz; /** * @Column(type="json", nullable=true) */ + #[Column(type: 'json', nullable: true)] private $json; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="dummyRelation", indexBy="gen_value_col_id", orphanRemoval=true) */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'dummyRelation', indexBy: 'gen_value_col_id', orphanRemoval: true)] protected $dummyGeneratedValueList; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php index a00856ed7331e..23609fb1827d0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php @@ -19,10 +19,12 @@ * * @author Udaltsov Valentin */ +#[Embeddable] class DoctrineEmbeddable { /** * @Column(type="string") */ + #[Column(type: 'string')] protected $field; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php index 8a63a4e8db248..edd6b01e9f5c6 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php @@ -18,36 +18,43 @@ /** * @Entity */ +#[Entity] class DoctrineEnum { /** * @Id * @Column(type="smallint") */ + #[Id, Column(type: 'smallint')] public $id; /** * @Column(type="string", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString") */ + #[Column(type: 'string', enumType: EnumString::class)] protected $enumString; /** * @Column(type="integer", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt") */ + #[Column(type: 'integer', enumType: EnumInt::class)] protected $enumInt; /** * @Column(type="array", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString") */ + #[Column(type: 'array', enumType: EnumString::class)] protected $enumStringArray; /** * @Column(type="simple_array", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt") */ + #[Column(type: 'simple_array', enumType: EnumInt::class)] protected $enumIntArray; /** * @Column(type="custom_foo", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt") */ + #[Column(type: 'custom_foo', enumType: EnumInt::class)] protected $enumCustom; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php index bebe51dbc711d..93e9818f4383c 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php @@ -32,7 +32,7 @@ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $pla return $platform->getClobTypeDeclarationSQL([]); } - public function convertToDatabaseValue($value, AbstractPlatform $platform): mixed + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string { if (null === $value) { return null; @@ -44,7 +44,7 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): mixe return $foo->bar; } - public function convertToPHPValue($value, AbstractPlatform $platform): mixed + public function convertToPHPValue($value, AbstractPlatform $platform): ?Foo { if (null === $value) { return null; diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineGeneratedValue.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineGeneratedValue.php index 9e7612fa35ae4..90fec268506e8 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineGeneratedValue.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineGeneratedValue.php @@ -22,6 +22,7 @@ * * @Entity */ +#[Entity] class DoctrineGeneratedValue { /** @@ -29,21 +30,25 @@ class DoctrineGeneratedValue * @GeneratedValue(strategy="AUTO") * @Column(type="integer") */ + #[Id, GeneratedValue(strategy: 'AUTO'), Column(type: 'integer')] public $id; /** * @Column */ + #[Column] public $foo; /** * @var int * @Column(type="integer", name="gen_value_col_id") */ + #[Column(type: 'integer', name: 'gen_value_col_id')] public $valueId; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="generatedValueRelation", indexBy="rguid_column", orphanRemoval=true) */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'generatedValueRelation', indexBy: 'rguid_column', orphanRemoval: true)] protected $relationList; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php index 91115abb719ec..3310f9e0d06a0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php @@ -22,54 +22,67 @@ * * @author Kévin Dunglas */ +#[Entity] class DoctrineRelation { /** * @Id * @Column(type="smallint") */ + #[Id, Column(type: 'smallint')] public $id; /** * @Column(type="guid", name="rguid_column") */ + #[Column(type: 'guid', name: 'rguid_column')] protected $rguid; /** * @Column(type="guid") * @ManyToOne(targetEntity="DoctrineDummy", inversedBy="indexedFoo") */ + #[Column(type: 'guid')] + #[ManyToOne(targetEntity: DoctrineDummy::class, inversedBy: 'indexedFoo')] protected $foo; /** * @ManyToOne(targetEntity="DoctrineDummy") */ + #[ManyToOne(targetEntity: DoctrineDummy::class)] protected $baz; /** * @Column(type="datetime") */ + #[Column(type: 'datetime')] private $dt; /** * @Column(type="foo") */ + #[Column(type: 'foo')] private $customType; /** * @Column(type="guid", name="different_than_field") * @ManyToOne(targetEntity="DoctrineDummy", inversedBy="indexedBuz") */ + #[Column(type: 'guid', name: 'different_than_field')] + #[ManyToOne(targetEntity: DoctrineDummy::class, inversedBy: 'indexedBuz')] protected $buzField; /** * @ManyToOne(targetEntity="DoctrineDummy", inversedBy="dummyGeneratedValueList") */ + #[ManyToOne(targetEntity: DoctrineDummy::class, inversedBy: 'dummyGeneratedValueList')] private $dummyRelation; /** * @ManyToOne(targetEntity="DoctrineGeneratedValue", inversedBy="relationList") * @JoinColumn(name="gen_value_col_id", referencedColumnName="gen_value_col_id") */ + #[ManyToOne(targetEntity: DoctrineGeneratedValue::class, inversedBy: 'relationList')] + #[JoinColumn(name: 'gen_value_col_id', referencedColumnName: 'gen_value_col_id')] private $generatedValueRelation; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php index aace866128b0e..053f8bec0d19b 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php @@ -21,16 +21,19 @@ * * @author Udaltsov Valentin */ +#[Entity] class DoctrineWithEmbedded { /** * @Id * @Column(type="smallint") */ + #[Id, Column(type: 'smallint')] public $id; /** * @Embedded(class="DoctrineEmbeddable") */ + #[Embedded(class: DoctrineEmbeddable::class)] protected $embedded; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaListenerTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaListenerTest.php index 6cec07f820f42..e429dca192f6d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaListenerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaListenerTest.php @@ -35,7 +35,7 @@ public function testPostGenerateSchema() $dbalAdapter = $this->createMock(DoctrineDbalAdapter::class); $dbalAdapter->expects($this->once()) ->method('configureSchema') - ->with($schema, $dbalConnection); + ->with($schema, $dbalConnection, fn () => true); $subscriber = new DoctrineDbalCacheAdapterSchemaListener([$dbalAdapter]); $subscriber->postGenerateSchema($event); diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/LockStoreSchemaListenerTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/LockStoreSchemaListenerTest.php index d8d06a5fe0524..6f23d680feb9f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/LockStoreSchemaListenerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/LockStoreSchemaListenerTest.php @@ -17,6 +17,7 @@ use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\SchemaListener\LockStoreSchemaListener; +use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Store\DoctrineDbalStore; class LockStoreSchemaListenerTest extends TestCase @@ -39,4 +40,20 @@ public function testPostGenerateSchemaLockPdo() $subscriber = new LockStoreSchemaListener([$lockStore]); $subscriber->postGenerateSchema($event); } + + public function testPostGenerateSchemaWithInvalidLockStore() + { + $entityManager = $this->createMock(EntityManagerInterface::class); + $entityManager->expects($this->once()) + ->method('getConnection') + ->willReturn($this->createMock(Connection::class)); + $event = new GenerateSchemaEventArgs($entityManager, new Schema()); + + $subscriber = new LockStoreSchemaListener((static function (): \Generator { + yield $this->createMock(DoctrineDbalStore::class); + + throw new InvalidArgumentException('Unsupported Connection'); + })()); + $subscriber->postGenerateSchema($event); + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php index 6c58fec7ce939..7321ddd30e814 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php @@ -38,7 +38,7 @@ public function testPostGenerateSchema() $doctrineTransport = $this->createMock(DoctrineTransport::class); $doctrineTransport->expects($this->once()) ->method('configureSchema') - ->with($schema, $dbalConnection); + ->with($schema, $dbalConnection, fn () => true); $otherTransport = $this->createMock(TransportInterface::class); $otherTransport->expects($this->never()) ->method($this->anything()); @@ -49,6 +49,10 @@ public function testPostGenerateSchema() public function testOnSchemaCreateTable() { + if (!class_exists(SchemaCreateTableEventArgs::class)) { + self::markTestSkipped('This test requires DBAL < 4.'); + } + $platform = $this->createMock(AbstractPlatform::class); $table = new Table('queue_table'); $event = new SchemaCreateTableEventArgs($table, [], [], $platform); @@ -81,6 +85,10 @@ public function testOnSchemaCreateTable() public function testOnSchemaCreateTableNoExtraSql() { + if (!class_exists(SchemaCreateTableEventArgs::class)) { + self::markTestSkipped('This test requires DBAL < 4.'); + } + $platform = $this->createMock(AbstractPlatform::class); $table = new Table('queue_table'); $event = new SchemaCreateTableEventArgs($table, [], [], $platform); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php index 9d8b9256f4c9d..de9ae48e041d1 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php @@ -12,6 +12,8 @@ namespace Security\RememberMe; use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\ORM\ORMSetup; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider; use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; @@ -120,10 +122,19 @@ public function testVerifyOutdatedTokenAfterParallelRequestFailsAfter60Seconds() */ private function bootstrapProvider() { + $config = ORMSetup::createConfiguration(true); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + if (method_exists($config, 'setLazyGhostObjectEnabled')) { + $config->setLazyGhostObjectEnabled(true); + } + $connection = DriverManager::getConnection([ 'driver' => 'pdo_sqlite', - 'url' => 'sqlite:///:memory:', - ]); + 'memory' => true, + ], $config); $connection->{method_exists($connection, 'executeStatement') ? 'executeStatement' : 'executeUpdate'}(<<< 'SQL' CREATE TABLE rememberme_token ( series char(88) UNIQUE PRIMARY KEY NOT NULL, diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index c17c3e2c93501..9275dc46bd11f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php @@ -12,10 +12,12 @@ namespace Symfony\Bridge\Doctrine\Tests\Security\User; use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Tools\SchemaTool; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectRepository; +use Doctrine\Persistence\Proxy; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Security\User\EntityUserProvider; use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface; @@ -48,7 +50,7 @@ public function testRefreshUserGetsUserByPrimaryKey() $this->assertSame($user1, $provider->refreshUser($user1)); } - public function testLoadUserByUsername() + public function testLoadUserByIdentifier() { $em = DoctrineTestHelper::createTestEntityManager(); $this->createSchema($em); @@ -63,7 +65,7 @@ public function testLoadUserByUsername() $this->assertSame($user, $provider->loadUserByIdentifier('user1')); } - public function testLoadUserByUsernameWithUserLoaderRepositoryAndWithoutProperty() + public function testLoadUserByIdentifierWithUserLoaderRepositoryAndWithoutProperty() { $user = new User(1, 1, 'user1'); @@ -85,7 +87,7 @@ public function testLoadUserByUsernameWithUserLoaderRepositoryAndWithoutProperty $this->assertSame($user, $provider->loadUserByIdentifier('user1')); } - public function testLoadUserByUsernameWithNonUserLoaderRepositoryAndWithoutProperty() + public function testLoadUserByIdentifierWithNonUserLoaderRepositoryAndWithoutProperty() { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('You must either make the "Symfony\Bridge\Doctrine\Tests\Fixtures\User" entity Doctrine Repository ("Doctrine\ORM\EntityRepository") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration.'); @@ -149,7 +151,7 @@ public function testSupportProxy() $this->assertTrue($provider->supportsClass($user2::class)); } - public function testLoadUserByUserNameShouldLoadUserWhenProperInterfaceProvided() + public function testLoadUserByIdentifierShouldLoadUserWhenProperInterfaceProvided() { $repository = $this->createMock(UserLoaderRepository::class); $repository->expects($this->once()) @@ -167,7 +169,7 @@ public function testLoadUserByUserNameShouldLoadUserWhenProperInterfaceProvided( $provider->loadUserByIdentifier('name'); } - public function testLoadUserByUserNameShouldDeclineInvalidInterface() + public function testLoadUserByIdentifierShouldDeclineInvalidInterface() { $this->expectException(\InvalidArgumentException::class); $repository = $this->createMock(ObjectRepository::class); @@ -197,6 +199,27 @@ public function testPasswordUpgrades() $provider->upgradePassword($user, 'foobar'); } + public function testRefreshedUserProxyIsLoaded() + { + $em = DoctrineTestHelper::createTestEntityManager(); + $this->createSchema($em); + + $user = new User(1, 1, 'user1'); + + $em->persist($user); + $em->flush(); + $em->clear(); + + // store a proxy in the identity map + $em->getReference(User::class, ['id1' => 1, 'id2' => 1]); + + $provider = new EntityUserProvider($this->getManager($em), User::class); + $refreshedUser = $provider->refreshUser($user); + + $this->assertInstanceOf(Proxy::class, $refreshedUser); + $this->assertTrue($refreshedUser->__isInitialized()); + } + private function getManager($em, $name = null) { $manager = $this->createMock(ManagerRegistry::class); @@ -229,12 +252,12 @@ private function createSchema($em) } } -abstract class UserLoaderRepository implements ObjectRepository, UserLoaderInterface +abstract class UserLoaderRepository extends EntityRepository implements UserLoaderInterface { abstract public function loadUserByIdentifier(string $identifier): ?UserInterface; } -abstract class PasswordUpgraderRepository implements ObjectRepository, PasswordUpgraderInterface +abstract class PasswordUpgraderRepository extends EntityRepository implements PasswordUpgraderInterface { abstract public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php b/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php index 04021b7fd2ea0..c7a3a94f86513 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php +++ b/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php @@ -12,8 +12,8 @@ namespace Symfony\Bridge\Doctrine\Tests; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Repository\RepositoryFactory; -use Doctrine\Persistence\ObjectRepository; /** * @author Andreas Braun @@ -21,25 +21,25 @@ final class TestRepositoryFactory implements RepositoryFactory { /** - * @var array + * @var array */ private array $repositoryList = []; - public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository + public function getRepository(EntityManagerInterface $entityManager, $entityName): EntityRepository { $repositoryHash = $this->getRepositoryHash($entityManager, $entityName); return $this->repositoryList[$repositoryHash] ??= $this->createRepository($entityManager, $entityName); } - public function setRepository(EntityManagerInterface $entityManager, string $entityName, ObjectRepository $repository): void + public function setRepository(EntityManagerInterface $entityManager, string $entityName, EntityRepository $repository): void { $repositoryHash = $this->getRepositoryHash($entityManager, $entityName); $this->repositoryList[$repositoryHash] = $repository; } - private function createRepository(EntityManagerInterface $entityManager, string $entityName): ObjectRepository + private function createRepository(EntityManagerInterface $entityManager, string $entityName): EntityRepository { $metadata = $entityManager->getClassMetadata($entityName); $repositoryClassName = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php index 06114aeea0050..ea4ecef538b73 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php @@ -12,9 +12,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\MariaDBPlatform; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; use PHPUnit\Framework\TestCase; @@ -24,6 +25,11 @@ // DBAL 2 compatibility class_exists('Doctrine\DBAL\Platforms\PostgreSqlPlatform'); +// DBAL 3 compatibility +class_exists('Doctrine\DBAL\Platforms\SqlitePlatform'); + +// DBAL 3 compatibility +class_exists('Doctrine\DBAL\Platforms\SqlitePlatform'); final class UlidTypeTest extends TestCase { @@ -84,25 +90,25 @@ public function testNotSupportedTypeConversionForDatabaseValue() { $this->expectException(ConversionException::class); - $this->type->convertToDatabaseValue(new \stdClass(), new SqlitePlatform()); + $this->type->convertToDatabaseValue(new \stdClass(), new SQLitePlatform()); } public function testNullConversionForDatabaseValue() { - $this->assertNull($this->type->convertToDatabaseValue(null, new SqlitePlatform())); + $this->assertNull($this->type->convertToDatabaseValue(null, new SQLitePlatform())); } public function testUlidInterfaceConvertsToPHPValue() { $ulid = $this->createMock(AbstractUid::class); - $actual = $this->type->convertToPHPValue($ulid, new SqlitePlatform()); + $actual = $this->type->convertToPHPValue($ulid, new SQLitePlatform()); $this->assertSame($ulid, $actual); } public function testUlidConvertsToPHPValue() { - $ulid = $this->type->convertToPHPValue(self::DUMMY_ULID, new SqlitePlatform()); + $ulid = $this->type->convertToPHPValue(self::DUMMY_ULID, new SQLitePlatform()); $this->assertInstanceOf(Ulid::class, $ulid); $this->assertEquals(self::DUMMY_ULID, $ulid->__toString()); @@ -112,19 +118,19 @@ public function testInvalidUlidConversionForPHPValue() { $this->expectException(ConversionException::class); - $this->type->convertToPHPValue('abcdefg', new SqlitePlatform()); + $this->type->convertToPHPValue('abcdefg', new SQLitePlatform()); } public function testNullConversionForPHPValue() { - $this->assertNull($this->type->convertToPHPValue(null, new SqlitePlatform())); + $this->assertNull($this->type->convertToPHPValue(null, new SQLitePlatform())); } public function testReturnValueIfUlidForPHPValue() { $ulid = new Ulid(); - $this->assertSame($ulid, $this->type->convertToPHPValue($ulid, new SqlitePlatform())); + $this->assertSame($ulid, $this->type->convertToPHPValue($ulid, new SQLitePlatform())); } public function testGetName() @@ -140,17 +146,19 @@ public function testGetGuidTypeDeclarationSQL(AbstractPlatform $platform, string $this->assertEquals($expectedDeclaration, $this->type->getSqlDeclaration(['length' => 36], $platform)); } - public static function provideSqlDeclarations(): array + public static function provideSqlDeclarations(): \Generator { - return [ - [new PostgreSQLPlatform(), 'UUID'], - [new SqlitePlatform(), 'BLOB'], - [new MySQLPlatform(), 'BINARY(16)'], - ]; + yield [new PostgreSQLPlatform(), 'UUID']; + yield [new SQLitePlatform(), 'BLOB']; + yield [new MySQLPlatform(), 'BINARY(16)']; + + if (class_exists(MariaDBPlatform::class)) { + yield [new MariaDBPlatform(), 'BINARY(16)']; + } } public function testRequiresSQLCommentHint() { - $this->assertTrue($this->type->requiresSQLCommentHint(new SqlitePlatform())); + $this->assertTrue($this->type->requiresSQLCommentHint(new SQLitePlatform())); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php index d49afc5f97ec4..120887ef3653a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\MariaDBPlatform; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; @@ -152,13 +153,15 @@ public function testGetGuidTypeDeclarationSQL(AbstractPlatform $platform, string $this->assertEquals($expectedDeclaration, $this->type->getSqlDeclaration(['length' => 36], $platform)); } - public static function provideSqlDeclarations(): array + public static function provideSqlDeclarations(): \Generator { - return [ - [new PostgreSQLPlatform(), 'UUID'], - [new SqlitePlatform(), 'BLOB'], - [new MySQLPlatform(), 'BINARY(16)'], - ]; + yield [new PostgreSQLPlatform(), 'UUID']; + yield [new SqlitePlatform(), 'BLOB']; + yield [new MySQLPlatform(), 'BINARY(16)']; + + if (class_exists(MariaDBPlatform::class)) { + yield [new MariaDBPlatform(), 'BINARY(16)']; + } } public function testRequiresSQLCommentHint() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 3176ac22f9678..0f7a3f7022fc3 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -13,11 +13,13 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\Types\Type; +use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Tools\SchemaTool; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectManager; -use Doctrine\Persistence\ObjectRepository; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity2; @@ -58,7 +60,7 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase protected $registry; /** - * @var ObjectRepository + * @var MockObject&EntityRepository */ protected $repository; @@ -95,7 +97,8 @@ protected function createRegistryMock($em = null) protected function createRepositoryMock() { - $repository = $this->getMockBuilder(ObjectRepository::class) + $repository = $this->getMockBuilder(EntityRepository::class) + ->disableOriginalConstructor() ->onlyMethods(['find', 'findAll', 'findOneBy', 'findBy', 'getClassName']) ->addMethods(['findByCustom']) ->getMock() @@ -112,7 +115,9 @@ protected function createEntityManagerMock($repositoryMock) ->willReturn($repositoryMock) ; - $classMetadata = $this->createMock(ClassMetadataInfo::class); + $classMetadata = $this->createMock( + class_exists(ClassMetadataInfo::class) ? ClassMetadataInfo::class : ClassMetadata::class + ); $classMetadata ->expects($this->any()) ->method('hasField') @@ -251,8 +256,9 @@ public function testValidateUniquenessWithNull(UniqueEntity $constraint) /** * @dataProvider provideConstraintsWithIgnoreNullDisabled + * @dataProvider provideConstraintsWithIgnoreNullEnabledOnFirstField */ - public function testValidateUniquenessWithIgnoreNullDisabled(UniqueEntity $constraint) + public function testValidateUniquenessWithIgnoreNullDisableOnSecondField(UniqueEntity $constraint) { $entity1 = new DoubleNameEntity(1, 'Foo', null); $entity2 = new DoubleNameEntity(2, 'Foo', null); @@ -304,6 +310,7 @@ public function testAllConfiguredFieldsAreCheckedOfBeingMappedByDoctrineWithIgno /** * @dataProvider provideConstraintsWithIgnoreNullEnabled + * @dataProvider provideConstraintsWithIgnoreNullEnabledOnFirstField */ public function testNoValidationIfFirstFieldIsNullAndNullValuesAreIgnored(UniqueEntity $constraint) { @@ -338,6 +345,18 @@ public static function provideConstraintsWithIgnoreNullEnabled(): iterable yield 'Named arguments' => [new UniqueEntity(message: 'myMessage', fields: ['name', 'name2'], em: 'foo', ignoreNull: true)]; } + public static function provideConstraintsWithIgnoreNullEnabledOnFirstField(): iterable + { + yield 'Doctrine style (name field)' => [new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['name', 'name2'], + 'em' => self::EM_NAME, + 'ignoreNull' => 'name', + ])]; + + yield 'Named arguments (name field)' => [new UniqueEntity(message: 'myMessage', fields: ['name', 'name2'], em: 'foo', ignoreNull: 'name')]; + } + public function testValidateUniquenessWithValidCustomErrorPath() { $constraint = new UniqueEntity([ @@ -464,7 +483,7 @@ public function testValidateResultTypes($entity1, $result) $this->assertNoViolation(); } - public static function resultTypesProvider() + public static function resultTypesProvider(): array { $entity = new SingleIntIdEntity(1, 'foo'); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php index eb83aecbf5990..cbeee6333448e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Validator; use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\BaseUser; @@ -39,9 +40,12 @@ class DoctrineLoaderTest extends TestCase { public function testLoadClassMetadata() { - $validator = Validation::createValidatorBuilder() - ->enableAnnotationMapping(true) - ->addDefaultDoctrineAnnotationReader() + $validatorBuilder = Validation::createValidatorBuilder()->enableAnnotationMapping(true); + if (class_exists(AnnotationDriver::class) && method_exists($validatorBuilder, 'addDefaultDoctrineAnnotationReader')) { + $validatorBuilder->addDefaultDoctrineAnnotationReader(); + } + + $validator = $validatorBuilder ->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{^Symfony\\\\Bridge\\\\Doctrine\\\\Tests\\\\Fixtures\\\\DoctrineLoader}')) ->getValidator() ; @@ -92,9 +96,6 @@ public function testLoadClassMetadata() $parentClassMetadata = $validator->getMetadataFor(new DoctrineLoaderParentEntity()); - $publicParentMaxLengthMetadata = $parentClassMetadata->getPropertyMetadata('publicParentMaxLength'); - $this->assertCount(0, $publicParentMaxLengthMetadata); - $privateParentMaxLengthMetadata = $parentClassMetadata->getPropertyMetadata('privateParentMaxLength'); $this->assertCount(1, $privateParentMaxLengthMetadata); $privateParentMaxLengthConstraints = $privateParentMaxLengthMetadata[0]->getConstraints(); @@ -149,10 +150,15 @@ public function testExtractEnum() $this->markTestSkipped('The "enumType" requires doctrine/orm 2.11.'); } - $validator = Validation::createValidatorBuilder() + $validatorBuilder = Validation::createValidatorBuilder() ->addMethodMapping('loadValidatorMetadata') - ->enableAnnotationMapping(true) - ->addDefaultDoctrineAnnotationReader() + ->enableAnnotationMapping(true); + + if (class_exists(AnnotationDriver::class) && method_exists($validatorBuilder, 'addDefaultDoctrineAnnotationReader')) { + $validatorBuilder->addDefaultDoctrineAnnotationReader(); + } + + $validator = $validatorBuilder ->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{^Symfony\\\\Bridge\\\\Doctrine\\\\Tests\\\\Fixtures\\\\DoctrineLoader}')) ->getValidator() ; @@ -168,9 +174,13 @@ public function testExtractEnum() public function testFieldMappingsConfiguration() { - $validator = Validation::createValidatorBuilder() - ->enableAnnotationMapping(true) - ->addDefaultDoctrineAnnotationReader() + $validatorBuilder = Validation::createValidatorBuilder()->enableAnnotationMapping(true); + + if (class_exists(AnnotationDriver::class) && method_exists($validatorBuilder, 'addDefaultDoctrineAnnotationReader')) { + $validatorBuilder->addDefaultDoctrineAnnotationReader(); + } + + $validator = $validatorBuilder ->addXmlMappings([__DIR__.'/../Resources/validator/BaseUser.xml']) ->addLoader( new DoctrineLoader( @@ -190,7 +200,7 @@ public function testFieldMappingsConfiguration() /** * @dataProvider regexpProvider */ - public function testClassValidator(bool $expected, string $classValidatorRegexp = null) + public function testClassValidator(bool $expected, ?string $classValidatorRegexp = null) { $doctrineLoader = new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), $classValidatorRegexp, false); @@ -198,7 +208,7 @@ public function testClassValidator(bool $expected, string $classValidatorRegexp $this->assertSame($expected, $doctrineLoader->loadClassMetadata($classMetadata)); } - public static function regexpProvider() + public static function regexpProvider(): array { return [ [false, null], @@ -210,9 +220,13 @@ public static function regexpProvider() public function testClassNoAutoMapping() { - $validator = Validation::createValidatorBuilder() - ->enableAnnotationMapping(true) - ->addDefaultDoctrineAnnotationReader() + $validatorBuilder = Validation::createValidatorBuilder()->enableAnnotationMapping(true); + + if (class_exists(AnnotationDriver::class) && method_exists($validatorBuilder, 'addDefaultDoctrineAnnotationReader')) { + $validatorBuilder->addDefaultDoctrineAnnotationReader(); + } + + $validator = $validatorBuilder ->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{.*}')) ->getValidator(); diff --git a/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php b/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php index 6d7aac1487704..8efc4dff1f490 100644 --- a/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php +++ b/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php @@ -13,6 +13,8 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Exception\InvalidType; +use Doctrine\DBAL\Types\Exception\ValueNotConvertible; use Doctrine\DBAL\Types\Type; use Symfony\Component\Uid\AbstractUid; @@ -30,7 +32,7 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st } return $platform->getBinaryTypeDeclarationSQL([ - 'length' => '16', + 'length' => 16, 'fixed' => true, ]); } @@ -45,13 +47,13 @@ public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Ab } if (!\is_string($value)) { - throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]); + $this->throwInvalidType($value); } try { return $this->getUidClass()::fromString($value); } catch (\InvalidArgumentException $e) { - throw ConversionException::conversionFailed($value, $this->getName(), $e); + $this->throwValueNotConvertible($value, $e); } } @@ -71,13 +73,13 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?str } if (!\is_string($value)) { - throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]); + $this->throwInvalidType($value); } try { return $this->getUidClass()::fromString($value)->$toString(); - } catch (\InvalidArgumentException) { - throw ConversionException::conversionFailed($value, $this->getName()); + } catch (\InvalidArgumentException $e) { + $this->throwValueNotConvertible($value, $e); } } @@ -95,4 +97,22 @@ private function hasNativeGuidType(AbstractPlatform $platform): bool return $platform->getGuidTypeDeclarationSQL([]) !== $platform->$method(['fixed' => true, 'length' => 36]); } + + private function throwInvalidType(mixed $value): never + { + if (!class_exists(InvalidType::class)) { + throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]); + } + + throw InvalidType::new($value, $this->getName(), ['null', 'string', AbstractUid::class]); + } + + private function throwValueNotConvertible(mixed $value, \Throwable $previous): never + { + if (!class_exists(ValueNotConvertible::class)) { + throw ConversionException::conversionFailed($value, $this->getName(), $previous); + } + + throw ValueNotConvertible::new($value, $this->getName(), null, $previous); + } } diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php index da1873cb91856..91574a061150a 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php @@ -45,18 +45,19 @@ class UniqueEntity extends Constraint protected static $errorNames = self::ERROR_NAMES; /** - * @param array|string $fields the combination of fields that must contain unique values or a set of options + * @param array|string $fields The combination of fields that must contain unique values or a set of options + * @param bool|array|string $ignoreNull The combination of fields that ignore null values */ public function __construct( $fields, - string $message = null, - string $service = null, - string $em = null, - string $entityClass = null, - string $repositoryMethod = null, - string $errorPath = null, - bool $ignoreNull = null, - array $groups = null, + ?string $message = null, + ?string $service = null, + ?string $em = null, + ?string $entityClass = null, + ?string $repositoryMethod = null, + ?string $errorPath = null, + bool|string|array|null $ignoreNull = null, + ?array $groups = null, $payload = null, array $options = [] ) { diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 67575134b660b..a69bcad8ef323 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -87,7 +87,7 @@ public function validate(mixed $entity, Constraint $constraint) $class = $em->getClassMetadata($entity::class); $criteria = []; - $hasNullValue = false; + $hasIgnorableNullValue = false; foreach ($fields as $fieldName) { if (!$class->hasField($fieldName) && !$class->hasAssociation($fieldName)) { @@ -96,11 +96,9 @@ public function validate(mixed $entity, Constraint $constraint) $fieldValue = $class->reflFields[$fieldName]->getValue($entity); - if (null === $fieldValue) { - $hasNullValue = true; - } + if (null === $fieldValue && $this->ignoreNullForField($constraint, $fieldName)) { + $hasIgnorableNullValue = true; - if ($constraint->ignoreNull && null === $fieldValue) { continue; } @@ -116,7 +114,7 @@ public function validate(mixed $entity, Constraint $constraint) } // validation doesn't fail if one of the fields is null and if null values should be ignored - if ($hasNullValue && $constraint->ignoreNull) { + if ($hasIgnorableNullValue) { return; } @@ -195,6 +193,15 @@ public function validate(mixed $entity, Constraint $constraint) ->addViolation(); } + private function ignoreNullForField(UniqueEntity $constraint, string $fieldName): bool + { + if (\is_bool($constraint->ignoreNull)) { + return $constraint->ignoreNull; + } + + return \in_array($fieldName, (array) $constraint->ignoreNull, true); + } + private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, mixed $value): string { if (!\is_object($value) || $value instanceof \DateTimeInterface) { diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php index d0e976c2c8853..70063597b52eb 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php @@ -12,7 +12,7 @@ namespace Symfony\Bridge\Doctrine\Validator; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata as OrmClassMetadata; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Doctrine\Persistence\Mapping\MappingException; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; @@ -36,7 +36,7 @@ final class DoctrineLoader implements LoaderInterface private EntityManagerInterface $entityManager; private ?string $classValidatorRegexp; - public function __construct(EntityManagerInterface $entityManager, string $classValidatorRegexp = null) + public function __construct(EntityManagerInterface $entityManager, ?string $classValidatorRegexp = null) { $this->entityManager = $entityManager; $this->classValidatorRegexp = $classValidatorRegexp; @@ -51,7 +51,7 @@ public function loadClassMetadata(ClassMetadata $metadata): bool return false; } - if (!$doctrineMetadata instanceof ClassMetadataInfo) { + if (!$doctrineMetadata instanceof OrmClassMetadata) { return false; } @@ -105,7 +105,7 @@ public function loadClassMetadata(ClassMetadata $metadata): bool if (isset($mapping['originalClass']) && !str_contains($mapping['declaredField'], '.')) { $metadata->addPropertyConstraint($mapping['declaredField'], new Valid()); $loaded = true; - } elseif (property_exists($className, $mapping['fieldName'])) { + } elseif (property_exists($className, $mapping['fieldName']) && (!$doctrineMetadata->isMappedSuperclass || $metadata->getReflectionClass()->getProperty($mapping['fieldName'])->isPrivate())) { $metadata->addPropertyConstraint($mapping['fieldName'], new Length(['max' => $mapping['length']])); $loaded = true; } diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 98721385f4e5e..130d2cc21b2b1 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -41,13 +41,13 @@ "symfony/stopwatch": "^5.4|^6.0", "symfony/translation": "^5.4|^6.0", "symfony/uid": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", + "symfony/validator": "^5.4.25|~6.2.12|^6.3.1", "symfony/var-dumper": "^5.4|^6.0", "doctrine/annotations": "^1.13.1|^2", "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", - "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.12", + "doctrine/dbal": "^2.13.1|^3|^4", + "doctrine/orm": "^2.12|^3", "psr/log": "^1|^2|^3" }, "conflict": { @@ -55,7 +55,6 @@ "doctrine/dbal": "<2.13.1", "doctrine/lexer": "<1.1", "doctrine/orm": "<2.12", - "phpunit/phpunit": "<5.4.3", "symfony/cache": "<5.4", "symfony/dependency-injection": "<6.2", "symfony/form": "<5.4.21|>=6,<6.2.7", @@ -66,7 +65,7 @@ "symfony/property-info": "<5.4", "symfony/security-bundle": "<5.4", "symfony/security-core": "<6.0", - "symfony/validator": "<5.4" + "symfony/validator": "<5.4.25|>=6,<6.2.12|>=6.3,<6.3.1" }, "autoload": { "psr-4": { "Symfony\\Bridge\\Doctrine\\": "" }, diff --git a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php index 5210e8eefafd5..126394ec4c05a 100644 --- a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php +++ b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php @@ -13,7 +13,9 @@ use Monolog\Formatter\FormatterInterface; use Monolog\Handler\HandlerInterface; +use Monolog\Level; use Monolog\Logger; +use Monolog\LogRecord; use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter; use Symfony\Bridge\Monolog\Handler\ConsoleHandler; use Symfony\Component\Console\Attribute\AsCommand; @@ -156,6 +158,17 @@ private function displayLog(OutputInterface $output, int $clientId, array $recor $logBlock = sprintf(' ', self::BG_COLOR[$clientId % 8]); $output->write($logBlock); + if (Logger::API >= 3) { + $record = new LogRecord( + $record['datetime'], + $record['channel'], + Level::fromValue($record['level']), + $record['message'], + $record['context']->getValue(true), + $record['extra']->getValue(true), + ); + } + $this->handler->handle($record); } } diff --git a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php index 36de344954c1c..8656cde812c17 100644 --- a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php +++ b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php @@ -185,7 +185,7 @@ private function replacePlaceHolder(array $record): array return $record; } - private function dumpData(mixed $data, bool $colors = null): string + private function dumpData(mixed $data, ?bool $colors = null): string { if (!isset($this->dumper)) { return ''; diff --git a/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php index 14b7da442b605..6747bcc075435 100644 --- a/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php +++ b/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php @@ -26,7 +26,7 @@ class VarDumperFormatter implements FormatterInterface private VarCloner $cloner; - public function __construct(VarCloner $cloner = null) + public function __construct(?VarCloner $cloner = null) { $this->cloner = $cloner ?? new VarCloner(); } diff --git a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php index c3426bb51cb0f..80ee3051a8a55 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php @@ -103,7 +103,7 @@ class ConsoleHandler extends AbstractProcessingHandler implements EventSubscribe * @param array $verbosityLevelMap Array that maps the OutputInterface verbosity to a minimum logging * level (leave empty to use the default mapping) */ - public function __construct(OutputInterface $output = null, bool $bubble = true, array $verbosityLevelMap = [], array $consoleFormatterOptions = []) + public function __construct(?OutputInterface $output = null, bool $bubble = true, array $verbosityLevelMap = [], array $consoleFormatterOptions = []) { parent::__construct(Logger::DEBUG, $bubble); $this->output = $output; diff --git a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php index e387c869608e9..709f4ab2ddb72 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php @@ -61,7 +61,7 @@ class ElasticsearchLogstashHandler extends AbstractHandler */ private \SplObjectStorage $responses; - public function __construct(string $endpoint = 'http://127.0.0.1:9200', string $index = 'monolog', HttpClientInterface $client = null, string|int|Level $level = Logger::DEBUG, bool $bubble = true, string $elasticsearchVersion = '1.0.0') + public function __construct(string $endpoint = 'http://127.0.0.1:9200', string $index = 'monolog', ?HttpClientInterface $client = null, string|int|Level $level = Logger::DEBUG, bool $bubble = true, string $elasticsearchVersion = '1.0.0') { if (!interface_exists(HttpClientInterface::class)) { throw new \LogicException(sprintf('The "%s" handler needs an HTTP client. Try running "composer require symfony/http-client".', __CLASS__)); diff --git a/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php b/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php index 6aedc7c36e267..20d6c0eaee00b 100644 --- a/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php @@ -71,7 +71,7 @@ private function notify(array $records): void $this->notifier->send($notification, ...$this->notifier->getAdminRecipients()); } - private function getHighestRecord(array $records): array + private function getHighestRecord(array $records): array|LogRecord { $highestRecord = null; foreach ($records as $record) { diff --git a/src/Symfony/Bridge/Monolog/Logger.php b/src/Symfony/Bridge/Monolog/Logger.php index 367b3351ff102..9322059e411ce 100644 --- a/src/Symfony/Bridge/Monolog/Logger.php +++ b/src/Symfony/Bridge/Monolog/Logger.php @@ -22,7 +22,7 @@ */ class Logger extends BaseLogger implements DebugLoggerInterface, ResetInterface { - public function getLogs(Request $request = null): array + public function getLogs(?Request $request = null): array { if ($logger = $this->getDebugLogger()) { return $logger->getLogs($request); @@ -31,7 +31,7 @@ public function getLogs(Request $request = null): array return []; } - public function countErrors(Request $request = null): int + public function countErrors(?Request $request = null): int { if ($logger = $this->getDebugLogger()) { return $logger->countErrors($request); diff --git a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php index c1ce2898dab37..cbb28048afa69 100644 --- a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php @@ -26,7 +26,7 @@ class DebugProcessor implements DebugLoggerInterface, ResetInterface private array $errorCount = []; private ?RequestStack $requestStack; - public function __construct(RequestStack $requestStack = null) + public function __construct(?RequestStack $requestStack = null) { $this->requestStack = $requestStack; } @@ -68,7 +68,7 @@ private function doInvoke(array|LogRecord $record): array|LogRecord return $record; } - public function getLogs(Request $request = null): array + public function getLogs(?Request $request = null): array { if (null !== $request) { return $this->records[spl_object_id($request)] ?? []; @@ -81,7 +81,7 @@ public function getLogs(Request $request = null): array return array_merge(...array_values($this->records)); } - public function countErrors(Request $request = null): int + public function countErrors(?Request $request = null): int { if (null !== $request) { return $this->errorCount[spl_object_id($request)] ?? 0; diff --git a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php index e34219b97cc4c..8e5b6e7bd9e83 100644 --- a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php @@ -25,7 +25,7 @@ */ class WebProcessor extends BaseWebProcessor implements EventSubscriberInterface { - public function __construct(array $extraFields = null) + public function __construct(?array $extraFields = null) { // Pass an empty array as the default null value would access $_SERVER parent::__construct([], $extraFields); diff --git a/src/Symfony/Bridge/Monolog/Tests/ClassThatInheritLogger.php b/src/Symfony/Bridge/Monolog/Tests/ClassThatInheritLogger.php index e258c7942a20a..ff5ab0023295c 100644 --- a/src/Symfony/Bridge/Monolog/Tests/ClassThatInheritLogger.php +++ b/src/Symfony/Bridge/Monolog/Tests/ClassThatInheritLogger.php @@ -16,12 +16,12 @@ class ClassThatInheritLogger extends Logger { - public function getLogs(Request $request = null): array + public function getLogs(?Request $request = null): array { return parent::getLogs($request); } - public function countErrors(Request $request = null): int + public function countErrors(?Request $request = null): int { return parent::countErrors($request); } diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php index b2f8a79f965a7..6e91685e7bd86 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php @@ -82,7 +82,7 @@ public function testVerbosityMapping($verbosity, $level, $isHandling, array $map $this->assertFalse($handler->handle($infoRecord), 'The handler finished handling the log.'); } - public static function provideVerbosityMappingTests() + public static function provideVerbosityMappingTests(): array { return [ [OutputInterface::VERBOSITY_QUIET, Logger::ERROR, true], diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php b/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php index bc87c724c9d31..697b5872cb579 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php @@ -16,12 +16,12 @@ class ClassThatInheritDebugProcessor extends DebugProcessor { - public function getLogs(Request $request = null): array + public function getLogs(?Request $request = null): array { return parent::getLogs($request); } - public function countErrors(Request $request = null): int + public function countErrors(?Request $request = null): int { return parent::countErrors($request); } diff --git a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index 9c664176565b7..6c42e79218910 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add support for mocking the `enum_exists` function + * Enable reporting of deprecations triggered by Doctrine by default 6.2 --- diff --git a/src/Symfony/Bridge/PhpUnit/CoverageListener.php b/src/Symfony/Bridge/PhpUnit/CoverageListener.php index 766252b8728b7..65d6aa9dc9dcc 100644 --- a/src/Symfony/Bridge/PhpUnit/CoverageListener.php +++ b/src/Symfony/Bridge/PhpUnit/CoverageListener.php @@ -26,7 +26,7 @@ class CoverageListener implements TestListener private $sutFqcnResolver; private $warningOnSutNotFound; - public function __construct(callable $sutFqcnResolver = null, bool $warningOnSutNotFound = false) + public function __construct(?callable $sutFqcnResolver = null, bool $warningOnSutNotFound = false) { $this->sutFqcnResolver = $sutFqcnResolver ?? static function (Test $test): ?string { $class = \get_class($test); diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php index fd2730b0f1d5a..54182d2069c94 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php @@ -230,7 +230,9 @@ public function isBaselineDeprecation(Deprecation $deprecation): bool return false; } - if ($deprecation->originatesFromAnObject()) { + if ($deprecation->originatesFromDebugClassLoader()) { + $location = $deprecation->triggeringClass(); + } elseif ($deprecation->originatesFromAnObject()) { $location = $deprecation->originatingClass().'::'.$deprecation->originatingMethod(); } else { $location = 'procedural code'; diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index e18d9b1a289f2..79cfa0cc9fe85 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\PhpUnit\DeprecationErrorHandler; +use Doctrine\Deprecations\Deprecation as DoctrineDeprecation; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestSuite; use PHPUnit\Metadata\Api\Groups; @@ -40,6 +41,7 @@ class Deprecation private $originClass; private $originMethod; private $triggeringFile; + private $triggeringClass; /** @var string[] Absolute paths to vendor directories */ private static $vendors; @@ -60,9 +62,22 @@ class Deprecation */ public function __construct($message, array $trace, $file, $languageDeprecation = false) { - if (isset($trace[2]['function']) && 'trigger_deprecation' === $trace[2]['function']) { - $file = $trace[2]['file']; - array_splice($trace, 1, 1); + if (DebugClassLoader::class === ($trace[2]['class'] ?? '')) { + $this->triggeringClass = $trace[2]['args'][0]; + } + + switch ($trace[2]['function'] ?? '') { + case 'trigger_deprecation': + $file = $trace[2]['file']; + array_splice($trace, 1, 1); + break; + + case 'delegateTriggerToBackend': + if (DoctrineDeprecation::class === ($trace[2]['class'] ?? '')) { + $file = $trace[3]['file']; + array_splice($trace, 1, 2); + } + break; } $this->trace = $trace; @@ -157,6 +172,26 @@ private function lineShouldBeSkipped(array $line) return 'ReflectionMethod' === $class || 0 === strpos($class, 'PHPUnit\\'); } + /** + * @return bool + */ + public function originatesFromDebugClassLoader() + { + return isset($this->triggeringClass); + } + + /** + * @return string + */ + public function triggeringClass() + { + if (null === $this->triggeringClass) { + throw new \LogicException('Check with originatesFromDebugClassLoader() before calling this method.'); + } + + return $this->triggeringClass; + } + /** * @return bool */ diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php index b5f86d00a3b13..f660a8060f962 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php @@ -15,6 +15,7 @@ use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Configuration; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\DeprecationGroup; +use Symfony\Component\ErrorHandler\DebugClassLoader; class ConfigurationTest extends TestCase { @@ -248,8 +249,8 @@ public function testToleratesForIndividualGroups(string $deprecationsHelper, arr } } - public static function provideDataForToleratesForGroup() { - + public static function provideDataForToleratesForGroup(): iterable + { yield 'total threshold not reached' => ['max[total]=1', [ 'unsilenced' => 0, 'self' => 0, @@ -356,7 +357,7 @@ public function testBaselineGenerationEmptyFile() $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 1', $trace, ''))); $configuration->writeBaseline(); $this->assertEquals($filename, $configuration->getBaselineFile()); - $expected_baseline = [ + $expected = [ [ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest', 'message' => 'Test message 1', @@ -368,7 +369,7 @@ public function testBaselineGenerationEmptyFile() 'count' => 1, ], ]; - $this->assertEquals(json_encode($expected_baseline, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES), file_get_contents($filename)); + $this->assertEquals(json_encode($expected, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES), file_get_contents($filename)); } public function testBaselineGenerationNoFile() @@ -383,7 +384,7 @@ public function testBaselineGenerationNoFile() $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 1', $trace, ''))); $configuration->writeBaseline(); $this->assertEquals($filename, $configuration->getBaselineFile()); - $expected_baseline = [ + $expected = [ [ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest', 'message' => 'Test message 1', @@ -395,7 +396,7 @@ public function testBaselineGenerationNoFile() 'count' => 2, ], ]; - $this->assertEquals(json_encode($expected_baseline, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES), file_get_contents($filename)); + $this->assertEquals(json_encode($expected, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES), file_get_contents($filename)); } public function testExistingBaseline() @@ -447,7 +448,7 @@ public function testExistingBaselineAndGeneration() $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 3', $trace, ''))); $configuration->writeBaseline(); $this->assertEquals($filename, $configuration->getBaselineFile()); - $expected_baseline = [ + $expected = [ [ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest', 'message' => 'Test message 2', @@ -459,7 +460,44 @@ public function testExistingBaselineAndGeneration() 'count' => 1, ], ]; - $this->assertEquals(json_encode($expected_baseline, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES), file_get_contents($filename)); + $this->assertEquals(json_encode($expected, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES), file_get_contents($filename)); + } + + public function testBaselineGenerationWithDeprecationTriggeredByDebugClassLoader() + { + $filename = $this->createFile(); + $configuration = Configuration::fromUrlEncodedString('generateBaseline=true&baselineFile='.urlencode($filename)); + + $trace = debug_backtrace(); + $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Regular deprecation', $trace, ''))); + + $trace[2] = [ + 'class' => DebugClassLoader::class, + 'function' => 'testBaselineGenerationWithDeprecationTriggeredByDebugClassLoader', + 'args' => [self::class] + ]; + + $deprecation = new Deprecation('Deprecation by debug class loader', $trace, ''); + + $this->assertTrue($deprecation->originatesFromDebugClassLoader()); + + $this->assertTrue($configuration->isBaselineDeprecation($deprecation)); + + $configuration->writeBaseline(); + $this->assertEquals($filename, $configuration->getBaselineFile()); + $expected = [ + [ + 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest', + 'message' => 'Regular deprecation', + 'count' => 1, + ], + [ + 'location' => self::class, + 'message' => 'Deprecation by debug class loader', + 'count' => 1, + ], + ]; + $this->assertEquals(json_encode($expected, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES), file_get_contents($filename)); } public function testBaselineArgumentException() diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php index 5c7cf991b3f2f..4c17a806b4281 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php @@ -97,7 +97,7 @@ public function testItMutesOnlySpecificErrorMessagesWhenTheCallingCodeIsInPhpuni $this->assertSame($muted, $deprecation->isMuted()); } - public static function mutedProvider() + public static function mutedProvider(): iterable { yield 'not from phpunit, and not a whitelisted message' => [ false, @@ -147,7 +147,7 @@ public function testItTakesMutesDeprecationFromPhpUnitFiles() $this->assertTrue($deprecation->isMuted()); } - public static function providerGetTypeDetectsSelf() + public static function providerGetTypeDetectsSelf(): array { return [ 'not_from_vendors_file' => [Deprecation::TYPE_SELF, '', 'MyClass1', __FILE__], @@ -182,7 +182,7 @@ public function testGetTypeDetectsSelf(string $expectedType, string $message, st $this->assertSame($expectedType, $deprecation->getType()); } - public static function providerGetTypeUsesRightTrace() + public static function providerGetTypeUsesRightTrace(): array { $vendorDir = self::getVendorDir(); $fakeTrace = [ @@ -265,7 +265,7 @@ private static function removeDir($dir) rmdir($dir); } - public static function setupBeforeClass(): void + public static function setUpBeforeClass(): void { foreach (get_declared_classes() as $class) { if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php index f28933cf97357..b7967deffa998 100644 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php @@ -98,10 +98,8 @@ }; if (\PHP_VERSION_ID >= 80000) { - // PHP 8 requires PHPUnit 9.3+, PHP 8.1 requires PHPUnit 9.5+ - $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '9.5') ?: '9.5'; + $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '9.6') ?: '9.6'; } elseif (\PHP_VERSION_ID >= 70200) { - // PHPUnit 8 requires PHP 7.2+ $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '8.5') ?: '8.5'; } else { $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '7.5') ?: '7.5'; @@ -151,6 +149,11 @@ putenv('SYMFONY_DEPRECATIONS_HELPER=disabled'); } +if (!$getEnvVar('DOCTRINE_DEPRECATIONS')) { + putenv('DOCTRINE_DEPRECATIONS=trigger'); + $_SERVER['DOCTRINE_DEPRECATIONS'] = $_ENV['DOCTRINE_DEPRECATIONS'] = 'trigger'; +} + $COMPOSER = ($COMPOSER = getenv('COMPOSER_BINARY')) || file_exists($COMPOSER = $oldPwd.'/composer.phar') || ($COMPOSER = rtrim((string) ('\\' === \DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', shell_exec('where.exe composer.phar 2> NUL')) : shell_exec('which composer.phar 2> /dev/null')))) @@ -262,7 +265,7 @@ putenv("COMPOSER_ROOT_VERSION=$PHPUNIT_VERSION.99"); $q = '\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 80000 ? '"' : ''; // --no-suggest is not in the list to keep compat with composer 1.0, which is shipped with Ubuntu 16.04LTS - $exit = proc_close(proc_open("$q$COMPOSER install --no-dev --prefer-dist --no-progress $q", [], $p, getcwd())); + $exit = proc_close(proc_open("$q$COMPOSER update --no-dev --prefer-dist --no-progress $q", [], $p, getcwd())); putenv('COMPOSER_ROOT_VERSION'.(false !== $prevRoot ? '='.$prevRoot : '')); if ($prevCacheDir) { putenv("COMPOSER_CACHE_DIR=$prevCacheDir"); @@ -368,7 +371,7 @@ class_exists(\SymfonyExcludeListSimplePhpunit::class, false) && PHPUnit\Util\Bla } } -$cmd[0] = sprintf('%s %s --colors=always', $PHP, escapeshellarg("$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit")); +$cmd[0] = sprintf('%s %s --colors=%s', $PHP, escapeshellarg("$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit"), false === $getEnvVar('NO_COLOR') ? 'always' : 'never'); $cmd = str_replace('%', '%%', implode(' ', $cmd)).' %1$s'; if ('\\' === \DIRECTORY_SEPARATOR) { @@ -398,6 +401,9 @@ class_exists(\SymfonyExcludeListSimplePhpunit::class, false) && PHPUnit\Util\Bla } } + $lastOutput = null; + $lastOutputTime = null; + while ($runningProcs) { usleep(300000); $terminatedProcs = []; @@ -410,6 +416,26 @@ class_exists(\SymfonyExcludeListSimplePhpunit::class, false) && PHPUnit\Util\Bla } } + if (!$terminatedProcs && 1 === count($runningProcs)) { + $component = key($runningProcs); + + $output = file_get_contents("$component/phpunit.stdout"); + $output .= file_get_contents("$component/phpunit.stderr"); + + if ($lastOutput !== $output) { + $lastOutput = $output; + $lastOutputTime = microtime(true); + } elseif (microtime(true) - $lastOutputTime > 60) { + echo "\033[41mTimeout\033[0m $component\n\n"; + + if ('\\' === \DIRECTORY_SEPARATOR) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $procStatus['pid']), $output, $exitCode); + } else { + proc_terminate(current($runningProcs)); + } + } + } + foreach ($terminatedProcs as $component => $procStatus) { foreach (['out', 'err'] as $file) { $file = "$component/phpunit.std$file"; @@ -435,7 +461,7 @@ class SymfonyExcludeListSimplePhpunit { } } - array_splice($argv, 1, 0, ['--colors=always']); + array_splice($argv, 1, 0, ['--colors='.(false === $getEnvVar('NO_COLOR') ? 'always' : 'never')]); $_SERVER['argv'] = $argv; $_SERVER['argc'] = ++$argc; include "$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit"; diff --git a/src/Symfony/Bridge/PhpUnit/bootstrap.php b/src/Symfony/Bridge/PhpUnit/bootstrap.php index f2064368f41a3..2541cdd121a0c 100644 --- a/src/Symfony/Bridge/PhpUnit/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/bootstrap.php @@ -10,10 +10,11 @@ */ use Doctrine\Common\Annotations\AnnotationRegistry; +use Doctrine\Deprecations\Deprecation; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler; // Detect if we need to serialize deprecations to a file. -if ($file = getenv('SYMFONY_DEPRECATIONS_SERIALIZE')) { +if (in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $file = getenv('SYMFONY_DEPRECATIONS_SERIALIZE')) { DeprecationErrorHandler::collectDeprecations($file); return; @@ -24,9 +25,22 @@ return; } +if (isset($fileIdentifier)) { + unset($GLOBALS['__composer_autoload_files'][$fileIdentifier]); +} + // Enforce a consistent locale setlocale(\LC_ALL, 'C'); +if (class_exists(Deprecation::class)) { + Deprecation::withoutDeduplication(); + + if (\PHP_VERSION_ID < 80000) { + // Ignore deprecations about the annotation mapping driver when it's not possible to move to the attribute driver yet + Deprecation::ignoreDeprecations('https://github.com/doctrine/orm/issues/10098'); + } +} + if (!class_exists(AnnotationRegistry::class, false) && class_exists(AnnotationRegistry::class)) { if (method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { AnnotationRegistry::registerUniqueLoader('class_exists'); diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index fd610860f534f..c5ac19e7e3021 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -41,7 +41,7 @@ public function __construct(string $salt = '') $this->classGenerator = new BaseGeneratorStrategy(); } - public function isProxyCandidate(Definition $definition, bool &$asGhostObject = null, string $id = null): bool + public function isProxyCandidate(Definition $definition, ?bool &$asGhostObject = null, ?string $id = null): bool { $asGhostObject = false; @@ -61,23 +61,22 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ return <<createProxy('$proxyClass', static function () use (\$containerRef) { - return \\$proxyClass::staticProxyConstructor(static function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) use (\$containerRef) { - \$container = \$containerRef->get(); + $instantiation \$container->createProxy('$proxyClass', static fn () => \\$proxyClass::staticProxyConstructor( + static function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) use (\$container) { \$wrappedInstance = $factoryCode; \$proxy->setProxyInitializer(null); return true; - }); - }); + } + )); } EOF; } - public function getProxyCode(Definition $definition, string $id = null): string + public function getProxyCode(Definition $definition, ?string $id = null): string { $code = $this->classGenerator->generate($this->generateProxyClass($definition)); $code = preg_replace('/^(class [^ ]++ extends )([^\\\\])/', '$1\\\\$2', $code); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt index 84995384157b6..ad7a803cb6e8a 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt @@ -5,19 +5,16 @@ class LazyServiceProjectServiceContainer extends Container {%a protected static function getFooService($container, $lazyLoad = true) { - $containerRef = $container->ref; - if (true === $lazyLoad) { - return $container->services['foo'] = $container->createProxy('stdClass_%s', static function () use ($containerRef) { - return %S\stdClass_%s(static function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($containerRef) { - $container = $containerRef->get(); - $wrappedInstance = self::getFooService($containerRef->get(), false); + return $container->services['foo'] = $container->createProxy('stdClass_%s', static fn () => %S\stdClass_%s( + static function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { + $wrappedInstance = self::getFooService($container, false); $proxy->setProxyInitializer(null); return true; - }); - }); + } + )); } return new \stdClass(); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php index 12a7de3483761..c0399ae3340f3 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php @@ -8,19 +8,17 @@ public function getFooService($lazyLoad = true) { $container = $this; - $containerRef = \WeakReference::create($this); if (true === $lazyLoad) { - return $container->privates['foo'] = $container->createProxy('SunnyInterface_1eff735', static function () use ($containerRef) { - return \SunnyInterface_1eff735::staticProxyConstructor(static function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($containerRef) { - $container = $containerRef->get(); + return $container->privates['foo'] = $container->createProxy('SunnyInterface_1eff735', static fn () => \SunnyInterface_1eff735::staticProxyConstructor( + static function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { $wrappedInstance = $container->getFooService(false); $proxy->setProxyInitializer(null); return true; - }); - }); + } + )); } return new Symfony\Bridge\ProxyManager\Tests\LazyProxy\PhpDumper\DummyClass(); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index ea376464fc925..3652275c2bb65 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -130,7 +130,6 @@ public function testGetProxyFactoryCodeForInterface() public function getFooService(\$lazyLoad = true) { \$container = \$this; - \$containerRef = \\WeakReference::create(\$this); {$factory} return new {$class}(); } diff --git a/src/Symfony/Bridge/Twig/AppVariable.php b/src/Symfony/Bridge/Twig/AppVariable.php index 8bfaa0a22c601..e87775f4fb291 100644 --- a/src/Symfony/Bridge/Twig/AppVariable.php +++ b/src/Symfony/Bridge/Twig/AppVariable.php @@ -13,7 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -112,7 +112,7 @@ public function getRequest(): ?Request /** * Returns the current session. */ - public function getSession(): ?Session + public function getSession(): ?SessionInterface { if (!isset($this->requestStack)) { throw new \RuntimeException('The "app.session" variable is not available.'); @@ -161,7 +161,7 @@ public function getLocale(): string * * getFlashes('notice') returns a simple array with flash messages of that type * * getFlashes(['notice', 'error']) returns a nested array of type => messages. */ - public function getFlashes(string|array $types = null): array + public function getFlashes(string|array|null $types = null): array { try { if (null === $session = $this->getSession()) { @@ -171,6 +171,12 @@ public function getFlashes(string|array $types = null): array return []; } + // In 7.0 (when symfony/http-foundation: 6.4 is required) this can be updated to + // check if the session is an instance of FlashBagAwareSessionInterface + if (!method_exists($session, 'getFlashBag')) { + return []; + } + if (null === $types || '' === $types || [] === $types) { return $session->getFlashBag()->all(); } diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index 43e4d9c9f12c6..3a85ab429b777 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -48,7 +48,7 @@ class DebugCommand extends Command private ?FileLinkFormatter $fileLinkFormatter; - public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, FileLinkFormatter $fileLinkFormatter = null) + public function __construct(Environment $twig, ?string $projectDir = null, array $bundlesMetadata = [], ?string $twigDefaultPath = null, ?FileLinkFormatter $fileLinkFormatter = null) { parent::__construct(); @@ -217,7 +217,7 @@ private function displayPathsJson(SymfonyStyle $io, string $name): void $io->writeln(json_encode($data)); } - private function displayGeneralText(SymfonyStyle $io, string $filter = null): void + private function displayGeneralText(SymfonyStyle $io, ?string $filter = null): void { $decorated = $io->isDecorated(); $types = ['functions', 'filters', 'tests', 'globals']; @@ -279,7 +279,7 @@ private function displayGeneralJson(SymfonyStyle $io, ?string $filter): void $io->writeln($decorated ? OutputFormatter::escape($data) : $data); } - private function getLoaderPaths(string $name = null): array + private function getLoaderPaths(?string $name = null): array { $loaderPaths = []; foreach ($this->getFilesystemLoaders() as $loader) { diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index e059740a1375d..bc0a53ce997d8 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -224,7 +224,7 @@ private function displayJson(OutputInterface $output, array $filesInfo): int return min($errors, 1); } - private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, GithubActionReporter $githubReporter = null): void + private function renderException(SymfonyStyle $output, string $template, Error $exception, ?string $file = null, ?GithubActionReporter $githubReporter = null): void { $line = $exception->getTemplateLine(); diff --git a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php index e261a0505b048..34ae3730fc17f 100644 --- a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php +++ b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php @@ -32,13 +32,13 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf private ?Environment $twig; private array $computed; - public function __construct(Profile $profile, Environment $twig = null) + public function __construct(Profile $profile, ?Environment $twig = null) { $this->profile = $profile; $this->twig = $twig; } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { } diff --git a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php index 9086c22d5c3cb..50d8b44d2a742 100644 --- a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php +++ b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php @@ -32,7 +32,7 @@ class TwigErrorRenderer implements ErrorRendererInterface /** * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it */ - public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorRenderer = null, bool|callable $debug = false) + public function __construct(Environment $twig, ?HtmlErrorRenderer $fallbackErrorRenderer = null, bool|callable $debug = false) { $this->twig = $twig; $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); diff --git a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php index feb25ed5c2062..7a7aba0d69148 100644 --- a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php @@ -43,7 +43,7 @@ public function getFunctions(): array * If the package used to generate the path is an instance of * UrlPackage, you will always get a URL and not a path. */ - public function getAssetUrl(string $path, string $packageName = null): string + public function getAssetUrl(string $path, ?string $packageName = null): string { return $this->packages->getUrl($path, $packageName); } @@ -51,7 +51,7 @@ public function getAssetUrl(string $path, string $packageName = null): string /** * Returns the version of an asset. */ - public function getAssetVersion(string $path, string $packageName = null): string + public function getAssetVersion(string $path, ?string $packageName = null): string { return $this->packages->getVersion($path, $packageName); } diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index 748d60cb154b1..9c180094ffbef 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -36,8 +36,8 @@ public function __construct(string|FileLinkFormatter $fileLinkFormat, string $pr public function getFilters(): array { return [ - new TwigFilter('abbr_class', $this->abbrClass(...), ['is_safe' => ['html']]), - new TwigFilter('abbr_method', $this->abbrMethod(...), ['is_safe' => ['html']]), + new TwigFilter('abbr_class', $this->abbrClass(...), ['is_safe' => ['html'], 'pre_escape' => 'html']), + new TwigFilter('abbr_method', $this->abbrMethod(...), ['is_safe' => ['html'], 'pre_escape' => 'html']), new TwigFilter('format_args', $this->formatArgs(...), ['is_safe' => ['html']]), new TwigFilter('format_args_as_text', $this->formatArgsAsText(...)), new TwigFilter('file_excerpt', $this->fileExcerpt(...), ['is_safe' => ['html']]), @@ -79,22 +79,25 @@ public function formatArgs(array $args): string $result = []; foreach ($args as $key => $item) { if ('object' === $item[0]) { + $item[1] = htmlspecialchars($item[1], \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); $parts = explode('\\', $item[1]); $short = array_pop($parts); $formattedValue = sprintf('object(%s)', $item[1], $short); } elseif ('array' === $item[0]) { - $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : htmlspecialchars(var_export($item[1], true), \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset)); } elseif ('null' === $item[0]) { $formattedValue = 'null'; } elseif ('boolean' === $item[0]) { - $formattedValue = ''.strtolower(var_export($item[1], true)).''; + $formattedValue = ''.strtolower(htmlspecialchars(var_export($item[1], true), \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset)).''; } elseif ('resource' === $item[0]) { $formattedValue = 'resource'; + } elseif (preg_match('/[^\x07-\x0D\x1B\x20-\xFF]/', $item[1])) { + $formattedValue = 'binary string'; } else { $formattedValue = str_replace("\n", '', htmlspecialchars(var_export($item[1], true), \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset)); } - $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); + $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", htmlspecialchars($key, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset), $formattedValue); } return implode(', ', $result); @@ -117,11 +120,21 @@ public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?stri // highlight_file could throw warnings // see https://bugs.php.net/25725 $code = @highlight_file($file, true); - // remove main code/span tags - $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); - // split multiline spans - $code = preg_replace_callback('#]++)>((?:[^<]*+
)++[^<]*+)
#', fn ($m) => "".str_replace('
', "

", $m[2]).'', $code); - $content = explode('
', $code); + if (\PHP_VERSION_ID >= 80300) { + // remove main pre/code tags + $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); + // split multiline code tags + $code = preg_replace_callback('#]++)>((?:[^<]*+\\n)++[^<]*+)#', fn ($m) => "".str_replace("\n", "\n", $m[2]).'', $code); + // Convert spaces to html entities to preserve indentation when rendered + $code = str_replace(' ', ' ', $code); + $content = explode("\n", $code); + } else { + // remove main code/span tags + $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); + // split multiline spans + $code = preg_replace_callback('#]++)>((?:[^<]*+
)++[^<]*+)
#', fn ($m) => "".str_replace('
', "

", $m[2]).'', $code); + $content = explode('
', $code); + } $lines = []; if (0 > $srcContext) { @@ -141,16 +154,19 @@ public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?stri /** * Formats a file path. */ - public function formatFile(string $file, int $line, string $text = null): string + public function formatFile(string $file, int $line, ?string $text = null): string { $file = trim($file); if (null === $text) { - $text = $file; - if (null !== $rel = $this->getFileRelative($text)) { - $rel = explode('/', $rel, 2); - $text = sprintf('%s%s', $this->projectDir, $rel[0], '/'.($rel[1] ?? '')); + if (null !== $rel = $this->getFileRelative($file)) { + $rel = explode('/', htmlspecialchars($rel, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset), 2); + $text = sprintf('%s%s', htmlspecialchars($this->projectDir, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset), $rel[0], '/'.($rel[1] ?? '')); + } else { + $text = htmlspecialchars($file, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); } + } else { + $text = htmlspecialchars($text, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); } if (0 < $line) { diff --git a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php index c84e1e751a0f8..1bf2beeed5d1c 100644 --- a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php @@ -29,7 +29,7 @@ final class DumpExtension extends AbstractExtension private ClonerInterface $cloner; private ?HtmlDumper $dumper; - public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null) + public function __construct(ClonerInterface $cloner, ?HtmlDumper $dumper = null) { $this->cloner = $cloner; $this->dumper = $dumper; diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index 827145963a8e6..673f8199f9a2a 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -35,7 +35,7 @@ final class FormExtension extends AbstractExtension { private ?TranslatorInterface $translator; - public function __construct(TranslatorInterface $translator = null) + public function __construct(?TranslatorInterface $translator = null) { $this->translator = $translator; } diff --git a/src/Symfony/Bridge/Twig/Extension/HtmlSanitizerExtension.php b/src/Symfony/Bridge/Twig/Extension/HtmlSanitizerExtension.php index bec5ceb94e34e..9549c2a36e1bd 100644 --- a/src/Symfony/Bridge/Twig/Extension/HtmlSanitizerExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HtmlSanitizerExtension.php @@ -33,7 +33,7 @@ public function getFilters(): array ]; } - public function sanitize(string $html, string $sanitizer = null): string + public function sanitize(string $html, ?string $sanitizer = null): string { return $this->sanitizers->get($sanitizer ?? $this->defaultSanitizer)->sanitize($html); } diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php index b059bf1aae4c3..5456de33d2b6a 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php @@ -25,7 +25,7 @@ final class HttpKernelRuntime private FragmentHandler $handler; private ?FragmentUriGeneratorInterface $fragmentUriGenerator; - public function __construct(FragmentHandler $handler, FragmentUriGeneratorInterface $fragmentUriGenerator = null) + public function __construct(FragmentHandler $handler, ?FragmentUriGeneratorInterface $fragmentUriGenerator = null) { $this->handler = $handler; $this->fragmentUriGenerator = $fragmentUriGenerator; diff --git a/src/Symfony/Bridge/Twig/Extension/ImportMapExtension.php b/src/Symfony/Bridge/Twig/Extension/ImportMapExtension.php new file mode 100644 index 0000000000000..2156c74d5a0c9 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/ImportMapExtension.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * @author Kévin Dunglas + */ +final class ImportMapExtension extends AbstractExtension +{ + public function getFunctions(): array + { + return [ + new TwigFunction('importmap', [ImportMapRuntime::class, 'importmap'], ['is_safe' => ['html']]), + ]; + } +} diff --git a/src/Symfony/Bridge/Twig/Extension/ImportMapRuntime.php b/src/Symfony/Bridge/Twig/Extension/ImportMapRuntime.php new file mode 100644 index 0000000000000..aa68111b7b819 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/ImportMapRuntime.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; + +/** + * @author Kévin Dunglas + */ +class ImportMapRuntime +{ + public function __construct(private readonly ImportMapRenderer $importMapRenderer) + { + } + + public function importmap(?string $entryPoint = 'app', array $attributes = []): string + { + return $this->importMapRenderer->render($entryPoint, $attributes); + } +} diff --git a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php index abced287f999c..a576a6dd6b152 100644 --- a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php @@ -42,7 +42,7 @@ public function getFunctions(): array * * @param string|null $key The firewall key or null to use the current firewall key */ - public function getLogoutPath(string $key = null): string + public function getLogoutPath(?string $key = null): string { return $this->generator->getLogoutPath($key); } @@ -52,7 +52,7 @@ public function getLogoutPath(string $key = null): string * * @param string|null $key The firewall key or null to use the current firewall key */ - public function getLogoutUrl(string $key = null): string + public function getLogoutUrl(?string $key = null): string { return $this->generator->getLogoutUrl($key); } diff --git a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php index f63aa41cf2738..ab56f22a1efd6 100644 --- a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php @@ -28,7 +28,7 @@ final class ProfilerExtension extends BaseProfilerExtension */ private \SplObjectStorage $events; - public function __construct(Profile $profile, Stopwatch $stopwatch = null) + public function __construct(Profile $profile, ?Stopwatch $stopwatch = null) { parent::__construct($profile); diff --git a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php index 25d1cab2cfa9f..028325b4f6aae 100644 --- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php @@ -28,13 +28,13 @@ final class SecurityExtension extends AbstractExtension private ?AuthorizationCheckerInterface $securityChecker; private ?ImpersonateUrlGenerator $impersonateUrlGenerator; - public function __construct(AuthorizationCheckerInterface $securityChecker = null, ImpersonateUrlGenerator $impersonateUrlGenerator = null) + public function __construct(?AuthorizationCheckerInterface $securityChecker = null, ?ImpersonateUrlGenerator $impersonateUrlGenerator = null) { $this->securityChecker = $securityChecker; $this->impersonateUrlGenerator = $impersonateUrlGenerator; } - public function isGranted(mixed $role, mixed $object = null, string $field = null): bool + public function isGranted(mixed $role, mixed $object = null, ?string $field = null): bool { if (null === $this->securityChecker) { return false; @@ -51,7 +51,7 @@ public function isGranted(mixed $role, mixed $object = null, string $field = nul } } - public function getImpersonateExitUrl(string $exitTo = null): string + public function getImpersonateExitUrl(?string $exitTo = null): string { if (null === $this->impersonateUrlGenerator) { return ''; @@ -60,7 +60,7 @@ public function getImpersonateExitUrl(string $exitTo = null): string return $this->impersonateUrlGenerator->generateExitUrl($exitTo); } - public function getImpersonateExitPath(string $exitTo = null): string + public function getImpersonateExitPath(?string $exitTo = null): string { if (null === $this->impersonateUrlGenerator) { return ''; diff --git a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php index 972cd1acda44c..49df52cff7e58 100644 --- a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php @@ -26,7 +26,7 @@ final class StopwatchExtension extends AbstractExtension private ?Stopwatch $stopwatch; private bool $enabled; - public function __construct(Stopwatch $stopwatch = null, bool $enabled = true) + public function __construct(?Stopwatch $stopwatch = null, bool $enabled = true) { $this->stopwatch = $stopwatch; $this->enabled = $enabled; diff --git a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php index 67835e2b87e75..ba5758f3f1bfc 100644 --- a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php @@ -37,7 +37,7 @@ final class TranslationExtension extends AbstractExtension private ?TranslatorInterface $translator; private ?TranslationNodeVisitor $translationNodeVisitor; - public function __construct(TranslatorInterface $translator = null, TranslationNodeVisitor $translationNodeVisitor = null) + public function __construct(?TranslatorInterface $translator = null, ?TranslationNodeVisitor $translationNodeVisitor = null) { $this->translator = $translator; $this->translationNodeVisitor = $translationNodeVisitor; @@ -96,7 +96,7 @@ public function getTranslationNodeVisitor(): TranslationNodeVisitor /** * @param array|string $arguments Can be the locale as a string when $message is a TranslatableInterface */ - public function trans(string|\Stringable|TranslatableInterface|null $message, array|string $arguments = [], string $domain = null, string $locale = null, int $count = null): string + public function trans(string|\Stringable|TranslatableInterface|null $message, array|string $arguments = [], ?string $domain = null, ?string $locale = null, ?int $count = null): string { if ($message instanceof TranslatableInterface) { if ([] !== $arguments && !\is_string($arguments)) { @@ -125,7 +125,7 @@ public function trans(string|\Stringable|TranslatableInterface|null $message, ar return $this->getTranslator()->trans($message, $arguments, $domain, $locale); } - public function createTranslatable(string $message, array $parameters = [], string $domain = null): TranslatableMessage + public function createTranslatable(string $message, array $parameters = [], ?string $domain = null): TranslatableMessage { if (!class_exists(TranslatableMessage::class)) { throw new \LogicException(sprintf('You cannot use the "%s" as the Translation Component is not installed. Try running "composer require symfony/translation".', __CLASS__)); diff --git a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php index 661a063f98e61..b50130ccbc5a9 100644 --- a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @@ -48,7 +48,7 @@ public function getFunctions(): array /** * Returns true if the transition is enabled. */ - public function canTransition(object $subject, string $transitionName, string $name = null): bool + public function canTransition(object $subject, string $transitionName, ?string $name = null): bool { return $this->workflowRegistry->get($subject, $name)->can($subject, $transitionName); } @@ -58,12 +58,12 @@ public function canTransition(object $subject, string $transitionName, string $n * * @return Transition[] */ - public function getEnabledTransitions(object $subject, string $name = null): array + public function getEnabledTransitions(object $subject, ?string $name = null): array { return $this->workflowRegistry->get($subject, $name)->getEnabledTransitions($subject); } - public function getEnabledTransition(object $subject, string $transition, string $name = null): ?Transition + public function getEnabledTransition(object $subject, string $transition, ?string $name = null): ?Transition { return $this->workflowRegistry->get($subject, $name)->getEnabledTransition($subject, $transition); } @@ -71,7 +71,7 @@ public function getEnabledTransition(object $subject, string $transition, string /** * Returns true if the place is marked. */ - public function hasMarkedPlace(object $subject, string $placeName, string $name = null): bool + public function hasMarkedPlace(object $subject, string $placeName, ?string $name = null): bool { return $this->workflowRegistry->get($subject, $name)->getMarking($subject)->has($placeName); } @@ -81,7 +81,7 @@ public function hasMarkedPlace(object $subject, string $placeName, string $name * * @return string[]|int[] */ - public function getMarkedPlaces(object $subject, bool $placesNameOnly = true, string $name = null): array + public function getMarkedPlaces(object $subject, bool $placesNameOnly = true, ?string $name = null): array { $places = $this->workflowRegistry->get($subject, $name)->getMarking($subject)->getPlaces(); @@ -99,7 +99,7 @@ public function getMarkedPlaces(object $subject, bool $placesNameOnly = true, st * Use a string (the place name) to get place metadata * Use a Transition instance to get transition metadata */ - public function getMetadata(object $subject, string $key, string|Transition $metadataSubject = null, string $name = null): mixed + public function getMetadata(object $subject, string $key, string|Transition|null $metadataSubject = null, ?string $name = null): mixed { return $this ->workflowRegistry @@ -109,7 +109,7 @@ public function getMetadata(object $subject, string $key, string|Transition $met ; } - public function buildTransitionBlockerList(object $subject, string $transitionName, string $name = null): TransitionBlockerList + public function buildTransitionBlockerList(object $subject, string $transitionName, ?string $name = null): TransitionBlockerList { $workflow = $this->workflowRegistry->get($subject, $name); diff --git a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php index d418ee2f38634..a6e19daf8f6fe 100644 --- a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php +++ b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php @@ -29,7 +29,7 @@ final class BodyRenderer implements BodyRendererInterface private array $context; private HtmlToTextConverterInterface $converter; - public function __construct(Environment $twig, array $context = [], HtmlToTextConverterInterface $converter = null) + public function __construct(Environment $twig, array $context = [], ?HtmlToTextConverterInterface $converter = null) { $this->twig = $twig; $this->context = $context; diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index 5bd54e64463e5..6e33d33dfa89a 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -42,7 +42,7 @@ class NotificationEmail extends TemplatedEmail ]; private bool $rendered = false; - public function __construct(Headers $headers = null, AbstractPart $body = null) + public function __construct(?Headers $headers = null, ?AbstractPart $body = null) { $missingPackages = []; if (!class_exists(CssInlinerExtension::class)) { @@ -63,7 +63,7 @@ public function __construct(Headers $headers = null, AbstractPart $body = null) /** * Creates a NotificationEmail instance that is appropriate to send to normal (non-admin) users. */ - public static function asPublicEmail(Headers $headers = null, AbstractPart $body = null): self + public static function asPublicEmail(?Headers $headers = null, ?AbstractPart $body = null): self { $email = new static($headers, $body); $email->markAsPublic(); @@ -174,6 +174,26 @@ public function getHtmlTemplate(): ?string return '@email/'.$this->theme.'/notification/body.html.twig'; } + /** + * @return $this + */ + public function context(array $context): static + { + $parentContext = []; + + foreach ($context as $key => $value) { + if (\array_key_exists($key, $this->context)) { + $this->context[$key] = $value; + } else { + $parentContext[$key] = $value; + } + } + + parent::context($parentContext); + + return $this; + } + public function getContext(): array { return array_merge($this->context, parent::getContext()); diff --git a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php index c4e0003fac1a4..e72335a5ececd 100644 --- a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php @@ -43,7 +43,7 @@ public function toName(): string * @param string|null $contentType The media type (i.e. MIME type) of the image file (e.g. 'image/png'). * Some email clients require this to display embedded images. */ - public function image(string $image, string $contentType = null): string + public function image(string $image, ?string $contentType = null): string { $file = $this->twig->getLoader()->getSourceContext($image); $body = $file->getPath() ? new File($file->getPath()) : $file->getCode(); @@ -59,7 +59,7 @@ public function image(string $image, string $contentType = null): string * @param string|null $contentType The media type (i.e. MIME type) of the file (e.g. 'application/pdf'). * Some email clients require this to display attached files. */ - public function attach(string $file, string $name = null, string $contentType = null): void + public function attach(string $file, ?string $name = null, ?string $contentType = null): void { $file = $this->twig->getLoader()->getSourceContext($file); $body = $file->getPath() ? new File($file->getPath()) : $file->getCode(); diff --git a/src/Symfony/Bridge/Twig/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php index 8ce2bd8c4fa51..086ff7a68aaea 100644 --- a/src/Symfony/Bridge/Twig/Node/DumpNode.php +++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php @@ -21,7 +21,7 @@ final class DumpNode extends Node { private string $varPrefix; - public function __construct(string $varPrefix, ?Node $values, int $lineno, string $tag = null) + public function __construct(string $varPrefix, ?Node $values, int $lineno, ?string $tag = null) { $nodes = []; if (null !== $values) { diff --git a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php index e37311267bb17..2d4659ae7bb61 100644 --- a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php +++ b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php @@ -20,7 +20,7 @@ */ final class FormThemeNode extends Node { - public function __construct(Node $form, Node $resources, int $lineno, string $tag = null, bool $only = false) + public function __construct(Node $form, Node $resources, int $lineno, ?string $tag = null, bool $only = false) { parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno, $tag); } diff --git a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php index 9967639d16636..fa8653c238a1e 100644 --- a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php +++ b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Twig\Node; use Twig\Compiler; +use Twig\Extension\CoreExtension; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FunctionExpression; @@ -50,7 +51,7 @@ public function compile(Compiler $compiler): void $labelIsExpression = false; // Only insert the label into the array if it is not empty - if (!twig_test_empty($label->getAttribute('value'))) { + if (null !== $label->getAttribute('value') && false !== $label->getAttribute('value') && '' !== (string) $label->getAttribute('value')) { $originalVariables = $variables; $variables = new ArrayExpression([], $lineno); $labelKey = new ConstantExpression('label', $lineno); @@ -97,7 +98,12 @@ public function compile(Compiler $compiler): void // Check at runtime whether the label is empty. // If not, add it to the array at runtime. - $compiler->raw('(twig_test_empty($_label_ = '); + if (method_exists(CoreExtension::class, 'testEmpty')) { + $compiler->raw('(CoreExtension::testEmpty($_label_ = '); + } else { + $compiler->raw('(twig_test_empty($_label_ = '); + } + $compiler->subcompile($label); $compiler->raw(') ? [] : ["label" => $_label_])'); } diff --git a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php index cfa4d8a197f9b..796ee4dab8d16 100644 --- a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php +++ b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php @@ -22,7 +22,7 @@ */ final class StopwatchNode extends Node { - public function __construct(Node $name, Node $body, AssignNameExpression $var, int $lineno = 0, string $tag = null) + public function __construct(Node $name, Node $body, AssignNameExpression $var, int $lineno = 0, ?string $tag = null) { parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag); } diff --git a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php index df29f0a19931f..5a96d7420122f 100644 --- a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php +++ b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php @@ -20,7 +20,7 @@ */ final class TransDefaultDomainNode extends Node { - public function __construct(AbstractExpression $expr, int $lineno = 0, string $tag = null) + public function __construct(AbstractExpression $expr, int $lineno = 0, ?string $tag = null) { parent::__construct(['expr' => $expr], [], $lineno, $tag); } diff --git a/src/Symfony/Bridge/Twig/Node/TransNode.php b/src/Symfony/Bridge/Twig/Node/TransNode.php index 8a126ba569172..881104c8cc3fd 100644 --- a/src/Symfony/Bridge/Twig/Node/TransNode.php +++ b/src/Symfony/Bridge/Twig/Node/TransNode.php @@ -24,7 +24,7 @@ */ final class TransNode extends Node { - public function __construct(Node $body, Node $domain = null, AbstractExpression $count = null, AbstractExpression $vars = null, AbstractExpression $locale = null, int $lineno = 0, string $tag = null) + public function __construct(Node $body, ?Node $domain = null, ?AbstractExpression $count = null, ?AbstractExpression $vars = null, ?AbstractExpression $locale = null, int $lineno = 0, ?string $tag = null) { $nodes = ['body' => $body]; if (null !== $domain) { diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php index efa354d03feac..66904b09b5303 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php @@ -20,7 +20,7 @@ class Scope private array $data = []; private bool $left = false; - public function __construct(self $parent = null) + public function __construct(?self $parent = null) { $this->parent = $parent; } diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php index d5e95040d6bf2..3a7ea67cac90b 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php @@ -149,6 +149,22 @@ private function getReadDomainFromNode(Node $node): ?string return $node->getAttribute('value'); } + if ( + $node instanceof FunctionExpression + && 'constant' === $node->getAttribute('name') + ) { + $nodeArguments = $node->getNode('arguments'); + if ($nodeArguments->getIterator()->current() instanceof ConstantExpression) { + $constantName = $nodeArguments->getIterator()->current()->getAttribute('value'); + if (\defined($constantName)) { + $value = \constant($constantName); + if (\is_string($value)) { + return $value; + } + } + } + } + return self::UNDEFINED_DOMAIN; } diff --git a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php index 764eade4d9171..1cb98d9b5ce7d 100644 --- a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php +++ b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php @@ -44,7 +44,7 @@ public function testDebug($debugFlag) $this->assertEquals($debugFlag, $this->appVariable->getDebug()); } - public static function debugDataProvider() + public static function debugDataProvider(): array { return [ 'debug on' => [true], diff --git a/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php index 3d61d6eed7458..8a67932fe3b94 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php @@ -317,7 +317,7 @@ public static function provideCompletionSuggestions(): iterable yield 'option --format' => [['--format', ''], ['text', 'json']]; } - private function createCommandTester(array $paths = [], array $bundleMetadata = [], string $defaultPath = null, bool $useChainLoader = false, array $globals = []): CommandTester + private function createCommandTester(array $paths = [], array $bundleMetadata = [], ?string $defaultPath = null, bool $useChainLoader = false, array $globals = []): CommandTester { $projectDir = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'; $loader = new FilesystemLoader([], $projectDir); diff --git a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php index 75203f9c899e0..51bd80ab5f653 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php @@ -146,7 +146,7 @@ public function testComplete(array $input, array $expectedSuggestions) $this->assertSame($expectedSuggestions, $tester->complete($input)); } - public static function provideCompletionSuggestions() + public static function provideCompletionSuggestions(): iterable { yield 'option' => [['--format', ''], ['txt', 'json', 'github']]; } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php index af1a013a556fa..f7f6418a5de3c 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php @@ -2735,7 +2735,7 @@ public function testHelpWithTranslatableMessage() public function testHelpWithTranslatableInterface() { $message = new class() implements TranslatableInterface { - public function trans(TranslatorInterface $translator, string $locale = null): string + public function trans(TranslatorInterface $translator, ?string $locale = null): string { return $translator->trans('foo'); } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php index 38983cbd697f1..c7c859f067c7a 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php @@ -14,6 +14,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Extension\CodeExtension; use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Twig\Environment; +use Twig\Loader\ArrayLoader; class CodeExtensionTest extends TestCase { @@ -28,42 +30,136 @@ public function testFileRelative() $this->assertEquals('file.txt', $this->getExtension()->getFileRelative(\DIRECTORY_SEPARATOR.'project'.\DIRECTORY_SEPARATOR.'file.txt')); } - /** - * @dataProvider getClassNameProvider - */ - public function testGettingClassAbbreviation($class, $abbr) + public function testClassAbbreviationIntegration() { - $this->assertEquals($this->getExtension()->abbrClass($class), $abbr); + $data = [ + 'fqcn' => 'F\Q\N\Foo', + 'xss' => ''; + $importMapRenderer->expects($this->once()) + ->method('render') + ->with('application') + ->willReturn($expected); + $runtime = new ImportMapRuntime($importMapRenderer); + + $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); + $mockRuntimeLoader + ->method('load') + ->willReturnMap([ + [ImportMapRuntime::class, $runtime], + ]) + ; + $twig->addRuntimeLoader($mockRuntimeLoader); + + $this->assertSame($expected, $twig->render('template')); + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php index 12d6bc5e2a000..96f707cdfdf2c 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php @@ -206,7 +206,7 @@ public function testDefaultTranslationDomainWithNamedArguments() $this->assertEquals('foo (custom)foo (foo)foo (custom)foo (custom)foo (fr)foo (custom)foo (fr)', trim($template->render([]))); } - private function getTemplate($template, TranslatorInterface $translator = null): TemplateWrapper + private function getTemplate($template, ?TranslatorInterface $translator = null): TemplateWrapper { $translator ??= new Translator('en'); diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php index 6af152dad6c5e..c85b237bb38ed 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php @@ -131,7 +131,7 @@ public function testRenderedOnceUnserializableContext() $this->assertEquals('Text', $email->getTextBody()); } - private function prepareEmail(?string $text, ?string $html, array $context = [], HtmlToTextConverterInterface $converter = null): TemplatedEmail + private function prepareEmail(?string $text, ?string $html, array $context = [], ?HtmlToTextConverterInterface $converter = null): TemplatedEmail { $twig = new Environment(new ArrayLoader([ 'text' => $text, diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php index 6e48f2b4a5d61..979f2791d11b9 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php @@ -128,4 +128,54 @@ public function testPublicMailSubject() $headers = $email->getPreparedHeaders(); $this->assertSame('Foo', $headers->get('Subject')->getValue()); } + + public function testContext() + { + $email = new NotificationEmail(); + $email->context(['some' => 'context']); + + $this->assertSame([ + 'importance' => NotificationEmail::IMPORTANCE_LOW, + 'content' => '', + 'exception' => false, + 'action_text' => null, + 'action_url' => null, + 'markdown' => false, + 'raw' => false, + 'footer_text' => 'Notification email sent by Symfony', + 'some' => 'context', + ], $email->getContext()); + + $context = $email->getContext(); + $context['foo'] = 'bar'; + $email->context($context); + + $this->assertSame([ + 'importance' => NotificationEmail::IMPORTANCE_LOW, + 'content' => '', + 'exception' => false, + 'action_text' => null, + 'action_url' => null, + 'markdown' => false, + 'raw' => false, + 'footer_text' => 'Notification email sent by Symfony', + 'some' => 'context', + 'foo' => 'bar', + ], $email->getContext()); + + $email->action('Action Text', 'Action URL'); + + $this->assertSame([ + 'importance' => NotificationEmail::IMPORTANCE_LOW, + 'content' => '', + 'exception' => false, + 'action_text' => 'Action Text', + 'action_url' => 'Action URL', + 'markdown' => false, + 'raw' => false, + 'footer_text' => 'Notification email sent by Symfony', + 'some' => 'context', + 'foo' => 'bar', + ], $email->getContext()); + } } diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php index 019be16ff4bcf..48b7d27e1215a 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php @@ -95,8 +95,7 @@ public function testSymfonySerialize() } ] }, - "body": null, - "message": null + "body": null } EOF; diff --git a/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php index ab45b83fecd72..cf98191233057 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php @@ -47,9 +47,9 @@ public function testCompile() { $form = new NameExpression('form', 0); $resources = new ArrayExpression([ - new ConstantExpression(0, 0), - new ConstantExpression('tpl1', 0), new ConstantExpression(1, 0), + new ConstantExpression('tpl1', 0), + new ConstantExpression(0, 0), new ConstantExpression('tpl2', 0), ], 0); @@ -62,7 +62,7 @@ public function testCompile() $this->assertEquals( sprintf( - '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, [0 => "tpl1", 1 => "tpl2"], true);', + '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, [1 => "tpl1", 0 => "tpl2"], true);', $this->getVariableGetter('form') ), trim($compiler->compile($node)->getSource()) @@ -72,7 +72,7 @@ public function testCompile() $this->assertEquals( sprintf( - '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, [0 => "tpl1", 1 => "tpl2"], false);', + '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, [1 => "tpl1", 0 => "tpl2"], false);', $this->getVariableGetter('form') ), trim($compiler->compile($node)->getSource()) diff --git a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php index 42cb1762b050d..b259990e0b7ad 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode; use Twig\Compiler; use Twig\Environment; +use Twig\Extension\CoreExtension; use Twig\Loader\LoaderInterface; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConditionalExpression; @@ -226,8 +227,9 @@ public function testCompileLabelWithLabelThatEvaluatesToNull() // https://github.com/symfony/symfony/issues/5029 $this->assertEquals( sprintf( - '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', (twig_test_empty($_label_ = ((true) ? (null) : (null))) ? [] : ["label" => $_label_]))', - $this->getVariableGetter('form') + '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', (%s($_label_ = ((true) ? (null) : (null))) ? [] : ["label" => $_label_]))', + $this->getVariableGetter('form'), + method_exists(CoreExtension::class, 'testEmpty') ? 'CoreExtension::testEmpty' : 'twig_test_empty' ), trim($compiler->compile($node)->getSource()) ); @@ -263,8 +265,9 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() // https://github.com/symfony/symfony/issues/5029 $this->assertEquals( sprintf( - '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', ["foo" => "bar", "label" => "value in attributes"] + (twig_test_empty($_label_ = ((true) ? (null) : (null))) ? [] : ["label" => $_label_]))', - $this->getVariableGetter('form') + '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', ["foo" => "bar", "label" => "value in attributes"] + (%s($_label_ = ((true) ? (null) : (null))) ? [] : ["label" => $_label_]))', + $this->getVariableGetter('form'), + method_exists(CoreExtension::class, 'testEmpty') ? 'CoreExtension::testEmpty' : 'twig_test_empty' ), trim($compiler->compile($node)->getSource()) ); diff --git a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php index 7d7d65adbdbef..f9ae8c348e0fb 100644 --- a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php @@ -22,6 +22,8 @@ class TwigExtractorTest extends TestCase { + public const CUSTOM_DOMAIN = 'domain'; + /** * @dataProvider getExtractData */ @@ -76,6 +78,11 @@ public static function getExtractData() // make sure this works with twig's named arguments ['{{ "new key" | trans(domain="domain") }}', ['new key' => 'domain']], + // make sure this works with const domain + ['{{ "new key" | trans({}, constant(\'Symfony\\\\Bridge\\\\Twig\\\\Tests\\\\Translation\\\\TwigExtractorTest::CUSTOM_DOMAIN\')) }}', ['new key' => self::CUSTOM_DOMAIN]], + ['{% trans from constant(\'Symfony\\\\Bridge\\\\Twig\\\\Tests\\\\Translation\\\\TwigExtractorTest::CUSTOM_DOMAIN\') %}new key{% endtrans %}', ['new key' => self::CUSTOM_DOMAIN]], + ['{{ t("new key", {}, constant(\'Symfony\\\\Bridge\\\\Twig\\\\Tests\\\\Translation\\\\TwigExtractorTest::CUSTOM_DOMAIN\')) | trans() }}', ['new key' => self::CUSTOM_DOMAIN]], + // concat translations ['{{ ("new" ~ " key") | trans() }}', ['new key' => 'messages']], ['{{ ("another " ~ "new " ~ "key") | trans() }}', ['another new key' => 'messages']], diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 8cf462fea5938..87ff81eb8bf5f 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -26,6 +26,7 @@ "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", "symfony/dependency-injection": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", "symfony/form": "^6.3", @@ -43,7 +44,7 @@ "symfony/security-core": "^5.4|^6.0", "symfony/security-csrf": "^5.4|^6.0", "symfony/security-http": "^5.4|^6.0", - "symfony/serializer": "^6.2", + "symfony/serializer": "~6.3.12|^6.4.3", "symfony/stopwatch": "^5.4|^6.0", "symfony/console": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", diff --git a/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php b/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php index f0cffcd238ece..7a82e048306a0 100644 --- a/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php +++ b/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php @@ -31,7 +31,7 @@ class ServerDumpPlaceholderCommand extends Command { private $replacedCommand; - public function __construct(DumpServer $server = null, array $descriptors = []) + public function __construct(?DumpServer $server = null, array $descriptors = []) { $this->replacedCommand = new ServerDumpCommand((new \ReflectionClass(DumpServer::class))->newInstanceWithoutConstructor(), $descriptors); diff --git a/src/Symfony/Bundle/DebugBundle/DebugBundle.php b/src/Symfony/Bundle/DebugBundle/DebugBundle.php index a24321e22910d..0af84fb5a08c9 100644 --- a/src/Symfony/Bundle/DebugBundle/DebugBundle.php +++ b/src/Symfony/Bundle/DebugBundle/DebugBundle.php @@ -35,14 +35,19 @@ public function boot() // The dump data collector is used by default, so dump output is sent to // the WDT. In a CLI context, if dump is used too soon, the data collector // will buffer it, and release it at the end of the script. - VarDumper::setHandler(function ($var) use ($container) { + VarDumper::setHandler(function ($var, ?string $label = null) use ($container) { $dumper = $container->get('data_collector.dump'); $cloner = $container->get('var_dumper.cloner'); - $handler = function ($var) use ($dumper, $cloner) { - $dumper->dump($cloner->cloneVar($var)); + $handler = function ($var, ?string $label = null) use ($dumper, $cloner) { + $var = $cloner->cloneVar($var); + if (null !== $label) { + $var = $var->withContext(['label' => $label]); + } + + $dumper->dump($var); }; VarDumper::setHandler($handler); - $handler($var); + $handler($var, $label); }); } } diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php index eadeafba55832..726b181beac48 100644 --- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php @@ -14,6 +14,7 @@ use Symfony\Bridge\Monolog\Command\ServerLogCommand; use Symfony\Bundle\DebugBundle\Command\ServerDumpPlaceholderCommand; use Symfony\Component\Config\FileLocator; +use Symfony\Component\Console\Command\Command; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; @@ -92,7 +93,7 @@ public function load(array $configs, ContainerBuilder $container) ; } - if (!class_exists(ServerLogCommand::class)) { + if (!class_exists(Command::class) || !class_exists(ServerLogCommand::class)) { $container->removeDefinition('monolog.command.server_log'); } } diff --git a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig index 04b694955051e..e98e524748f48 100644 --- a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig +++ b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig @@ -11,6 +11,9 @@ {% for dump in collector.getDumps('html') %}
+ {% if dump.label is defined and '' != dump.label %} + {{ dump.label }} in + {% endif %} {% if dump.file %} {% set link = dump.file|file_link(dump.line) %} {% if link %} @@ -45,7 +48,12 @@ {% for dump in collector.getDumps('html') %}
-