vendor/easycorp/easyadmin-bundle/src/Factory/FieldFactory.php line 72
<?phpnamespace EasyCorp\Bundle\EasyAdminBundle\Factory;use Doctrine\DBAL\Types\Types;use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField;use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;use EasyCorp\Bundle\EasyAdminBundle\Field\DateField;use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;use EasyCorp\Bundle\EasyAdminBundle\Field\Field;use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField;use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;use EasyCorp\Bundle\EasyAdminBundle\Field\TimeField;use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EaFormRowType;use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminTabType;use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;use EasyCorp\Bundle\EasyAdminBundle\Security\Permission;use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;/*** @author Javier Eguiluz <javier.eguiluz@gmail.com>*/final class FieldFactory{private static array $doctrineTypeToFieldFqcn = [Types::ARRAY => ArrayField::class,Types::BIGINT => TextField::class,Types::BINARY => TextareaField::class,Types::BLOB => TextareaField::class,Types::BOOLEAN => BooleanField::class,Types::DATE_MUTABLE => DateField::class,Types::DATE_IMMUTABLE => DateField::class,Types::DATEINTERVAL => TextField::class,Types::DATETIME_MUTABLE => DateTimeField::class,Types::DATETIME_IMMUTABLE => DateTimeField::class,Types::DATETIMETZ_MUTABLE => DateTimeField::class,Types::DATETIMETZ_IMMUTABLE => DateTimeField::class,Types::DECIMAL => NumberField::class,Types::FLOAT => NumberField::class,Types::GUID => TextField::class,Types::INTEGER => IntegerField::class,Types::JSON => TextField::class,Types::OBJECT => TextField::class,Types::SIMPLE_ARRAY => ArrayField::class,Types::SMALLINT => IntegerField::class,Types::STRING => TextField::class,Types::TEXT => TextareaField::class,Types::TIME_MUTABLE => TimeField::class,Types::TIME_IMMUTABLE => TimeField::class,];private AdminContextProvider $adminContextProvider;private AuthorizationCheckerInterface $authorizationChecker;private iterable $fieldConfigurators;public function __construct(AdminContextProvider $adminContextProvider, AuthorizationCheckerInterface $authorizationChecker, iterable $fieldConfigurators){$this->adminContextProvider = $adminContextProvider;$this->authorizationChecker = $authorizationChecker;$this->fieldConfigurators = $fieldConfigurators;}public function processFields(EntityDto $entityDto, FieldCollection $fields): void{$this->preProcessFields($fields, $entityDto);$context = $this->adminContextProvider->getContext();$currentPage = $context->getCrud()->getCurrentPage();$isDetailOrIndex = \in_array($currentPage, [Crud::PAGE_INDEX, Crud::PAGE_DETAIL], true);foreach ($fields as $fieldDto) {if ((null !== $currentPage && false === $fieldDto->isDisplayedOn($currentPage))|| false === $this->authorizationChecker->isGranted(Permission::EA_VIEW_FIELD, $fieldDto)) {$fields->unset($fieldDto);continue;}// "form rows" only make sense in pages that contain formsif ($isDetailOrIndex && EaFormRowType::class === $fieldDto->getFormType()) {$fields->unset($fieldDto);continue;}// when creating new entities with "useEntryCrudForm" on an edit page we must// explicitly check for the "new" page because $currentPage will be "edit"if ((null === $entityDto->getInstance()) && !$fieldDto->isDisplayedOn(Crud::PAGE_NEW)) {$fields->unset($fieldDto);continue;}foreach ($this->fieldConfigurators as $configurator) {if (!$configurator->supports($fieldDto, $entityDto)) {continue;}$configurator->configure($fieldDto, $entityDto, $context);}foreach ($fieldDto->getFormThemes() as $formThemePath) {$context?->getCrud()?->addFormTheme($formThemePath);}$fields->set($fieldDto);}$isPageWhereTabsAreVisible = \in_array($currentPage, [Crud::PAGE_DETAIL, Crud::PAGE_EDIT, Crud::PAGE_NEW], true);if ($isPageWhereTabsAreVisible) {$this->checkOrphanTabFields($fields, $context);}$entityDto->setFields($fields);}private function preProcessFields(FieldCollection $fields, EntityDto $entityDto): void{if ($fields->isEmpty()) {return;}// this is needed to handle this edge-case: the list of fields include one or more form panels,// but the first fields of the list don't belong to any panel. We must create an automatic empty// form panel for those "orphaned fields" so they are displayed as expected$firstFieldIsAFormPanel = $fields->first()->isFormDecorationField();foreach ($fields as $fieldDto) {if (!$firstFieldIsAFormPanel && $fieldDto->isFormDecorationField()) {$fields->prepend(FormField::addPanel()->getAsDto());break;}}foreach ($fields as $fieldDto) {if (Field::class !== $fieldDto->getFieldFqcn()) {continue;}// this is a virtual field, so we can't autoconfigure itif (!$entityDto->hasProperty($fieldDto->getProperty())) {continue;}if ($fieldDto->getProperty() === $entityDto->getPrimaryKeyName()) {$guessedFieldFqcn = IdField::class;} else {$doctrinePropertyType = $entityDto->getPropertyMetadata($fieldDto->getProperty())->get('type');$guessedFieldFqcn = self::$doctrineTypeToFieldFqcn[$doctrinePropertyType] ?? null;if (null === $guessedFieldFqcn) {throw new \RuntimeException(sprintf('The Doctrine type of the "%s" field is "%s", which is not supported by EasyAdmin. For Doctrine\'s Custom Mapping Types have a look at EasyAdmin\'s field docs.', $fieldDto->getProperty(), $doctrinePropertyType));}}$fields->set($this->transformField($fieldDto, $guessedFieldFqcn));}}// transforms a generic Field class into a specific <type>Field class (e.g. DateTimeField)private function transformField(FieldDto $fieldDto, string $newFieldFqcn): FieldDto{/** @var FieldDto $newField */$newField = $newFieldFqcn::new($fieldDto->getProperty())->getAsDto();$newField->setUniqueId($fieldDto->getUniqueId());$newField->setFieldFqcn($newFieldFqcn);$newField->setDisplayedOn($fieldDto->getDisplayedOn());$newField->setValue($fieldDto->getValue());$newField->setFormattedValue($fieldDto->getFormattedValue());$newField->setCssClass(trim($newField->getCssClass().' '.$fieldDto->getCssClass()));$newField->setColumns($fieldDto->getColumns());$newField->setTranslationParameters($fieldDto->getTranslationParameters());$newField->setAssets($newField->getAssets()->mergeWith($fieldDto->getAssets()));foreach ($fieldDto->getFormThemes() as $formThemePath) {$newField->addFormTheme($formThemePath);}$customFormTypeOptions = $fieldDto->getFormTypeOptions();$defaultFormTypeOptions = $newField->getFormTypeOptions();$newField->setFormTypeOptions(array_merge($defaultFormTypeOptions, $customFormTypeOptions));$customFieldOptions = $fieldDto->getCustomOptions()->all();$defaultFieldOptions = $newField->getCustomOptions()->all();$mergedFieldOptions = array_merge($defaultFieldOptions, $customFieldOptions);$newField->setCustomOptions($mergedFieldOptions);if (null !== $fieldDto->getLabel()) {$newField->setLabel($fieldDto->getLabel());}if (null !== $fieldDto->isVirtual()) {$newField->setVirtual($fieldDto->isVirtual());}if (null !== $fieldDto->getTextAlign()) {$newField->setTextAlign($fieldDto->getTextAlign());}if (null !== $fieldDto->isSortable()) {$newField->setSortable($fieldDto->isSortable());}if (null !== $fieldDto->getPermission()) {$newField->setPermission($fieldDto->getPermission());}if (null !== $fieldDto->getHelp()) {$newField->setHelp($fieldDto->getHelp());}if (null !== $fieldDto->getFormType()) {$newField->setFormType($fieldDto->getFormType());}// don't copy the template name and path from the original Field class// (because they are just 'crud/field/text' and ' @EasyAdmin/crud/field/text.html.twig')// and use the template name/path from the new specific field (e.g. 'crud/field/datetime')return $newField;}/*** When rendering fields using tabs, all fields must belong to some tab.*/private function checkOrphanTabFields(FieldCollection $fields, AdminContext $context): void{$hasTabs = false;$isTabField = static fn (FieldDto $fieldDto) => EasyAdminTabType::class === $fieldDto->getFormType();$isFormField = static fn (FieldDto $fieldDto) => FormField::class === $fieldDto->getFieldFqcn();foreach ($fields as $fieldDto) {if ($isTabField($fieldDto)) {$hasTabs = true;break;}}if (!$hasTabs || $isTabField($fields->first())) {return;}$orphanFieldNames = [];foreach ($fields as $field) {if ($isTabField($field)) {break;}if ($isFormField($field)) {continue;}$orphanFieldNames[] = $field->getProperty();}throw new \RuntimeException(sprintf('The "%s" page of "%s" uses tabs to display its fields, but the following fields don\'t belong to any tab: %s. Use "FormField::addTab(\'...\')" to add a tab before those fields.', $context->getCrud()->getCurrentPage(), $context->getCrud()->getControllerFqcn(), implode(', ', $orphanFieldNames)));}}