Skip to content
Merged

V1 #50

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
FROM php:8.4-cli-trixie

RUN apt-get update && apt-get install -y --no-install-recommends \
file \
git \
file \
git \
openssh-client \
&& rm -rf /var/lib/apt/lists/*
&& rm -rf /var/lib/apt/lists/*

ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/

RUN set -eux; \
install-php-extensions \
@composer \
apcu \
opcache \
xdebug \
;
install-php-extensions \
@composer \
apcu \
opcache \
xdebug \
;

RUN useradd -m vscode

Expand Down
17 changes: 16 additions & 1 deletion src/A2lixAutoFormBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
namespace A2lix\AutoFormBundle;

use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

final class A2lixAutoFormBundle extends AbstractBundle
final class A2lixAutoFormBundle extends AbstractBundle implements CompilerPassInterface
{
#[\Override]
public function configure(DefinitionConfigurator $definition): void
Expand All @@ -42,4 +43,18 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
->arg('$globalExcludedChildren', $config['children_excluded'])
;
}

public function build(ContainerBuilder $container): void
{
$container->addCompilerPass($this);
}

public function process(ContainerBuilder $container): void
{
if ($container->hasExtension('a2lix_translation_form')) {
$container->getDefinition('a2lix_auto_form.form.type.auto_type')
->setArgument('$globalTranslatedChildren', true)
;
}
}
}
2 changes: 2 additions & 0 deletions src/Form/Attribute/AutoTypeCustom.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function __construct(
private ?string $name = null,
private ?bool $excluded = null,
private ?bool $embedded = null,
private ?bool $translated = null,
private ?array $groups = null,
) {}

Expand All @@ -45,6 +46,7 @@ public function getOptions(): array
...(null !== $this->name ? ['child_name' => $this->name] : []),
...(null !== $this->excluded ? ['child_excluded' => $this->excluded] : []),
...(null !== $this->embedded ? ['child_embedded' => $this->embedded] : []),
...(null !== $this->embedded ? ['child_translated' => $this->translated] : []),
...(null !== $this->groups ? ['child_groups' => $this->groups] : []),
];
}
Expand Down
59 changes: 48 additions & 11 deletions src/Form/Builder/AutoTypeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use A2lix\AutoFormBundle\Form\Attribute\AutoTypeCustom;
use A2lix\AutoFormBundle\Form\Type\AutoType;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
Expand All @@ -26,6 +27,7 @@
* child_name?: string,
* child_excluded?: bool,
* child_embedded?: bool,
* child_translated?: bool,
* child_groups?: list<string>,
* ...
* }
Expand All @@ -35,6 +37,7 @@
* children: array<string, ChildOptions|ChildBuilderCallable>,
* children_excluded: list<string>|"*",
* children_embedded: list<string>|"*",
* children_translated: bool,
* children_groups: list<string>|null,
* builder: FormBuilderCallable|null,
* }
Expand All @@ -52,6 +55,7 @@
public function buildChildren(FormBuilderInterface $builder, array $formOptions): void
{
$dataClass = $this->getDataClass($form = $builder->getForm());
dump($dataClass);

if (null === $classProperties = $this->propertyInfoExtractor->getProperties($dataClass)) {
throw new \RuntimeException(\sprintf('Unable to extract properties of "%s".', $dataClass));
Expand All @@ -61,7 +65,7 @@
$allChildrenExcluded = '*' === $formOptions['children_excluded'];
$allChildrenEmbedded = '*' === $formOptions['children_embedded'];
$childrenGroups = $formOptions['children_groups'] ?? ['Default'];
$formLevel = $this->getFormLevel($form);
$formDepth = $this->getFormDepth($form);

/** @var list<string> $classProperties */
foreach ($classProperties as $classProperty) {
Expand Down Expand Up @@ -116,12 +120,17 @@
// PropertyInfo? Enrich childOptions
if (null !== $propTypeInfo = $this->propertyInfoExtractor->getType($dataClass, $classProperty)) {
// @phpstan-ignore argument.type
$formChildTranslated = ($formOptions['children_translated'] || ($childOptions['child_translated'] ?? false))

Check failure on line 123 in src/Form/Builder/AutoTypeBuilder.php

View workflow job for this annotation

GitHub Actions / Static Analysis

No error with identifier argument.type is reported on line 123.

Check failure on line 123 in src/Form/Builder/AutoTypeBuilder.php

View workflow job for this annotation

GitHub Actions / Static Analysis

No error with identifier argument.type is reported on line 123.
&& ('translations' === $classProperty);
// @phpstan-ignore argument.type
$formChildEmbedded = $allChildrenEmbedded || \in_array($classProperty, $formOptions['children_embedded'], true)
|| ($childOptions['child_embedded'] ?? false);

if ($formChildEmbedded) {
$childOptions = $this->updateChildOptions($childOptions, $propTypeInfo, $formLevel);
}
$childOptions = match (true) {
$formChildTranslated => $this->updateTranslatedChildOptions($childOptions, $propTypeInfo, $refProperty),
$formChildEmbedded => $this->updateEmbeddedChildOptions($childOptions, $propTypeInfo, $refProperty, $formDepth),
default => $childOptions,
};
}

$this->addChild($builder, $classProperty, $childOptions);
Expand Down Expand Up @@ -171,6 +180,7 @@
$options['child_type'],
$options['child_excluded'],
$options['child_embedded'],
$options['child_translated'],
$options['child_groups'],
);

