welcome back to dyb-tech
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
<?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
|
||||
{
|
||||
private ObjectManager $manager;
|
||||
private string $class;
|
||||
private ?IdReader $idReader;
|
||||
private ?EntityLoaderInterface $objectLoader;
|
||||
|
||||
/**
|
||||
* 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(ObjectManager $manager, string $class, ?IdReader $idReader = null, ?EntityLoaderInterface $objectLoader = null)
|
||||
{
|
||||
$classMetadata = $manager->getClassMetadata($class);
|
||||
|
||||
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->manager = $manager;
|
||||
$this->class = $classMetadata->getName();
|
||||
$this->idReader = $idReader;
|
||||
$this->objectLoader = $objectLoader;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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\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 ObjectManager $om;
|
||||
private ClassMetadata $classMetadata;
|
||||
private bool $singleId;
|
||||
private bool $intId;
|
||||
private string $idField;
|
||||
private ?self $associationIdReader = null;
|
||||
|
||||
public function __construct(ObjectManager $om, ClassMetadata $classMetadata)
|
||||
{
|
||||
$ids = $classMetadata->getIdentifierFieldNames();
|
||||
$idType = $classMetadata->getTypeOfField(current($ids));
|
||||
|
||||
$this->om = $om;
|
||||
$this->classMetadata = $classMetadata;
|
||||
$this->singleId = 1 === \count($ids);
|
||||
$this->intId = $this->singleId && \in_array($idType, ['integer', 'smallint', 'bigint']);
|
||||
$this->idField = current($ids);
|
||||
|
||||
// single field association are resolved, since the schema column could be an int
|
||||
if ($this->singleId && $classMetadata->hasAssociation($this->idField)) {
|
||||
$this->associationIdReader = new self($om, $om->getClassMetadata(
|
||||
$classMetadata->getAssociationTargetClass($this->idField)
|
||||
));
|
||||
|
||||
$this->singleId = $this->associationIdReader->isSingleId();
|
||||
$this->intId = $this->associationIdReader->isIntId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?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\Connection;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
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
|
||||
{
|
||||
/**
|
||||
* Contains the query builder that builds the query for fetching the
|
||||
* entities.
|
||||
*
|
||||
* This property should only be accessed through queryBuilder.
|
||||
*/
|
||||
private QueryBuilder $queryBuilder;
|
||||
|
||||
public function __construct(QueryBuilder $queryBuilder)
|
||||
{
|
||||
$this->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'])) {
|
||||
$parameterType = class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY;
|
||||
|
||||
// Filter out non-integer values (e.g. ""). If we don't, some
|
||||
// databases such as PostgreSQL fail.
|
||||
$values = array_values(array_filter($values, fn ($v) => (string) $v === (string) (int) $v || ctype_digit($v)));
|
||||
} elseif (\in_array($type, ['ulid', 'uuid', 'guid'])) {
|
||||
$parameterType = class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY;
|
||||
|
||||
// Like above, but we just filter out empty strings.
|
||||
$values = array_values(array_filter($values, fn ($v) => '' !== (string) $v));
|
||||
|
||||
// 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 = class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY;
|
||||
}
|
||||
if (!$values) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $qb->andWhere($where)
|
||||
->getQuery()
|
||||
->setParameter($parameter, $values, $parameterType)
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user