init
This commit is contained in:
245
backend/vendor/symfony/doctrine-bridge/ArgumentResolver/EntityValueResolver.php
vendored
Normal file
245
backend/vendor/symfony/doctrine-bridge/ArgumentResolver/EntityValueResolver.php
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\ArgumentResolver;
|
||||
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
|
||||
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||
use Symfony\Component\HttpKernel\Exception\NearMissValueResolverException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Yields the entity matching the criteria provided in the route.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
final class EntityValueResolver implements ValueResolverInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ManagerRegistry $registry,
|
||||
private ?ExpressionLanguage $expressionLanguage = null,
|
||||
private MapEntity $defaults = new MapEntity(),
|
||||
/** @var array<class-string, class-string> */
|
||||
private readonly array $typeAliases = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function resolve(Request $request, ArgumentMetadata $argument): array
|
||||
{
|
||||
if (\is_object($request->attributes->get($argument->getName()))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$options = $argument->getAttributes(MapEntity::class, ArgumentMetadata::IS_INSTANCEOF);
|
||||
$options = ($options[0] ?? $this->defaults)->withDefaults($this->defaults, $argument->getType());
|
||||
|
||||
if (!$options->class || $options->disabled) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$options->class = $this->typeAliases[$options->class] ?? $options->class;
|
||||
|
||||
if (!$manager = $this->getManager($options->objectManager, $options->class)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$message = '';
|
||||
if (null !== $options->expr) {
|
||||
if (null === $object = $this->findViaExpression($manager, $request, $options)) {
|
||||
$message = \sprintf(' The expression "%s" returned null.', $options->expr);
|
||||
}
|
||||
// find by identifier?
|
||||
} elseif (false === $object = $this->find($manager, $request, $options, $argument)) {
|
||||
// find by criteria
|
||||
if (!$criteria = $this->getCriteria($request, $options, $manager, $argument)) {
|
||||
if (!class_exists(NearMissValueResolverException::class)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
throw new NearMissValueResolverException(\sprintf('Cannot find mapping for "%s": declare one using either the #[MapEntity] attribute or mapped route parameters.', $options->class));
|
||||
}
|
||||
try {
|
||||
$object = $manager->getRepository($options->class)->findOneBy($criteria);
|
||||
} catch (NoResultException|ConversionException) {
|
||||
$object = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $object && !$argument->isNullable()) {
|
||||
throw new NotFoundHttpException($options->message ?? (\sprintf('"%s" object not found by "%s".', $options->class, self::class).$message));
|
||||
}
|
||||
|
||||
return [$object];
|
||||
}
|
||||
|
||||
private function getManager(?string $name, string $class): ?ObjectManager
|
||||
{
|
||||
if (null === $name) {
|
||||
return $this->registry->getManagerForClass($class);
|
||||
}
|
||||
|
||||
try {
|
||||
$manager = $this->registry->getManager($name);
|
||||
} catch (\InvalidArgumentException) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $manager->getMetadataFactory()->isTransient($class) ? null : $manager;
|
||||
}
|
||||
|
||||
private function find(ObjectManager $manager, Request $request, MapEntity $options, ArgumentMetadata $argument): false|object|null
|
||||
{
|
||||
if ($options->mapping || $options->exclude) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$id = $this->getIdentifier($request, $options, $argument);
|
||||
if (false === $id || null === $id) {
|
||||
return $id;
|
||||
}
|
||||
if (\is_array($id) && \in_array(null, $id, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($options->evictCache && $manager instanceof EntityManagerInterface) {
|
||||
$cacheProvider = $manager->getCache();
|
||||
if ($cacheProvider && $cacheProvider->containsEntity($options->class, $id)) {
|
||||
$cacheProvider->evictEntity($options->class, $id);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return $manager->getRepository($options->class)->find($id);
|
||||
} catch (NoResultException|ConversionException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function getIdentifier(Request $request, MapEntity $options, ArgumentMetadata $argument): mixed
|
||||
{
|
||||
if (\is_array($options->id)) {
|
||||
$id = [];
|
||||
foreach ($options->id as $field) {
|
||||
// Convert "%s_uuid" to "foobar_uuid"
|
||||
if (str_contains($field, '%s')) {
|
||||
$field = \sprintf($field, $argument->getName());
|
||||
}
|
||||
|
||||
$id[$field] = $request->attributes->get($field);
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
if ($options->id) {
|
||||
return $request->attributes->get($options->id) ?? ($options->stripNull ? false : null);
|
||||
}
|
||||
|
||||
$name = $argument->getName();
|
||||
|
||||
if ($request->attributes->has($name)) {
|
||||
if (\is_array($id = $request->attributes->get($name))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($request->attributes->get('_route_mapping') ?? [] as $parameter => $attribute) {
|
||||
if ($name === $attribute) {
|
||||
$options->mapping = [$name => $parameter];
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $id ?? ($options->stripNull ? false : null);
|
||||
}
|
||||
|
||||
if ($request->attributes->has('id')) {
|
||||
return $request->attributes->get('id') ?? ($options->stripNull ? false : null);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getCriteria(Request $request, MapEntity $options, ObjectManager $manager, ArgumentMetadata $argument): array
|
||||
{
|
||||
if (!($mapping = $options->mapping) && \is_array($criteria = $request->attributes->get($argument->getName()))) {
|
||||
foreach ($options->exclude as $exclude) {
|
||||
unset($criteria[$exclude]);
|
||||
}
|
||||
|
||||
if ($options->stripNull) {
|
||||
$criteria = array_filter($criteria, static fn ($value) => null !== $value);
|
||||
}
|
||||
|
||||
return $criteria;
|
||||
} elseif (null === $mapping) {
|
||||
trigger_deprecation('symfony/doctrine-bridge', '7.1', 'Relying on auto-mapping for Doctrine entities is deprecated for argument $%s of "%s": declare the mapping using either the #[MapEntity] attribute or mapped route parameters.', $argument->getName(), method_exists($argument, 'getControllerName') ? $argument->getControllerName() : 'n/a');
|
||||
$mapping = $request->attributes->keys();
|
||||
}
|
||||
|
||||
if ($mapping && array_is_list($mapping)) {
|
||||
$mapping = array_combine($mapping, $mapping);
|
||||
}
|
||||
|
||||
foreach ($options->exclude as $exclude) {
|
||||
unset($mapping[$exclude]);
|
||||
}
|
||||
|
||||
if (!$mapping) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$criteria = [];
|
||||
$metadata = null === $options->mapping ? $manager->getClassMetadata($options->class) : false;
|
||||
|
||||
foreach ($mapping as $attribute => $field) {
|
||||
if ($metadata && !$metadata->hasField($field) && (!$metadata->hasAssociation($field) || !$metadata->isSingleValuedAssociation($field))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$criteria[$field] = $request->attributes->get($attribute);
|
||||
}
|
||||
|
||||
if ($options->stripNull) {
|
||||
$criteria = array_filter($criteria, static fn ($value) => null !== $value);
|
||||
}
|
||||
|
||||
return $criteria;
|
||||
}
|
||||
|
||||
private function findViaExpression(ObjectManager $manager, Request $request, MapEntity $options): object|iterable|null
|
||||
{
|
||||
if (!$this->expressionLanguage) {
|
||||
throw new \LogicException(\sprintf('You cannot use the "%s" if the ExpressionLanguage component is not available. Try running "composer require symfony/expression-language".', __CLASS__));
|
||||
}
|
||||
|
||||
$repository = $manager->getRepository($options->class);
|
||||
$variables = array_merge($request->attributes->all(), [
|
||||
'repository' => $repository,
|
||||
'request' => $request,
|
||||
]);
|
||||
|
||||
try {
|
||||
return $this->expressionLanguage->evaluate($options->expr, $variables);
|
||||
} catch (NoResultException|ConversionException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
84
backend/vendor/symfony/doctrine-bridge/Attribute/MapEntity.php
vendored
Normal file
84
backend/vendor/symfony/doctrine-bridge/Attribute/MapEntity.php
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Attribute;
|
||||
|
||||
use Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver;
|
||||
use Symfony\Component\HttpKernel\Attribute\ValueResolver;
|
||||
|
||||
/**
|
||||
* Indicates that a controller argument should receive an Entity.
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_PARAMETER)]
|
||||
class MapEntity extends ValueResolver
|
||||
{
|
||||
/**
|
||||
* @param class-string|null $class The entity class
|
||||
* @param string|null $objectManager Specify the object manager used to retrieve the entity
|
||||
* @param string|null $expr An expression to fetch the entity using the {@see https://symfony.com/doc/current/components/expression_language.html ExpressionLanguage} syntax.
|
||||
* Any request attribute are available as a variable, and your entity repository in the 'repository' variable.
|
||||
* @param array<string, string>|null $mapping Configures the properties and values to use with the findOneBy() method
|
||||
* The key is the route placeholder name and the value is the Doctrine property name
|
||||
* @param string[]|null $exclude Configures the properties that should be used in the findOneBy() method by excluding
|
||||
* one or more properties so that not all are used
|
||||
* @param bool|null $stripNull Whether to prevent null values from being used as parameters in the query (defaults to false)
|
||||
* @param string[]|string|null $id If an id option is configured and matches a route parameter, then the resolver will find by the primary key
|
||||
* @param bool|null $evictCache If true, forces Doctrine to always fetch the entity from the database instead of cache (defaults to false)
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $class = null,
|
||||
public ?string $objectManager = null,
|
||||
public ?string $expr = null,
|
||||
public ?array $mapping = null,
|
||||
public ?array $exclude = null,
|
||||
public ?bool $stripNull = null,
|
||||
public array|string|null $id = null,
|
||||
public ?bool $evictCache = null,
|
||||
bool $disabled = false,
|
||||
string $resolver = EntityValueResolver::class,
|
||||
public ?string $message = null,
|
||||
) {
|
||||
parent::__construct($resolver, $disabled);
|
||||
$this->selfValidate();
|
||||
}
|
||||
|
||||
public function withDefaults(self $defaults, ?string $class): static
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->class ??= class_exists($class ?? '') || interface_exists($class ?? '', false) ? $class : null;
|
||||
$clone->objectManager ??= $defaults->objectManager;
|
||||
$clone->expr ??= $defaults->expr;
|
||||
$clone->mapping ??= $defaults->mapping;
|
||||
$clone->exclude ??= $defaults->exclude ?? [];
|
||||
$clone->stripNull ??= $defaults->stripNull ?? false;
|
||||
$clone->id ??= $defaults->id;
|
||||
$clone->evictCache ??= $defaults->evictCache ?? false;
|
||||
$clone->message ??= $defaults->message;
|
||||
|
||||
$clone->selfValidate();
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
private function selfValidate(): void
|
||||
{
|
||||
if (!$this->id) {
|
||||
return;
|
||||
}
|
||||
if ($this->mapping) {
|
||||
throw new \LogicException('The "id" and "mapping" options cannot be used together on #[MapEntity] attributes.');
|
||||
}
|
||||
if ($this->exclude) {
|
||||
throw new \LogicException('The "id" and "exclude" options cannot be used together on #[MapEntity] attributes.');
|
||||
}
|
||||
$this->mapping = [];
|
||||
}
|
||||
}
|
||||
215
backend/vendor/symfony/doctrine-bridge/CHANGELOG.md
vendored
Normal file
215
backend/vendor/symfony/doctrine-bridge/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
7.4
|
||||
---
|
||||
|
||||
* Deprecate `UniqueEntity::getRequiredOptions()` and `UniqueEntity::getDefaultOption()`
|
||||
* Use a single table named `schema_subscriber_check_` in schema listeners to detect same database connections
|
||||
* Add support for `Symfony\Component\Clock\DatePoint` as `DayPointType` and `TimePointType` Doctrine type
|
||||
* Deprecate the `AbstractDoctrineExtension` class; its code is incorporated into the extension classes of Doctrine bundles
|
||||
|
||||
7.3
|
||||
---
|
||||
|
||||
* Reset the manager registry using native lazy objects when applicable
|
||||
* Deprecate the `DoctrineExtractor::getTypes()` method, use `DoctrineExtractor::getType()` instead
|
||||
* Add support for `Symfony\Component\Clock\DatePoint` as `DatePointType` Doctrine type
|
||||
* Improve exception message when `EntityValueResolver` gets no mapping information
|
||||
* Add type aliases support to `EntityValueResolver`
|
||||
|
||||
7.2
|
||||
---
|
||||
|
||||
* Accept `ReadableCollection` in `CollectionToArrayTransformer`
|
||||
|
||||
7.1
|
||||
---
|
||||
|
||||
* Allow `EntityValueResolver` to return a list of entities
|
||||
* Add support for auto-closing idle connections
|
||||
* Allow validating every class against `UniqueEntity` constraint
|
||||
* Deprecate auto-mapping of entities in favor of mapped route parameters
|
||||
|
||||
7.0
|
||||
---
|
||||
|
||||
* Remove `DoctrineDbalCacheAdapterSchemaSubscriber`, use `DoctrineDbalCacheAdapterSchemaListener` instead
|
||||
* Remove `MessengerTransportDoctrineSchemaSubscriber`, use `MessengerTransportDoctrineSchemaListener` instead
|
||||
* Remove `RememberMeTokenProviderDoctrineSchemaSubscriber`, use `RememberMeTokenProviderDoctrineSchemaListener` instead
|
||||
* Remove `DbalLogger`, use a middleware instead
|
||||
* Remove `DoctrineDataCollector::addLogger()`, use a `DebugDataHolder` instead
|
||||
* Remove `ContainerAwareLoader`, use dependency injection in your fixtures instead
|
||||
* `ContainerAwareEventManager::getListeners()` must be called with an event name
|
||||
* DoctrineBridge now requires `doctrine/event-manager:^2`
|
||||
* Add parameter `$isSameDatabase` to `DoctrineTokenProvider::configureSchema()`
|
||||
|
||||
6.4
|
||||
---
|
||||
|
||||
* [BC BREAK] Add argument `$buildDir` to `ProxyCacheWarmer::warmUp()`
|
||||
* [BC BREAK] Add return type-hints to `EntityFactory`
|
||||
* Deprecate `DbalLogger`, use a middleware instead
|
||||
* Deprecate not constructing `DoctrineDataCollector` with an instance of `DebugDataHolder`
|
||||
* Deprecate `DoctrineDataCollector::addLogger()`, use a `DebugDataHolder` instead
|
||||
* Deprecate `ContainerAwareLoader`, use dependency injection in your fixtures instead
|
||||
* Always pass the `Request` object to `EntityValueResolver`'s expression
|
||||
* [BC BREAK] Change argument `$lastUsed` of `DoctrineTokenProvider::updateToken()` to accept `DateTimeInterface`
|
||||
|
||||
6.3
|
||||
---
|
||||
|
||||
* Deprecate passing Doctrine subscribers to `ContainerAwareEventManager` class, use listeners instead
|
||||
* Add `AbstractSchemaListener`, `LockStoreSchemaListener` and `PdoSessionHandlerSchemaListener`
|
||||
* 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
|
||||
---
|
||||
|
||||
* Add `#[MapEntity]` with its corresponding `EntityValueResolver`
|
||||
* Add `NAME` constant to `UlidType` and `UuidType`
|
||||
|
||||
6.0
|
||||
---
|
||||
|
||||
* Remove `DoctrineTestHelper` and `TestRepositoryFactory`
|
||||
|
||||
5.4
|
||||
---
|
||||
|
||||
* Add `DoctrineOpenTransactionLoggerMiddleware` to log when a transaction has been left open
|
||||
* Deprecate `PdoCacheAdapterDoctrineSchemaSubscriber` and add `DoctrineDbalCacheAdapterSchemaSubscriber` instead
|
||||
* `UniqueEntity` constraint retrieves a maximum of two entities if the default repository method is used.
|
||||
* Add support for the newer bundle structure to `AbstractDoctrineExtension::loadMappingInformation()`
|
||||
* Add argument `$bundleDir` to `AbstractDoctrineExtension::getMappingDriverBundleConfigDefaults()`
|
||||
* Add argument `$bundleDir` to `AbstractDoctrineExtension::getMappingResourceConfigDirectory()`
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* Deprecate `UserLoaderInterface::loadUserByUsername()` in favor of `UserLoaderInterface::loadUserByIdentifier()
|
||||
* Deprecate `DoctrineTestHelper` and `TestRepositoryFactory`
|
||||
* [BC BREAK] Remove `UuidV*Generator` classes
|
||||
* Add `UuidGenerator`
|
||||
* Add support for the new security-core `TokenVerifierInterface` in `DoctrineTokenProvider`, fixing parallel requests handling in remember-me
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* added support for symfony/uid as `UlidType` and `UuidType` as Doctrine types
|
||||
* added `UlidGenerator`, `UuidV1Generator`, `UuidV4Generator` and `UuidV6Generator`
|
||||
|
||||
5.0.0
|
||||
-----
|
||||
|
||||
* the `getMetadataDriverClass()` method is abstract and must be implemented by class extending `AbstractDoctrineExtension`
|
||||
* passing an `IdReader` to the `DoctrineChoiceLoader` when the query cannot be optimized with single id field, throws an exception; pass `null` instead
|
||||
* not explicitly passing an instance of `IdReader` to `DoctrineChoiceLoader` when it can optimize single id field, will not apply any optimization
|
||||
* `DoctrineExtractor` now requires an `EntityManagerInterface` on instantiation
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] using null as `$classValidatorRegexp` value in `DoctrineLoader::__construct` will not enable auto-mapping for all classes anymore, use `'{.*}'` instead.
|
||||
* added `DoctrineClearEntityManagerWorkerSubscriber`
|
||||
* deprecated `RegistryInterface`, use `Doctrine\Persistence\ManagerRegistry`
|
||||
* added support for invokable event listeners
|
||||
* added `getMetadataDriverClass` method to deprecate class parameters in service configuration files
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* changed guessing of DECIMAL to set the `input` option of `NumberType` to string
|
||||
* deprecated not passing an `IdReader` to the `DoctrineChoiceLoader` when query can be optimized with a single id field
|
||||
* deprecated passing an `IdReader` to the `DoctrineChoiceLoader` when entities have a composite id
|
||||
* added two Messenger middleware: `DoctrinePingConnectionMiddleware` and `DoctrineCloseConnectionMiddleware`
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* deprecated injecting `ClassMetadataFactory` in `DoctrineExtractor`,
|
||||
an instance of `EntityManagerInterface` should be injected instead
|
||||
* added support for `simple_array` type
|
||||
* the `DoctrineTransactionMiddlewareFactory` class has been removed
|
||||
|
||||
4.1.0
|
||||
-----
|
||||
|
||||
* added support for datetime immutable types in form type guesser
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
||||
* the first constructor argument of the `DoctrineChoiceLoader` class must be
|
||||
an `ObjectManager` implementation
|
||||
* removed the `MergeDoctrineCollectionListener::onBind()` method
|
||||
* trying to reset a non-lazy manager service using the `ManagerRegistry::resetService()`
|
||||
method throws an exception
|
||||
* removed the `DoctrineParserCache` class
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* added support for doctrine/dbal v2.6 types
|
||||
* added cause of UniqueEntity constraint violation
|
||||
* deprecated `DbalSessionHandler` and `DbalSessionHandlerSchema` in favor of
|
||||
`Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler`
|
||||
|
||||
3.1.0
|
||||
-----
|
||||
|
||||
* added "{{ value }}" message placeholder to UniqueEntityValidator
|
||||
* deprecated `MergeDoctrineCollectionListener::onBind` in favor of
|
||||
`MergeDoctrineCollectionListener::onSubmit`
|
||||
* deprecated passing `ChoiceListFactoryInterface` as first argument of
|
||||
`DoctrineChoiceLoader`'s constructor
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
* removed `EntityChoiceList`
|
||||
* removed `$manager` (2nd) and `$class` (3th) arguments of `ORMQueryBuilderLoader`
|
||||
* removed passing a query builder closure to `ORMQueryBuilderLoader`
|
||||
* removed `loader` and `property` options of the `DoctrineType`
|
||||
|
||||
2.8.0
|
||||
-----
|
||||
|
||||
* deprecated using the entity provider with a Doctrine repository implementing UserProviderInterface
|
||||
* added UserLoaderInterface for loading users through Doctrine.
|
||||
|
||||
2.7.0
|
||||
-----
|
||||
|
||||
* added DoctrineChoiceLoader
|
||||
* deprecated EntityChoiceList
|
||||
* deprecated passing a query builder closure to ORMQueryBuilderLoader
|
||||
* deprecated $manager and $em arguments of ORMQueryBuilderLoader
|
||||
* added optional arguments $propertyAccessor and $choiceListFactory to DoctrineOrmExtension constructor
|
||||
* deprecated "loader" and "property" options of DoctrineType
|
||||
|
||||
2.4.0
|
||||
-----
|
||||
|
||||
* deprecated DoctrineOrmTestCase class
|
||||
|
||||
2.2.0
|
||||
-----
|
||||
|
||||
* added an optional PropertyAccessorInterface parameter to DoctrineType,
|
||||
EntityType and EntityChoiceList
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
* added a default implementation of the ManagerRegistry
|
||||
* added a session storage for Doctrine DBAL
|
||||
* DoctrineOrmTypeGuesser now guesses "collection" for array Doctrine type
|
||||
* DoctrineType now caches its choice lists in order to improve performance
|
||||
* DoctrineType now uses ManagerRegistry::getManagerForClass() if the option "em" is not set
|
||||
* UniqueEntity validation constraint now accepts a "repositoryMethod" option that will be used to check for uniqueness instead of the default "findBy"
|
||||
* [BC BREAK] the DbalLogger::log() visibility has been changed from public to
|
||||
protected
|
||||
73
backend/vendor/symfony/doctrine-bridge/CacheWarmer/ProxyCacheWarmer.php
vendored
Normal file
73
backend/vendor/symfony/doctrine-bridge/CacheWarmer/ProxyCacheWarmer.php
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\CacheWarmer;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
|
||||
|
||||
/**
|
||||
* The proxy generator cache warmer generates all entity proxies.
|
||||
*
|
||||
* In the process of generating proxies the cache for all the metadata is primed also,
|
||||
* since this information is necessary to build the proxies in the first place.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*
|
||||
* @final since Symfony 7.1
|
||||
*/
|
||||
class ProxyCacheWarmer implements CacheWarmerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ManagerRegistry $registry,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This cache warmer is not optional, without proxies fatal error occurs!
|
||||
*/
|
||||
public function isOptional(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function warmUp(string $cacheDir, ?string $buildDir = null): array
|
||||
{
|
||||
$files = [];
|
||||
foreach ($this->registry->getManagers() as $em) {
|
||||
// we need the directory no matter the proxy cache generation strategy
|
||||
if (!is_dir($proxyCacheDir = $em->getConfiguration()->getProxyDir())) {
|
||||
if (false === @mkdir($proxyCacheDir, 0o777, true) && !is_dir($proxyCacheDir)) {
|
||||
throw new \RuntimeException(\sprintf('Unable to create the Doctrine Proxy directory "%s".', $proxyCacheDir));
|
||||
}
|
||||
} elseif (!is_writable($proxyCacheDir)) {
|
||||
throw new \RuntimeException(\sprintf('The Doctrine Proxy directory "%s" is not writeable for the current system user.', $proxyCacheDir));
|
||||
}
|
||||
|
||||
// if proxies are autogenerated we don't need to generate them in the cache warmer
|
||||
if ($em->getConfiguration()->getAutoGenerateProxyClasses()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classes = $em->getMetadataFactory()->getAllMetadata();
|
||||
|
||||
$em->getProxyFactory()->generateProxyClasses($classes);
|
||||
|
||||
foreach (scandir($proxyCacheDir) as $file) {
|
||||
if (!is_dir($file = $proxyCacheDir.'/'.$file)) {
|
||||
$files[] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
217
backend/vendor/symfony/doctrine-bridge/ContainerAwareEventManager.php
vendored
Normal file
217
backend/vendor/symfony/doctrine-bridge/ContainerAwareEventManager.php
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine;
|
||||
|
||||
use Doctrine\Common\EventArgs;
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\Common\EventSubscriber;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Allows lazy loading of listener and subscriber services.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class ContainerAwareEventManager extends EventManager
|
||||
{
|
||||
private array $initialized = [];
|
||||
private bool $initializedSubscribers = false;
|
||||
private array $initializedHashMapping = [];
|
||||
private array $methods = [];
|
||||
|
||||
/**
|
||||
* @param list<array{string[], string|object}> $listeners List of [events, listener] tuples
|
||||
*/
|
||||
public function __construct(
|
||||
private ContainerInterface $container,
|
||||
private array $listeners = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function dispatchEvent(string $eventName, ?EventArgs $eventArgs = null): void
|
||||
{
|
||||
if (!$this->initializedSubscribers) {
|
||||
$this->initializeSubscribers();
|
||||
}
|
||||
if (!isset($this->listeners[$eventName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$eventArgs ??= EventArgs::getEmptyInstance();
|
||||
|
||||
if (!isset($this->initialized[$eventName])) {
|
||||
$this->initializeListeners($eventName);
|
||||
}
|
||||
|
||||
foreach ($this->listeners[$eventName] as $hash => $listener) {
|
||||
$listener->{$this->methods[$eventName][$hash]}($eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public function getListeners(string $event): array
|
||||
{
|
||||
if (!$this->initializedSubscribers) {
|
||||
$this->initializeSubscribers();
|
||||
}
|
||||
if (!isset($this->initialized[$event])) {
|
||||
$this->initializeListeners($event);
|
||||
}
|
||||
|
||||
return $this->listeners[$event];
|
||||
}
|
||||
|
||||
public function getAllListeners(): array
|
||||
{
|
||||
if (!$this->initializedSubscribers) {
|
||||
$this->initializeSubscribers();
|
||||
}
|
||||
|
||||
foreach ($this->listeners as $event => $listeners) {
|
||||
if (!isset($this->initialized[$event])) {
|
||||
$this->initializeListeners($event);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->listeners;
|
||||
}
|
||||
|
||||
public function hasListeners(string $event): bool
|
||||
{
|
||||
if (!$this->initializedSubscribers) {
|
||||
$this->initializeSubscribers();
|
||||
}
|
||||
|
||||
return isset($this->listeners[$event]) && $this->listeners[$event];
|
||||
}
|
||||
|
||||
public function addEventListener(string|array $events, object|string $listener): void
|
||||
{
|
||||
if (!$this->initializedSubscribers) {
|
||||
$this->initializeSubscribers();
|
||||
}
|
||||
|
||||
$hash = $this->getHash($listener);
|
||||
|
||||
foreach ((array) $events as $event) {
|
||||
// Overrides listener if a previous one was associated already
|
||||
// Prevents duplicate listeners on same event (same instance only)
|
||||
$this->listeners[$event][$hash] = $listener;
|
||||
|
||||
if (\is_string($listener)) {
|
||||
unset($this->initialized[$event]);
|
||||
unset($this->initializedHashMapping[$event][$hash]);
|
||||
} else {
|
||||
$this->methods[$event][$hash] = $this->getMethod($listener, $event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function removeEventListener(string|array $events, object|string $listener): void
|
||||
{
|
||||
if (!$this->initializedSubscribers) {
|
||||
$this->initializeSubscribers();
|
||||
}
|
||||
|
||||
$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]);
|
||||
}
|
||||
|
||||
if (isset($this->methods[$event][$hash])) {
|
||||
unset($this->methods[$event][$hash]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addEventSubscriber(EventSubscriber $subscriber): void
|
||||
{
|
||||
if (!$this->initializedSubscribers) {
|
||||
$this->initializeSubscribers();
|
||||
}
|
||||
|
||||
parent::addEventSubscriber($subscriber);
|
||||
}
|
||||
|
||||
public function removeEventSubscriber(EventSubscriber $subscriber): void
|
||||
{
|
||||
if (!$this->initializedSubscribers) {
|
||||
$this->initializeSubscribers();
|
||||
}
|
||||
|
||||
parent::removeEventSubscriber($subscriber);
|
||||
}
|
||||
|
||||
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)) {
|
||||
$listener = $this->container->get($listener);
|
||||
$newHash = $this->getHash($listener);
|
||||
|
||||
$this->initializedHashMapping[$eventName][$hash] = $newHash;
|
||||
|
||||
$listeners[$newHash] = $listener;
|
||||
|
||||
$this->methods[$eventName][$newHash] = $this->getMethod($listener, $eventName);
|
||||
} else {
|
||||
$listeners[$hash] = $listener;
|
||||
}
|
||||
}
|
||||
|
||||
$this->listeners[$eventName] = $listeners;
|
||||
}
|
||||
|
||||
private function initializeSubscribers(): void
|
||||
{
|
||||
$this->initializedSubscribers = true;
|
||||
$listeners = $this->listeners;
|
||||
$this->listeners = [];
|
||||
foreach ($listeners as $listener) {
|
||||
if (\is_array($listener)) {
|
||||
$this->addEventListener(...$listener);
|
||||
continue;
|
||||
}
|
||||
|
||||
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) ? get_debug_type($listener) : $listener));
|
||||
}
|
||||
}
|
||||
|
||||
private function getHash(string|object $listener): string
|
||||
{
|
||||
if (\is_string($listener)) {
|
||||
return '_service_'.$listener;
|
||||
}
|
||||
|
||||
return spl_object_hash($listener);
|
||||
}
|
||||
|
||||
private function getMethod(object $listener, string $event): string
|
||||
{
|
||||
if (!method_exists($listener, $event) && method_exists($listener, '__invoke')) {
|
||||
return '__invoke';
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
}
|
||||
222
backend/vendor/symfony/doctrine-bridge/DataCollector/DoctrineDataCollector.php
vendored
Normal file
222
backend/vendor/symfony/doctrine-bridge/DataCollector/DoctrineDataCollector.php
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\DataCollector;
|
||||
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
use Symfony\Component\VarDumper\Caster\Caster;
|
||||
use Symfony\Component\VarDumper\Cloner\Stub;
|
||||
|
||||
/**
|
||||
* DoctrineDataCollector.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class DoctrineDataCollector extends DataCollector
|
||||
{
|
||||
private array $connections;
|
||||
private array $managers;
|
||||
|
||||
public function __construct(
|
||||
private ManagerRegistry $registry,
|
||||
private DebugDataHolder $debugDataHolder,
|
||||
) {
|
||||
$this->connections = $registry->getConnectionNames();
|
||||
$this->managers = $registry->getManagerNames();
|
||||
}
|
||||
|
||||
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
|
||||
{
|
||||
$this->data = [
|
||||
'queries' => $this->collectQueries(),
|
||||
'connections' => $this->connections,
|
||||
'managers' => $this->managers,
|
||||
];
|
||||
}
|
||||
|
||||
private function collectQueries(): array
|
||||
{
|
||||
$queries = [];
|
||||
|
||||
foreach ($this->debugDataHolder->getData() as $name => $data) {
|
||||
$queries[$name] = $this->sanitizeQueries($name, $data);
|
||||
}
|
||||
|
||||
return $queries;
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->data = [];
|
||||
$this->debugDataHolder->reset();
|
||||
}
|
||||
|
||||
public function getManagers(): array
|
||||
{
|
||||
return $this->data['managers'];
|
||||
}
|
||||
|
||||
public function getConnections(): array
|
||||
{
|
||||
return $this->data['connections'];
|
||||
}
|
||||
|
||||
public function getQueryCount(): int
|
||||
{
|
||||
return array_sum(array_map('count', $this->data['queries']));
|
||||
}
|
||||
|
||||
public function getQueries(): array
|
||||
{
|
||||
return $this->data['queries'];
|
||||
}
|
||||
|
||||
public function getTime(): float
|
||||
{
|
||||
$time = 0;
|
||||
foreach ($this->data['queries'] as $queries) {
|
||||
foreach ($queries as $query) {
|
||||
$time += $query['executionMS'];
|
||||
}
|
||||
}
|
||||
|
||||
return $time;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'db';
|
||||
}
|
||||
|
||||
protected function getCasters(): array
|
||||
{
|
||||
return parent::getCasters() + [
|
||||
ObjectParameter::class => static function (ObjectParameter $o, array $a, Stub $s): array {
|
||||
$s->class = $o->getClass();
|
||||
$s->value = $o->getObject();
|
||||
|
||||
$r = new \ReflectionClass($o->getClass());
|
||||
if ($f = $r->getFileName()) {
|
||||
$s->attr['file'] = $f;
|
||||
$s->attr['line'] = $r->getStartLine();
|
||||
} else {
|
||||
unset($s->attr['file']);
|
||||
unset($s->attr['line']);
|
||||
}
|
||||
|
||||
if ($error = $o->getError()) {
|
||||
return [Caster::PREFIX_VIRTUAL.'⚠' => $error->getMessage()];
|
||||
}
|
||||
|
||||
if ($o->isStringable()) {
|
||||
return [Caster::PREFIX_VIRTUAL.'__toString()' => (string) $o->getObject()];
|
||||
}
|
||||
|
||||
return [Caster::PREFIX_VIRTUAL.'⚠' => \sprintf('Object of class "%s" could not be converted to string.', $o->getClass())];
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private function sanitizeQueries(string $connectionName, array $queries): array
|
||||
{
|
||||
foreach ($queries as $i => $query) {
|
||||
$queries[$i] = $this->sanitizeQuery($connectionName, $query);
|
||||
}
|
||||
|
||||
return $queries;
|
||||
}
|
||||
|
||||
private function sanitizeQuery(string $connectionName, array $query): array
|
||||
{
|
||||
$query['explainable'] = true;
|
||||
$query['runnable'] = true;
|
||||
$query['params'] ??= [];
|
||||
if (!\is_array($query['params'])) {
|
||||
$query['params'] = [$query['params']];
|
||||
}
|
||||
if (!\is_array($query['types'])) {
|
||||
$query['types'] = [];
|
||||
}
|
||||
foreach ($query['params'] as $j => $param) {
|
||||
$e = null;
|
||||
if (isset($query['types'][$j])) {
|
||||
// Transform the param according to the type
|
||||
$type = $query['types'][$j];
|
||||
if (\is_string($type)) {
|
||||
$type = Type::getType($type);
|
||||
}
|
||||
if ($type instanceof Type) {
|
||||
$query['types'][$j] = $type->getBindingType();
|
||||
try {
|
||||
$param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform());
|
||||
} catch (\TypeError|ConversionException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[$query['params'][$j], $explainable, $runnable] = $this->sanitizeParam($param, $e);
|
||||
if (!$explainable) {
|
||||
$query['explainable'] = false;
|
||||
}
|
||||
|
||||
if (!$runnable) {
|
||||
$query['runnable'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
$query['params'] = $this->cloneVar($query['params']);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a param.
|
||||
*
|
||||
* The return value is an array with the sanitized value and a boolean
|
||||
* indicating if the original value was kept (allowing to use the sanitized
|
||||
* value to explain the query).
|
||||
*/
|
||||
private function sanitizeParam(mixed $var, ?\Throwable $error): array
|
||||
{
|
||||
if (\is_object($var)) {
|
||||
return [$o = new ObjectParameter($var, $error), false, $o->isStringable() && !$error];
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
return ['⚠ '.$error->getMessage(), false, false];
|
||||
}
|
||||
|
||||
if (\is_array($var)) {
|
||||
$a = [];
|
||||
$explainable = $runnable = true;
|
||||
foreach ($var as $k => $v) {
|
||||
[$value, $e, $r] = $this->sanitizeParam($v, null);
|
||||
$explainable = $explainable && $e;
|
||||
$runnable = $runnable && $r;
|
||||
$a[$k] = $value;
|
||||
}
|
||||
|
||||
return [$a, $explainable, $runnable];
|
||||
}
|
||||
|
||||
if (\is_resource($var)) {
|
||||
return [\sprintf('/* Resource(%s) */', get_resource_type($var)), false, false];
|
||||
}
|
||||
|
||||
return [$var, true, true];
|
||||
}
|
||||
}
|
||||
46
backend/vendor/symfony/doctrine-bridge/DataCollector/ObjectParameter.php
vendored
Normal file
46
backend/vendor/symfony/doctrine-bridge/DataCollector/ObjectParameter.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\DataCollector;
|
||||
|
||||
final class ObjectParameter
|
||||
{
|
||||
private bool $stringable;
|
||||
private string $class;
|
||||
|
||||
public function __construct(
|
||||
private readonly object $object,
|
||||
private readonly ?\Throwable $error,
|
||||
) {
|
||||
$this->stringable = $this->object instanceof \Stringable;
|
||||
$this->class = $object::class;
|
||||
}
|
||||
|
||||
public function getObject(): object
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
public function getError(): ?\Throwable
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function isStringable(): bool
|
||||
{
|
||||
return $this->stringable;
|
||||
}
|
||||
|
||||
public function getClass(): string
|
||||
{
|
||||
return $this->class;
|
||||
}
|
||||
}
|
||||
430
backend/vendor/symfony/doctrine-bridge/DependencyInjection/AbstractDoctrineExtension.php
vendored
Normal file
430
backend/vendor/symfony/doctrine-bridge/DependencyInjection/AbstractDoctrineExtension.php
vendored
Normal file
@@ -0,0 +1,430 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
|
||||
trigger_deprecation('symfony/doctrine-bridge', '7.4', 'The "%s" class is deprecated, the code is incorporated into the extension classes of Doctrine bundles.', AbstractDoctrineExtension::class);
|
||||
|
||||
/**
|
||||
* This abstract classes groups common code that Doctrine Object Manager extensions (ORM, MongoDB, CouchDB) need.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*
|
||||
* @deprecated since Symfony 7.4, the code is incorporated into the extension classes of Doctrine bundles
|
||||
*/
|
||||
abstract class AbstractDoctrineExtension extends Extension
|
||||
{
|
||||
/**
|
||||
* Used inside metadata driver method to simplify aggregation of data.
|
||||
*/
|
||||
protected array $aliasMap = [];
|
||||
|
||||
/**
|
||||
* Used inside metadata driver method to simplify aggregation of data.
|
||||
*/
|
||||
protected array $drivers = [];
|
||||
|
||||
/**
|
||||
* @param array $objectManager A configured object manager
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function loadMappingInformation(array $objectManager, ContainerBuilder $container): void
|
||||
{
|
||||
if ($objectManager['auto_mapping']) {
|
||||
// automatically register bundle mappings
|
||||
foreach (array_keys($container->getParameter('kernel.bundles')) as $bundle) {
|
||||
if (!isset($objectManager['mappings'][$bundle])) {
|
||||
$objectManager['mappings'][$bundle] = [
|
||||
'mapping' => true,
|
||||
'is_bundle' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($objectManager['mappings'] as $mappingName => $mappingConfig) {
|
||||
if (null !== $mappingConfig && false === $mappingConfig['mapping']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mappingConfig = array_replace([
|
||||
'dir' => false,
|
||||
'type' => false,
|
||||
'prefix' => false,
|
||||
], (array) $mappingConfig);
|
||||
|
||||
$mappingConfig['dir'] = $container->getParameterBag()->resolveValue($mappingConfig['dir']);
|
||||
// a bundle configuration is detected by realizing that the specified dir is not absolute and existing
|
||||
if (!isset($mappingConfig['is_bundle'])) {
|
||||
$mappingConfig['is_bundle'] = !is_dir($mappingConfig['dir']);
|
||||
}
|
||||
|
||||
if ($mappingConfig['is_bundle']) {
|
||||
$bundle = null;
|
||||
$bundleMetadata = null;
|
||||
foreach ($container->getParameter('kernel.bundles') as $name => $class) {
|
||||
if ($mappingName === $name) {
|
||||
$bundle = new \ReflectionClass($class);
|
||||
$bundleMetadata = $container->getParameter('kernel.bundles_metadata')[$name];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $bundle) {
|
||||
throw new \InvalidArgumentException(\sprintf('Bundle "%s" does not exist or it is not enabled.', $mappingName));
|
||||
}
|
||||
|
||||
$mappingConfig = $this->getMappingDriverBundleConfigDefaults($mappingConfig, $bundle, $container, $bundleMetadata['path']);
|
||||
if (!$mappingConfig) {
|
||||
continue;
|
||||
}
|
||||
} elseif (!$mappingConfig['type']) {
|
||||
$mappingConfig['type'] = 'attribute';
|
||||
}
|
||||
|
||||
$this->assertValidMappingConfiguration($mappingConfig, $objectManager['name']);
|
||||
$this->setMappingDriverConfig($mappingConfig, $mappingName);
|
||||
$this->setMappingDriverAlias($mappingConfig, $mappingName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the alias for this mapping driver.
|
||||
*
|
||||
* Aliases can be used in the Query languages of all the Doctrine object managers to simplify writing tasks.
|
||||
*/
|
||||
protected function setMappingDriverAlias(array $mappingConfig, string $mappingName): void
|
||||
{
|
||||
if (isset($mappingConfig['alias'])) {
|
||||
$this->aliasMap[$mappingConfig['alias']] = $mappingConfig['prefix'];
|
||||
} else {
|
||||
$this->aliasMap[$mappingName] = $mappingConfig['prefix'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the mapping driver configuration for later use with the object managers metadata driver chain.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function setMappingDriverConfig(array $mappingConfig, string $mappingName): void
|
||||
{
|
||||
$mappingDirectory = $mappingConfig['dir'];
|
||||
if (!is_dir($mappingDirectory)) {
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid Doctrine mapping path given. Cannot load Doctrine mapping/bundle named "%s".', $mappingName));
|
||||
}
|
||||
|
||||
$this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = realpath($mappingDirectory) ?: $mappingDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is a bundle controlled mapping all the missing information can be autodetected by this method.
|
||||
*
|
||||
* 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
|
||||
{
|
||||
$bundleClassDir = \dirname($bundle->getFileName());
|
||||
$bundleDir ??= $bundleClassDir;
|
||||
|
||||
if (!$bundleConfig['type']) {
|
||||
$bundleConfig['type'] = $this->detectMetadataDriver($bundleDir, $container);
|
||||
|
||||
if (!$bundleConfig['type'] && $bundleDir !== $bundleClassDir) {
|
||||
$bundleConfig['type'] = $this->detectMetadataDriver($bundleClassDir, $container);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bundleConfig['type']) {
|
||||
// skip this bundle, no mapping information was found.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$bundleConfig['dir']) {
|
||||
if (\in_array($bundleConfig['type'], ['staticphp', 'attribute'], true)) {
|
||||
$bundleConfig['dir'] = $bundleClassDir.'/'.$this->getMappingObjectDefaultName();
|
||||
} else {
|
||||
$bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingResourceConfigDirectory($bundleDir);
|
||||
}
|
||||
} else {
|
||||
$bundleConfig['dir'] = $bundleDir.'/'.$bundleConfig['dir'];
|
||||
}
|
||||
|
||||
if (!$bundleConfig['prefix']) {
|
||||
$bundleConfig['prefix'] = $bundle->getNamespaceName().'\\'.$this->getMappingObjectDefaultName();
|
||||
}
|
||||
|
||||
return $bundleConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all the collected mapping information with the object manager by registering the appropriate mapping drivers.
|
||||
*/
|
||||
protected function registerMappingDrivers(array $objectManager, ContainerBuilder $container): void
|
||||
{
|
||||
// configure metadata driver for each bundle based on the type of mapping files found
|
||||
if ($container->hasDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'))) {
|
||||
$chainDriverDef = $container->getDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'));
|
||||
} else {
|
||||
$chainDriverDef = new Definition($this->getMetadataDriverClass('driver_chain'));
|
||||
}
|
||||
|
||||
foreach ($this->drivers as $driverType => $driverPaths) {
|
||||
$mappingService = $this->getObjectManagerElementName($objectManager['name'].'_'.$driverType.'_metadata_driver');
|
||||
if ($container->hasDefinition($mappingService)) {
|
||||
$mappingDriverDef = $container->getDefinition($mappingService);
|
||||
$args = $mappingDriverDef->getArguments();
|
||||
$args[0] = array_merge(array_values($driverPaths), $args[0]);
|
||||
$mappingDriverDef->setArguments($args);
|
||||
} else {
|
||||
$mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [
|
||||
array_values($driverPaths),
|
||||
]);
|
||||
}
|
||||
if (str_contains($mappingDriverDef->getClass(), 'yml') || str_contains($mappingDriverDef->getClass(), 'xml')
|
||||
|| str_contains($mappingDriverDef->getClass(), 'Yaml') || str_contains($mappingDriverDef->getClass(), 'Xml')
|
||||
) {
|
||||
$mappingDriverDef->setArguments([array_flip($driverPaths)]);
|
||||
$mappingDriverDef->addMethodCall('setGlobalBasename', ['mapping']);
|
||||
}
|
||||
|
||||
$container->setDefinition($mappingService, $mappingDriverDef);
|
||||
|
||||
foreach ($driverPaths as $prefix => $driverPath) {
|
||||
$chainDriverDef->addMethodCall('addDriver', [new Reference($mappingService), $prefix]);
|
||||
}
|
||||
}
|
||||
|
||||
$container->setDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'), $chainDriverDef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assertion if the specified mapping information is valid.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function assertValidMappingConfiguration(array $mappingConfig, string $objectManagerName): void
|
||||
{
|
||||
if (!$mappingConfig['type'] || !$mappingConfig['dir'] || !$mappingConfig['prefix']) {
|
||||
throw new \InvalidArgumentException(\sprintf('Mapping definitions for Doctrine manager "%s" require at least the "type", "dir" and "prefix" options.', $objectManagerName));
|
||||
}
|
||||
|
||||
if (!is_dir($mappingConfig['dir'])) {
|
||||
throw new \InvalidArgumentException(\sprintf('Specified non-existing directory "%s" as Doctrine mapping source.', $mappingConfig['dir']));
|
||||
}
|
||||
|
||||
if (!\in_array($mappingConfig['type'], ['xml', 'yml', 'php', 'staticphp', 'attribute'], true)) {
|
||||
throw new \InvalidArgumentException(\sprintf('Can only configure "xml", "yml", "php", "staticphp" or "attribute" through the DoctrineBundle. Use your own bundle to configure other metadata drivers. You can register them by adding a new driver to the "%s" service definition.', $this->getObjectManagerElementName($objectManagerName.'_metadata_driver')));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects what metadata driver to use for the supplied directory.
|
||||
*/
|
||||
protected function detectMetadataDriver(string $dir, ContainerBuilder $container): ?string
|
||||
{
|
||||
$configPath = $this->getMappingResourceConfigDirectory($dir);
|
||||
$extension = $this->getMappingResourceExtension();
|
||||
|
||||
if (glob($dir.'/'.$configPath.'/*.'.$extension.'.xml', \GLOB_NOSORT)) {
|
||||
$driver = 'xml';
|
||||
} elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.yml', \GLOB_NOSORT)) {
|
||||
$driver = 'yml';
|
||||
} elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.php', \GLOB_NOSORT)) {
|
||||
$driver = 'php';
|
||||
} else {
|
||||
// add the closest existing directory as a resource
|
||||
$resource = $dir.'/'.$configPath;
|
||||
while (!is_dir($resource)) {
|
||||
$resource = \dirname($resource);
|
||||
}
|
||||
$container->fileExists($resource, false);
|
||||
|
||||
if ($container->fileExists($dir.'/'.$this->getMappingObjectDefaultName(), false)) {
|
||||
return 'attribute';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
$container->fileExists($dir.'/'.$configPath, false);
|
||||
|
||||
return $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a configured object manager metadata, query or result cache driver.
|
||||
*
|
||||
* @throws \InvalidArgumentException in case of unknown driver type
|
||||
*/
|
||||
protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, string $cacheName): void
|
||||
{
|
||||
$this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName.'_driver'], $container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a cache driver.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function loadCacheDriver(string $cacheName, string $objectManagerName, array $cacheDriver, ContainerBuilder $container): string
|
||||
{
|
||||
$cacheDriverServiceId = $this->getObjectManagerElementName($objectManagerName.'_'.$cacheName);
|
||||
|
||||
switch ($cacheDriver['type']) {
|
||||
case 'service':
|
||||
$container->setAlias($cacheDriverServiceId, new Alias($cacheDriver['id'], false));
|
||||
|
||||
return $cacheDriverServiceId;
|
||||
case 'memcached':
|
||||
$memcachedClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.memcached.class').'%';
|
||||
$memcachedInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.memcached_instance.class').'%';
|
||||
$memcachedHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.memcached_host').'%';
|
||||
$memcachedPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.memcached_port').'%';
|
||||
$cacheDef = new Definition($memcachedClass);
|
||||
$memcachedInstance = new Definition($memcachedInstanceClass);
|
||||
$memcachedInstance->addMethodCall('addServer', [
|
||||
$memcachedHost, $memcachedPort,
|
||||
]);
|
||||
$container->setDefinition($this->getObjectManagerElementName(\sprintf('%s_memcached_instance', $objectManagerName)), $memcachedInstance);
|
||||
$cacheDef->addMethodCall('setMemcached', [new Reference($this->getObjectManagerElementName(\sprintf('%s_memcached_instance', $objectManagerName)))]);
|
||||
break;
|
||||
case 'redis':
|
||||
case 'valkey':
|
||||
$redisClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.redis.class').'%';
|
||||
$redisInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.redis_instance.class').'%';
|
||||
$redisHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.redis_host').'%';
|
||||
$redisPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.redis_port').'%';
|
||||
$cacheDef = new Definition($redisClass);
|
||||
$redisInstance = new Definition($redisInstanceClass);
|
||||
$redisInstance->addMethodCall('connect', [
|
||||
$redisHost, $redisPort,
|
||||
]);
|
||||
$container->setDefinition($this->getObjectManagerElementName(\sprintf('%s_redis_instance', $objectManagerName)), $redisInstance);
|
||||
$cacheDef->addMethodCall('setRedis', [new Reference($this->getObjectManagerElementName(\sprintf('%s_redis_instance', $objectManagerName)))]);
|
||||
break;
|
||||
case 'apc':
|
||||
case 'apcu':
|
||||
case 'array':
|
||||
case 'xcache':
|
||||
case 'wincache':
|
||||
case 'zenddata':
|
||||
$cacheDef = new Definition('%'.$this->getObjectManagerElementName(\sprintf('cache.%s.class', $cacheDriver['type'])).'%');
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException(\sprintf('"%s" is an unrecognized Doctrine cache driver.', $cacheDriver['type']));
|
||||
}
|
||||
|
||||
if (!isset($cacheDriver['namespace'])) {
|
||||
// generate a unique namespace for the given application
|
||||
if ($container->hasParameter('cache.prefix.seed')) {
|
||||
$seed = $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed'));
|
||||
} else {
|
||||
$seed = '_'.$container->getParameter('kernel.project_dir');
|
||||
$seed .= '.'.$container->getParameter('kernel.container_class');
|
||||
}
|
||||
|
||||
$namespace = 'sf_'.$this->getMappingResourceExtension().'_'.$objectManagerName.'_'.ContainerBuilder::hash($seed);
|
||||
|
||||
$cacheDriver['namespace'] = $namespace;
|
||||
}
|
||||
|
||||
$cacheDef->addMethodCall('setNamespace', [$cacheDriver['namespace']]);
|
||||
|
||||
$container->setDefinition($cacheDriverServiceId, $cacheDef);
|
||||
|
||||
return $cacheDriverServiceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a modified version of $managerConfigs.
|
||||
*
|
||||
* The manager called $autoMappedManager will map all bundles that are not mapped by other managers.
|
||||
*/
|
||||
protected function fixManagersAutoMappings(array $managerConfigs, array $bundles): array
|
||||
{
|
||||
if ($autoMappedManager = $this->validateAutoMapping($managerConfigs)) {
|
||||
foreach (array_keys($bundles) as $bundle) {
|
||||
foreach ($managerConfigs as $manager) {
|
||||
if (isset($manager['mappings'][$bundle])) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
$managerConfigs[$autoMappedManager]['mappings'][$bundle] = [
|
||||
'mapping' => true,
|
||||
'is_bundle' => true,
|
||||
];
|
||||
}
|
||||
$managerConfigs[$autoMappedManager]['auto_mapping'] = false;
|
||||
}
|
||||
|
||||
return $managerConfigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefixes the relative dependency injection container path with the object manager prefix.
|
||||
*
|
||||
* @example $name is 'entity_manager' then the result would be 'doctrine.orm.entity_manager'
|
||||
*/
|
||||
abstract protected function getObjectManagerElementName(string $name): string;
|
||||
|
||||
/**
|
||||
* Noun that describes the mapped objects such as Entity or Document.
|
||||
*
|
||||
* Will be used for autodetection of persistent objects directory.
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Extension used by the mapping files.
|
||||
*/
|
||||
abstract protected function getMappingResourceExtension(): string;
|
||||
|
||||
/**
|
||||
* The class name used by the various mapping drivers.
|
||||
*/
|
||||
abstract protected function getMetadataDriverClass(string $driverType): string;
|
||||
|
||||
/**
|
||||
* Search for a manager that is declared as 'auto_mapping' = true.
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
private function validateAutoMapping(array $managerConfigs): ?string
|
||||
{
|
||||
$autoMappedManager = null;
|
||||
foreach ($managerConfigs as $name => $manager) {
|
||||
if (!$manager['auto_mapping']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null !== $autoMappedManager) {
|
||||
throw new \LogicException(\sprintf('You cannot enable "auto_mapping" on more than one manager at the same time (found in "%s" and "%s"").', $autoMappedManager, $name));
|
||||
}
|
||||
|
||||
$autoMappedManager = $name;
|
||||
}
|
||||
|
||||
return $autoMappedManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Registers additional validators.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class DoctrineValidationPass implements CompilerPassInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $managerType,
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$this->updateValidatorMappingFiles($container, 'xml', 'xml');
|
||||
$this->updateValidatorMappingFiles($container, 'yaml', 'yml');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the validation mapping files for the format and extends them with
|
||||
* files matching a doctrine search pattern (Resources/config/validation.orm.xml).
|
||||
*/
|
||||
private function updateValidatorMappingFiles(ContainerBuilder $container, string $mapping, string $extension): void
|
||||
{
|
||||
if (!$container->hasParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = $container->getParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files');
|
||||
$validationPath = '/config/validation.'.$this->managerType.'.'.$extension;
|
||||
|
||||
foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
|
||||
if ($container->fileExists($file = $bundle['path'].'/Resources'.$validationPath) || $container->fileExists($file = $bundle['path'].$validationPath)) {
|
||||
$files[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
$container->setParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files', $files);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
|
||||
|
||||
use Symfony\Bridge\Doctrine\Types\DatePointType;
|
||||
use Symfony\Bridge\Doctrine\Types\DayPointType;
|
||||
use Symfony\Bridge\Doctrine\Types\TimePointType;
|
||||
use Symfony\Component\Clock\DatePoint;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
final class RegisterDatePointTypePass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!class_exists(DatePoint::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$container->hasParameter('doctrine.dbal.connection_factory.types')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$types = $container->getParameter('doctrine.dbal.connection_factory.types');
|
||||
|
||||
$types['date_point'] ??= ['class' => DatePointType::class];
|
||||
$types['day_point'] ??= ['class' => DayPointType::class];
|
||||
$types['time_point'] ??= ['class' => TimePointType::class];
|
||||
|
||||
$container->setParameter('doctrine.dbal.connection_factory.types', $types);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
|
||||
|
||||
use Symfony\Bridge\Doctrine\ContainerAwareEventManager;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Registers event listeners to the available doctrine connections.
|
||||
*
|
||||
* @author Jeremy Mikola <jmikola@gmail.com>
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
* @author David Maicher <mail@dmaicher.de>
|
||||
*/
|
||||
class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface
|
||||
{
|
||||
private array $connections;
|
||||
|
||||
/**
|
||||
* @var array<string, Definition>
|
||||
*/
|
||||
private array $eventManagers = [];
|
||||
|
||||
/**
|
||||
* @param string $managerTemplate sprintf() template for generating the event
|
||||
* manager's service ID for a connection name
|
||||
* @param string $tagPrefix Tag prefix for listeners
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $connectionsParameter,
|
||||
private readonly string $managerTemplate,
|
||||
private readonly string $tagPrefix,
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasParameter($this->connectionsParameter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->connections = $container->getParameter($this->connectionsParameter);
|
||||
$listenerRefs = $this->addTaggedServices($container);
|
||||
|
||||
// replace service container argument of event managers with smaller service locator
|
||||
// so services can even remain private
|
||||
foreach ($listenerRefs as $connection => $refs) {
|
||||
$this->getEventManagerDef($container, $connection)
|
||||
->replaceArgument(0, ServiceLocatorTagPass::register($container, $refs));
|
||||
}
|
||||
}
|
||||
|
||||
private function addTaggedServices(ContainerBuilder $container): array
|
||||
{
|
||||
$listenerRefs = [];
|
||||
$managerDefs = [];
|
||||
foreach ($this->findAndSortTags($container) as [$id, $tag]) {
|
||||
$connections = isset($tag['connection'])
|
||||
? [$container->getParameterBag()->resolveValue($tag['connection'])]
|
||||
: array_keys($this->connections);
|
||||
if (!isset($tag['event'])) {
|
||||
throw new InvalidArgumentException(\sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id));
|
||||
}
|
||||
foreach ($connections as $con) {
|
||||
if (!isset($this->connections[$con])) {
|
||||
throw new RuntimeException(\sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: "%s".', $con, $id, implode('", "', array_keys($this->connections))));
|
||||
}
|
||||
|
||||
if (!isset($managerDefs[$con])) {
|
||||
$managerDef = $parentDef = $this->getEventManagerDef($container, $con);
|
||||
while (!$parentDef->getClass() && $parentDef instanceof ChildDefinition) {
|
||||
$parentDef = $container->findDefinition($parentDef->getParent());
|
||||
}
|
||||
$managerClass = $container->getParameterBag()->resolveValue($parentDef->getClass());
|
||||
$managerDefs[$con] = [$managerDef, $managerClass];
|
||||
} else {
|
||||
[$managerDef, $managerClass] = $managerDefs[$con];
|
||||
}
|
||||
|
||||
if (ContainerAwareEventManager::class === $managerClass) {
|
||||
$refs = $managerDef->getArguments()[1] ?? [];
|
||||
$listenerRefs[$con][$id] = new Reference($id);
|
||||
$refs[] = [[$tag['event']], $id];
|
||||
$managerDef->setArgument(1, $refs);
|
||||
} else {
|
||||
$managerDef->addMethodCall('addEventListener', [[$tag['event']], new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $listenerRefs;
|
||||
}
|
||||
|
||||
private function getEventManagerDef(ContainerBuilder $container, string $name): Definition
|
||||
{
|
||||
if (!isset($this->eventManagers[$name])) {
|
||||
$this->eventManagers[$name] = $container->getDefinition(\sprintf($this->managerTemplate, $name));
|
||||
}
|
||||
|
||||
return $this->eventManagers[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and orders all service tags with the given name by their priority.
|
||||
*
|
||||
* The order of additions must be respected for services having the same priority,
|
||||
* and knowing that the \SplPriorityQueue class does not respect the FIFO method,
|
||||
* we should not use this class.
|
||||
*
|
||||
* @see https://bugs.php.net/53710
|
||||
* @see https://bugs.php.net/60926
|
||||
*/
|
||||
private function findAndSortTags(ContainerBuilder $container): array
|
||||
{
|
||||
$sortedTags = [];
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->tagPrefix.'.event_listener', true) as $serviceId => $tags) {
|
||||
foreach ($tags as $attributes) {
|
||||
$priority = $attributes['priority'] ?? 0;
|
||||
$sortedTags[$priority][] = [$serviceId, $attributes];
|
||||
}
|
||||
}
|
||||
|
||||
krsort($sortedTags);
|
||||
|
||||
return array_merge(...$sortedTags);
|
||||
}
|
||||
}
|
||||
169
backend/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterMappingsPass.php
vendored
Normal file
169
backend/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterMappingsPass.php
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Base class for the doctrine bundles to provide a compiler pass class that
|
||||
* helps to register doctrine mappings.
|
||||
*
|
||||
* The compiler pass is meant to register the mappings with the metadata
|
||||
* chain driver corresponding to one of the object managers.
|
||||
*
|
||||
* For concrete implementations, see the RegisterXyMappingsPass classes
|
||||
* in the DoctrineBundle resp.
|
||||
* DoctrineMongodbBundle, DoctrineCouchdbBundle and DoctrinePhpcrBundle.
|
||||
*
|
||||
* @author David Buchmann <david@liip.ch>
|
||||
*/
|
||||
abstract class RegisterMappingsPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* The $managerParameters is an ordered list of container parameters that could provide the
|
||||
* name of the manager to register these namespaces and alias on. The first non-empty name
|
||||
* is used, the others skipped.
|
||||
*
|
||||
* The $aliasMap parameter can be used to define bundle namespace shortcuts like the
|
||||
* DoctrineBundle provides automatically for objects in the default Entity/Document folder.
|
||||
*
|
||||
* @param Definition|Reference $driver Driver DI definition or reference
|
||||
* @param string[] $namespaces List of namespaces handled by $driver
|
||||
* @param string[] $managerParameters list of container parameters that could
|
||||
* hold the manager name
|
||||
* @param string $driverPattern Pattern for the metadata chain driver service ids (e.g. "doctrine.orm.%s_metadata_driver")
|
||||
* @param string|false $enabledParameter Service container parameter that must be
|
||||
* present to enable the mapping (regardless of the
|
||||
* parameter value). Pass false to not do any check.
|
||||
* @param string $configurationPattern Pattern for the Configuration service name,
|
||||
* for example 'doctrine.orm.%s_configuration'.
|
||||
* @param string $registerAliasMethodName Method name to call on the configuration service. This
|
||||
* depends on the Doctrine implementation.
|
||||
* For example addEntityNamespace.
|
||||
* @param string[] $aliasMap Map of alias to namespace
|
||||
*/
|
||||
public function __construct(
|
||||
protected Definition|Reference $driver,
|
||||
protected array $namespaces,
|
||||
protected array $managerParameters,
|
||||
protected string $driverPattern,
|
||||
protected string|false $enabledParameter = false,
|
||||
private readonly string $configurationPattern = '',
|
||||
private readonly string $registerAliasMethodName = '',
|
||||
private readonly array $aliasMap = [],
|
||||
) {
|
||||
if ($aliasMap && (!$configurationPattern || !$registerAliasMethodName)) {
|
||||
throw new \InvalidArgumentException('configurationPattern and registerAliasMethodName are required to register namespace alias.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register mappings and alias with the metadata drivers.
|
||||
*/
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$this->enabled($container)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mappingDriverDef = $this->getDriver($container);
|
||||
$chainDriverDefService = $this->getChainDriverServiceName($container);
|
||||
// Definition for a Doctrine\Persistence\Mapping\Driver\MappingDriverChain
|
||||
$chainDriverDef = $container->getDefinition($chainDriverDefService);
|
||||
foreach ($this->namespaces as $namespace) {
|
||||
$chainDriverDef->addMethodCall('addDriver', [$mappingDriverDef, $namespace]);
|
||||
}
|
||||
|
||||
if (!\count($this->aliasMap)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$configurationServiceName = $this->getConfigurationServiceName($container);
|
||||
// Definition of the Doctrine\...\Configuration class specific to the Doctrine flavour.
|
||||
$configurationServiceDefinition = $container->getDefinition($configurationServiceName);
|
||||
foreach ($this->aliasMap as $alias => $namespace) {
|
||||
$configurationServiceDefinition->addMethodCall($this->registerAliasMethodName, [$alias, $namespace]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the service name of the metadata chain driver that the mappings
|
||||
* should be registered with.
|
||||
*
|
||||
* @throws InvalidArgumentException if non of the managerParameters has a
|
||||
* non-empty value
|
||||
*/
|
||||
protected function getChainDriverServiceName(ContainerBuilder $container): string
|
||||
{
|
||||
return \sprintf($this->driverPattern, $this->getManagerName($container));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the service definition for the metadata driver.
|
||||
*
|
||||
* @param ContainerBuilder $container Passed on in case an extending class
|
||||
* needs access to the container
|
||||
*/
|
||||
protected function getDriver(ContainerBuilder $container): Definition|Reference
|
||||
{
|
||||
return $this->driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the service name from the pattern and the configured manager name.
|
||||
*
|
||||
* @throws InvalidArgumentException if none of the managerParameters has a
|
||||
* non-empty value
|
||||
*/
|
||||
private function getConfigurationServiceName(ContainerBuilder $container): string
|
||||
{
|
||||
return \sprintf($this->configurationPattern, $this->getManagerName($container));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the manager name.
|
||||
*
|
||||
* The default implementation loops over the managerParameters and returns
|
||||
* the first non-empty parameter.
|
||||
*
|
||||
* @throws InvalidArgumentException if none of the managerParameters is found in the container
|
||||
*/
|
||||
private function getManagerName(ContainerBuilder $container): string
|
||||
{
|
||||
foreach ($this->managerParameters as $param) {
|
||||
if ($container->hasParameter($param)) {
|
||||
$name = $container->getParameter($param);
|
||||
if ($name) {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(\sprintf('Could not find the manager name parameter in the container. Tried the following parameter names: "%s".', implode('", "', $this->managerParameters)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this mapping should be activated or not. This allows
|
||||
* to take this decision with the container builder available.
|
||||
*
|
||||
* This default implementation checks if the class has the enabledParameter
|
||||
* configured and if so if that parameter is present in the container.
|
||||
*/
|
||||
protected function enabled(ContainerBuilder $container): bool
|
||||
{
|
||||
return !$this->enabledParameter || $container->hasParameter($this->enabledParameter);
|
||||
}
|
||||
}
|
||||
44
backend/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterUidTypePass.php
vendored
Normal file
44
backend/vendor/symfony/doctrine-bridge/DependencyInjection/CompilerPass/RegisterUidTypePass.php
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
|
||||
|
||||
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||
use Symfony\Bridge\Doctrine\Types\UuidType;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Uid\AbstractUid;
|
||||
|
||||
final class RegisterUidTypePass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!class_exists(AbstractUid::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$container->hasParameter('doctrine.dbal.connection_factory.types')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$typeDefinition = $container->getParameter('doctrine.dbal.connection_factory.types');
|
||||
|
||||
if (!isset($typeDefinition['uuid'])) {
|
||||
$typeDefinition['uuid'] = ['class' => UuidType::class];
|
||||
}
|
||||
|
||||
if (!isset($typeDefinition['ulid'])) {
|
||||
$typeDefinition['ulid'] = ['class' => UlidType::class];
|
||||
}
|
||||
|
||||
$container->setParameter('doctrine.dbal.connection_factory.types', $typeDefinition);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\DependencyInjection\Security\UserProvider;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* EntityFactory creates services for Doctrine user provider.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class EntityFactory implements UserProviderFactoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $key,
|
||||
private readonly string $providerId,
|
||||
) {
|
||||
}
|
||||
|
||||
public function create(ContainerBuilder $container, string $id, array $config): void
|
||||
{
|
||||
$container
|
||||
->setDefinition($id, new ChildDefinition($this->providerId))
|
||||
->addArgument($config['class'])
|
||||
->addArgument($config['property'])
|
||||
->addArgument($config['manager_name'])
|
||||
;
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
$node
|
||||
->children()
|
||||
->scalarNode('class')
|
||||
->isRequired()
|
||||
->info('The full entity class name of your user class.')
|
||||
->cannotBeEmpty()
|
||||
->end()
|
||||
->scalarNode('property')->defaultNull()->end()
|
||||
->scalarNode('manager_name')->defaultNull()->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
107
backend/vendor/symfony/doctrine-bridge/Form/ChoiceList/DoctrineChoiceLoader.php
vendored
Normal file
107
backend/vendor/symfony/doctrine-bridge/Form/ChoiceList/DoctrineChoiceLoader.php
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
|
||||
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader;
|
||||
use Symfony\Component\Form\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* Loads choices using a Doctrine object manager.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class DoctrineChoiceLoader extends AbstractChoiceLoader
|
||||
{
|
||||
/** @var class-string */
|
||||
private readonly string $class;
|
||||
|
||||
/**
|
||||
* Creates a new choice loader.
|
||||
*
|
||||
* Optionally, an implementation of {@link EntityLoaderInterface} can be
|
||||
* passed which optimizes the object loading for one of the Doctrine
|
||||
* mapper implementations.
|
||||
*
|
||||
* @param string $class The class name of the loaded objects
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ObjectManager $manager,
|
||||
string $class,
|
||||
private readonly ?IdReader $idReader = null,
|
||||
private readonly ?EntityLoaderInterface $objectLoader = null,
|
||||
) {
|
||||
if ($idReader && !$idReader->isSingleId()) {
|
||||
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->class = $manager->getClassMetadata($class)->getName();
|
||||
}
|
||||
|
||||
protected function loadChoices(): iterable
|
||||
{
|
||||
return $this->objectLoader
|
||||
? $this->objectLoader->getEntities()
|
||||
: $this->manager->getRepository($this->class)->findAll();
|
||||
}
|
||||
|
||||
protected function doLoadValuesForChoices(array $choices): array
|
||||
{
|
||||
// Optimize performance for single-field identifiers. We already
|
||||
// know that the IDs are used as values
|
||||
// Attention: This optimization does not check choices for existence
|
||||
if ($this->idReader) {
|
||||
throw new LogicException('Not defining the IdReader explicitly as a value callback when the query can be optimized is not supported.');
|
||||
}
|
||||
|
||||
return parent::doLoadValuesForChoices($choices);
|
||||
}
|
||||
|
||||
protected function doLoadChoicesForValues(array $values, ?callable $value): array
|
||||
{
|
||||
if ($this->idReader && null === $value) {
|
||||
throw new LogicException('Not defining the IdReader explicitly as a value callback when the query can be optimized is not supported.');
|
||||
}
|
||||
|
||||
$idReader = null;
|
||||
if (\is_array($value) && $value[0] instanceof IdReader) {
|
||||
$idReader = $value[0];
|
||||
} elseif ($value instanceof \Closure && ($rThis = (new \ReflectionFunction($value))->getClosureThis()) instanceof IdReader) {
|
||||
$idReader = $rThis;
|
||||
}
|
||||
|
||||
// Optimize performance in case we have an object loader and
|
||||
// a single-field identifier
|
||||
if ($idReader && $this->objectLoader) {
|
||||
$objects = [];
|
||||
$objectsById = [];
|
||||
|
||||
// Maintain order and indices from the given $values
|
||||
// An alternative approach to the following loop is to add the
|
||||
// "INDEX BY" clause to the Doctrine query in the loader,
|
||||
// but I'm not sure whether that's doable in a generic fashion.
|
||||
foreach ($this->objectLoader->getEntitiesByIds($idReader->getIdField(), $values) as $object) {
|
||||
$objectsById[$idReader->getIdValue($object)] = $object;
|
||||
}
|
||||
|
||||
foreach ($values as $i => $id) {
|
||||
if (isset($objectsById[$id])) {
|
||||
$objects[$i] = $objectsById[$id];
|
||||
}
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
return parent::doLoadChoicesForValues($values, $value);
|
||||
}
|
||||
}
|
||||
30
backend/vendor/symfony/doctrine-bridge/Form/ChoiceList/EntityLoaderInterface.php
vendored
Normal file
30
backend/vendor/symfony/doctrine-bridge/Form/ChoiceList/EntityLoaderInterface.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
|
||||
|
||||
/**
|
||||
* Custom loader for entities in the choice list.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
interface EntityLoaderInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array of entities that are valid choices in the corresponding choice list.
|
||||
*/
|
||||
public function getEntities(): array;
|
||||
|
||||
/**
|
||||
* Returns an array of entities matching the given identifiers.
|
||||
*/
|
||||
public function getEntitiesByIds(string $identifier, array $values): array;
|
||||
}
|
||||
109
backend/vendor/symfony/doctrine-bridge/Form/ChoiceList/IdReader.php
vendored
Normal file
109
backend/vendor/symfony/doctrine-bridge/Form/ChoiceList/IdReader.php
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
|
||||
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Symfony\Component\Form\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* A utility for reading object IDs.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class IdReader
|
||||
{
|
||||
private readonly bool $singleId;
|
||||
private readonly bool $intId;
|
||||
private readonly string $idField;
|
||||
private readonly ?self $associationIdReader;
|
||||
|
||||
public function __construct(
|
||||
private readonly ObjectManager $om,
|
||||
private readonly ClassMetadata $classMetadata,
|
||||
) {
|
||||
$ids = $classMetadata->getIdentifierFieldNames();
|
||||
$idType = $classMetadata->getTypeOfField(current($ids));
|
||||
|
||||
$singleId = 1 === \count($ids);
|
||||
$this->idField = current($ids);
|
||||
|
||||
// single field association are resolved, since the schema column could be an int
|
||||
if ($singleId && $classMetadata->hasAssociation($this->idField)) {
|
||||
$this->associationIdReader = new self($om, $om->getClassMetadata(
|
||||
$classMetadata->getAssociationTargetClass($this->idField)
|
||||
));
|
||||
|
||||
$singleId = $this->associationIdReader->isSingleId();
|
||||
$this->intId = $this->associationIdReader->isIntId();
|
||||
} else {
|
||||
$this->intId = $singleId && \in_array($idType, ['integer', 'smallint', 'bigint'], true);
|
||||
$this->associationIdReader = null;
|
||||
}
|
||||
|
||||
$this->singleId = $singleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the class has a single-column ID.
|
||||
*/
|
||||
public function isSingleId(): bool
|
||||
{
|
||||
return $this->singleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the class has a single-column integer ID.
|
||||
*/
|
||||
public function isIntId(): bool
|
||||
{
|
||||
return $this->intId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID value for an object.
|
||||
*
|
||||
* This method assumes that the object has a single-column ID.
|
||||
*/
|
||||
public function getIdValue(?object $object = null): string
|
||||
{
|
||||
if (!$object) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!$this->om->contains($object)) {
|
||||
throw new RuntimeException(\sprintf('Entity of type "%s" passed to the choice field must be managed. Maybe you forget to persist it in the entity manager?', get_debug_type($object)));
|
||||
}
|
||||
|
||||
$this->om->initializeObject($object);
|
||||
|
||||
$idValue = current($this->classMetadata->getIdentifierValues($object));
|
||||
|
||||
if ($this->associationIdReader) {
|
||||
$idValue = $this->associationIdReader->getIdValue($idValue);
|
||||
}
|
||||
|
||||
return (string) $idValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the ID field.
|
||||
*
|
||||
* This method assumes that the object has a single-column ID.
|
||||
*/
|
||||
public function getIdField(): string
|
||||
{
|
||||
return $this->idField;
|
||||
}
|
||||
}
|
||||
102
backend/vendor/symfony/doctrine-bridge/Form/ChoiceList/ORMQueryBuilderLoader.php
vendored
Normal file
102
backend/vendor/symfony/doctrine-bridge/Form/ChoiceList/ORMQueryBuilderLoader.php
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
|
||||
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Bridge\Doctrine\Types\AbstractUidType;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
/**
|
||||
* Loads entities using a {@link QueryBuilder} instance.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ORMQueryBuilderLoader implements EntityLoaderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly QueryBuilder $queryBuilder,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEntities(): array
|
||||
{
|
||||
return $this->queryBuilder->getQuery()->execute();
|
||||
}
|
||||
|
||||
public function getEntitiesByIds(string $identifier, array $values): array
|
||||
{
|
||||
if (null !== $this->queryBuilder->getMaxResults() || 0 < (int) $this->queryBuilder->getFirstResult()) {
|
||||
// an offset or a limit would apply on results including the where clause with submitted id values
|
||||
// that could make invalid choices valid
|
||||
$choices = [];
|
||||
$metadata = $this->queryBuilder->getEntityManager()->getClassMetadata(current($this->queryBuilder->getRootEntities()));
|
||||
|
||||
foreach ($this->getEntities() as $entity) {
|
||||
if (\in_array((string) current($metadata->getIdentifierValues($entity)), $values, true)) {
|
||||
$choices[] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
|
||||
$qb = clone $this->queryBuilder;
|
||||
$alias = current($qb->getRootAliases());
|
||||
$parameter = 'ORMQueryBuilderLoader_getEntitiesByIds_'.$identifier;
|
||||
$parameter = str_replace('.', '_', $parameter);
|
||||
$where = $qb->expr()->in($alias.'.'.$identifier, ':'.$parameter);
|
||||
|
||||
// Guess type
|
||||
$entity = current($qb->getRootEntities());
|
||||
$metadata = $qb->getEntityManager()->getClassMetadata($entity);
|
||||
if (\in_array($type = $metadata->getTypeOfField($identifier), ['integer', 'bigint', 'smallint'], true)) {
|
||||
$parameterType = ArrayParameterType::INTEGER;
|
||||
|
||||
// Filter out non-integer values (e.g. ""). If we don't, some
|
||||
// databases such as PostgreSQL fail.
|
||||
$values = array_values(array_filter($values, static fn ($v) => \is_string($v) && ctype_digit($v) || (string) $v === (string) (int) $v));
|
||||
} elseif (null !== $type && (\in_array($type, ['ulid', 'uuid', 'guid'], true) || (Type::hasType($type) && is_subclass_of(Type::getType($type), AbstractUidType::class)))) {
|
||||
$parameterType = ArrayParameterType::STRING;
|
||||
|
||||
// Like above, but we just filter out empty strings.
|
||||
$values = array_values(array_filter($values, fn ($v) => '' !== (string) $v));
|
||||
|
||||
// Convert values into right type
|
||||
if (Type::hasType($type)) {
|
||||
$doctrineType = Type::getType($type);
|
||||
$platform = $qb->getEntityManager()->getConnection()->getDatabasePlatform();
|
||||
foreach ($values as &$value) {
|
||||
try {
|
||||
$value = $doctrineType->convertToDatabaseValue($value, $platform);
|
||||
} catch (ConversionException $e) {
|
||||
throw new TransformationFailedException(\sprintf('Failed to transform "%s" into "%s".', $value, $type), 0, $e);
|
||||
}
|
||||
}
|
||||
unset($value);
|
||||
}
|
||||
} else {
|
||||
$parameterType = ArrayParameterType::STRING;
|
||||
}
|
||||
if (!$values) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $qb->andWhere($where)
|
||||
->getQuery()
|
||||
->setParameter($parameter, $values, $parameterType)
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
56
backend/vendor/symfony/doctrine-bridge/Form/DataTransformer/CollectionToArrayTransformer.php
vendored
Normal file
56
backend/vendor/symfony/doctrine-bridge/Form/DataTransformer/CollectionToArrayTransformer.php
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Form\DataTransformer;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\ReadableCollection;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @implements DataTransformerInterface<Collection|array, array>
|
||||
*/
|
||||
class CollectionToArrayTransformer implements DataTransformerInterface
|
||||
{
|
||||
public function transform(mixed $collection): mixed
|
||||
{
|
||||
if (null === $collection) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// For cases when the collection getter returns $collection->toArray()
|
||||
// in order to prevent modifications of the returned collection
|
||||
if (\is_array($collection)) {
|
||||
return $collection;
|
||||
}
|
||||
|
||||
if (!$collection instanceof ReadableCollection) {
|
||||
throw new TransformationFailedException(\sprintf('Expected a "%s" object.', ReadableCollection::class));
|
||||
}
|
||||
|
||||
return $collection->toArray();
|
||||
}
|
||||
|
||||
public function reverseTransform(mixed $array): Collection
|
||||
{
|
||||
if ('' === $array || null === $array) {
|
||||
$array = [];
|
||||
} else {
|
||||
$array = (array) $array;
|
||||
}
|
||||
|
||||
return new ArrayCollection($array);
|
||||
}
|
||||
}
|
||||
37
backend/vendor/symfony/doctrine-bridge/Form/DoctrineOrmExtension.php
vendored
Normal file
37
backend/vendor/symfony/doctrine-bridge/Form/DoctrineOrmExtension.php
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Form;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractExtension;
|
||||
use Symfony\Component\Form\FormTypeGuesserInterface;
|
||||
|
||||
class DoctrineOrmExtension extends AbstractExtension
|
||||
{
|
||||
public function __construct(
|
||||
protected ManagerRegistry $registry,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function loadTypes(): array
|
||||
{
|
||||
return [
|
||||
new EntityType($this->registry),
|
||||
];
|
||||
}
|
||||
|
||||
protected function loadTypeGuesser(): ?FormTypeGuesserInterface
|
||||
{
|
||||
return new DoctrineOrmTypeGuesser($this->registry);
|
||||
}
|
||||
}
|
||||
204
backend/vendor/symfony/doctrine-bridge/Form/DoctrineOrmTypeGuesser.php
vendored
Normal file
204
backend/vendor/symfony/doctrine-bridge/Form/DoctrineOrmTypeGuesser.php
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Form;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\Mapping\FieldMapping;
|
||||
use Doctrine\ORM\Mapping\JoinColumnMapping;
|
||||
use Doctrine\ORM\Mapping\MappingException as LegacyMappingException;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Doctrine\Persistence\Mapping\MappingException;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateIntervalType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TimeType;
|
||||
use Symfony\Component\Form\FormTypeGuesserInterface;
|
||||
use Symfony\Component\Form\Guess\Guess;
|
||||
use Symfony\Component\Form\Guess\TypeGuess;
|
||||
use Symfony\Component\Form\Guess\ValueGuess;
|
||||
|
||||
class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface
|
||||
{
|
||||
private array $cache = [];
|
||||
|
||||
public function __construct(
|
||||
protected ManagerRegistry $registry,
|
||||
) {
|
||||
}
|
||||
|
||||
public function guessType(string $class, string $property): ?TypeGuess
|
||||
{
|
||||
if (!$ret = $this->getMetadata($class)) {
|
||||
return new TypeGuess(TextType::class, [], Guess::LOW_CONFIDENCE);
|
||||
}
|
||||
|
||||
[$metadata, $name] = $ret;
|
||||
|
||||
if ($metadata->hasAssociation($property)) {
|
||||
$multiple = $metadata->isCollectionValuedAssociation($property);
|
||||
$mapping = $metadata->getAssociationMapping($property);
|
||||
|
||||
return new TypeGuess(EntityType::class, ['em' => $name, 'class' => $mapping['targetEntity'], 'multiple' => $multiple], Guess::HIGH_CONFIDENCE);
|
||||
}
|
||||
|
||||
return match ($metadata->getTypeOfField($property)) {
|
||||
'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,
|
||||
Types::DATETIMETZ_MUTABLE,
|
||||
'vardatetime' => new TypeGuess(DateTimeType::class, [], Guess::HIGH_CONFIDENCE),
|
||||
Types::DATETIME_IMMUTABLE,
|
||||
Types::DATETIMETZ_IMMUTABLE => new TypeGuess(DateTimeType::class, ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE),
|
||||
Types::DATEINTERVAL => new TypeGuess(DateIntervalType::class, [], Guess::HIGH_CONFIDENCE),
|
||||
Types::DATE_MUTABLE => new TypeGuess(DateType::class, [], Guess::HIGH_CONFIDENCE),
|
||||
Types::DATE_IMMUTABLE => new TypeGuess(DateType::class, ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE),
|
||||
Types::TIME_MUTABLE => new TypeGuess(TimeType::class, [], Guess::HIGH_CONFIDENCE),
|
||||
Types::TIME_IMMUTABLE => new TypeGuess(TimeType::class, ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE),
|
||||
Types::DECIMAL => new TypeGuess(NumberType::class, ['input' => 'string'], Guess::MEDIUM_CONFIDENCE),
|
||||
Types::FLOAT => new TypeGuess(NumberType::class, [], Guess::MEDIUM_CONFIDENCE),
|
||||
Types::INTEGER,
|
||||
Types::BIGINT,
|
||||
Types::SMALLINT => new TypeGuess(IntegerType::class, [], Guess::MEDIUM_CONFIDENCE),
|
||||
Types::STRING => new TypeGuess(TextType::class, [], Guess::MEDIUM_CONFIDENCE),
|
||||
Types::TEXT => new TypeGuess(TextareaType::class, [], Guess::MEDIUM_CONFIDENCE),
|
||||
default => new TypeGuess(TextType::class, [], Guess::LOW_CONFIDENCE),
|
||||
};
|
||||
}
|
||||
|
||||
public function guessRequired(string $class, string $property): ?ValueGuess
|
||||
{
|
||||
$classMetadatas = $this->getMetadata($class);
|
||||
|
||||
if (!$classMetadatas) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var ClassMetadataInfo $classMetadata */
|
||||
$classMetadata = $classMetadatas[0];
|
||||
|
||||
// Check whether the field exists and is nullable or not
|
||||
if (isset($classMetadata->fieldMappings[$property])) {
|
||||
if (!$classMetadata->isNullable($property) && Types::BOOLEAN !== $classMetadata->getTypeOfField($property)) {
|
||||
return new ValueGuess(true, Guess::HIGH_CONFIDENCE);
|
||||
}
|
||||
|
||||
return new ValueGuess(false, Guess::MEDIUM_CONFIDENCE);
|
||||
}
|
||||
|
||||
// Check whether the association exists, is a to-one association and its
|
||||
// join column is nullable or not
|
||||
if ($classMetadata->isAssociationWithSingleJoinColumn($property)) {
|
||||
$mapping = $classMetadata->getAssociationMapping($property);
|
||||
|
||||
if (null === self::getMappingValue($mapping['joinColumns'][0], 'nullable')) {
|
||||
// The "nullable" option defaults to true, in that case the
|
||||
// field should not be required.
|
||||
return new ValueGuess(false, Guess::HIGH_CONFIDENCE);
|
||||
}
|
||||
|
||||
return new ValueGuess(!self::getMappingValue($mapping['joinColumns'][0], 'nullable'), Guess::HIGH_CONFIDENCE);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function guessMaxLength(string $class, string $property): ?ValueGuess
|
||||
{
|
||||
$ret = $this->getMetadata($class);
|
||||
if ($ret && isset($ret[0]->fieldMappings[$property])) {
|
||||
$mapping = $ret[0]->getFieldMapping($property);
|
||||
|
||||
$length = $mapping instanceof FieldMapping ? $mapping->length : ($mapping['length'] ?? null);
|
||||
|
||||
if (null !== $length) {
|
||||
return new ValueGuess($length, Guess::HIGH_CONFIDENCE);
|
||||
}
|
||||
|
||||
if (\in_array($ret[0]->getTypeOfField($property), [Types::DECIMAL, Types::FLOAT], true)) {
|
||||
return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function guessPattern(string $class, string $property): ?ValueGuess
|
||||
{
|
||||
$ret = $this->getMetadata($class);
|
||||
if ($ret && isset($ret[0]->fieldMappings[$property])) {
|
||||
if (\in_array($ret[0]->getTypeOfField($property), [Types::DECIMAL, Types::FLOAT], true)) {
|
||||
return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of object
|
||||
*
|
||||
* @param class-string<T> $class
|
||||
*
|
||||
* @return array{0:ClassMetadata<T>, 1:string}|null
|
||||
*/
|
||||
protected function getMetadata(string $class): ?array
|
||||
{
|
||||
// normalize class name
|
||||
$class = self::getRealClass(ltrim($class, '\\'));
|
||||
|
||||
if (\array_key_exists($class, $this->cache)) {
|
||||
return $this->cache[$class];
|
||||
}
|
||||
|
||||
$this->cache[$class] = null;
|
||||
foreach ($this->registry->getManagers() as $name => $em) {
|
||||
try {
|
||||
return $this->cache[$class] = [$em->getClassMetadata($class), $name];
|
||||
} catch (MappingException) {
|
||||
// not an entity or mapped super class
|
||||
} catch (LegacyMappingException) {
|
||||
// not an entity or mapped super class, using Doctrine ORM 2.2
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function getRealClass(string $class): string
|
||||
{
|
||||
if (false === $pos = strrpos($class, '\\'.Proxy::MARKER.'\\')) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return substr($class, $pos + Proxy::MARKER_LENGTH + 2);
|
||||
}
|
||||
|
||||
private static function getMappingValue(array|JoinColumnMapping $mapping, string $key): mixed
|
||||
{
|
||||
if ($mapping instanceof JoinColumnMapping) {
|
||||
return $mapping->$key ?? null;
|
||||
}
|
||||
|
||||
return $mapping[$key] ?? null;
|
||||
}
|
||||
}
|
||||
52
backend/vendor/symfony/doctrine-bridge/Form/EventListener/MergeDoctrineCollectionListener.php
vendored
Normal file
52
backend/vendor/symfony/doctrine-bridge/Form/EventListener/MergeDoctrineCollectionListener.php
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Form\EventListener;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
|
||||
/**
|
||||
* Merge changes from the request to a Doctrine\Common\Collections\Collection instance.
|
||||
*
|
||||
* This works with ORM, MongoDB and CouchDB instances of the collection interface.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @see Collection
|
||||
*/
|
||||
class MergeDoctrineCollectionListener implements EventSubscriberInterface
|
||||
{
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
// Higher priority than core MergeCollectionListener so that this one
|
||||
// is called before
|
||||
return [
|
||||
FormEvents::SUBMIT => [
|
||||
['onSubmit', 5],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function onSubmit(FormEvent $event): void
|
||||
{
|
||||
$collection = $event->getForm()->getData();
|
||||
$data = $event->getData();
|
||||
|
||||
// If all items were removed, call clear which has a higher
|
||||
// performance on persistent collections
|
||||
if ($collection instanceof Collection && 0 === \count($data)) {
|
||||
$collection->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
256
backend/vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php
vendored
Normal file
256
backend/vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php
vendored
Normal file
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Form\Type;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader;
|
||||
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface;
|
||||
use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader;
|
||||
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
|
||||
use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
|
||||
use Symfony\Component\Form\Exception\RuntimeException;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
abstract class DoctrineType extends AbstractType implements ResetInterface
|
||||
{
|
||||
/**
|
||||
* @var IdReader[]
|
||||
*/
|
||||
private array $idReaders = [];
|
||||
|
||||
/**
|
||||
* @var EntityLoaderInterface[]
|
||||
*/
|
||||
private array $entityLoaders = [];
|
||||
|
||||
/**
|
||||
* Creates the label for a choice.
|
||||
*
|
||||
* For backwards compatibility, objects are cast to strings by default.
|
||||
*
|
||||
* @internal This method is public to be usable as callback. It should not
|
||||
* be used in user code.
|
||||
*/
|
||||
public static function createChoiceLabel(object $choice): string
|
||||
{
|
||||
return (string) $choice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the field name for a choice.
|
||||
*
|
||||
* This method is used to generate field names if the underlying object has
|
||||
* a single-column integer ID. In that case, the value of the field is
|
||||
* the ID of the object. That ID is also used as field name.
|
||||
*
|
||||
* @param string $value The choice value. Corresponds to the object's ID here.
|
||||
*
|
||||
* @internal This method is public to be usable as callback. It should not
|
||||
* be used in user code.
|
||||
*/
|
||||
public static function createChoiceName(object $choice, int|string $key, string $value): string
|
||||
{
|
||||
return str_replace('-', '_', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets important parts from QueryBuilder that will allow to cache its results.
|
||||
* For instance in ORM two query builders with an equal SQL string and
|
||||
* equal parameters are considered to be equal.
|
||||
*
|
||||
* @param object $queryBuilder A query builder, type declaration is not present here as there
|
||||
* is no common base class for the different implementations
|
||||
*
|
||||
* @internal This method is public to be usable as callback. It should not
|
||||
* be used in user code.
|
||||
*/
|
||||
public function getQueryBuilderPartsForCachingHash(object $queryBuilder): ?array
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function __construct(
|
||||
protected ManagerRegistry $registry,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
if ($options['multiple'] && interface_exists(Collection::class)) {
|
||||
$builder
|
||||
->addEventSubscriber(new MergeDoctrineCollectionListener())
|
||||
->addViewTransformer(new CollectionToArrayTransformer(), true)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$choiceLoader = function (Options $options) {
|
||||
// Unless the choices are given explicitly, load them on demand
|
||||
if (null === $options['choices']) {
|
||||
// If there is no QueryBuilder we can safely cache
|
||||
$vary = [$options['em'], $options['class']];
|
||||
|
||||
// also if concrete Type can return important QueryBuilder parts to generate
|
||||
// hash key we go for it as well, otherwise fallback on the instance
|
||||
if ($options['query_builder']) {
|
||||
$vary[] = $this->getQueryBuilderPartsForCachingHash($options['query_builder']) ?? $options['query_builder'];
|
||||
}
|
||||
|
||||
return ChoiceList::loader($this, new DoctrineChoiceLoader(
|
||||
$options['em'],
|
||||
$options['class'],
|
||||
$options['id_reader'],
|
||||
$this->getCachedEntityLoader(
|
||||
$options['em'],
|
||||
$options['query_builder'] ?? $options['em']->getRepository($options['class'])->createQueryBuilder('e'),
|
||||
$options['class'],
|
||||
$vary
|
||||
)
|
||||
), $vary);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
$choiceName = function (Options $options) {
|
||||
// If the object has a single-column, numeric ID, use that ID as
|
||||
// field name. We can only use numeric IDs as names, as we cannot
|
||||
// guarantee that a non-numeric ID contains a valid form name
|
||||
if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isIntId()) {
|
||||
return ChoiceList::fieldName($this, [__CLASS__, 'createChoiceName']);
|
||||
}
|
||||
|
||||
// Otherwise, an incrementing integer is used as name automatically
|
||||
return null;
|
||||
};
|
||||
|
||||
// The choices are always indexed by ID (see "choices" normalizer
|
||||
// and DoctrineChoiceLoader), unless the ID is composite. Then they
|
||||
// are indexed by an incrementing integer.
|
||||
// Use the ID/incrementing integer as choice value.
|
||||
$choiceValue = function (Options $options) {
|
||||
// If the entity has a single-column ID, use that ID as value
|
||||
if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isSingleId()) {
|
||||
return ChoiceList::value($this, $options['id_reader']->getIdValue(...), $options['id_reader']);
|
||||
}
|
||||
|
||||
// Otherwise, an incrementing integer is used as value automatically
|
||||
return null;
|
||||
};
|
||||
|
||||
$emNormalizer = function (Options $options, $em) {
|
||||
if (null !== $em) {
|
||||
if ($em instanceof ObjectManager) {
|
||||
return $em;
|
||||
}
|
||||
|
||||
return $this->registry->getManager($em);
|
||||
}
|
||||
|
||||
$em = $this->registry->getManagerForClass($options['class']);
|
||||
|
||||
if (null === $em) {
|
||||
throw new RuntimeException(\sprintf('Class "%s" seems not to be a managed Doctrine entity. Did you forget to map it?', $options['class']));
|
||||
}
|
||||
|
||||
return $em;
|
||||
};
|
||||
|
||||
// Invoke the query builder closure so that we can cache choice lists
|
||||
// for equal query builders
|
||||
$queryBuilderNormalizer = function (Options $options, $queryBuilder) {
|
||||
if (\is_callable($queryBuilder)) {
|
||||
$queryBuilder = $queryBuilder($options['em']->getRepository($options['class']));
|
||||
}
|
||||
|
||||
return $queryBuilder;
|
||||
};
|
||||
|
||||
// Set the "id_reader" option via the normalizer. This option is not
|
||||
// supposed to be set by the user.
|
||||
// The ID reader is a utility that is needed to read the object IDs
|
||||
// when generating the field values. The callback generating the
|
||||
// field values has no access to the object manager or the class
|
||||
// of the field, so we store that information in the reader.
|
||||
// The reader is cached so that two choice lists for the same class
|
||||
// (and hence with the same reader) can successfully be cached.
|
||||
$idReaderNormalizer = fn (Options $options) => $this->getCachedIdReader($options['em'], $options['class']);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'em' => null,
|
||||
'query_builder' => null,
|
||||
'choices' => null,
|
||||
'choice_loader' => $choiceLoader,
|
||||
'choice_label' => ChoiceList::label($this, [__CLASS__, 'createChoiceLabel']),
|
||||
'choice_name' => $choiceName,
|
||||
'choice_value' => $choiceValue,
|
||||
'id_reader' => null, // internal
|
||||
'choice_translation_domain' => false,
|
||||
]);
|
||||
|
||||
$resolver->setRequired(['class']);
|
||||
|
||||
$resolver->setNormalizer('em', $emNormalizer);
|
||||
$resolver->setNormalizer('query_builder', $queryBuilderNormalizer);
|
||||
$resolver->setNormalizer('id_reader', $idReaderNormalizer);
|
||||
|
||||
$resolver->setAllowedTypes('em', ['null', 'string', ObjectManager::class]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default loader object.
|
||||
*/
|
||||
abstract public function getLoader(ObjectManager $manager, object $queryBuilder, string $class): EntityLoaderInterface;
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->idReaders = [];
|
||||
$this->entityLoaders = [];
|
||||
}
|
||||
|
||||
private function getCachedIdReader(ObjectManager $manager, string $class): ?IdReader
|
||||
{
|
||||
$hash = CachingFactoryDecorator::generateHash([$manager, $class]);
|
||||
|
||||
if (isset($this->idReaders[$hash])) {
|
||||
return $this->idReaders[$hash];
|
||||
}
|
||||
|
||||
$idReader = new IdReader($manager, $manager->getClassMetadata($class));
|
||||
|
||||
// don't cache the instance for composite ids that cannot be optimized
|
||||
return $this->idReaders[$hash] = $idReader->isSingleId() ? $idReader : null;
|
||||
}
|
||||
|
||||
private function getCachedEntityLoader(ObjectManager $manager, object $queryBuilder, string $class, array $vary): EntityLoaderInterface
|
||||
{
|
||||
$hash = CachingFactoryDecorator::generateHash($vary);
|
||||
|
||||
return $this->entityLoaders[$hash] ??= $this->getLoader($manager, $queryBuilder, $class);
|
||||
}
|
||||
}
|
||||
93
backend/vendor/symfony/doctrine-bridge/Form/Type/EntityType.php
vendored
Normal file
93
backend/vendor/symfony/doctrine-bridge/Form/Type/EntityType.php
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Form\Type;
|
||||
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class EntityType extends DoctrineType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
// Invoke the query builder closure so that we can cache choice lists
|
||||
// for equal query builders
|
||||
$queryBuilderNormalizer = function (Options $options, $queryBuilder) {
|
||||
if (\is_callable($queryBuilder)) {
|
||||
$queryBuilder = $queryBuilder($options['em']->getRepository($options['class']));
|
||||
|
||||
if (null !== $queryBuilder && !$queryBuilder instanceof QueryBuilder) {
|
||||
throw new UnexpectedTypeException($queryBuilder, QueryBuilder::class);
|
||||
}
|
||||
}
|
||||
|
||||
return $queryBuilder;
|
||||
};
|
||||
|
||||
$resolver->setNormalizer('query_builder', $queryBuilderNormalizer);
|
||||
$resolver->setAllowedTypes('query_builder', ['null', 'callable', QueryBuilder::class]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default loader object.
|
||||
*
|
||||
* @param QueryBuilder $queryBuilder
|
||||
*/
|
||||
public function getLoader(ObjectManager $manager, object $queryBuilder, string $class): ORMQueryBuilderLoader
|
||||
{
|
||||
if (!$queryBuilder instanceof QueryBuilder) {
|
||||
throw new \TypeError(\sprintf('Expected an instance of "%s", but got "%s".', QueryBuilder::class, get_debug_type($queryBuilder)));
|
||||
}
|
||||
|
||||
return new ORMQueryBuilderLoader($queryBuilder);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'entity';
|
||||
}
|
||||
|
||||
/**
|
||||
* We consider two query builders with an equal SQL string and
|
||||
* equal parameters to be equal.
|
||||
*
|
||||
* @param QueryBuilder $queryBuilder
|
||||
*
|
||||
* @internal This method is public to be usable as callback. It should not
|
||||
* be used in user code.
|
||||
*/
|
||||
public function getQueryBuilderPartsForCachingHash(object $queryBuilder): ?array
|
||||
{
|
||||
if (!$queryBuilder instanceof QueryBuilder) {
|
||||
throw new \TypeError(\sprintf('Expected an instance of "%s", but got "%s".', QueryBuilder::class, get_debug_type($queryBuilder)));
|
||||
}
|
||||
|
||||
return [
|
||||
$queryBuilder->getQuery()->getSQL(),
|
||||
array_map($this->parameterToArray(...), $queryBuilder->getParameters()->toArray()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a query parameter to an array.
|
||||
*/
|
||||
private function parameterToArray(Parameter $parameter): array
|
||||
{
|
||||
return [$parameter->getName(), $parameter->getType(), $parameter->getValue()];
|
||||
}
|
||||
}
|
||||
43
backend/vendor/symfony/doctrine-bridge/IdGenerator/UlidGenerator.php
vendored
Normal file
43
backend/vendor/symfony/doctrine-bridge/IdGenerator/UlidGenerator.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\IdGenerator;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Id\AbstractIdGenerator;
|
||||
use Symfony\Component\Uid\Factory\UlidFactory;
|
||||
use Symfony\Component\Uid\Ulid;
|
||||
|
||||
final class UlidGenerator extends AbstractIdGenerator
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ?UlidFactory $factory = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* doctrine/orm < 2.11 BC layer.
|
||||
*/
|
||||
public function generate(EntityManager $em, $entity): Ulid
|
||||
{
|
||||
return $this->generateId($em, $entity);
|
||||
}
|
||||
|
||||
public function generateId(EntityManagerInterface $em, $entity): Ulid
|
||||
{
|
||||
if ($this->factory) {
|
||||
return $this->factory->create();
|
||||
}
|
||||
|
||||
return new Ulid();
|
||||
}
|
||||
}
|
||||
81
backend/vendor/symfony/doctrine-bridge/IdGenerator/UuidGenerator.php
vendored
Normal file
81
backend/vendor/symfony/doctrine-bridge/IdGenerator/UuidGenerator.php
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\IdGenerator;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Id\AbstractIdGenerator;
|
||||
use Symfony\Component\Uid\Factory\NameBasedUuidFactory;
|
||||
use Symfony\Component\Uid\Factory\RandomBasedUuidFactory;
|
||||
use Symfony\Component\Uid\Factory\TimeBasedUuidFactory;
|
||||
use Symfony\Component\Uid\Factory\UuidFactory;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
final class UuidGenerator extends AbstractIdGenerator
|
||||
{
|
||||
private readonly UuidFactory $protoFactory;
|
||||
private UuidFactory|NameBasedUuidFactory|RandomBasedUuidFactory|TimeBasedUuidFactory $factory;
|
||||
private ?string $entityGetter = null;
|
||||
|
||||
public function __construct(?UuidFactory $factory = null)
|
||||
{
|
||||
$this->protoFactory = $this->factory = $factory ?? new UuidFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* doctrine/orm < 2.11 BC layer.
|
||||
*/
|
||||
public function generate(EntityManager $em, $entity): Uuid
|
||||
{
|
||||
return $this->generateId($em, $entity);
|
||||
}
|
||||
|
||||
public function generateId(EntityManagerInterface $em, $entity): Uuid
|
||||
{
|
||||
if (null !== $this->entityGetter) {
|
||||
if (\is_callable([$entity, $this->entityGetter])) {
|
||||
return $this->factory->create($entity->{$this->entityGetter}());
|
||||
}
|
||||
|
||||
return $this->factory->create($entity->{$this->entityGetter});
|
||||
}
|
||||
|
||||
return $this->factory->create();
|
||||
}
|
||||
|
||||
public function nameBased(string $entityGetter, Uuid|string|null $namespace = null): static
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->factory = $clone->protoFactory->nameBased($namespace);
|
||||
$clone->entityGetter = $entityGetter;
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function randomBased(): static
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->factory = $clone->protoFactory->randomBased();
|
||||
$clone->entityGetter = null;
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function timeBased(Uuid|string|null $node = null): static
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->factory = $clone->protoFactory->timeBased($node);
|
||||
$clone->entityGetter = null;
|
||||
|
||||
return $clone;
|
||||
}
|
||||
}
|
||||
19
backend/vendor/symfony/doctrine-bridge/LICENSE
vendored
Normal file
19
backend/vendor/symfony/doctrine-bridge/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
120
backend/vendor/symfony/doctrine-bridge/ManagerRegistry.php
vendored
Normal file
120
backend/vendor/symfony/doctrine-bridge/ManagerRegistry.php
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine;
|
||||
|
||||
use Doctrine\Persistence\AbstractManagerRegistry;
|
||||
use ProxyManager\Proxy\GhostObjectInterface;
|
||||
use ProxyManager\Proxy\LazyLoadingInterface;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Symfony\Component\VarExporter\LazyObjectInterface;
|
||||
|
||||
/**
|
||||
* References Doctrine connections and entity/document managers.
|
||||
*
|
||||
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
|
||||
*/
|
||||
abstract class ManagerRegistry extends AbstractManagerRegistry
|
||||
{
|
||||
protected Container $container;
|
||||
|
||||
protected function getService($name): object
|
||||
{
|
||||
return $this->container->get($name);
|
||||
}
|
||||
|
||||
protected function resetService($name): void
|
||||
{
|
||||
if (!$this->container->initialized($name)) {
|
||||
return;
|
||||
}
|
||||
$manager = $this->container->get($name);
|
||||
|
||||
if ($manager instanceof LazyObjectInterface) {
|
||||
if (!$manager->resetLazyObject()) {
|
||||
throw new \LogicException(\sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (\PHP_VERSION_ID < 80400) {
|
||||
if (!$manager instanceof LazyLoadingInterface) {
|
||||
throw new \LogicException(\sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name));
|
||||
}
|
||||
trigger_deprecation('symfony/doctrine-bridge', '7.3', 'Support for proxy-manager is deprecated.');
|
||||
|
||||
if ($manager instanceof GhostObjectInterface) {
|
||||
throw new \LogicException('Resetting a lazy-ghost-object manager service is not supported.');
|
||||
}
|
||||
$manager->setProxyInitializer(\Closure::bind(
|
||||
function (&$wrappedInstance, LazyLoadingInterface $manager) use ($name) {
|
||||
$name = $this->aliases[$name] ?? $name;
|
||||
$wrappedInstance = match (true) {
|
||||
isset($this->fileMap[$name]) => $this->load($this->fileMap[$name], false),
|
||||
!$method = $this->methodMap[$name] ?? null => throw new \LogicException(\sprintf('The "%s" service is synthetic and cannot be reset.', $name)),
|
||||
(new \ReflectionMethod($this, $method))->isStatic() => $this->{$method}($this, false),
|
||||
default => $this->{$method}(false),
|
||||
};
|
||||
$manager->setProxyInitializer(null);
|
||||
|
||||
return true;
|
||||
},
|
||||
$this->container,
|
||||
Container::class
|
||||
));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$r = new \ReflectionClass($manager);
|
||||
|
||||
if ($r->isUninitializedLazyObject($manager)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$asProxy = $r->initializeLazyObject($manager) !== $manager;
|
||||
$initializer = \Closure::bind(
|
||||
function ($manager) use ($name, $asProxy) {
|
||||
$name = $this->aliases[$name] ?? $name;
|
||||
if ($asProxy) {
|
||||
$manager = false;
|
||||
}
|
||||
|
||||
$manager = match (true) {
|
||||
isset($this->fileMap[$name]) => $this->load($this->fileMap[$name], $manager),
|
||||
!$method = $this->methodMap[$name] ?? null => throw new \LogicException(\sprintf('The "%s" service is synthetic and cannot be reset.', $name)),
|
||||
(new \ReflectionMethod($this, $method))->isStatic() => $this->{$method}($this, $manager),
|
||||
default => $this->{$method}($manager),
|
||||
};
|
||||
|
||||
if ($asProxy) {
|
||||
return $manager;
|
||||
}
|
||||
},
|
||||
$this->container,
|
||||
Container::class
|
||||
);
|
||||
|
||||
try {
|
||||
if ($asProxy) {
|
||||
$r->resetAsLazyProxy($manager, $initializer);
|
||||
} else {
|
||||
$r->resetAsLazyGhost($manager, $initializer);
|
||||
}
|
||||
} catch (\Error $e) {
|
||||
if (__FILE__ !== $e->getFile()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
throw new \LogicException(\sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name), 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
backend/vendor/symfony/doctrine-bridge/Messenger/AbstractDoctrineMiddleware.php
vendored
Normal file
46
backend/vendor/symfony/doctrine-bridge/Messenger/AbstractDoctrineMiddleware.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Messenger;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
|
||||
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
|
||||
use Symfony\Component\Messenger\Middleware\StackInterface;
|
||||
|
||||
/**
|
||||
* @author Konstantin Myakshin <molodchick@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractDoctrineMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected ManagerRegistry $managerRegistry,
|
||||
protected ?string $entityManagerName = null,
|
||||
) {
|
||||
}
|
||||
|
||||
final public function handle(Envelope $envelope, StackInterface $stack): Envelope
|
||||
{
|
||||
try {
|
||||
$entityManager = $this->managerRegistry->getManager($this->entityManagerName);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new UnrecoverableMessageHandlingException($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return $this->handleForManager($entityManager, $envelope, $stack);
|
||||
}
|
||||
|
||||
abstract protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope;
|
||||
}
|
||||
55
backend/vendor/symfony/doctrine-bridge/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php
vendored
Normal file
55
backend/vendor/symfony/doctrine-bridge/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Messenger;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||
use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent;
|
||||
|
||||
/**
|
||||
* Clears entity managers between messages being handled to avoid outdated data.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@symfonycasts.com>
|
||||
*/
|
||||
class DoctrineClearEntityManagerWorkerSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ManagerRegistry $managerRegistry,
|
||||
) {
|
||||
}
|
||||
|
||||
public function onWorkerMessageHandled(): void
|
||||
{
|
||||
$this->clearEntityManagers();
|
||||
}
|
||||
|
||||
public function onWorkerMessageFailed(): void
|
||||
{
|
||||
$this->clearEntityManagers();
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
WorkerMessageHandledEvent::class => 'onWorkerMessageHandled',
|
||||
WorkerMessageFailedEvent::class => 'onWorkerMessageFailed',
|
||||
];
|
||||
}
|
||||
|
||||
private function clearEntityManagers(): void
|
||||
{
|
||||
foreach ($this->managerRegistry->getManagers() as $manager) {
|
||||
$manager->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
38
backend/vendor/symfony/doctrine-bridge/Messenger/DoctrineCloseConnectionMiddleware.php
vendored
Normal file
38
backend/vendor/symfony/doctrine-bridge/Messenger/DoctrineCloseConnectionMiddleware.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Messenger;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Middleware\StackInterface;
|
||||
use Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp;
|
||||
|
||||
/**
|
||||
* Closes connection and therefore saves number of connections.
|
||||
*
|
||||
* @author Fuong <insidestyles@gmail.com>
|
||||
*/
|
||||
class DoctrineCloseConnectionMiddleware extends AbstractDoctrineMiddleware
|
||||
{
|
||||
protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope
|
||||
{
|
||||
try {
|
||||
$connection = $entityManager->getConnection();
|
||||
|
||||
return $stack->next()->handle($envelope, $stack);
|
||||
} finally {
|
||||
if (null !== $envelope->last(ConsumedByWorkerStamp::class)) {
|
||||
$connection->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
backend/vendor/symfony/doctrine-bridge/Messenger/DoctrineOpenTransactionLoggerMiddleware.php
vendored
Normal file
57
backend/vendor/symfony/doctrine-bridge/Messenger/DoctrineOpenTransactionLoggerMiddleware.php
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Messenger;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Middleware\StackInterface;
|
||||
|
||||
/**
|
||||
* Middleware to log when transaction has been left open.
|
||||
*
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
*/
|
||||
class DoctrineOpenTransactionLoggerMiddleware extends AbstractDoctrineMiddleware
|
||||
{
|
||||
private bool $isHandling = false;
|
||||
|
||||
public function __construct(
|
||||
ManagerRegistry $managerRegistry,
|
||||
?string $entityManagerName = null,
|
||||
private readonly ?LoggerInterface $logger = null,
|
||||
) {
|
||||
parent::__construct($managerRegistry, $entityManagerName);
|
||||
}
|
||||
|
||||
protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope
|
||||
{
|
||||
if ($this->isHandling) {
|
||||
return $stack->next()->handle($envelope, $stack);
|
||||
}
|
||||
|
||||
$this->isHandling = true;
|
||||
$initialTransactionLevel = $entityManager->getConnection()->getTransactionNestingLevel();
|
||||
|
||||
try {
|
||||
return $stack->next()->handle($envelope, $stack);
|
||||
} finally {
|
||||
if ($entityManager->getConnection()->getTransactionNestingLevel() > $initialTransactionLevel) {
|
||||
$this->logger?->error('A handler opened a transaction but did not close it.', [
|
||||
'message' => $envelope->getMessage(),
|
||||
]);
|
||||
}
|
||||
$this->isHandling = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
backend/vendor/symfony/doctrine-bridge/Messenger/DoctrinePingConnectionMiddleware.php
vendored
Normal file
61
backend/vendor/symfony/doctrine-bridge/Messenger/DoctrinePingConnectionMiddleware.php
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Messenger;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Middleware\StackInterface;
|
||||
use Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp;
|
||||
|
||||
/**
|
||||
* Checks whether the connection is still open or reconnects otherwise.
|
||||
*
|
||||
* @author Fuong <insidestyles@gmail.com>
|
||||
*/
|
||||
class DoctrinePingConnectionMiddleware extends AbstractDoctrineMiddleware
|
||||
{
|
||||
protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope
|
||||
{
|
||||
if (null !== $envelope->last(ConsumedByWorkerStamp::class)) {
|
||||
$this->pingConnection($entityManager);
|
||||
}
|
||||
|
||||
return $stack->next()->handle($envelope, $stack);
|
||||
}
|
||||
|
||||
private function pingConnection(EntityManagerInterface $entityManager): void
|
||||
{
|
||||
$connection = $entityManager->getConnection();
|
||||
|
||||
try {
|
||||
$this->executeDummySql($connection);
|
||||
} catch (DBALException) {
|
||||
$connection->close();
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
56
backend/vendor/symfony/doctrine-bridge/Messenger/DoctrineTransactionMiddleware.php
vendored
Normal file
56
backend/vendor/symfony/doctrine-bridge/Messenger/DoctrineTransactionMiddleware.php
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Messenger;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||
use Symfony\Component\Messenger\Middleware\StackInterface;
|
||||
use Symfony\Component\Messenger\Stamp\HandledStamp;
|
||||
|
||||
/**
|
||||
* Wraps all handlers in a single doctrine transaction.
|
||||
*
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
class DoctrineTransactionMiddleware extends AbstractDoctrineMiddleware
|
||||
{
|
||||
protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope
|
||||
{
|
||||
$entityManager->getConnection()->beginTransaction();
|
||||
|
||||
$success = false;
|
||||
try {
|
||||
$envelope = $stack->next()->handle($envelope, $stack);
|
||||
$entityManager->flush();
|
||||
$entityManager->getConnection()->commit();
|
||||
|
||||
$success = true;
|
||||
|
||||
return $envelope;
|
||||
} catch (\Throwable $exception) {
|
||||
if ($exception instanceof HandlerFailedException) {
|
||||
// Remove all HandledStamp from the envelope so the retry will execute all handlers again.
|
||||
// When a handler fails, the queries of allegedly successful previous handlers just got rolled back.
|
||||
throw new HandlerFailedException($exception->getEnvelope()->withoutAll(HandledStamp::class), $exception->getWrappedExceptions());
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
} finally {
|
||||
$connection = $entityManager->getConnection();
|
||||
|
||||
if (!$success && $connection->isTransactionActive()) {
|
||||
$connection->rollBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
126
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/Connection.php
vendored
Normal file
126
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/Connection.php
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware;
|
||||
use Doctrine\DBAL\Driver\Result;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
|
||||
/**
|
||||
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Connection extends AbstractConnectionMiddleware
|
||||
{
|
||||
public function __construct(
|
||||
ConnectionInterface $connection,
|
||||
private readonly DebugDataHolder $debugDataHolder,
|
||||
private readonly ?Stopwatch $stopwatch,
|
||||
private readonly 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 {
|
||||
$affectedRows = parent::exec($sql);
|
||||
} finally {
|
||||
$query->stop();
|
||||
$this->stopwatch?->stop('doctrine');
|
||||
}
|
||||
|
||||
return $affectedRows;
|
||||
}
|
||||
|
||||
public function beginTransaction(): void
|
||||
{
|
||||
$query = new Query('"START TRANSACTION"');
|
||||
$this->debugDataHolder->addQuery($this->connectionName, $query);
|
||||
|
||||
$this->stopwatch?->start('doctrine', 'doctrine');
|
||||
$query->start();
|
||||
|
||||
try {
|
||||
parent::beginTransaction();
|
||||
} finally {
|
||||
$query->stop();
|
||||
$this->stopwatch?->stop('doctrine');
|
||||
}
|
||||
}
|
||||
|
||||
public function commit(): void
|
||||
{
|
||||
$query = new Query('"COMMIT"');
|
||||
$this->debugDataHolder->addQuery($this->connectionName, $query);
|
||||
|
||||
$this->stopwatch?->start('doctrine', 'doctrine');
|
||||
$query->start();
|
||||
|
||||
try {
|
||||
parent::commit();
|
||||
} finally {
|
||||
$query->stop();
|
||||
$this->stopwatch?->stop('doctrine');
|
||||
}
|
||||
}
|
||||
|
||||
public function rollBack(): void
|
||||
{
|
||||
$query = new Query('"ROLLBACK"');
|
||||
$this->debugDataHolder->addQuery($this->connectionName, $query);
|
||||
|
||||
$this->stopwatch?->start('doctrine', 'doctrine');
|
||||
$query->start();
|
||||
|
||||
try {
|
||||
parent::rollBack();
|
||||
} finally {
|
||||
$query->stop();
|
||||
$this->stopwatch?->stop('doctrine');
|
||||
}
|
||||
}
|
||||
}
|
||||
133
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/DBAL3/Connection.php
vendored
Normal file
133
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/DBAL3/Connection.php
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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 <laurent.voullemier@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Connection extends AbstractConnectionMiddleware
|
||||
{
|
||||
private int $nestingLevel = 0;
|
||||
|
||||
public function __construct(
|
||||
ConnectionInterface $connection,
|
||||
private readonly DebugDataHolder $debugDataHolder,
|
||||
private readonly ?Stopwatch $stopwatch,
|
||||
private readonly 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
76
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/DBAL3/Statement.php
vendored
Normal file
76
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/DBAL3/Statement.php
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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 <laurent.voullemier@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Statement extends AbstractStatementMiddleware
|
||||
{
|
||||
private readonly Query $query;
|
||||
|
||||
public function __construct(
|
||||
StatementInterface $statement,
|
||||
private readonly DebugDataHolder $debugDataHolder,
|
||||
private readonly string $connectionName,
|
||||
string $sql,
|
||||
private readonly ?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');
|
||||
}
|
||||
}
|
||||
}
|
||||
48
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/DebugDataHolder.php
vendored
Normal file
48
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/DebugDataHolder.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
|
||||
*/
|
||||
class DebugDataHolder
|
||||
{
|
||||
private array $data = [];
|
||||
|
||||
public function addQuery(string $connectionName, Query $query): void
|
||||
{
|
||||
$this->data[$connectionName][] = [
|
||||
'sql' => $query->getSql(),
|
||||
'params' => $query->getParams(),
|
||||
'types' => $query->getTypes(),
|
||||
'executionMS' => $query->getDuration(...), // stop() may not be called at this point
|
||||
];
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
foreach ($this->data as $connectionName => $dataForConn) {
|
||||
foreach ($dataForConn as $idx => $data) {
|
||||
if (\is_callable($data['executionMS'])) {
|
||||
$this->data[$connectionName][$idx]['executionMS'] = $data['executionMS']();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
||||
55
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/Driver.php
vendored
Normal file
55
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/Driver.php
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Doctrine\DBAL\Driver as DriverInterface;
|
||||
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
|
||||
/**
|
||||
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Driver extends AbstractDriverMiddleware
|
||||
{
|
||||
public function __construct(
|
||||
DriverInterface $driver,
|
||||
private readonly DebugDataHolder $debugDataHolder,
|
||||
private readonly ?Stopwatch $stopwatch,
|
||||
private readonly string $connectionName,
|
||||
) {
|
||||
parent::__construct($driver);
|
||||
}
|
||||
|
||||
public function connect(array $params): ConnectionInterface
|
||||
{
|
||||
$connection = parent::connect($params);
|
||||
|
||||
if ('void' !== (string) (new \ReflectionMethod(ConnectionInterface::class, 'commit'))->getReturnType()) {
|
||||
return new DBAL3\Connection(
|
||||
$connection,
|
||||
$this->debugDataHolder,
|
||||
$this->stopwatch,
|
||||
$this->connectionName
|
||||
);
|
||||
}
|
||||
|
||||
return new Connection(
|
||||
$connection,
|
||||
$this->debugDataHolder,
|
||||
$this->stopwatch,
|
||||
$this->connectionName
|
||||
);
|
||||
}
|
||||
}
|
||||
36
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/Middleware.php
vendored
Normal file
36
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/Middleware.php
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Doctrine\DBAL\Driver as DriverInterface;
|
||||
use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
|
||||
/**
|
||||
* Middleware to collect debug data.
|
||||
*
|
||||
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
|
||||
*/
|
||||
final class Middleware implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DebugDataHolder $debugDataHolder,
|
||||
private readonly ?Stopwatch $stopwatch,
|
||||
private readonly string $connectionName = 'default',
|
||||
) {
|
||||
}
|
||||
|
||||
public function wrap(DriverInterface $driver): DriverInterface
|
||||
{
|
||||
return new Driver($driver, $this->debugDataHolder, $this->stopwatch, $this->connectionName);
|
||||
}
|
||||
}
|
||||
113
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/Query.php
vendored
Normal file
113
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/Query.php
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
/**
|
||||
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Query
|
||||
{
|
||||
private array $params = [];
|
||||
|
||||
/** @var array<ParameterType|int> */
|
||||
private array $types = [];
|
||||
|
||||
private ?float $start = null;
|
||||
private ?float $duration = null;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $sql,
|
||||
) {
|
||||
}
|
||||
|
||||
public function start(): void
|
||||
{
|
||||
$this->start = microtime(true);
|
||||
}
|
||||
|
||||
public function stop(): void
|
||||
{
|
||||
if (null !== $this->start) {
|
||||
$this->duration = microtime(true) - $this->start;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
$this->params[$idx] = &$variable;
|
||||
$this->types[$idx] = $type;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
$this->params[$idx] = $value;
|
||||
$this->types[$idx] = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, string|int|float> $values
|
||||
*/
|
||||
public function setValues(array $values): void
|
||||
{
|
||||
foreach ($values as $param => $value) {
|
||||
$this->setValue($param, $value, ParameterType::STRING);
|
||||
}
|
||||
}
|
||||
|
||||
public function getSql(): string
|
||||
{
|
||||
return $this->sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string|int|float>
|
||||
*/
|
||||
public function getParams(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, int|ParameterType>
|
||||
*/
|
||||
public function getTypes(): array
|
||||
{
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query duration in seconds.
|
||||
*/
|
||||
public function getDuration(): ?float
|
||||
{
|
||||
return $this->duration;
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$copy = [];
|
||||
foreach ($this->params as $param => $valueOrVariable) {
|
||||
$copy[$param] = $valueOrVariable;
|
||||
}
|
||||
$this->params = $copy;
|
||||
}
|
||||
}
|
||||
64
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/Statement.php
vendored
Normal file
64
backend/vendor/symfony/doctrine-bridge/Middleware/Debug/Statement.php
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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;
|
||||
|
||||
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\Component\Stopwatch\Stopwatch;
|
||||
|
||||
/**
|
||||
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Statement extends AbstractStatementMiddleware
|
||||
{
|
||||
private Query $query;
|
||||
|
||||
public function __construct(
|
||||
StatementInterface $statement,
|
||||
private readonly DebugDataHolder $debugDataHolder,
|
||||
private readonly string $connectionName,
|
||||
string $sql,
|
||||
private readonly ?Stopwatch $stopwatch = null,
|
||||
) {
|
||||
parent::__construct($statement);
|
||||
|
||||
$this->query = new Query($sql);
|
||||
}
|
||||
|
||||
public function bindValue(int|string $param, mixed $value, ParameterType $type): void
|
||||
{
|
||||
$this->query->setValue($param, $value, $type);
|
||||
|
||||
parent::bindValue($param, $value, $type);
|
||||
}
|
||||
|
||||
public function execute(): ResultInterface
|
||||
{
|
||||
// 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();
|
||||
} finally {
|
||||
$query->stop();
|
||||
$this->stopwatch?->stop('doctrine');
|
||||
}
|
||||
}
|
||||
}
|
||||
40
backend/vendor/symfony/doctrine-bridge/Middleware/IdleConnection/Driver.php
vendored
Normal file
40
backend/vendor/symfony/doctrine-bridge/Middleware/IdleConnection/Driver.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Middleware\IdleConnection;
|
||||
|
||||
use Doctrine\DBAL\Driver as DriverInterface;
|
||||
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
|
||||
final class Driver extends AbstractDriverMiddleware
|
||||
{
|
||||
/**
|
||||
* @param \ArrayObject<string, int> $connectionExpiries
|
||||
*/
|
||||
public function __construct(
|
||||
DriverInterface $driver,
|
||||
private \ArrayObject $connectionExpiries,
|
||||
private readonly int $ttl,
|
||||
private readonly string $connectionName,
|
||||
) {
|
||||
parent::__construct($driver);
|
||||
}
|
||||
|
||||
public function connect(array $params): ConnectionInterface
|
||||
{
|
||||
$timestamp = time();
|
||||
$connection = parent::connect($params);
|
||||
$this->connectionExpiries[$this->connectionName] = $timestamp + $this->ttl;
|
||||
|
||||
return $connection;
|
||||
}
|
||||
}
|
||||
59
backend/vendor/symfony/doctrine-bridge/Middleware/IdleConnection/Listener.php
vendored
Normal file
59
backend/vendor/symfony/doctrine-bridge/Middleware/IdleConnection/Listener.php
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Middleware\IdleConnection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
final class Listener implements EventSubscriberInterface
|
||||
{
|
||||
/**
|
||||
* @param \ArrayObject<string, int> $connectionExpiries
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly \ArrayObject $connectionExpiries,
|
||||
private ContainerInterface $container,
|
||||
) {
|
||||
}
|
||||
|
||||
public function onKernelRequest(RequestEvent $event): void
|
||||
{
|
||||
if (HttpKernelInterface::MAIN_REQUEST !== $event->getRequestType()) {
|
||||
return;
|
||||
}
|
||||
$timestamp = time();
|
||||
|
||||
foreach ($this->connectionExpiries as $name => $expiry) {
|
||||
if ($timestamp >= $expiry) {
|
||||
// unset before so that we won't retry in case of any failure
|
||||
$this->connectionExpiries->offsetUnset($name);
|
||||
|
||||
try {
|
||||
$connection = $this->container->get("doctrine.dbal.{$name}_connection");
|
||||
$connection->close();
|
||||
} catch (\Exception) {
|
||||
// ignore exceptions to remain fail-safe
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
KernelEvents::REQUEST => ['onKernelRequest', 192], // before session listeners since they could use the DB
|
||||
];
|
||||
}
|
||||
}
|
||||
439
backend/vendor/symfony/doctrine-bridge/PropertyInfo/DoctrineExtractor.php
vendored
Normal file
439
backend/vendor/symfony/doctrine-bridge/PropertyInfo/DoctrineExtractor.php
vendored
Normal file
@@ -0,0 +1,439 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\PropertyInfo;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\BigIntType;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\EmbeddedClassMapping;
|
||||
use Doctrine\ORM\Mapping\FieldMapping;
|
||||
use Doctrine\ORM\Mapping\JoinColumnMapping;
|
||||
use Doctrine\ORM\Mapping\MappingException as OrmMappingException;
|
||||
use Doctrine\Persistence\Mapping\MappingException;
|
||||
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\Type as LegacyType;
|
||||
use Symfony\Component\TypeInfo\Type;
|
||||
use Symfony\Component\TypeInfo\TypeIdentifier;
|
||||
|
||||
/**
|
||||
* Extracts data using Doctrine ORM and ODM metadata.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getProperties(string $class, array $context = []): ?array
|
||||
{
|
||||
if (null === $metadata = $this->getMetadata($class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames());
|
||||
|
||||
if ($metadata instanceof ClassMetadata && $metadata->embeddedClasses) {
|
||||
$properties = array_filter($properties, fn ($property) => !str_contains($property, '.'));
|
||||
|
||||
$properties = array_merge($properties, array_keys($metadata->embeddedClasses));
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
public function getType(string $class, string $property, array $context = []): ?Type
|
||||
{
|
||||
if (null === $metadata = $this->getMetadata($class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($metadata->hasAssociation($property)) {
|
||||
$class = $metadata->getAssociationTargetClass($property);
|
||||
|
||||
if ($metadata->isSingleValuedAssociation($property)) {
|
||||
if ($metadata instanceof ClassMetadata) {
|
||||
$associationMapping = $metadata->getAssociationMapping($property);
|
||||
$nullable = $this->isAssociationNullable($associationMapping);
|
||||
} else {
|
||||
$nullable = false;
|
||||
}
|
||||
|
||||
return $nullable ? Type::nullable(Type::object($class)) : Type::object($class);
|
||||
}
|
||||
|
||||
$collectionKeyType = TypeIdentifier::INT;
|
||||
|
||||
if ($metadata instanceof ClassMetadata) {
|
||||
$associationMapping = $metadata->getAssociationMapping($property);
|
||||
|
||||
if (self::getMappingValue($associationMapping, 'indexBy')) {
|
||||
$subMetadata = $this->entityManager->getClassMetadata(self::getMappingValue($associationMapping, 'targetEntity'));
|
||||
|
||||
// Check if indexBy value is a property
|
||||
$fieldName = self::getMappingValue($associationMapping, 'indexBy');
|
||||
if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) {
|
||||
$fieldName = $subMetadata->getFieldForColumn(self::getMappingValue($associationMapping, 'indexBy'));
|
||||
// Not a property, maybe a column name?
|
||||
if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) {
|
||||
// Maybe the column name is the association join column?
|
||||
$associationMapping = $subMetadata->getAssociationMapping($fieldName);
|
||||
|
||||
$indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($fieldName);
|
||||
$subMetadata = $this->entityManager->getClassMetadata(self::getMappingValue($associationMapping, 'targetEntity'));
|
||||
|
||||
// Not a property, maybe a column name?
|
||||
if (null === ($typeOfField = $subMetadata->getTypeOfField($indexProperty))) {
|
||||
$fieldName = $subMetadata->getFieldForColumn($indexProperty);
|
||||
$typeOfField = $subMetadata->getTypeOfField($fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$collectionKeyType = $this->getTypeIdentifier($typeOfField)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Type::collection(Type::object(Collection::class), Type::object($class), Type::builtin($collectionKeyType));
|
||||
}
|
||||
|
||||
if ($metadata instanceof ClassMetadata && isset($metadata->embeddedClasses[$property])) {
|
||||
return Type::object(self::getMappingValue($metadata->embeddedClasses[$property], 'class'));
|
||||
}
|
||||
|
||||
if (!$metadata->hasField($property)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$typeOfField = $metadata->getTypeOfField($property);
|
||||
|
||||
if (!$typeIdentifier = $this->getTypeIdentifier($typeOfField)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nullable = $metadata instanceof ClassMetadata && $metadata->isNullable($property);
|
||||
|
||||
// DBAL 4 has a special fallback strategy for BINGINT (int -> string)
|
||||
if (Types::BIGINT === $typeOfField && !method_exists(BigIntType::class, 'getName')) {
|
||||
return $nullable ? Type::nullable(Type::union(Type::int(), Type::string())) : Type::union(Type::int(), Type::string());
|
||||
}
|
||||
|
||||
$enumType = null;
|
||||
|
||||
if (null !== $enumClass = self::getMappingValue($metadata->getFieldMapping($property), 'enumType') ?? null) {
|
||||
$enumType = $nullable ? Type::nullable(Type::enum($enumClass)) : Type::enum($enumClass);
|
||||
}
|
||||
|
||||
$builtinType = $nullable ? Type::nullable(Type::builtin($typeIdentifier)) : Type::builtin($typeIdentifier);
|
||||
|
||||
return match ($typeIdentifier) {
|
||||
TypeIdentifier::OBJECT => match ($typeOfField) {
|
||||
Types::DATE_MUTABLE, Types::DATETIME_MUTABLE, Types::DATETIMETZ_MUTABLE, 'vardatetime', Types::TIME_MUTABLE => $nullable ? Type::nullable(Type::object(\DateTime::class)) : Type::object(\DateTime::class),
|
||||
Types::DATE_IMMUTABLE, Types::DATETIME_IMMUTABLE, Types::DATETIMETZ_IMMUTABLE, Types::TIME_IMMUTABLE => $nullable ? Type::nullable(Type::object(\DateTimeImmutable::class)) : Type::object(\DateTimeImmutable::class),
|
||||
Types::DATEINTERVAL => $nullable ? Type::nullable(Type::object(\DateInterval::class)) : Type::object(\DateInterval::class),
|
||||
default => $builtinType,
|
||||
},
|
||||
TypeIdentifier::ARRAY => match ($typeOfField) {
|
||||
'array', 'json_array' => $enumType ? null : ($nullable ? Type::nullable(Type::array()) : Type::array()),
|
||||
Types::SIMPLE_ARRAY => $nullable ? Type::nullable(Type::list($enumType ?? Type::string())) : Type::list($enumType ?? Type::string()),
|
||||
default => $builtinType,
|
||||
},
|
||||
TypeIdentifier::INT, TypeIdentifier::STRING => $enumType ? $enumType : $builtinType,
|
||||
default => $builtinType,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Symfony 7.3, use "getType" instead
|
||||
*/
|
||||
public function getTypes(string $class, string $property, array $context = []): ?array
|
||||
{
|
||||
trigger_deprecation('symfony/property-info', '7.3', 'The "%s()" method is deprecated, use "%s::getType()" instead.', __METHOD__, self::class);
|
||||
|
||||
if (null === $metadata = $this->getMetadata($class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($metadata->hasAssociation($property)) {
|
||||
$class = $metadata->getAssociationTargetClass($property);
|
||||
|
||||
if ($metadata->isSingleValuedAssociation($property)) {
|
||||
if ($metadata instanceof ClassMetadata) {
|
||||
$associationMapping = $metadata->getAssociationMapping($property);
|
||||
|
||||
$nullable = $this->isAssociationNullable($associationMapping);
|
||||
} else {
|
||||
$nullable = false;
|
||||
}
|
||||
|
||||
return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, $class)];
|
||||
}
|
||||
|
||||
$collectionKeyType = LegacyType::BUILTIN_TYPE_INT;
|
||||
|
||||
if ($metadata instanceof ClassMetadata) {
|
||||
$associationMapping = $metadata->getAssociationMapping($property);
|
||||
|
||||
if (self::getMappingValue($associationMapping, 'indexBy')) {
|
||||
$subMetadata = $this->entityManager->getClassMetadata(self::getMappingValue($associationMapping, 'targetEntity'));
|
||||
|
||||
// Check if indexBy value is a property
|
||||
$fieldName = self::getMappingValue($associationMapping, 'indexBy');
|
||||
if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) {
|
||||
$fieldName = $subMetadata->getFieldForColumn(self::getMappingValue($associationMapping, 'indexBy'));
|
||||
// Not a property, maybe a column name?
|
||||
if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) {
|
||||
// Maybe the column name is the association join column?
|
||||
$associationMapping = $subMetadata->getAssociationMapping($fieldName);
|
||||
|
||||
$indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($fieldName);
|
||||
$subMetadata = $this->entityManager->getClassMetadata(self::getMappingValue($associationMapping, 'targetEntity'));
|
||||
|
||||
// Not a property, maybe a column name?
|
||||
if (null === ($typeOfField = $subMetadata->getTypeOfField($indexProperty))) {
|
||||
$fieldName = $subMetadata->getFieldForColumn($indexProperty);
|
||||
$typeOfField = $subMetadata->getTypeOfField($fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$collectionKeyType = $this->getTypeIdentifierLegacy($typeOfField)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [new LegacyType(
|
||||
LegacyType::BUILTIN_TYPE_OBJECT,
|
||||
false,
|
||||
Collection::class,
|
||||
true,
|
||||
new LegacyType($collectionKeyType),
|
||||
new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, $class)
|
||||
)];
|
||||
}
|
||||
|
||||
if ($metadata instanceof ClassMetadata && isset($metadata->embeddedClasses[$property])) {
|
||||
return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, self::getMappingValue($metadata->embeddedClasses[$property], 'class'))];
|
||||
}
|
||||
|
||||
if ($metadata->hasField($property)) {
|
||||
$typeOfField = $metadata->getTypeOfField($property);
|
||||
|
||||
if (!$builtinType = $this->getTypeIdentifierLegacy($typeOfField)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nullable = $metadata instanceof ClassMetadata && $metadata->isNullable($property);
|
||||
|
||||
// DBAL 4 has a special fallback strategy for BINGINT (int -> string)
|
||||
if (Types::BIGINT === $typeOfField && !method_exists(BigIntType::class, 'getName')) {
|
||||
return [
|
||||
new LegacyType(LegacyType::BUILTIN_TYPE_INT, $nullable),
|
||||
new LegacyType(LegacyType::BUILTIN_TYPE_STRING, $nullable),
|
||||
];
|
||||
}
|
||||
|
||||
$enumType = null;
|
||||
if (null !== $enumClass = self::getMappingValue($metadata->getFieldMapping($property), 'enumType') ?? null) {
|
||||
$enumType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, $enumClass);
|
||||
}
|
||||
|
||||
switch ($builtinType) {
|
||||
case LegacyType::BUILTIN_TYPE_OBJECT:
|
||||
switch ($typeOfField) {
|
||||
case Types::DATE_MUTABLE:
|
||||
case Types::DATETIME_MUTABLE:
|
||||
case Types::DATETIMETZ_MUTABLE:
|
||||
case 'vardatetime':
|
||||
case Types::TIME_MUTABLE:
|
||||
return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime')];
|
||||
|
||||
case Types::DATE_IMMUTABLE:
|
||||
case Types::DATETIME_IMMUTABLE:
|
||||
case Types::DATETIMETZ_IMMUTABLE:
|
||||
case Types::TIME_IMMUTABLE:
|
||||
return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, 'DateTimeImmutable')];
|
||||
|
||||
case Types::DATEINTERVAL:
|
||||
return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, 'DateInterval')];
|
||||
}
|
||||
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_ARRAY:
|
||||
switch ($typeOfField) {
|
||||
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;
|
||||
}
|
||||
|
||||
return [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true)];
|
||||
|
||||
case Types::SIMPLE_ARRAY:
|
||||
return [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $enumType ?? new LegacyType(LegacyType::BUILTIN_TYPE_STRING))];
|
||||
}
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_INT:
|
||||
case LegacyType::BUILTIN_TYPE_STRING:
|
||||
if ($enumType) {
|
||||
return [$enumType];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return [new LegacyType($builtinType, $nullable)];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isReadable(string $class, string $property, array $context = []): ?bool
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isWritable(string $class, string $property, array $context = []): ?bool
|
||||
{
|
||||
if (
|
||||
null === ($metadata = $this->getMetadata($class))
|
||||
|| ClassMetadata::GENERATOR_TYPE_NONE === $metadata->generatorType
|
||||
|| !\in_array($property, $metadata->getIdentifierFieldNames(), true)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getMetadata(string $class): ?ClassMetadata
|
||||
{
|
||||
try {
|
||||
return $this->entityManager->getClassMetadata($class);
|
||||
} catch (MappingException|OrmMappingException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an association is nullable.
|
||||
*
|
||||
* @param array<string, mixed>|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 $associationMapping): bool
|
||||
{
|
||||
if (self::getMappingValue($associationMapping, 'id')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!self::getMappingValue($associationMapping, 'joinColumns')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$joinColumns = self::getMappingValue($associationMapping, 'joinColumns');
|
||||
foreach ($joinColumns as $joinColumn) {
|
||||
if (false === self::getMappingValue($joinColumn, 'nullable')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the corresponding built-in PHP type.
|
||||
*/
|
||||
private function getTypeIdentifier(string $doctrineType): ?TypeIdentifier
|
||||
{
|
||||
return match ($doctrineType) {
|
||||
Types::SMALLINT,
|
||||
Types::INTEGER => TypeIdentifier::INT,
|
||||
Types::FLOAT => TypeIdentifier::FLOAT,
|
||||
Types::BIGINT,
|
||||
Types::STRING,
|
||||
Types::TEXT,
|
||||
Types::GUID,
|
||||
Types::DECIMAL => TypeIdentifier::STRING,
|
||||
Types::BOOLEAN => TypeIdentifier::BOOL,
|
||||
Types::BLOB,
|
||||
Types::BINARY => TypeIdentifier::RESOURCE,
|
||||
'object', // DBAL < 4
|
||||
Types::DATE_MUTABLE,
|
||||
Types::DATETIME_MUTABLE,
|
||||
Types::DATETIMETZ_MUTABLE,
|
||||
'vardatetime',
|
||||
Types::TIME_MUTABLE,
|
||||
Types::DATE_IMMUTABLE,
|
||||
Types::DATETIME_IMMUTABLE,
|
||||
Types::DATETIMETZ_IMMUTABLE,
|
||||
Types::TIME_IMMUTABLE,
|
||||
Types::DATEINTERVAL => TypeIdentifier::OBJECT,
|
||||
'array', // DBAL < 4
|
||||
'json_array', // DBAL < 3
|
||||
Types::SIMPLE_ARRAY => TypeIdentifier::ARRAY,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
private function getTypeIdentifierLegacy(string $doctrineType): ?string
|
||||
{
|
||||
return match ($doctrineType) {
|
||||
Types::SMALLINT,
|
||||
Types::INTEGER => LegacyType::BUILTIN_TYPE_INT,
|
||||
Types::FLOAT => LegacyType::BUILTIN_TYPE_FLOAT,
|
||||
Types::BIGINT,
|
||||
Types::STRING,
|
||||
Types::TEXT,
|
||||
Types::GUID,
|
||||
Types::DECIMAL => LegacyType::BUILTIN_TYPE_STRING,
|
||||
Types::BOOLEAN => LegacyType::BUILTIN_TYPE_BOOL,
|
||||
Types::BLOB,
|
||||
Types::BINARY => LegacyType::BUILTIN_TYPE_RESOURCE,
|
||||
'object', // DBAL < 4
|
||||
Types::DATE_MUTABLE,
|
||||
Types::DATETIME_MUTABLE,
|
||||
Types::DATETIMETZ_MUTABLE,
|
||||
'vardatetime',
|
||||
Types::TIME_MUTABLE,
|
||||
Types::DATE_IMMUTABLE,
|
||||
Types::DATETIME_IMMUTABLE,
|
||||
Types::DATETIMETZ_IMMUTABLE,
|
||||
Types::TIME_IMMUTABLE,
|
||||
Types::DATEINTERVAL => LegacyType::BUILTIN_TYPE_OBJECT,
|
||||
'array', // DBAL < 4
|
||||
'json_array', // DBAL < 3
|
||||
Types::SIMPLE_ARRAY => LegacyType::BUILTIN_TYPE_ARRAY,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
private static function getMappingValue(array|AssociationMapping|EmbeddedClassMapping|FieldMapping|JoinColumnMapping $mapping, string $key): mixed
|
||||
{
|
||||
if ($mapping instanceof AssociationMapping || $mapping instanceof EmbeddedClassMapping || $mapping instanceof FieldMapping || $mapping instanceof JoinColumnMapping) {
|
||||
return $mapping->$key ?? null;
|
||||
}
|
||||
|
||||
return $mapping[$key] ?? null;
|
||||
}
|
||||
}
|
||||
13
backend/vendor/symfony/doctrine-bridge/README.md
vendored
Normal file
13
backend/vendor/symfony/doctrine-bridge/README.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
Doctrine Bridge
|
||||
===============
|
||||
|
||||
The Doctrine bridge provides integration for
|
||||
[Doctrine](http://www.doctrine-project.org/) with various Symfony components.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
||||
107
backend/vendor/symfony/doctrine-bridge/SchemaListener/AbstractSchemaListener.php
vendored
Normal file
107
backend/vendor/symfony/doctrine-bridge/SchemaListener/AbstractSchemaListener.php
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\SchemaListener;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Exception\ConnectionException;
|
||||
use Doctrine\DBAL\Exception\DatabaseObjectExistsException;
|
||||
use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
|
||||
use Doctrine\DBAL\Schema\Name\Identifier;
|
||||
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
|
||||
use Doctrine\DBAL\Schema\NamedObject;
|
||||
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
|
||||
|
||||
abstract class AbstractSchemaListener
|
||||
{
|
||||
abstract public function postGenerateSchema(GenerateSchemaEventArgs $event): void;
|
||||
|
||||
protected function filterSchemaChanges(Schema $schema, Connection $connection, callable $configurator): void
|
||||
{
|
||||
$filter = $connection->getConfiguration()->getSchemaAssetsFilter();
|
||||
|
||||
if (null === $filter) {
|
||||
$configurator();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$getNames = static fn ($array) => array_map(static fn ($object) => $object instanceof NamedObject ? $object->getObjectName()->toString() : $object->getName(), $array);
|
||||
$previousTableNames = $getNames($schema->getTables());
|
||||
$previousSequenceNames = $getNames($schema->getSequences());
|
||||
|
||||
$configurator();
|
||||
|
||||
foreach (array_diff($getNames($schema->getTables()), $previousTableNames) as $addedTable) {
|
||||
if (!$filter($addedTable)) {
|
||||
$schema->dropTable($addedTable);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_diff($getNames($schema->getSequences()), $previousSequenceNames) as $addedSequence) {
|
||||
if (!$filter($addedSequence)) {
|
||||
$schema->dropSequence($addedSequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure(\Closure(string): mixed): bool
|
||||
*/
|
||||
protected function getIsSameDatabaseChecker(Connection $connection): \Closure
|
||||
{
|
||||
return static function (\Closure $exec) use ($connection): bool {
|
||||
$schemaManager = method_exists($connection, 'createSchemaManager') ? $connection->createSchemaManager() : $connection->getSchemaManager();
|
||||
$key = bin2hex(random_bytes(7));
|
||||
$table = new Table('schema_subscriber_check_');
|
||||
$table->addColumn('id', Types::INTEGER)
|
||||
->setAutoincrement(true)
|
||||
->setNotnull(true);
|
||||
$table->addColumn('random_key', Types::STRING)
|
||||
->setLength(14)
|
||||
->setNotNull(true)
|
||||
;
|
||||
|
||||
if (class_exists(PrimaryKeyConstraint::class)) {
|
||||
$table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
|
||||
} else {
|
||||
$table->setPrimaryKey(['id']);
|
||||
}
|
||||
|
||||
try {
|
||||
$schemaManager->createTable($table);
|
||||
} catch (DatabaseObjectExistsException) {
|
||||
}
|
||||
|
||||
$connection->executeStatement('INSERT INTO schema_subscriber_check_ (random_key) VALUES (:key)', ['key' => $key], ['key' => Types::STRING]);
|
||||
|
||||
try {
|
||||
$exec(\sprintf('DELETE FROM schema_subscriber_check_ WHERE random_key = %s', $connection->getDatabasePlatform()->quoteStringLiteral($key)));
|
||||
} catch (DatabaseObjectNotFoundException|ConnectionException|\PDOException) {
|
||||
}
|
||||
|
||||
try {
|
||||
return !$connection->executeStatement('DELETE FROM schema_subscriber_check_ WHERE random_key = :key', ['key' => $key], ['key' => Types::STRING]);
|
||||
} finally {
|
||||
if (!$connection->executeQuery('SELECT count(id) FROM schema_subscriber_check_')->fetchOne()) {
|
||||
try {
|
||||
$schemaManager->dropTable('schema_subscriber_check_');
|
||||
} catch (DatabaseObjectNotFoundException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
43
backend/vendor/symfony/doctrine-bridge/SchemaListener/DoctrineDbalCacheAdapterSchemaListener.php
vendored
Normal file
43
backend/vendor/symfony/doctrine-bridge/SchemaListener/DoctrineDbalCacheAdapterSchemaListener.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\SchemaListener;
|
||||
|
||||
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
|
||||
use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
|
||||
|
||||
/**
|
||||
* Automatically adds the cache table needed for the DoctrineDbalAdapter of
|
||||
* the Cache component.
|
||||
*/
|
||||
class DoctrineDbalCacheAdapterSchemaListener extends AbstractSchemaListener
|
||||
{
|
||||
/**
|
||||
* @param iterable<mixed, DoctrineDbalAdapter> $dbalAdapters
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly iterable $dbalAdapters,
|
||||
) {
|
||||
}
|
||||
|
||||
public function postGenerateSchema(GenerateSchemaEventArgs $event): void
|
||||
{
|
||||
$connection = $event->getEntityManager()->getConnection();
|
||||
$schema = $event->getSchema();
|
||||
|
||||
foreach ($this->dbalAdapters as $dbalAdapter) {
|
||||
$isSameDatabaseChecker = $this->getIsSameDatabaseChecker($connection);
|
||||
$this->filterSchemaChanges($schema, $connection, static function () use ($dbalAdapter, $schema, $connection, $isSameDatabaseChecker) {
|
||||
$dbalAdapter->configureSchema($schema, $connection, $isSameDatabaseChecker);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
44
backend/vendor/symfony/doctrine-bridge/SchemaListener/LockStoreSchemaListener.php
vendored
Normal file
44
backend/vendor/symfony/doctrine-bridge/SchemaListener/LockStoreSchemaListener.php
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\SchemaListener;
|
||||
|
||||
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
|
||||
use Symfony\Component\Lock\PersistingStoreInterface;
|
||||
use Symfony\Component\Lock\Store\DoctrineDbalStore;
|
||||
|
||||
final class LockStoreSchemaListener extends AbstractSchemaListener
|
||||
{
|
||||
/**
|
||||
* @param iterable<mixed, PersistingStoreInterface> $stores
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly iterable $stores,
|
||||
) {
|
||||
}
|
||||
|
||||
public function postGenerateSchema(GenerateSchemaEventArgs $event): void
|
||||
{
|
||||
$connection = $event->getEntityManager()->getConnection();
|
||||
$schema = $event->getSchema();
|
||||
|
||||
foreach ($this->stores as $store) {
|
||||
if (!$store instanceof DoctrineDbalStore) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isSameDatabaseChecker = $this->getIsSameDatabaseChecker($connection);
|
||||
$this->filterSchemaChanges($schema, $connection, static function () use ($store, $schema, $isSameDatabaseChecker) {
|
||||
$store->configureSchema($schema, $isSameDatabaseChecker);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\SchemaListener;
|
||||
|
||||
use Doctrine\DBAL\Event\SchemaCreateTableEventArgs;
|
||||
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
|
||||
use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport;
|
||||
use Symfony\Component\Messenger\Transport\TransportInterface;
|
||||
|
||||
/**
|
||||
* Automatically adds any required database tables to the Doctrine Schema.
|
||||
*/
|
||||
class MessengerTransportDoctrineSchemaListener extends AbstractSchemaListener
|
||||
{
|
||||
private const PROCESSING_TABLE_FLAG = self::class.':processing';
|
||||
|
||||
/**
|
||||
* @param iterable<mixed, TransportInterface> $transports
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly iterable $transports,
|
||||
) {
|
||||
}
|
||||
|
||||
public function postGenerateSchema(GenerateSchemaEventArgs $event): void
|
||||
{
|
||||
$connection = $event->getEntityManager()->getConnection();
|
||||
$schema = $event->getSchema();
|
||||
|
||||
foreach ($this->transports as $transport) {
|
||||
if (!$transport instanceof DoctrineTransport) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isSameDatabaseChecker = $this->getIsSameDatabaseChecker($connection);
|
||||
$this->filterSchemaChanges($schema, $connection, static function () use ($transport, $schema, $connection, $isSameDatabaseChecker) {
|
||||
$transport->configureSchema($schema, $connection, $isSameDatabaseChecker);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function onSchemaCreateTable(SchemaCreateTableEventArgs $event): void
|
||||
{
|
||||
$table = $event->getTable();
|
||||
|
||||
// if this method triggers a nested create table below, allow Doctrine to work like normal
|
||||
if ($table->hasOption(self::PROCESSING_TABLE_FLAG)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->transports as $transport) {
|
||||
if (!$transport instanceof DoctrineTransport) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$extraSql = $transport->getExtraSetupSqlForTable($table)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// avoid this same listener from creating a loop on this table
|
||||
$table->addOption(self::PROCESSING_TABLE_FLAG, true);
|
||||
$createTableSql = $event->getPlatform()->getCreateTableSQL($table);
|
||||
|
||||
/*
|
||||
* Add all the SQL needed to create the table and tell Doctrine
|
||||
* to "preventDefault" so that only our SQL is used. This is
|
||||
* the only way to inject some extra SQL.
|
||||
*/
|
||||
$event->addSql($createTableSql);
|
||||
foreach ($extraSql as $sql) {
|
||||
$event->addSql($sql);
|
||||
}
|
||||
$event->preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
backend/vendor/symfony/doctrine-bridge/SchemaListener/PdoSessionHandlerSchemaListener.php
vendored
Normal file
43
backend/vendor/symfony/doctrine-bridge/SchemaListener/PdoSessionHandlerSchemaListener.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\SchemaListener;
|
||||
|
||||
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
|
||||
|
||||
final class PdoSessionHandlerSchemaListener extends AbstractSchemaListener
|
||||
{
|
||||
private PdoSessionHandler $sessionHandler;
|
||||
|
||||
public function __construct(\SessionHandlerInterface $sessionHandler)
|
||||
{
|
||||
if ($sessionHandler instanceof PdoSessionHandler) {
|
||||
$this->sessionHandler = $sessionHandler;
|
||||
}
|
||||
}
|
||||
|
||||
public function postGenerateSchema(GenerateSchemaEventArgs $event): void
|
||||
{
|
||||
if (!isset($this->sessionHandler)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection = $event->getEntityManager()->getConnection();
|
||||
$schema = $event->getSchema();
|
||||
$isSameDatabaseChecker = $this->getIsSameDatabaseChecker($connection);
|
||||
$sessionHandler = $this->sessionHandler;
|
||||
|
||||
$this->filterSchemaChanges($schema, $connection, static function () use ($sessionHandler, $schema, $isSameDatabaseChecker) {
|
||||
$sessionHandler->configureSchema($schema, $isSameDatabaseChecker);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\SchemaListener;
|
||||
|
||||
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
|
||||
use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider;
|
||||
use Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler;
|
||||
use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface;
|
||||
|
||||
/**
|
||||
* Automatically adds the rememberme table needed for the {@see DoctrineTokenProvider}.
|
||||
*/
|
||||
class RememberMeTokenProviderDoctrineSchemaListener extends AbstractSchemaListener
|
||||
{
|
||||
/**
|
||||
* @param iterable<mixed, RememberMeHandlerInterface> $rememberMeHandlers
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly iterable $rememberMeHandlers,
|
||||
) {
|
||||
}
|
||||
|
||||
public function postGenerateSchema(GenerateSchemaEventArgs $event): void
|
||||
{
|
||||
$connection = $event->getEntityManager()->getConnection();
|
||||
$schema = $event->getSchema();
|
||||
|
||||
foreach ($this->rememberMeHandlers as $rememberMeHandler) {
|
||||
if (
|
||||
$rememberMeHandler instanceof PersistentRememberMeHandler
|
||||
&& ($tokenProvider = $rememberMeHandler->getTokenProvider()) instanceof DoctrineTokenProvider
|
||||
) {
|
||||
$isSameDatabaseChecker = $this->getIsSameDatabaseChecker($connection);
|
||||
$this->filterSchemaChanges($schema, $connection, static function () use ($tokenProvider, $schema, $connection, $isSameDatabaseChecker) {
|
||||
/* @var DoctrineTokenProvider $tokenProvider */
|
||||
$tokenProvider->configureSchema($schema, $connection, $isSameDatabaseChecker);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
220
backend/vendor/symfony/doctrine-bridge/Security/RememberMe/DoctrineTokenProvider.php
vendored
Normal file
220
backend/vendor/symfony/doctrine-bridge/Security/RememberMe/DoctrineTokenProvider.php
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Security\RememberMe;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Schema\Name\Identifier;
|
||||
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
|
||||
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
|
||||
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\RememberMe\TokenVerifierInterface;
|
||||
use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
|
||||
|
||||
/**
|
||||
* This class provides storage for the tokens that is set in "remember-me"
|
||||
* cookies. This way no password secrets will be stored in the cookies on
|
||||
* the client machine, and thus the security is improved.
|
||||
*
|
||||
* This depends only on doctrine in order to get a database connection
|
||||
* and to do the conversion of the datetime column.
|
||||
*
|
||||
* In order to use this class, you need the following table in your database:
|
||||
*
|
||||
* CREATE TABLE `rememberme_token` (
|
||||
* `series` char(88) UNIQUE PRIMARY KEY NOT NULL,
|
||||
* `value` char(88) NOT NULL,
|
||||
* `lastUsed` datetime NOT NULL,
|
||||
* `class` varchar(100) DEFAULT '' NOT NULL,
|
||||
* `username` varchar(200) NOT NULL
|
||||
* );
|
||||
*
|
||||
* (the `class` column is for BC with tables created with before Symfony 8)
|
||||
*/
|
||||
final class DoctrineTokenProvider implements TokenProviderInterface, TokenVerifierInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Connection $conn,
|
||||
) {
|
||||
}
|
||||
|
||||
public function loadTokenBySeries(string $series): PersistentTokenInterface
|
||||
{
|
||||
$sql = 'SELECT class, username, value, lastUsed FROM rememberme_token WHERE series=:series';
|
||||
$paramValues = ['series' => $series];
|
||||
$paramTypes = ['series' => ParameterType::STRING];
|
||||
$stmt = $this->conn->executeQuery($sql, $paramValues, $paramTypes);
|
||||
|
||||
// fetching numeric because column name casing depends on platform, eg. Oracle converts all not quoted names to uppercase
|
||||
$row = $stmt->fetchNumeric() ?: throw new TokenNotFoundException('No token found.');
|
||||
|
||||
[$class, $username, $value, $last_used] = $row;
|
||||
|
||||
if (method_exists(PersistentToken::class, 'getClass')) {
|
||||
return new PersistentToken($class, $username, $series, $value, new \DateTimeImmutable($last_used), false);
|
||||
}
|
||||
|
||||
return new PersistentToken($username, $series, $value, new \DateTimeImmutable($last_used));
|
||||
}
|
||||
|
||||
public function deleteTokenBySeries(string $series): void
|
||||
{
|
||||
$sql = 'DELETE FROM rememberme_token WHERE series=:series';
|
||||
$paramValues = ['series' => $series];
|
||||
$paramTypes = ['series' => ParameterType::STRING];
|
||||
$this->conn->executeStatement($sql, $paramValues, $paramTypes);
|
||||
}
|
||||
|
||||
public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void
|
||||
{
|
||||
$sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed WHERE series=:series';
|
||||
$paramValues = [
|
||||
'value' => $tokenValue,
|
||||
'lastUsed' => \DateTimeImmutable::createFromInterface($lastUsed),
|
||||
'series' => $series,
|
||||
];
|
||||
$paramTypes = [
|
||||
'value' => ParameterType::STRING,
|
||||
'lastUsed' => Types::DATETIME_IMMUTABLE,
|
||||
'series' => ParameterType::STRING,
|
||||
];
|
||||
$updated = $this->conn->executeStatement($sql, $paramValues, $paramTypes);
|
||||
if ($updated < 1) {
|
||||
throw new TokenNotFoundException('No token found.');
|
||||
}
|
||||
}
|
||||
|
||||
public function createNewToken(PersistentTokenInterface $token): void
|
||||
{
|
||||
$sql = 'INSERT INTO rememberme_token (class, username, series, value, lastUsed) VALUES (:class, :username, :series, :value, :lastUsed)';
|
||||
$paramValues = [
|
||||
'class' => method_exists($token, 'getClass') ? $token->getClass(false) : '',
|
||||
'username' => $token->getUserIdentifier(),
|
||||
'series' => $token->getSeries(),
|
||||
'value' => $token->getTokenValue(),
|
||||
'lastUsed' => \DateTimeImmutable::createFromInterface($token->getLastUsed()),
|
||||
];
|
||||
$paramTypes = [
|
||||
'class' => ParameterType::STRING,
|
||||
'username' => ParameterType::STRING,
|
||||
'series' => ParameterType::STRING,
|
||||
'value' => ParameterType::STRING,
|
||||
'lastUsed' => Types::DATETIME_IMMUTABLE,
|
||||
];
|
||||
$this->conn->executeStatement($sql, $paramValues, $paramTypes);
|
||||
}
|
||||
|
||||
public function verifyToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue): bool
|
||||
{
|
||||
// Check if the token value matches the current persisted token
|
||||
if (hash_equals($token->getTokenValue(), $tokenValue)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generate an alternative series id here by changing the suffix == to _
|
||||
// this is needed to be able to store an older token value in the database
|
||||
// which has a PRIMARY(series), and it works as long as series ids are
|
||||
// generated using base64_encode(random_bytes(64)) which always outputs
|
||||
// a == suffix, but if it should not work for some reason we abort
|
||||
// for safety
|
||||
$tmpSeries = preg_replace('{=+$}', '_', $token->getSeries());
|
||||
if ($tmpSeries === $token->getSeries()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the previous token is present. If the given $tokenValue
|
||||
// matches the previous token (and it is outdated by at most 60seconds)
|
||||
// we also accept it as a valid value.
|
||||
try {
|
||||
$tmpToken = $this->loadTokenBySeries($tmpSeries);
|
||||
} catch (TokenNotFoundException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($tmpToken->getLastUsed()->getTimestamp() + 60 < time()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hash_equals($tmpToken->getTokenValue(), $tokenValue);
|
||||
}
|
||||
|
||||
public function updateExistingToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void
|
||||
{
|
||||
if (!$token instanceof PersistentToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Persist a copy of the previous token for authentication
|
||||
// in verifyToken should the old token still be sent by the browser
|
||||
// in a request concurrent to the one that did this token update
|
||||
$tmpSeries = preg_replace('{=+$}', '_', $token->getSeries());
|
||||
// if we cannot generate a unique series it is not worth trying further
|
||||
if ($tmpSeries === $token->getSeries()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->conn->beginTransaction();
|
||||
try {
|
||||
$this->deleteTokenBySeries($tmpSeries);
|
||||
$lastUsed = \DateTime::createFromInterface($lastUsed);
|
||||
|
||||
if (method_exists(PersistentToken::class, 'getClass')) {
|
||||
$persistentToken = new PersistentToken($token->getClass(false), $token->getUserIdentifier(), $tmpSeries, $token->getTokenValue(), $lastUsed, false);
|
||||
} else {
|
||||
$persistentToken = new PersistentToken($token->getUserIdentifier(), $tmpSeries, $token->getTokenValue(), $lastUsed);
|
||||
}
|
||||
|
||||
$this->createNewToken($persistentToken);
|
||||
|
||||
$this->conn->commit();
|
||||
} catch (\Exception $e) {
|
||||
$this->conn->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Table to the Schema if "remember me" uses this Connection.
|
||||
*/
|
||||
public function configureSchema(Schema $schema, Connection $forConnection, \Closure $isSameDatabase): void
|
||||
{
|
||||
if ($schema->hasTable('rememberme_token')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($forConnection !== $this->conn && !$isSameDatabase($this->conn->executeStatement(...))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addTableToSchema($schema);
|
||||
}
|
||||
|
||||
private function addTableToSchema(Schema $schema): void
|
||||
{
|
||||
$table = $schema->createTable('rememberme_token');
|
||||
$table->addColumn('series', Types::STRING, ['length' => 88]);
|
||||
$table->addColumn('value', Types::STRING, ['length' => 88]);
|
||||
$table->addColumn('lastUsed', Types::DATETIME_IMMUTABLE);
|
||||
$table->addColumn('class', Types::STRING, ['length' => 100, 'default' => '']);
|
||||
$table->addColumn('username', Types::STRING, ['length' => 200]);
|
||||
|
||||
if (class_exists(PrimaryKeyConstraint::class)) {
|
||||
$table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('series'))], true));
|
||||
} else {
|
||||
$table->setPrimaryKey(['series']);
|
||||
}
|
||||
}
|
||||
}
|
||||
167
backend/vendor/symfony/doctrine-bridge/Security/User/EntityUserProvider.php
vendored
Normal file
167
backend/vendor/symfony/doctrine-bridge/Security/User/EntityUserProvider.php
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Security\User;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
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;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
|
||||
/**
|
||||
* Wrapper around a Doctrine ObjectManager.
|
||||
*
|
||||
* Provides provisioning for Doctrine entity users.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*
|
||||
* @template TUser of UserInterface
|
||||
*
|
||||
* @template-implements UserProviderInterface<TUser>
|
||||
*/
|
||||
class EntityUserProvider implements UserProviderInterface, PasswordUpgraderInterface
|
||||
{
|
||||
private string $class;
|
||||
|
||||
public function __construct(
|
||||
private readonly ManagerRegistry $registry,
|
||||
private readonly string $classOrAlias,
|
||||
private readonly ?string $property = null,
|
||||
private readonly ?string $managerName = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?array $attributes
|
||||
*/
|
||||
public function loadUserByIdentifier(string $identifier/* , ?array $attributes = null */): UserInterface
|
||||
{
|
||||
$repository = $this->getRepository();
|
||||
if (null !== $this->property) {
|
||||
$user = $repository->findOneBy([$this->property => $identifier]);
|
||||
} else {
|
||||
if (!$repository instanceof UserLoaderInterface) {
|
||||
throw new \InvalidArgumentException(\sprintf('You must either make the "%s" entity Doctrine Repository ("%s") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration.', $this->classOrAlias, get_debug_type($repository)));
|
||||
}
|
||||
|
||||
if (null === $attributes = \func_num_args() > 1 ? func_get_arg(1) : null) {
|
||||
$user = $repository->loadUserByIdentifier($identifier);
|
||||
} else {
|
||||
$user = $repository->loadUserByIdentifier($identifier, $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $user) {
|
||||
$e = new UserNotFoundException(\sprintf('User "%s" not found.', $identifier));
|
||||
$e->setUserIdentifier($identifier);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function refreshUser(UserInterface $user): UserInterface
|
||||
{
|
||||
$class = $this->getClass();
|
||||
if (!$user instanceof $class) {
|
||||
throw new UnsupportedUserException(\sprintf('Instances of "%s" are not supported.', get_debug_type($user)));
|
||||
}
|
||||
|
||||
$repository = $this->getRepository();
|
||||
if ($repository instanceof UserProviderInterface) {
|
||||
$refreshedUser = $repository->refreshUser($user);
|
||||
} else {
|
||||
// The user must be reloaded via the primary key as all other data
|
||||
// might have changed without proper persistence in the database.
|
||||
// That's the case when the user has been changed by a form with
|
||||
// validation errors.
|
||||
if (!$id = $this->getClassMetadata()->getIdentifierValues($user)) {
|
||||
throw new \InvalidArgumentException('You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.');
|
||||
}
|
||||
|
||||
$refreshedUser = $repository->find($id);
|
||||
if (null === $refreshedUser) {
|
||||
$e = new UserNotFoundException('User with id '.json_encode($id).' not found.');
|
||||
$e->setUserIdentifier(json_encode($id));
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if ($refreshedUser instanceof Proxy && !$refreshedUser->__isInitialized()) {
|
||||
$refreshedUser->__load();
|
||||
} elseif (\PHP_VERSION_ID >= 80400 && ($r = new \ReflectionClass($refreshedUser))->isUninitializedLazyObject($refreshedUser)) {
|
||||
$r->initializeLazyObject($refreshedUser);
|
||||
}
|
||||
|
||||
return $refreshedUser;
|
||||
}
|
||||
|
||||
public function supportsClass(string $class): bool
|
||||
{
|
||||
return $class === $this->getClass() || is_subclass_of($class, $this->getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
|
||||
{
|
||||
$class = $this->getClass();
|
||||
if (!$user instanceof $class) {
|
||||
throw new UnsupportedUserException(\sprintf('Instances of "%s" are not supported.', get_debug_type($user)));
|
||||
}
|
||||
|
||||
$repository = $this->getRepository();
|
||||
if ($repository instanceof PasswordUpgraderInterface) {
|
||||
$repository->upgradePassword($user, $newHashedPassword);
|
||||
}
|
||||
}
|
||||
|
||||
private function getObjectManager(): ObjectManager
|
||||
{
|
||||
return $this->registry->getManager($this->managerName);
|
||||
}
|
||||
|
||||
private function getRepository(): ObjectRepository
|
||||
{
|
||||
return $this->getObjectManager()->getRepository($this->classOrAlias);
|
||||
}
|
||||
|
||||
private function getClass(): string
|
||||
{
|
||||
if (!isset($this->class)) {
|
||||
$class = $this->classOrAlias;
|
||||
|
||||
if (str_contains($class, ':')) {
|
||||
$class = $this->getClassMetadata()->getName();
|
||||
}
|
||||
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
return $this->class;
|
||||
}
|
||||
|
||||
private function getClassMetadata(): ClassMetadata
|
||||
{
|
||||
return $this->getObjectManager()->getClassMetadata($this->classOrAlias);
|
||||
}
|
||||
}
|
||||
35
backend/vendor/symfony/doctrine-bridge/Security/User/UserLoaderInterface.php
vendored
Normal file
35
backend/vendor/symfony/doctrine-bridge/Security/User/UserLoaderInterface.php
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Security\User;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Represents a class that loads UserInterface objects from Doctrine source for the authentication system.
|
||||
*
|
||||
* This interface is meant to facilitate the loading of a User from Doctrine source using a custom method.
|
||||
* If you want to implement your own logic of retrieving the user from Doctrine your repository should implement this
|
||||
* interface.
|
||||
*
|
||||
* @see UserInterface
|
||||
*
|
||||
* @author Michal Trojanowski <michal@kmt-studio.pl>
|
||||
*/
|
||||
interface UserLoaderInterface
|
||||
{
|
||||
/**
|
||||
* Loads the user for the given user identifier (e.g. username or email).
|
||||
*
|
||||
* This method must return null if the user is not found.
|
||||
*/
|
||||
public function loadUserByIdentifier(string $identifier): ?UserInterface;
|
||||
}
|
||||
113
backend/vendor/symfony/doctrine-bridge/Types/AbstractUidType.php
vendored
Normal file
113
backend/vendor/symfony/doctrine-bridge/Types/AbstractUidType.php
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\Exception\InvalidType;
|
||||
use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Symfony\Component\Uid\AbstractUid;
|
||||
|
||||
abstract class AbstractUidType extends Type
|
||||
{
|
||||
/**
|
||||
* @return class-string<AbstractUid>
|
||||
*/
|
||||
abstract protected function getUidClass(): string;
|
||||
|
||||
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
|
||||
{
|
||||
if ($this->hasNativeGuidType($platform)) {
|
||||
return $platform->getGuidTypeDeclarationSQL($column);
|
||||
}
|
||||
|
||||
return $platform->getBinaryTypeDeclarationSQL([
|
||||
'length' => 16,
|
||||
'fixed' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ConversionException
|
||||
*/
|
||||
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?AbstractUid
|
||||
{
|
||||
if ($value instanceof AbstractUid || null === $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (!\is_string($value)) {
|
||||
$this->throwInvalidType($value);
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->getUidClass()::fromString($value);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->throwValueNotConvertible($value, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ConversionException
|
||||
*/
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
|
||||
{
|
||||
$toString = $this->hasNativeGuidType($platform) ? 'toRfc4122' : 'toBinary';
|
||||
|
||||
if ($value instanceof AbstractUid) {
|
||||
return $value->$toString();
|
||||
}
|
||||
|
||||
if (null === $value || '' === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!\is_string($value)) {
|
||||
$this->throwInvalidType($value);
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->getUidClass()::fromString($value)->$toString();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->throwValueNotConvertible($value, $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private function hasNativeGuidType(AbstractPlatform $platform): bool
|
||||
{
|
||||
return $platform->getGuidTypeDeclarationSQL([]) !== $platform->getStringTypeDeclarationSQL(['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);
|
||||
}
|
||||
}
|
||||
44
backend/vendor/symfony/doctrine-bridge/Types/DatePointType.php
vendored
Normal file
44
backend/vendor/symfony/doctrine-bridge/Types/DatePointType.php
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\DateTimeImmutableType;
|
||||
use Symfony\Component\Clock\DatePoint;
|
||||
|
||||
final class DatePointType extends DateTimeImmutableType
|
||||
{
|
||||
public const NAME = 'date_point';
|
||||
|
||||
/**
|
||||
* @param T $value
|
||||
*
|
||||
* @return (T is null ? null : DatePoint)
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DatePoint
|
||||
{
|
||||
if (null === $value || $value instanceof DatePoint) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$value = parent::convertToPHPValue($value, $platform);
|
||||
|
||||
return DatePoint::createFromInterface($value);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
}
|
||||
40
backend/vendor/symfony/doctrine-bridge/Types/DayPointType.php
vendored
Normal file
40
backend/vendor/symfony/doctrine-bridge/Types/DayPointType.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\DateImmutableType;
|
||||
use Symfony\Component\Clock\DatePoint;
|
||||
|
||||
final class DayPointType extends DateImmutableType
|
||||
{
|
||||
public const NAME = 'day_point';
|
||||
|
||||
/**
|
||||
* @return ($value is null ? null : DatePoint)
|
||||
*/
|
||||
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DatePoint
|
||||
{
|
||||
if (null === $value || $value instanceof DatePoint) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$value = parent::convertToPHPValue($value, $platform);
|
||||
|
||||
return DatePoint::createFromInterface($value);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
}
|
||||
40
backend/vendor/symfony/doctrine-bridge/Types/TimePointType.php
vendored
Normal file
40
backend/vendor/symfony/doctrine-bridge/Types/TimePointType.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\TimeImmutableType;
|
||||
use Symfony\Component\Clock\DatePoint;
|
||||
|
||||
final class TimePointType extends TimeImmutableType
|
||||
{
|
||||
public const NAME = 'time_point';
|
||||
|
||||
/**
|
||||
* @return ($value is null ? null : DatePoint)
|
||||
*/
|
||||
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DatePoint
|
||||
{
|
||||
if (null === $value || $value instanceof DatePoint) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$value = parent::convertToPHPValue($value, $platform);
|
||||
|
||||
return DatePoint::createFromInterface($value);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
}
|
||||
29
backend/vendor/symfony/doctrine-bridge/Types/UlidType.php
vendored
Normal file
29
backend/vendor/symfony/doctrine-bridge/Types/UlidType.php
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Types;
|
||||
|
||||
use Symfony\Component\Uid\Ulid;
|
||||
|
||||
final class UlidType extends AbstractUidType
|
||||
{
|
||||
public const NAME = 'ulid';
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
protected function getUidClass(): string
|
||||
{
|
||||
return Ulid::class;
|
||||
}
|
||||
}
|
||||
29
backend/vendor/symfony/doctrine-bridge/Types/UuidType.php
vendored
Normal file
29
backend/vendor/symfony/doctrine-bridge/Types/UuidType.php
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Types;
|
||||
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
final class UuidType extends AbstractUidType
|
||||
{
|
||||
public const NAME = 'uuid';
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
protected function getUidClass(): string
|
||||
{
|
||||
return Uuid::class;
|
||||
}
|
||||
}
|
||||
130
backend/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntity.php
vendored
Normal file
130
backend/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntity.php
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Attribute\HasNamedArguments;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Constraint for the Unique Entity validator.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
|
||||
class UniqueEntity extends Constraint
|
||||
{
|
||||
public const NOT_UNIQUE_ERROR = '23bd9dbf-6b9b-41cd-a99e-4844bcf3077f';
|
||||
|
||||
protected const ERROR_NAMES = [
|
||||
self::NOT_UNIQUE_ERROR => 'NOT_UNIQUE_ERROR',
|
||||
];
|
||||
|
||||
public string $message = 'This value is already used.';
|
||||
public string $service = 'doctrine.orm.validator.unique';
|
||||
public ?string $em = null;
|
||||
public ?string $entityClass = null;
|
||||
public string $repositoryMethod = 'findBy';
|
||||
public array|string $fields = [];
|
||||
public ?string $errorPath = null;
|
||||
public bool|array|string $ignoreNull = true;
|
||||
public array $identifierFieldNames = [];
|
||||
|
||||
/**
|
||||
* @param array|string $fields The combination of fields that must contain unique values or a set of options
|
||||
* @param bool|string[]|string $ignoreNull The combination of fields that ignore null values
|
||||
* @param string|null $em The entity manager used to query for uniqueness instead of the manager of this class
|
||||
* @param string|null $entityClass The entity class to enforce uniqueness on instead of the current class
|
||||
* @param string|null $repositoryMethod The repository method to check uniqueness instead of findBy. The method will receive as its argument
|
||||
* a fieldName => value associative array according to the fields option configuration
|
||||
* @param string|null $errorPath Bind the constraint violation to this field instead of the first one in the fields option configuration
|
||||
*/
|
||||
#[HasNamedArguments]
|
||||
public function __construct(
|
||||
array|string $fields,
|
||||
?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 $identifierFieldNames = null,
|
||||
?array $groups = null,
|
||||
$payload = null,
|
||||
?array $options = null,
|
||||
) {
|
||||
if (\is_array($fields) && \is_string(key($fields)) && [] === array_diff(array_keys($fields), array_merge(array_keys(get_class_vars(static::class)), ['value']))) {
|
||||
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
|
||||
|
||||
$options = array_merge($fields, $options ?? []);
|
||||
$fields = null;
|
||||
} else {
|
||||
if (\is_array($options)) {
|
||||
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
|
||||
|
||||
$options['fields'] = $fields;
|
||||
$fields = null;
|
||||
} else {
|
||||
$options = null;
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($options, $groups, $payload);
|
||||
|
||||
$this->fields = $fields ?? $this->fields;
|
||||
$this->message = $message ?? $this->message;
|
||||
$this->service = $service ?? $this->service;
|
||||
$this->em = $em ?? $this->em;
|
||||
$this->entityClass = $entityClass ?? $this->entityClass;
|
||||
$this->repositoryMethod = $repositoryMethod ?? $this->repositoryMethod;
|
||||
$this->errorPath = $errorPath ?? $this->errorPath;
|
||||
$this->ignoreNull = $ignoreNull ?? $this->ignoreNull;
|
||||
$this->identifierFieldNames = $identifierFieldNames ?? $this->identifierFieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Symfony 7.4
|
||||
*/
|
||||
public function getRequiredOptions(): array
|
||||
{
|
||||
if (0 === \func_num_args() || func_get_arg(0)) {
|
||||
trigger_deprecation('symfony/doctrine-bridge', '7.4', 'The %s() method is deprecated.', __METHOD__);
|
||||
}
|
||||
|
||||
return ['fields'];
|
||||
}
|
||||
|
||||
/**
|
||||
* The validator must be defined as a service with this name.
|
||||
*/
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
public function getTargets(): string|array
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Symfony 7.4
|
||||
*/
|
||||
public function getDefaultOption(): ?string
|
||||
{
|
||||
if (0 === \func_num_args() || func_get_arg(0)) {
|
||||
trigger_deprecation('symfony/doctrine-bridge', '7.4', 'The %s() method is deprecated.', __METHOD__);
|
||||
}
|
||||
|
||||
return 'fields';
|
||||
}
|
||||
}
|
||||
314
backend/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntityValidator.php
vendored
Normal file
314
backend/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntityValidator.php
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Validator\Constraints;
|
||||
|
||||
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\MappingException as PersistenceMappingException;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* Unique Entity Validator checks if one or a set of fields contain unique values.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class UniqueEntityValidator extends ConstraintValidator
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ManagerRegistry $registry,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UnexpectedTypeException
|
||||
* @throws ConstraintDefinitionException
|
||||
*/
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
if (!$constraint instanceof UniqueEntity) {
|
||||
throw new UnexpectedTypeException($constraint, UniqueEntity::class);
|
||||
}
|
||||
|
||||
if (!\is_array($constraint->fields) && !\is_string($constraint->fields)) {
|
||||
throw new UnexpectedTypeException($constraint->fields, 'array');
|
||||
}
|
||||
|
||||
if (null !== $constraint->errorPath && !\is_string($constraint->errorPath)) {
|
||||
throw new UnexpectedTypeException($constraint->errorPath, 'string or null');
|
||||
}
|
||||
|
||||
$fields = (array) $constraint->fields;
|
||||
|
||||
if (0 === \count($fields)) {
|
||||
throw new ConstraintDefinitionException('At least one field has to be specified.');
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\is_object($value)) {
|
||||
throw new UnexpectedValueException($value, 'object');
|
||||
}
|
||||
|
||||
$entityClass = $constraint->entityClass ?? $value::class;
|
||||
|
||||
if ($constraint->em) {
|
||||
try {
|
||||
$em = $this->registry->getManager($constraint->em);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new ConstraintDefinitionException(\sprintf('Object manager "%s" does not exist.', $constraint->em), 0, $e);
|
||||
}
|
||||
} else {
|
||||
$em = $this->registry->getManagerForClass($entityClass);
|
||||
|
||||
if (!$em) {
|
||||
throw new ConstraintDefinitionException(\sprintf('Unable to find the object manager associated with an entity of class "%s".', $entityClass));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$em->getRepository($value::class);
|
||||
$isValueEntity = true;
|
||||
} catch (ORMMappingException|PersistenceMappingException) {
|
||||
$isValueEntity = false;
|
||||
}
|
||||
|
||||
$class = $em->getClassMetadata($entityClass);
|
||||
|
||||
$criteria = [];
|
||||
$hasIgnorableNullValue = false;
|
||||
|
||||
$fieldValues = $this->getFieldValues($value, $class, $fields, $isValueEntity);
|
||||
|
||||
foreach ($fieldValues as $fieldName => $fieldValue) {
|
||||
if (null === $fieldValue && $this->ignoreNullForField($constraint, $fieldName)) {
|
||||
$hasIgnorableNullValue = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$criteria[$fieldName] = $fieldValue;
|
||||
|
||||
if (\is_object($criteria[$fieldName]) && $class->hasAssociation($fieldName)) {
|
||||
/* Ensure the Proxy is initialized before using reflection to
|
||||
* read its identifiers. This is necessary because the wrapped
|
||||
* getter methods in the Proxy are being bypassed.
|
||||
*/
|
||||
$em->initializeObject($criteria[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
// validation doesn't fail if one of the fields is null and if null values should be ignored
|
||||
if ($hasIgnorableNullValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// skip validation if there are no criteria (this can happen when the
|
||||
// "ignoreNull" option is enabled and fields to be checked are null
|
||||
if (!$criteria) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $constraint->entityClass) {
|
||||
/* Retrieve repository from given entity name.
|
||||
* We ensure the retrieved repository can handle the entity
|
||||
* by checking the entity is the same, or subclass of the supported entity.
|
||||
*/
|
||||
$repository = $em->getRepository($constraint->entityClass);
|
||||
$supportedClass = $repository->getClassName();
|
||||
|
||||
if ($isValueEntity && !$value instanceof $supportedClass) {
|
||||
$class = $em->getClassMetadata($value::class);
|
||||
throw new ConstraintDefinitionException(\sprintf('The "%s" entity repository does not support the "%s" entity. The entity should be an instance of or extend "%s".', $constraint->entityClass, $class->getName(), $supportedClass));
|
||||
}
|
||||
} else {
|
||||
$repository = $em->getRepository($value::class);
|
||||
}
|
||||
|
||||
$arguments = [$criteria];
|
||||
|
||||
/* If the default repository method is used, it is always enough to retrieve at most two entities because:
|
||||
* - No entity returned, the current entity is definitely unique.
|
||||
* - More than one entity returned, the current entity cannot be unique.
|
||||
* - One entity returned the uniqueness depends on the current entity.
|
||||
*/
|
||||
if ('findBy' === $constraint->repositoryMethod) {
|
||||
$arguments = [$criteria, null, 2];
|
||||
}
|
||||
|
||||
$result = $repository->{$constraint->repositoryMethod}(...$arguments);
|
||||
|
||||
if ($result instanceof \IteratorAggregate) {
|
||||
$result = $result->getIterator();
|
||||
}
|
||||
|
||||
/* If the result is a MongoCursor, it must be advanced to the first
|
||||
* element. Rewinding should have no ill effect if $result is another
|
||||
* iterator implementation.
|
||||
*/
|
||||
if ($result instanceof \Iterator) {
|
||||
$result->rewind();
|
||||
if ($result instanceof \Countable && 1 < \count($result)) {
|
||||
$result = [$result->current(), $result->current()];
|
||||
} else {
|
||||
$result = $result->valid() && null !== $result->current() ? [$result->current()] : [];
|
||||
}
|
||||
} elseif (\is_array($result)) {
|
||||
reset($result);
|
||||
} else {
|
||||
$result = null === $result ? [] : [$result];
|
||||
}
|
||||
|
||||
/* If no entity matched the query criteria or a single entity matched,
|
||||
* which is the same as the entity being validated, the criteria is
|
||||
* unique.
|
||||
*/
|
||||
if (!$result || (1 === \count($result) && current($result) === $value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If a single entity matched the query criteria, which is the same as
|
||||
* the entity being updated by validated object, the criteria is unique.
|
||||
*/
|
||||
if (!$isValueEntity && !empty($constraint->identifierFieldNames) && 1 === \count($result)) {
|
||||
$fieldValues = $this->getFieldValues($value, $class, $constraint->identifierFieldNames);
|
||||
if (array_values($class->getIdentifierFieldNames()) != array_values($constraint->identifierFieldNames)) {
|
||||
throw new ConstraintDefinitionException(\sprintf('The "%s" entity identifier field names should be "%s", not "%s".', $entityClass, implode(', ', $class->getIdentifierFieldNames()), implode(', ', $constraint->identifierFieldNames)));
|
||||
}
|
||||
|
||||
$entityMatched = true;
|
||||
|
||||
foreach ($constraint->identifierFieldNames as $identifierFieldName) {
|
||||
$propertyValue = $this->getPropertyValue($entityClass, $identifierFieldName, current($result));
|
||||
if ($fieldValues[$identifierFieldName] instanceof \Stringable) {
|
||||
$fieldValues[$identifierFieldName] = (string) $fieldValues[$identifierFieldName];
|
||||
}
|
||||
if ($propertyValue instanceof \Stringable) {
|
||||
$propertyValue = (string) $propertyValue;
|
||||
}
|
||||
if ($fieldValues[$identifierFieldName] !== $propertyValue) {
|
||||
$entityMatched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($entityMatched) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$errorPath = $constraint->errorPath ?? current($fields);
|
||||
$invalidValue = $criteria[$errorPath] ?? $criteria[current($fields)];
|
||||
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->atPath($errorPath)
|
||||
->setParameter('{{ value }}', $this->formatWithIdentifiers($em, $class, $invalidValue))
|
||||
->setInvalidValue($invalidValue)
|
||||
->setCode(UniqueEntity::NOT_UNIQUE_ERROR)
|
||||
->setCause($result)
|
||||
->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) {
|
||||
return $this->formatValue($value, self::PRETTY_DATE);
|
||||
}
|
||||
|
||||
if ($value instanceof \Stringable) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
if ($class->getName() !== $idClass = $value::class) {
|
||||
// non-unique value might be a composite PK that consists of other entity objects
|
||||
if ($em->getMetadataFactory()->hasMetadataFor($idClass)) {
|
||||
$identifiers = $em->getClassMetadata($idClass)->getIdentifierValues($value);
|
||||
} else {
|
||||
// this case might happen if the non-unique column has a custom doctrine type and its value is an object
|
||||
// in which case we cannot get any identifiers for it
|
||||
$identifiers = [];
|
||||
}
|
||||
} else {
|
||||
$identifiers = $class->getIdentifierValues($value);
|
||||
}
|
||||
|
||||
if (!$identifiers) {
|
||||
return \sprintf('object("%s")', $idClass);
|
||||
}
|
||||
|
||||
array_walk($identifiers, function (&$id, $field) {
|
||||
if (!\is_object($id) || $id instanceof \DateTimeInterface) {
|
||||
$idAsString = $this->formatValue($id, self::PRETTY_DATE);
|
||||
} else {
|
||||
$idAsString = \sprintf('object("%s")', $id::class);
|
||||
}
|
||||
|
||||
$id = \sprintf('%s => %s', $field, $idAsString);
|
||||
});
|
||||
|
||||
return \sprintf('object("%s") identified by (%s)', $idClass, implode(', ', $identifiers));
|
||||
}
|
||||
|
||||
private function getFieldValues(mixed $object, ClassMetadata $class, array $fields, bool $isValueEntity = false): array
|
||||
{
|
||||
if (!$isValueEntity) {
|
||||
$reflectionObject = new \ReflectionObject($object);
|
||||
}
|
||||
|
||||
$fieldValues = [];
|
||||
$objectClass = $object::class;
|
||||
|
||||
foreach ($fields as $objectFieldName => $entityFieldName) {
|
||||
if (!$class->hasField($entityFieldName) && !$class->hasAssociation($entityFieldName)) {
|
||||
throw new ConstraintDefinitionException(\sprintf('The field "%s" is not mapped by Doctrine, so it cannot be validated for uniqueness.', $entityFieldName));
|
||||
}
|
||||
|
||||
$fieldName = \is_int($objectFieldName) ? $entityFieldName : $objectFieldName;
|
||||
if (!$isValueEntity && !$reflectionObject->hasProperty($fieldName)) {
|
||||
throw new ConstraintDefinitionException(\sprintf('The field "%s" is not a property of class "%s".', $fieldName, $objectClass));
|
||||
}
|
||||
|
||||
if ($isValueEntity && $object instanceof ($class->getName()) && property_exists($class, 'propertyAccessors')) {
|
||||
$fieldValues[$entityFieldName] = $class->propertyAccessors[$fieldName]->getValue($object);
|
||||
} elseif ($isValueEntity && $object instanceof ($class->getName())) {
|
||||
$fieldValues[$entityFieldName] = $class->reflFields[$fieldName]->getValue($object);
|
||||
} else {
|
||||
$fieldValues[$entityFieldName] = $this->getPropertyValue($objectClass, $fieldName, $object);
|
||||
}
|
||||
}
|
||||
|
||||
return $fieldValues;
|
||||
}
|
||||
|
||||
private function getPropertyValue(string $class, string $name, mixed $object): mixed
|
||||
{
|
||||
$property = new \ReflectionProperty($class, $name);
|
||||
|
||||
return $property->getValue($object);
|
||||
}
|
||||
}
|
||||
33
backend/vendor/symfony/doctrine-bridge/Validator/DoctrineInitializer.php
vendored
Normal file
33
backend/vendor/symfony/doctrine-bridge/Validator/DoctrineInitializer.php
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Validator;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\Validator\ObjectInitializerInterface;
|
||||
|
||||
/**
|
||||
* Automatically loads proxy object before validation.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class DoctrineInitializer implements ObjectInitializerInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected ManagerRegistry $registry,
|
||||
) {
|
||||
}
|
||||
|
||||
public function initialize(object $object): void
|
||||
{
|
||||
$this->registry->getManagerForClass($object::class)?->initializeObject($object);
|
||||
}
|
||||
}
|
||||
144
backend/vendor/symfony/doctrine-bridge/Validator/DoctrineLoader.php
vendored
Normal file
144
backend/vendor/symfony/doctrine-bridge/Validator/DoctrineLoader.php
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Validator;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata as OrmClassMetadata;
|
||||
use Doctrine\ORM\Mapping\FieldMapping;
|
||||
use Doctrine\ORM\Mapping\MappingException as OrmMappingException;
|
||||
use Doctrine\Persistence\Mapping\MappingException;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
use Symfony\Component\Validator\Constraints\Valid;
|
||||
use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Validator\Mapping\Loader\AutoMappingTrait;
|
||||
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
|
||||
|
||||
/**
|
||||
* Guesses and loads the appropriate constraints using Doctrine's metadata.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
final class DoctrineLoader implements LoaderInterface
|
||||
{
|
||||
use AutoMappingTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly ?string $classValidatorRegexp = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function loadClassMetadata(ClassMetadata $metadata): bool
|
||||
{
|
||||
$className = $metadata->getClassName();
|
||||
try {
|
||||
$doctrineMetadata = $this->entityManager->getClassMetadata($className);
|
||||
} catch (MappingException|OrmMappingException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$doctrineMetadata instanceof OrmClassMetadata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$loaded = false;
|
||||
$enabledForClass = $this->isAutoMappingEnabledForClass($metadata, $this->classValidatorRegexp);
|
||||
|
||||
/* Available keys:
|
||||
- type
|
||||
- scale
|
||||
- length
|
||||
- unique
|
||||
- nullable
|
||||
- precision
|
||||
*/
|
||||
$existingUniqueFields = $this->getExistingUniqueFields($metadata);
|
||||
|
||||
// Type and nullable aren't handled here, use the PropertyInfo Loader instead.
|
||||
foreach ($doctrineMetadata->fieldMappings as $mapping) {
|
||||
$enabledForProperty = $enabledForClass;
|
||||
$lengthConstraint = null;
|
||||
foreach ($metadata->getPropertyMetadata(self::getFieldMappingValue($mapping, 'fieldName')) as $propertyMetadata) {
|
||||
// Enabling or disabling auto-mapping explicitly always takes precedence
|
||||
if (AutoMappingStrategy::DISABLED === $propertyMetadata->getAutoMappingStrategy()) {
|
||||
continue 2;
|
||||
}
|
||||
if (AutoMappingStrategy::ENABLED === $propertyMetadata->getAutoMappingStrategy()) {
|
||||
$enabledForProperty = true;
|
||||
}
|
||||
|
||||
foreach ($propertyMetadata->getConstraints() as $constraint) {
|
||||
if ($constraint instanceof Length) {
|
||||
$lengthConstraint = $constraint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$enabledForProperty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (true === (self::getFieldMappingValue($mapping, 'unique') ?? false) && !isset($existingUniqueFields[self::getFieldMappingValue($mapping, 'fieldName')])) {
|
||||
$metadata->addConstraint(new UniqueEntity(fields: self::getFieldMappingValue($mapping, 'fieldName')));
|
||||
$loaded = true;
|
||||
}
|
||||
|
||||
if (null === (self::getFieldMappingValue($mapping, 'length') ?? null) || null !== (self::getFieldMappingValue($mapping, 'enumType') ?? null) || !\in_array(self::getFieldMappingValue($mapping, 'type'), ['string', 'text'], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $lengthConstraint) {
|
||||
if (self::getFieldMappingValue($mapping, 'originalClass') && !str_contains(self::getFieldMappingValue($mapping, 'declaredField'), '.')) {
|
||||
$metadata->addPropertyConstraint(self::getFieldMappingValue($mapping, 'declaredField'), new Valid());
|
||||
$loaded = true;
|
||||
} elseif (property_exists($className, self::getFieldMappingValue($mapping, 'fieldName')) && (!$doctrineMetadata->isMappedSuperclass || $metadata->getReflectionClass()->getProperty(self::getFieldMappingValue($mapping, 'fieldName'))->isPrivate())) {
|
||||
$metadata->addPropertyConstraint(self::getFieldMappingValue($mapping, 'fieldName'), new Length(max: self::getFieldMappingValue($mapping, 'length')));
|
||||
$loaded = true;
|
||||
}
|
||||
} elseif (null === $lengthConstraint->max) {
|
||||
// If a Length constraint exists and no max length has been explicitly defined, set it
|
||||
$lengthConstraint->max = self::getFieldMappingValue($mapping, 'length');
|
||||
}
|
||||
}
|
||||
|
||||
return $loaded;
|
||||
}
|
||||
|
||||
private function getExistingUniqueFields(ClassMetadata $metadata): array
|
||||
{
|
||||
$fields = [];
|
||||
foreach ($metadata->getConstraints() as $constraint) {
|
||||
if (!$constraint instanceof UniqueEntity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\is_string($constraint->fields)) {
|
||||
$fields[$constraint->fields] = true;
|
||||
} elseif (\is_array($constraint->fields) && 1 === \count($constraint->fields)) {
|
||||
$fields[$constraint->fields[0]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
private static function getFieldMappingValue(array|FieldMapping $mapping, string $key): mixed
|
||||
{
|
||||
if ($mapping instanceof FieldMapping) {
|
||||
return $mapping->$key ?? null;
|
||||
}
|
||||
|
||||
return $mapping[$key] ?? null;
|
||||
}
|
||||
}
|
||||
76
backend/vendor/symfony/doctrine-bridge/composer.json
vendored
Normal file
76
backend/vendor/symfony/doctrine-bridge/composer.json
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "symfony/doctrine-bridge",
|
||||
"type": "symfony-bridge",
|
||||
"description": "Provides integration for Doctrine with various Symfony components",
|
||||
"keywords": [],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"doctrine/event-manager": "^2",
|
||||
"doctrine/persistence": "^3.1|^4",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-ctype": "~1.8",
|
||||
"symfony/polyfill-mbstring": "~1.0",
|
||||
"symfony/service-contracts": "^2.5|^3"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/cache": "^6.4|^7.0|^8.0",
|
||||
"symfony/config": "^6.4|^7.0|^8.0",
|
||||
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
|
||||
"symfony/doctrine-messenger": "^6.4|^7.0|^8.0",
|
||||
"symfony/expression-language": "^6.4|^7.0|^8.0",
|
||||
"symfony/form": "^7.2|^8.0",
|
||||
"symfony/http-kernel": "^6.4|^7.0|^8.0",
|
||||
"symfony/lock": "^6.4|^7.0|^8.0",
|
||||
"symfony/messenger": "^6.4|^7.0|^8.0",
|
||||
"symfony/property-access": "^6.4|^7.0|^8.0",
|
||||
"symfony/property-info": "^6.4|^7.0|^8.0",
|
||||
"symfony/security-core": "^6.4|^7.0|^8.0",
|
||||
"symfony/stopwatch": "^6.4|^7.0|^8.0",
|
||||
"symfony/translation": "^6.4|^7.0|^8.0",
|
||||
"symfony/type-info": "^7.1.8|^8.0",
|
||||
"symfony/uid": "^6.4|^7.0|^8.0",
|
||||
"symfony/validator": "^7.4|^8.0",
|
||||
"symfony/var-dumper": "^6.4|^7.0|^8.0",
|
||||
"doctrine/collections": "^1.8|^2.0",
|
||||
"doctrine/data-fixtures": "^1.1|^2",
|
||||
"doctrine/dbal": "^3.6|^4",
|
||||
"doctrine/orm": "^2.15|^3",
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/collections": "<1.8",
|
||||
"doctrine/dbal": "<3.6",
|
||||
"doctrine/lexer": "<1.1",
|
||||
"doctrine/orm": "<2.15",
|
||||
"symfony/cache": "<6.4",
|
||||
"symfony/dependency-injection": "<6.4",
|
||||
"symfony/form": "<6.4.6|>=7,<7.0.6",
|
||||
"symfony/http-foundation": "<6.4",
|
||||
"symfony/http-kernel": "<6.4",
|
||||
"symfony/lock": "<6.4",
|
||||
"symfony/messenger": "<6.4",
|
||||
"symfony/property-info": "<6.4",
|
||||
"symfony/security-bundle": "<6.4",
|
||||
"symfony/security-core": "<6.4",
|
||||
"symfony/validator": "<7.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Bridge\\Doctrine\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
Reference in New Issue
Block a user