Expand All @@ -193,14 +203,41 @@

throw new \RuntimeException('Unable to get dataClass');
}
/**
* @param ChildOptions $baseChildOptions
*
* @return ChildOptions
*/
private function updateTranslatedChildOptions(
array $baseChildOptions,
TypeInfo $propTypeInfo,
\ReflectionProperty $refProperty,
): array {
if (!$propTypeInfo instanceof TypeInfo\CollectionType) {
return [];
}

dump($refProperty);

return [

Check failure on line 222 in src/Form/Builder/AutoTypeBuilder.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Method A2lix\AutoFormBundle\Form\Builder\AutoTypeBuilder::updateTranslatedChildOptions() should return array{child_type?: class-string, child_name?: string, child_excluded?: bool, child_embedded?: bool, child_translated?: bool, child_groups?: list<string>} but returns array{child_type: 'A2lix…'|class-string, translation_class: mixed, required: bool, child_name?: string, child_excluded?: bool, child_embedded?: bool, child_translated?: bool, child_groups?: list<string>}.

Check failure on line 222 in src/Form/Builder/AutoTypeBuilder.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Method A2lix\AutoFormBundle\Form\Builder\AutoTypeBuilder::updateTranslatedChildOptions() should return array{child_type?: class-string, child_name?: string, child_excluded?: bool, child_embedded?: bool, child_translated?: bool, child_groups?: list<string>} but returns array{child_type: 'A2lix…'|class-string, translation_class: mixed, required: bool, child_name?: string, child_excluded?: bool, child_embedded?: bool, child_translated?: bool, child_groups?: list<string>}.
'child_type' => 'A2lix\TranslationFormBundle\Form\Type\TranslationsType',
'translation_class' => $propTypeInfo->getCollectionValueType()->getClassName(),

Check failure on line 224 in src/Form/Builder/AutoTypeBuilder.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Call to an undefined method Symfony\Component\TypeInfo\Type::getClassName().

Check failure on line 224 in src/Form/Builder/AutoTypeBuilder.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Call to an undefined method Symfony\Component\TypeInfo\Type::getClassName().
'required' => $propTypeInfo->isNullable(),
...$baseChildOptions,
];
}

