init
This commit is contained in:
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()];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user