vendor/easycorp/easyadmin-bundle/src/Factory/FieldFactory.php line 92

Open in your IDE?
  1. <?php
  2. namespace EasyCorp\Bundle\EasyAdminBundle\Factory;
  3. use Doctrine\DBAL\Types\Types;
  4. use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
  5. use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
  6. use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
  7. use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
  8. use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField;
  9. use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
  10. use EasyCorp\Bundle\EasyAdminBundle\Field\DateField;
  11. use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
  12. use EasyCorp\Bundle\EasyAdminBundle\Field\Field;
  13. use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;
  14. use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
  15. use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
  16. use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField;
  17. use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
  18. use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
  19. use EasyCorp\Bundle\EasyAdminBundle\Field\TimeField;
  20. use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EaFormRowType;
  21. use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
  22. use EasyCorp\Bundle\EasyAdminBundle\Security\Permission;
  23. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  24. /**
  25. * @author Javier Eguiluz <javier.eguiluz@gmail.com>
  26. */
  27. final class FieldFactory
  28. {
  29. private static $doctrineTypeToFieldFqcn = [
  30. Types::ARRAY => ArrayField::class,
  31. Types::BIGINT => TextField::class,
  32. Types::BINARY => TextareaField::class,
  33. Types::BLOB => TextareaField::class,
  34. Types::BOOLEAN => BooleanField::class,
  35. Types::DATE_MUTABLE => DateField::class,
  36. Types::DATE_IMMUTABLE => DateField::class,
  37. Types::DATEINTERVAL => TextField::class,
  38. Types::DATETIME_MUTABLE => DateTimeField::class,
  39. Types::DATETIME_IMMUTABLE => DateTimeField::class,
  40. Types::DATETIMETZ_MUTABLE => DateTimeField::class,
  41. Types::DATETIMETZ_IMMUTABLE => DateTimeField::class,
  42. Types::DECIMAL => NumberField::class,
  43. Types::FLOAT => NumberField::class,
  44. Types::GUID => TextField::class,
  45. Types::INTEGER => IntegerField::class,
  46. Types::JSON => TextField::class,
  47. Types::OBJECT => TextField::class,
  48. Types::SIMPLE_ARRAY => ArrayField::class,
  49. Types::SMALLINT => IntegerField::class,
  50. Types::STRING => TextField::class,
  51. Types::TEXT => TextareaField::class,
  52. Types::TIME_MUTABLE => TimeField::class,
  53. Types::TIME_IMMUTABLE => TimeField::class,
  54. ];
  55. private $adminContextProvider;
  56. private $authorizationChecker;
  57. private $fieldConfigurators;
  58. public function __construct(AdminContextProvider $adminContextProvider, AuthorizationCheckerInterface $authorizationChecker, iterable $fieldConfigurators)
  59. {
  60. $this->adminContextProvider = $adminContextProvider;
  61. $this->authorizationChecker = $authorizationChecker;
  62. $this->fieldConfigurators = $fieldConfigurators;
  63. }
  64. public function processFields(EntityDto $entityDto, FieldCollection $fields): void
  65. {
  66. $this->preProcessFields($fields, $entityDto);
  67. $context = $this->adminContextProvider->getContext();
  68. $currentPage = $context->getCrud()->getCurrentPage();
  69. $isDetailOrIndex = \in_array($currentPage, [Crud::PAGE_INDEX, Crud::PAGE_DETAIL], true);
  70. foreach ($fields as $fieldDto) {
  71. if ((null !== $currentPage && false === $fieldDto->isDisplayedOn($currentPage))
  72. || false === $this->authorizationChecker->isGranted(Permission::EA_VIEW_FIELD, $fieldDto)) {
  73. $fields->unset($fieldDto);
  74. continue;
  75. }
  76. // "form rows" only make sense in pages that contain forms
  77. if ($isDetailOrIndex && EaFormRowType::class === $fieldDto->getFormType()) {
  78. $fields->unset($fieldDto);
  79. continue;
  80. }
  81. foreach ($this->fieldConfigurators as $configurator) {
  82. if (!$configurator->supports($fieldDto, $entityDto)) {
  83. continue;
  84. }
  85. $configurator->configure($fieldDto, $entityDto, $context);
  86. }
  87. $fields->set($fieldDto);
  88. }
  89. $entityDto->setFields($fields);
  90. }
  91. private function preProcessFields(FieldCollection $fields, EntityDto $entityDto): void
  92. {
  93. if ($fields->isEmpty()) {
  94. return;
  95. }
  96. // this is needed to handle this edge-case: the list of fields include one or more form panels,
  97. // but the first fields of the list don't belong to any panel. We must create an automatic empty
  98. // form panel for those "orphaned fields" so they are displayed as expected
  99. $firstFieldIsAFormPanel = $fields->first()->isFormDecorationField();
  100. foreach ($fields as $fieldDto) {
  101. if (!$firstFieldIsAFormPanel && $fieldDto->isFormDecorationField()) {
  102. $fields->prepend(FormField::addPanel()->getAsDto());
  103. break;
  104. }
  105. }
  106. foreach ($fields as $fieldDto) {
  107. if (Field::class !== $fieldDto->getFieldFqcn()) {
  108. continue;
  109. }
  110. // this is a virtual field, so we can't autoconfigure it
  111. if (!$entityDto->hasProperty($fieldDto->getProperty())) {
  112. continue;
  113. }
  114. if ($fieldDto->getProperty() === $entityDto->getPrimaryKeyName()) {
  115. $guessedFieldFqcn = IdField::class;
  116. } else {
  117. $doctrinePropertyType = $entityDto->getPropertyMetadata($fieldDto->getProperty())->get('type');
  118. $guessedFieldFqcn = self::$doctrineTypeToFieldFqcn[$doctrinePropertyType] ?? null;
  119. if (null === $guessedFieldFqcn) {
  120. throw new \RuntimeException(sprintf('The Doctrine type of the "%s" field is "%s", which is not supported by EasyAdmin yet.', $fieldDto->getProperty(), $doctrinePropertyType));
  121. }
  122. }
  123. $fields->set($this->transformField($fieldDto, $guessedFieldFqcn));
  124. }
  125. }
  126. // transforms a generic Field class into a specific <type>Field class (e.g. DateTimeField)
  127. private function transformField(FieldDto $fieldDto, string $newFieldFqcn): FieldDto
  128. {
  129. /** @var FieldDto $newField */
  130. $newField = $newFieldFqcn::new($fieldDto->getProperty())->getAsDto();
  131. $newField->setUniqueId($fieldDto->getUniqueId());
  132. $newField->setFieldFqcn($newFieldFqcn);
  133. $newField->setDisplayedOn($fieldDto->getDisplayedOn());
  134. $newField->setValue($fieldDto->getValue());
  135. $newField->setFormattedValue($fieldDto->getFormattedValue());
  136. $newField->setCssClass(trim($newField->getCssClass().' '.$fieldDto->getCssClass()));
  137. $newField->setColumns($fieldDto->getColumns());
  138. $newField->setTranslationParameters($fieldDto->getTranslationParameters());
  139. $newField->setAssets($newField->getAssets()->mergeWith($fieldDto->getAssets()));
  140. $customFormTypeOptions = $fieldDto->getFormTypeOptions();
  141. $defaultFormTypeOptions = $newField->getFormTypeOptions();
  142. $newField->setFormTypeOptions(array_merge($defaultFormTypeOptions, $customFormTypeOptions));
  143. $customFieldOptions = $fieldDto->getCustomOptions()->all();
  144. $defaultFieldOptions = $newField->getCustomOptions()->all();
  145. $mergedFieldOptions = array_merge($defaultFieldOptions, $customFieldOptions);
  146. $newField->setCustomOptions($mergedFieldOptions);
  147. if (null !== $fieldDto->getLabel()) {
  148. $newField->setLabel($fieldDto->getLabel());
  149. }
  150. if (null !== $fieldDto->isVirtual()) {
  151. $newField->setVirtual($fieldDto->isVirtual());
  152. }
  153. if (null !== $fieldDto->getTextAlign()) {
  154. $newField->setTextAlign($fieldDto->getTextAlign());
  155. }
  156. if (null !== $fieldDto->isSortable()) {
  157. $newField->setSortable($fieldDto->isSortable());
  158. }
  159. if (null !== $fieldDto->getPermission()) {
  160. $newField->setPermission($fieldDto->getPermission());
  161. }
  162. if (null !== $fieldDto->getHelp()) {
  163. $newField->setHelp($fieldDto->getHelp());
  164. }
  165. if (null !== $fieldDto->getFormType()) {
  166. $newField->setFormType($fieldDto->getFormType());
  167. }
  168. // don't copy the template name and path from the original Field class
  169. // (because they are just 'crud/field/text' and ' @EasyAdmin/crud/field/text.html.twig')
  170. // and use the template name/path from the new specific field (e.g. 'crud/field/datetime')
  171. return $newField;
  172. }
  173. }