-
-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to use guessing with DTO instead of Entity? #226
Comments
@craigh do you get some error? if yes, then please write it here and also show me your form type. |
Hello! No, I do not receive any error, the guessing just fails so the input is displayed as a simple text box. The form generation is dynamic and complicated, but in the end it is simply this:
where: $field->name = 'state'
$field->type = null
$options = [] As I said, the form data is a DTO instead of an Entity. So I have tried various versions of attributes in the DTO definition: #[DoctrineAssert\EnumType(USStateType::class)]
public string $state; #[DoctrineAssert\EnumType(USStateType::class)]
public USStateType $state; public USStateType $state; none seem to have any effect. |
I am using the same Enum in a 'regular' form and it works fine there. So the Enum is setup properly when using an Entity-backed form. Thank you for quickly responding and trying to help me! 🙏 |
It's not possible to use form guesser for DTOs now. Because class EnumTypeGuesser extends DoctrineOrmTypeGuesser Enum Guesser extends Doctrine ORM Guesser, so it expects the mapped entity. To do what you want is needed a new Guesser, which will does somehow detection from any Plain PHP Class, not only from entities. |
Thank you, this was my assumption as well. I was hoping you might be able to extend Symfony's Validation guesser to add Enum types as well. I guess this goes in as a feature request. thanks! |
Here is something I hacked together. You could clean it up and add it to the bundle. // src/Form/Extension/Validator/EnumValidatorTypeGuesser.php
<?php
namespace App\Form\Extension\Validator;
use Fresh\DoctrineEnumBundle\DBAL\Types\AbstractEnumType;
use Fresh\DoctrineEnumBundle\Exception\EnumType\EnumTypeIsRegisteredButClassDoesNotExistException;
use Fresh\DoctrineEnumBundle\Validator\Constraints\EnumType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormTypeGuesserInterface;
use Symfony\Component\Form\Guess\Guess;
use Symfony\Component\Form\Guess\TypeGuess;
use Symfony\Component\Form\Guess\ValueGuess;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
class EnumValidatorTypeGuesser implements FormTypeGuesserInterface
{
/** @var string[] */
private array $registeredEnumTypes = [];
public function __construct(
private readonly MetadataFactoryInterface $metadataFactory,
array $registeredTypes,
) {
foreach ($registeredTypes as $type => $details) {
$this->registeredEnumTypes[$type] = $details['class'];
}
}
public function guessType(string $class, string $property): ?TypeGuess
{
return $this->guess($class, $property, function (Constraint $constraint) {
return $this->guessTypeForConstraint($constraint);
});
}
public function guessRequired(string $class, string $property): ?ValueGuess
{
return null;
}
public function guessMaxLength(string $class, string $property): ?ValueGuess
{
return null;
}
public function guessPattern(string $class, string $property): ?ValueGuess
{
return null;
}
/**
* Guesses a field class name for a given constraint.
*/
public function guessTypeForConstraint(Constraint $constraint): ?TypeGuess
{
if ($constraint instanceof EnumType) {
if (!\in_array($constraint->entity, $this->registeredEnumTypes, true)) {
return null;
}
$registeredEnumTypeFQCN = $constraint->entity;
if (!\class_exists($constraint->entity)) {
$exceptionMessage = \sprintf(
'ENUM type "%s" is registered as "%s", but that class does not exist',
$constraint->entity,
$registeredEnumTypeFQCN
);
throw new EnumTypeIsRegisteredButClassDoesNotExistException($exceptionMessage);
}
if (!\is_subclass_of($registeredEnumTypeFQCN, AbstractEnumType::class)) {
return null;
}
/** @var AbstractEnumType<int|string, int|string> $registeredEnumTypeFQCN */
$parameters = [
'choices' => $registeredEnumTypeFQCN::getChoices(), // Get the choices from the fully qualified class name
];
return new TypeGuess(ChoiceType::class, $parameters, Guess::VERY_HIGH_CONFIDENCE);
}
return null;
}
/**
* Iterates over the constraints of a property, executes a constraints on
* them and returns the best guess.
*/
protected function guess(string $class, string $property, \Closure $closure, $defaultValue = null): Guess|null
{
$guesses = [];
$classMetadata = $this->metadataFactory->getMetadataFor($class);
if ($classMetadata instanceof ClassMetadataInterface && $classMetadata->hasPropertyMetadata($property)) {
foreach ($classMetadata->getPropertyMetadata($property) as $memberMetadata) {
foreach ($memberMetadata->getConstraints() as $constraint) {
if ($guess = $closure($constraint)) {
$guesses[] = $guess;
}
}
}
}
if ($defaultValue !== null) {
$guesses[] = new ValueGuess($defaultValue, Guess::LOW_CONFIDENCE);
}
return Guess::getBestGuess($guesses);
}
} Config: # services.yaml
App\Form\Extension\Validator\EnumValidatorTypeGuesser:
arguments:
- '@validator.mapping.class_metadata_factory'
- "%doctrine.dbal.connection_factory.types%" |
I'm trying to use the FormType guessing feature of this bundle, but with a DTO and not an Entity. Is this possible? I see the guessing uses the
Type
- which I assume is from the@ORM\Column(type="MyCoolEnum")
declaration.DoctrineEnumBundle/Form/EnumTypeGuesser.php
Line 68 in 4448ef6
I guess Symfony uses the validation constraints instead. Is there a way this could be done?
The text was updated successfully, but these errors were encountered: