src/Form/AbstractBaseType.php line 319

Open in your IDE?
  1. <?php
  2. namespace App\Form;
  3. use App\Enum\Theme\Theme;
  4. use App\Library\Utils\Dev\ReflectionUtils\ReflectionUtils;
  5. use App\Library\Utils\Other\Other;
  6. use App\Service\BaseEntityService;
  7. use App\Service\RouteService;
  8. use App\Service\ServiceRetriever;
  9. use App\Service\ThemeService;
  10. use Symfony\Bridge\Doctrine\Form\Type\EntityType;
  11. use Symfony\Component\Form\AbstractType;
  12. use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
  13. use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
  14. use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
  15. use Symfony\Component\Form\Extension\Core\Type\DateType;
  16. use Symfony\Component\Form\Extension\Core\Type\EmailType;
  17. use Symfony\Component\Form\Extension\Core\Type\FileType;
  18. use Symfony\Component\Form\Extension\Core\Type\IntegerType;
  19. use Symfony\Component\Form\Extension\Core\Type\PasswordType;
  20. use Symfony\Component\Form\Extension\Core\Type\SubmitType;
  21. use Symfony\Component\Form\Extension\Core\Type\TextareaType;
  22. use Symfony\Component\Form\Extension\Core\Type\TextType;
  23. use Symfony\Component\Form\FormBuilderInterface;
  24. use Symfony\Component\Form\FormEvent;
  25. use Symfony\Component\Form\FormEvents;
  26. use Symfony\Component\Form\FormInterface;
  27. use Symfony\Component\OptionsResolver\OptionsResolver;
  28. use Symfony\Component\Routing\RouterInterface;
  29. use Symfony\Component\Validator\Constraints\File;
  30. use Symfony\Component\Validator\Constraints\Image;
  31. class AbstractBaseType extends AbstractType
  32. {
  33.     /** @var RouterInterface */
  34.     private $router;
  35.     private $addFormFieldCallbacks = [];
  36.     protected $textFields = [];
  37.     protected $imageFields = [];
  38.     protected $textAreaFields = [];
  39.     /**
  40.      * @var ThemeService
  41.      */
  42.     protected $themeService;
  43.     /**
  44.      * @var RouteService
  45.      */
  46.     protected $routeService;
  47.     /**
  48.      * @var ServiceRetriever
  49.      */
  50.     protected $serviceRetriever;
  51.     /**
  52.      * @var \App\Form\DataTransformer\DataTransformerRetriever
  53.      */
  54.     protected $dataTransformerRetriever;
  55.     /** @var FormBuilderInterface */
  56.      protected $builder;
  57.     /**
  58.      * @var ReflectionUtils|null
  59.      */
  60.     private $reflectionUtils;
  61.     public function __construct(RouterInterface $routerRouteService $routeServiceServiceRetriever $serviceRetriever)
  62.     {
  63.         $this->router $router;
  64.         // $this->themeService = $themeService;
  65.         $this->routeService $routeService;
  66.         $this->serviceRetriever $serviceRetriever;
  67.         $this->dataTransformerRetriever $this->serviceRetriever->getDataTransformerRetriever();
  68.         $this->reflectionUtils $this->serviceRetriever->getService(ReflectionUtils::class);
  69.     }
  70.     public function getBuilder(): FormBuilderInterface
  71.     {
  72.         if (!$this->builder) {
  73.             throw new \Exception("Form builder is not set.");
  74.         }
  75.         return $this->builder;
  76.     }
  77.     public function setBuilder(FormBuilderInterface $builder): void
  78.     {
  79.         $this->builder $builder;
  80.     }
  81.     public function buildForm(FormBuilderInterface $builder, array $options)
  82.     {
  83.         $this->addTextFields($builder$options);
  84.         foreach ($this->imageFields as $field) {
  85.             $this->addImageField($builder$field);
  86.         }
  87.         foreach ($this->textAreaFields as $field) {
  88.             $builder->add($fieldTextareaType::class, [
  89.                 'required' => false
  90.             ]);
  91.         }
  92.         $builder->addEventListener(FormEvents::PRE_SET_DATA,
  93.             function (FormEvent $event) {
  94.                 $form $event->getForm();
  95.                 $item $event->getData();
  96.                 foreach ($this->addFormFieldCallbacks as $addFormFieldCallback) {
  97.                     $addFormFieldCallback($form$item);
  98.                 }
  99.             }
  100.         );
  101.     }
  102.     protected function afterBuildForm(FormBuilderInterface $builder, array $options): void
  103.     {
  104.         if ($options['submitted_fields_only']) {
  105.             $this->handleSubmittedFieldsOnly($builder);
  106.         }
  107.     }
  108.     public function addEnumFormField(FormBuilderInterface $builderstring $namestring $enumClass,
  109.         array $allowedValues null, array $choices null, array $extraOptions = []) {
  110.         $reflection = new \ReflectionClass($enumClass);
  111.         if ($choices === null) {
  112.             $choices = [];
  113.             foreach ($reflection->getConstants() as $constName => $constValue) {
  114.                 if ($allowedValues && !in_array($constValue$allowedValues)) {
  115.                     continue;
  116.                 }
  117.                 $choices[$enumClass::getText($constValue)] = $constValue;
  118.             }
  119.         }
  120.         $builder->add($nameChoiceType::class, array_merge(self::getOptions([
  121.             'choices' => $choices,
  122.             'required' => !$this->reflectionUtils->isOrmFieldNullable($builder->getDataClass(), $name),
  123.         ]), $extraOptions));
  124.     }
  125.     public function addSelectFormField($name$options)
  126.     {
  127.         $addFormCallback = function (FormInterface $form$item) use ($name$options) {
  128.             $form->add($nameEntityType::class, array_merge([
  129.                 'class' => null,
  130.                 'choices' => [],
  131.                 'choice_label' => function($item) {
  132.                     return $item->toString();
  133.                 },
  134.                 'required' => false,
  135.                 'empty_data' => 0,
  136.                 'mapped' => true,
  137.                 'multiple' => false,
  138.                 'expanded' => false// Render as checkboxes
  139.             ], $options));
  140.         };
  141.         $this->addFormFieldCreateCallback($addFormCallback);
  142.     }
  143.     public function addImageField(FormBuilderInterface $builderstring $name, array $options null,
  144.         $acceptImages true$acceptVideos false)
  145.     {
  146.        $accept "";
  147.         $mimeTypes = [];
  148.        if ($acceptImages) {
  149.             $accept .= ($accept "," "") . "image/*";
  150.             $mimeTypes array_merge($mimeTypes, [
  151.                 'image/jpg',
  152.                 'image/jpeg',
  153.                 'image/png',
  154.                 'image/gif',
  155.             ]);
  156.         }
  157.         if ($acceptVideos) {
  158.             $accept .= ($accept "," "") . "video/*";
  159.             $mimeTypes array_merge($mimeTypes, [
  160.                 'video/mp4',
  161.                 'video/mov',
  162.             ]);
  163.         }
  164.         $constraints = [];
  165.         if ($acceptImages && !$acceptVideos) {
  166.             $constraints[] = new Image([]);
  167.         } else {
  168.             $constraints[] = new File([
  169.                 'mimeTypes' => $mimeTypes,
  170.                 'mimeTypesMessage' => 'Недопустимый формат файла',
  171.             ]);
  172.         }
  173.         $builder->add($nameFileType::class, array_merge([
  174.             'required' => false,
  175.             'data' => null,
  176.             'attr' => [
  177.                 'accept' => $accept,
  178.             ],
  179.             'constraints' => $constraints
  180.         ], $options ?? []));
  181.         $builder->add($name "Remove"CheckboxType::class, [
  182.             'mapped' => false,
  183.             'required' => false,
  184.         ]);
  185.     }
  186.     public function addTextField(string $nameFormBuilderInterface $builder, array $options null): self
  187.     {
  188.         $builder->add($nameTextType::class, $options ?? []);
  189.         return $this;
  190.     }
  191.     /**
  192.      * @param string|array $name
  193.      * @param FormBuilderInterface $builder
  194.      * @return void
  195.      */
  196.     public function addCheckboxField($nameFormBuilderInterface $builder, array $options null): self
  197.     {
  198.         $names $name;
  199.         if (!is_array($names)) {
  200.             $names = [$names];
  201.         }
  202.         $options array_merge([
  203.             'required' => false,
  204.         ], $options ?? []);
  205.         foreach ($names as $name) {
  206.             $builder->add($nameCheckboxType::class, $options);
  207.         }
  208.         return $this;
  209.     }
  210.     public function addFileField(FormBuilderInterface $builderstring $name, array $fileExtensions null, array $options = [])
  211.     {
  212.         $builder->add($nameFileType::class, array_merge(
  213.             [
  214.                 'required' => false,
  215.                 'data' => null,
  216.                 'constraints' => [
  217.                     new File(),
  218.                 ]
  219.             ],
  220.             ($fileExtensions ? [
  221.                 'attr' => [
  222.                     'accept' => implode(","$fileExtensions)
  223.                 ]
  224.             ] : []),
  225.             $options
  226.         ));
  227.     }
  228.     public function addDateField($fieldName)
  229.     {
  230.         $this->getBuilder()->add($fieldNameDateType::class, [
  231.             'widget' => 'single_text',
  232.         ]);
  233.     }
  234.     public function addDateTimeField($fieldName)
  235.     {
  236.         $this->getBuilder()->add($fieldNameDateTimeType::class, [
  237.             'widget' => 'single_text',
  238.         ]);
  239.     }
  240.     public function addDateFieldAsTextInput(FormBuilderInterface $builder$fieldNamebool $disabled false)
  241.     {
  242.         Other::forEach($fieldName, function ($fieldName) use ($builder$disabled) {
  243.             $builder->add($fieldNameTextType::class, [
  244.                 'required' => true,
  245.                 'disabled' => $disabled,
  246.             ]);
  247.             $builder->get($fieldName)->addModelTransformer($this->dataTransformerRetriever->getStringToDateTransformer());
  248.         });
  249.     }
  250.     /**
  251.      * Field entity service should have method: getChoiceLabel, get[EntityClass]Choices(). EntityClass - editing entity class.
  252.      * Example:
  253.      * Edit Story#carWash field.
  254.      * CarWashService should have methods:
  255.      * 1. getStoryChoices(Story $story) - get choices (all possible items) for Story's field - carWash.
  256.      * 2. getChoiceLabel(CarWash $carWash) - get choice label (html select option text) of CarWash.
  257.      * @param FormBuilderInterface $builder
  258.      * @param string $fieldEntityClass
  259.      * @param array|null $choicesQueryParams
  260.      * @return void
  261.      */
  262.     protected function addEntityField(FormBuilderInterface $builderstring $fieldEntityClass, array $options null,
  263.                                       $fieldName null$defaultValue null)
  264.     {
  265.         $item $builder->getData();
  266.         $entityClassShortName ReflectionUtils::getClassShortName(get_class($item));
  267.         $fieldEntityClassShortName ReflectionUtils::getClassShortName($fieldEntityClass);
  268.         $fieldName = ($fieldName ?: lcfirst($fieldEntityClassShortName));
  269.         $fieldEntityServiceGetter "get" $fieldEntityClassShortName "Service";
  270.         $choicesGetter "get" $entityClassShortName "Choices";
  271.         $choiceLabelGetter "getChoiceLabel";
  272.         $fieldNameUcfirst $fieldName ucfirst($fieldName) : null;
  273.         $fieldGetter "get" . ($fieldNameUcfirst ?: $fieldEntityClassShortName);
  274.         $fieldSetter "set" . ($fieldNameUcfirst ?: $fieldEntityClassShortName);
  275.         /** @var BaseEntityService $entityService */
  276.         $entityService $this->serviceRetriever->getService("App\Service\\$fieldEntityClassShortName\\" $fieldEntityClassShortName "Service");
  277.         $choices $entityService->$choicesGetter($item);
  278.         $currentFieldVal $item->$fieldGetter();
  279.         $options $this->getOptions($options);
  280.         $options array_merge([
  281.             'class' => $fieldEntityClass,
  282.             'choices' => $choices,
  283.             'choice_label' => function($entity) use ($entityService$choiceLabelGetter$item) {
  284.                 $params = (new \ReflectionMethod($entityService$choiceLabelGetter))->getParameters();
  285.                 if (count($params) === 2) {
  286.                     return $entityService->$choiceLabelGetter($entity$item);
  287.                 }
  288.                 return $entityService->$choiceLabelGetter($entity);
  289.             },
  290.             'required' => true,
  291.             'data' => $currentFieldVal ?? $defaultValue,
  292.             'empty_data' => null,
  293.             'placeholder' => $options["placeholder"]
  294.         ], $options);
  295.         $builder->add($fieldNameEntityType::class, $options);
  296.     }
  297.     private function getOptions(array $options null): array
  298.     {
  299.         return array_merge([
  300.             'required' => true,
  301.             "placeholder" => isset($options['required']) && $options['required'] ? 'Не выбрано' 'Нет',
  302.         ], $options ?? []);
  303.     }
  304.     /**
  305.      * @param array|string[] $fields
  306.      * @return void
  307.      */
  308.     public function setImageFields(array $fields)
  309.     {
  310.         $this->imageFields $fields;
  311.     }
  312.     /**
  313.      * @param array|string[] $fields
  314.      * @return void
  315.      */
  316.     public function setTextAreaFields(array $fields)
  317.     {
  318.         $this->textAreaFields $fields;
  319.     }
  320.     /**
  321.      * @param array|string[] $fields
  322.      * @return void
  323.      */
  324.     public function setTextFields(array $fields)
  325.     {
  326.         $this->textFields $fields;
  327.     }
  328.     private function addTextFields(FormBuilderInterface $builder, array $options)
  329.     {
  330.         foreach ($this->textFields as $field) {
  331.             $builder->add($fieldTextType::class, [
  332.                 'required' => false,
  333.             ]);
  334.         }
  335.     }
  336.     private function addFormFieldCreateCallback($callback)
  337.     {
  338.         $this->addFormFieldCallbacks[] = $callback;
  339.     }
  340.     /**
  341.      * Регистрирует PRE_SUBMIT listener, который удаляет из формы все поля,
  342.      * не пришедшие в теле запроса — Doctrine не тронет их в БД.
  343.      * Usage:
  344.      * $form = $this->createForm(UserSettingsType::class, $item, [
  345.      *      'submitted_fields_only' => true,
  346.      * ]);
  347.      */
  348.     public function configureOptions(OptionsResolver $resolver)
  349.     {
  350.         $resolver->setDefaults([
  351.             'submitted_fields_only' => false,
  352.         ]);
  353.         $resolver->setAllowedTypes('submitted_fields_only''bool');
  354.     }
  355.     protected function handleSubmittedFieldsOnly(FormBuilderInterface $builder): void
  356.     {
  357.         $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
  358.             $submittedData $event->getData() ?? [];
  359.             $form $event->getForm();
  360.             foreach (array_keys(iterator_to_array($form)) as $fieldName) {
  361.                 if (!array_key_exists($fieldName$submittedData)) {
  362.                     $form->remove($fieldName);
  363.                 }
  364.             }
  365.         });
  366.     }
  367.  }