vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php line 113

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Bridge\Doctrine\Form\Type;
  11. use Doctrine\Common\Collections\Collection;
  12. use Doctrine\Persistence\ManagerRegistry;
  13. use Doctrine\Persistence\ObjectManager;
  14. use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader;
  15. use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface;
  16. use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader;
  17. use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
  18. use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener;
  19. use Symfony\Component\Form\AbstractType;
  20. use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
  21. use Symfony\Component\Form\Exception\RuntimeException;
  22. use Symfony\Component\Form\FormBuilderInterface;
  23. use Symfony\Component\OptionsResolver\Options;
  24. use Symfony\Component\OptionsResolver\OptionsResolver;
  25. use Symfony\Contracts\Service\ResetInterface;
  26. abstract class DoctrineType extends AbstractType implements ResetInterface
  27. {
  28.     /**
  29.      * @var ManagerRegistry
  30.      */
  31.     protected $registry;
  32.     /**
  33.      * @var IdReader[]
  34.      */
  35.     private $idReaders = [];
  36.     /**
  37.      * @var DoctrineChoiceLoader[]
  38.      */
  39.     private $choiceLoaders = [];
  40.     /**
  41.      * Creates the label for a choice.
  42.      *
  43.      * For backwards compatibility, objects are cast to strings by default.
  44.      *
  45.      * @internal This method is public to be usable as callback. It should not
  46.      *           be used in user code.
  47.      */
  48.     public static function createChoiceLabel(object $choice): string
  49.     {
  50.         return (string) $choice;
  51.     }
  52.     /**
  53.      * Creates the field name for a choice.
  54.      *
  55.      * This method is used to generate field names if the underlying object has
  56.      * a single-column integer ID. In that case, the value of the field is
  57.      * the ID of the object. That ID is also used as field name.
  58.      *
  59.      * @param int|string $key   The choice key
  60.      * @param string     $value The choice value. Corresponds to the object's
  61.      *                          ID here.
  62.      *
  63.      * @internal This method is public to be usable as callback. It should not
  64.      *           be used in user code.
  65.      */
  66.     public static function createChoiceName(object $choice$keystring $value): string
  67.     {
  68.         return str_replace('-''_', (string) $value);
  69.     }
  70.     /**
  71.      * Gets important parts from QueryBuilder that will allow to cache its results.
  72.      * For instance in ORM two query builders with an equal SQL string and
  73.      * equal parameters are considered to be equal.
  74.      *
  75.      * @param object $queryBuilder A query builder, type declaration is not present here as there
  76.      *                             is no common base class for the different implementations
  77.      *
  78.      * @return array|null Array with important QueryBuilder parts or null if
  79.      *                    they can't be determined
  80.      *
  81.      * @internal This method is public to be usable as callback. It should not
  82.      *           be used in user code.
  83.      */
  84.     public function getQueryBuilderPartsForCachingHash($queryBuilder): ?array
  85.     {
  86.         return null;
  87.     }
  88.     public function __construct(ManagerRegistry $registry)
  89.     {
  90.         $this->registry $registry;
  91.     }
  92.     public function buildForm(FormBuilderInterface $builder, array $options)
  93.     {
  94.         if ($options['multiple'] && interface_exists(Collection::class)) {
  95.             $builder
  96.                 ->addEventSubscriber(new MergeDoctrineCollectionListener())
  97.                 ->addViewTransformer(new CollectionToArrayTransformer(), true)
  98.             ;
  99.         }
  100.     }
  101.     public function configureOptions(OptionsResolver $resolver)
  102.     {
  103.         $choiceLoader = function (Options $options) {
  104.             // Unless the choices are given explicitly, load them on demand
  105.             if (null === $options['choices']) {
  106.                 $hash null;
  107.                 $qbParts null;
  108.                 // If there is no QueryBuilder we can safely cache DoctrineChoiceLoader,
  109.                 // also if concrete Type can return important QueryBuilder parts to generate
  110.                 // hash key we go for it as well
  111.                 if (!$options['query_builder'] || null !== $qbParts $this->getQueryBuilderPartsForCachingHash($options['query_builder'])) {
  112.                     $hash CachingFactoryDecorator::generateHash([
  113.                         $options['em'],
  114.                         $options['class'],
  115.                         $qbParts,
  116.                     ]);
  117.                     if (isset($this->choiceLoaders[$hash])) {
  118.                         return $this->choiceLoaders[$hash];
  119.                     }
  120.                 }
  121.                 if (null !== $options['query_builder']) {
  122.                     $entityLoader $this->getLoader($options['em'], $options['query_builder'], $options['class']);
  123.                 } else {
  124.                     $queryBuilder $options['em']->getRepository($options['class'])->createQueryBuilder('e');
  125.                     $entityLoader $this->getLoader($options['em'], $queryBuilder$options['class']);
  126.                 }
  127.                 $doctrineChoiceLoader = new DoctrineChoiceLoader(
  128.                     $options['em'],
  129.                     $options['class'],
  130.                     $options['id_reader'],
  131.                     $entityLoader
  132.                 );
  133.                 if (null !== $hash) {
  134.                     $this->choiceLoaders[$hash] = $doctrineChoiceLoader;
  135.                 }
  136.                 return $doctrineChoiceLoader;
  137.             }
  138.             return null;
  139.         };
  140.         $choiceName = function (Options $options) {
  141.             // If the object has a single-column, numeric ID, use that ID as
  142.             // field name. We can only use numeric IDs as names, as we cannot
  143.             // guarantee that a non-numeric ID contains a valid form name
  144.             if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isIntId()) {
  145.                 return [__CLASS__'createChoiceName'];
  146.             }
  147.             // Otherwise, an incrementing integer is used as name automatically
  148.             return null;
  149.         };
  150.         // The choices are always indexed by ID (see "choices" normalizer
  151.         // and DoctrineChoiceLoader), unless the ID is composite. Then they
  152.         // are indexed by an incrementing integer.
  153.         // Use the ID/incrementing integer as choice value.
  154.         $choiceValue = function (Options $options) {
  155.             // If the entity has a single-column ID, use that ID as value
  156.             if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isSingleId()) {
  157.                 return [$options['id_reader'], 'getIdValue'];
  158.             }
  159.             // Otherwise, an incrementing integer is used as value automatically
  160.             return null;
  161.         };
  162.         $emNormalizer = function (Options $options$em) {
  163.             if (null !== $em) {
  164.                 if ($em instanceof ObjectManager) {
  165.                     return $em;
  166.                 }
  167.                 return $this->registry->getManager($em);
  168.             }
  169.             $em $this->registry->getManagerForClass($options['class']);
  170.             if (null === $em) {
  171.                 throw new RuntimeException(sprintf('Class "%s" seems not to be a managed Doctrine entity. Did you forget to map it?'$options['class']));
  172.             }
  173.             return $em;
  174.         };
  175.         // Invoke the query builder closure so that we can cache choice lists
  176.         // for equal query builders
  177.         $queryBuilderNormalizer = function (Options $options$queryBuilder) {
  178.             if (\is_callable($queryBuilder)) {
  179.                 $queryBuilder $queryBuilder($options['em']->getRepository($options['class']));
  180.             }
  181.             return $queryBuilder;
  182.         };
  183.         // Set the "id_reader" option via the normalizer. This option is not
  184.         // supposed to be set by the user.
  185.         $idReaderNormalizer = function (Options $options) {
  186.             $hash CachingFactoryDecorator::generateHash([
  187.                 $options['em'],
  188.                 $options['class'],
  189.             ]);
  190.             // The ID reader is a utility that is needed to read the object IDs
  191.             // when generating the field values. The callback generating the
  192.             // field values has no access to the object manager or the class
  193.             // of the field, so we store that information in the reader.
  194.             // The reader is cached so that two choice lists for the same class
  195.             // (and hence with the same reader) can successfully be cached.
  196.             if (!isset($this->idReaders[$hash])) {
  197.                 $classMetadata $options['em']->getClassMetadata($options['class']);
  198.                 $this->idReaders[$hash] = new IdReader($options['em'], $classMetadata);
  199.             }
  200.             if ($this->idReaders[$hash]->isSingleId()) {
  201.                 return $this->idReaders[$hash];
  202.             }
  203.             return null;
  204.         };
  205.         $resolver->setDefaults([
  206.             'em' => null,
  207.             'query_builder' => null,
  208.             'choices' => null,
  209.             'choice_loader' => $choiceLoader,
  210.             'choice_label' => [__CLASS__'createChoiceLabel'],
  211.             'choice_name' => $choiceName,
  212.             'choice_value' => $choiceValue,
  213.             'id_reader' => null// internal
  214.             'choice_translation_domain' => false,
  215.         ]);
  216.         $resolver->setRequired(['class']);
  217.         $resolver->setNormalizer('em'$emNormalizer);
  218.         $resolver->setNormalizer('query_builder'$queryBuilderNormalizer);
  219.         $resolver->setNormalizer('id_reader'$idReaderNormalizer);
  220.         $resolver->setAllowedTypes('em', ['null''string'ObjectManager::class]);
  221.     }
  222.     /**
  223.      * Return the default loader object.
  224.      *
  225.      * @param mixed $queryBuilder
  226.      *
  227.      * @return EntityLoaderInterface
  228.      */
  229.     abstract public function getLoader(ObjectManager $manager$queryBuilderstring $class);
  230.     public function getParent()
  231.     {
  232.         return 'Symfony\Component\Form\Extension\Core\Type\ChoiceType';
  233.     }
  234.     public function reset()
  235.     {
  236.         $this->choiceLoaders = [];
  237.     }
  238. }
  239. interface_exists(ObjectManager::class);