/**
* @param ChildOptions $baseChildOptions
*
* @return ChildOptions
*/
private function updateChildOptions(array $baseChildOptions, TypeInfo $propTypeInfo, int $formLevel): array
{
private function updateEmbeddedChildOptions(
array $baseChildOptions,
TypeInfo $propTypeInfo,
\ReflectionProperty $refProperty,
int $formDepth
): array {
// TypeInfo matching native FormType? Abort, guessers are enough
if (self::isTypeInfoWithMatchingNativeFormType($propTypeInfo)) {
return $baseChildOptions;
Expand All @@ -214,7 +251,7 @@
'allow_delete' => true,
'delete_empty' => true,
'by_reference' => false,
'prototype_name' => '__name'.$formLevel.'__',
'prototype_name' => '__name'.$formDepth.'__',
...$baseChildOptions,
];

Expand Down Expand Up @@ -279,18 +316,18 @@
/**
* @param FormInterface<mixed> $form
*/
private function getFormLevel(FormInterface $form): int
private function getFormDepth(FormInterface $form): int
{
if ($form->isRoot()) {
return 0;
}

$level = 0;
$depth = 0;
while (null !== $formParent = $form->getParent()) {
$form = $formParent;
++$level;
++$depth;
}

return $level;
return $depth;
}
}
33 changes: 28 additions & 5 deletions src/Form/Type/AutoType.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ final class AutoType extends AbstractType
{
/**
* @param list<string> $globalExcludedChildren
* @param list<string> $globalEmbeddedChildren
*/
public function __construct(
private readonly AutoTypeBuilder $autoTypeBuilder,
private readonly array $globalExcludedChildren = [],
private readonly array $globalEmbeddedChildren = [],
private readonly bool $globalTranslatedChildren = false,
) {}

#[\Override]
Expand All @@ -45,25 +48,45 @@ public function configureOptions(OptionsResolver $resolver): void
$resolver->setDefaults([
'children' => [],
'children_excluded' => $this->globalExcludedChildren,
'children_embedded' => [],
'children_embedded' => $this->globalEmbeddedChildren,
'children_translated' => $this->globalTranslatedChildren,
'children_groups' => null,
'builder' => null,
]);

$resolver->setAllowedTypes('children_excluded', 'string[]|string');
$resolver->setAllowedTypes('children_embedded', 'string[]|string');
$resolver->setAllowedTypes('children_excluded', 'string[]|string|callable');
$resolver->setInfo('children_excluded', 'An array of properties, the * wildcard, or a callable (mixed $previousValue): mixed');
$resolver->addNormalizer('children_excluded', static function (Options $options, mixed $value): mixed {
if (is_callable($value)) {
return ($value)($options['children_excluded']);
}

return $value;
});

$resolver->setAllowedTypes('children_embedded', 'string[]|string|callable');
$resolver->setInfo('children_embedded', 'An array of properties, the * wildcard, or a callable (mixed $previousValue): mixed');
$resolver->addNormalizer('children_embedded', static function (Options $options, mixed $value): mixed {
if (is_callable($value)) {
return ($value)($options['children_embedded']);
}

return $value;
});

$resolver->setAllowedTypes('children_translated', 'bool');
$resolver->setAllowedTypes('children_groups', 'string[]|null');
$resolver->setAllowedTypes('builder', 'callable|null');
$resolver->setInfo('builder', 'A callable that accepts two arguments (FormBuilderInterface $builder, string[] $classProperties). It should not return anything.');
$resolver->setInfo('builder', 'A callable (FormBuilderInterface $builder, string[] $classProperties): void');

// Others defaults FormType:class options
$resolver->setNormalizer('data_class', static function (Options $options, ?string $value): string {
if (null === $value) {
throw new \RuntimeException('Missing "data_class" option of "AutoType".');
}

return $value;
});

$resolver->setDefault('validation_groups', static function (Options $options): ?array {
/** @var list<string>|null */
return $options['children_groups'];
Expand Down
1 change: 1 addition & 0 deletions src/Form/TypeGuesser/TypeInfoTypeGuesser.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public function guessType(string $class, string $property): ?TypeGuess

// FormTypes handling 'multiple' option
if ($typeInfo->isIdentifiedBy(TypeIdentifier::ARRAY)) {
dump($typeInfo);
/** @var TypeInfo\CollectionType $typeInfo */
// @phpstan-ignore missingType.generics
$collValueType = $typeInfo->getCollectionValueType();
Expand Down
Loading