N°8772 - Form dependencies manager implementation - WIP

This commit is contained in:
Eric Espie
2025-10-23 15:11:14 +02:00
parent 98c0b11db7
commit fd449d195d
13 changed files with 133 additions and 55 deletions

View File

@@ -477,6 +477,7 @@ return array(
'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFormBlock' => $baseDir . '/sources/Forms/Block/Base/ChoiceFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\StringFormBlock' => $baseDir . '/sources/Forms/Block/Base/StringFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeChoiceFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/AttributeChoiceFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeValueChoiceFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/OqlFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\FormBlock' => $baseDir . '/sources/Forms/Block/FormBlock.php',
'Combodo\\iTop\\Forms\\Block\\FormInput' => $baseDir . '/sources/Forms/Block/FormInput.php',

View File

@@ -858,6 +858,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/ChoiceFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\StringFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/StringFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/AttributeChoiceFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeValueChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/OqlFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\FormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlock.php',
'Combodo\\iTop\\Forms\\Block\\FormInput' => __DIR__ . '/../..' . '/sources/Forms/Block/FormInput.php',

View File

@@ -32,9 +32,10 @@ abstract class AbstractFormBlock
return $this->aOptions;
}
public function AddSubFormBlock(AbstractFormBlock $oSubFormBlock): void
public function AddSubFormBlock(AbstractFormBlock $oSubFormBlock): AbstractFormBlock
{
$this->aSubFormBlocks[] = $oSubFormBlock;
return $this;
}
public function GetSubFormBlocks(): array
@@ -57,11 +58,40 @@ abstract class AbstractFormBlock
$this->aFormOutputs[$oFormOutput->GetName()] = $oFormOutput;
}
public function GetOutput(string $sName): FormOutput
public function GetOutput(string $sName): ?FormOutput
{
return $this->aFormOutputs[$sName];
}
/**
* @param string $sInputName
* @param string $sOutputBlockName
* @param string $sOutputName
*
* @return $this
* @throws \Combodo\iTop\Forms\Block\FormsBlockException
*/
public function DependsOn(string $sInputName, string $sOutputBlockName, string $sOutputName): AbstractFormBlock
{
$oFormInput = $this->GetInput($sInputName);
if (is_null($oFormInput)) {
throw new FormsBlockException('Missing input ' . $sInputName . ' for ' . $this->sName);
}
$oFormInput->Connect($sOutputBlockName, $sOutputName);
return $this;
}
public function HasConnections(): bool
{
foreach ($this->aFormInputs as $oFormInput) {
if ($oFormInput->HasConnections()) {
return true;
}
}
return false;
}
abstract public function GetFormType(): string;
abstract public function InitInputs(): void;

View File

@@ -7,11 +7,11 @@ use Combodo\iTop\Forms\Block\FormInput;
class AttributeChoiceFormBlock extends ChoiceFormBlock
{
public const INPUT_CLASS_NAME = 'class_name';
public function InitInputs(): void
{
$this->AddInput(new FormInput('class_name', 'string'));
$this->AddInput(new FormInput(self::INPUT_CLASS_NAME, 'string'));
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Combodo\iTop\Forms\Block\DataModel;
use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock;
use Combodo\iTop\Forms\Block\FormInput;
class AttributeValueChoiceFormBlock extends ChoiceFormBlock
{
public const INPUT_CLASS_NAME = 'class_name';
public const INPUT_ATTRIBUTE = 'attribute';
public function __construct(string $sName, array $aOptions = [])
{
$aOptions['multiple'] = true;
parent::__construct($sName, $aOptions);
}
public function InitInputs(): void
{
$this->AddInput(new FormInput(self::INPUT_CLASS_NAME, 'string'));
$this->AddInput(new FormInput(self::INPUT_ATTRIBUTE, 'string'));
}
}

View File

@@ -8,9 +8,12 @@ use Combodo\iTop\Forms\Block\FormOutput;
class OqlFormBlock extends StringFormBlock
{
public const OUTPUT_SELECTED_CLASS = 'selected_class';
public function InitOutputs(): void
{
$this->AddOutput(new FormOutput('selected_class', 'string'));
parent::InitOutputs();
$this->AddOutput(new FormOutput(self::OUTPUT_SELECTED_CLASS, 'string'));
}
}

View File

@@ -3,17 +3,17 @@
namespace Combodo\iTop\Forms\Block;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
class FormBlock extends AbstractFormBlock
{
public const OUTPUT_VALUE = 'value';
public function __construct(string $sName, array $aOptions = [])
{
$aOptions['form_block'] = $this;
parent::__construct($sName, $aOptions);
}
public function GetFormType(): string
{
return FormType::class;
@@ -25,14 +25,6 @@ class FormBlock extends AbstractFormBlock
public function InitOutputs(): void
{
}
public function Build(FormBuilderInterface $oBuilder): FormInterface
{
foreach ($this->GetSubFormBlocks() as $oSubForm) {
$oBuilder->add($oSubForm->GetName(), $oSubForm->GetFormType(), $oSubForm->GetOptions());
}
return $oBuilder->getForm();
$this->AddOutput(new FormOutput(self::OUTPUT_VALUE, 'string'));
}
}

View File

@@ -8,6 +8,8 @@ class FormInput
private string $sType;
private array $aConnections = [];
public function __construct(string $sName, string $sType)
{
$this->sName = $sName;
@@ -33,4 +35,19 @@ class FormInput
{
$this->sType = $sType;
}
public function Connect(string $sOutputBlockName, string $sOutputName)
{
$this->aConnections[] = ['block' => $sOutputBlockName, 'output' => $sOutputName];
}
public function GetConnections(): array
{
return $this->aConnections;
}
public function HasConnections(): bool
{
return count($this->aConnections) > 0;
}
}

View File

@@ -10,7 +10,7 @@ class FormOutput
private string $sType;
private AbstractConverter $oConverter;
private null|AbstractConverter $oConverter;
public function __construct(string $sName, string $sType, AbstractConverter $oConverter = null)
{
@@ -41,7 +41,7 @@ class FormOutput
public function GetOutputValue(mixed $oData): mixed
{
$this->oConverter->Convert($oData);
return $this->oConverter->Convert($oData);
}

View File

@@ -0,0 +1,14 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block;
use Combodo\iTop\Forms\FormsException;
class FormsBlockException extends FormsException
{
}

View File

@@ -2,7 +2,6 @@
namespace Combodo\iTop\Forms\FormBuilder;
use Combodo\iTop\Forms\Dependency\DependencyDescription;
use Combodo\iTop\Forms\Dependency\DependencyHandler;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -30,44 +29,30 @@ class FormBuilder implements FormBuilderInterface, \IteratorAggregate
*/
public function __construct(private FormBuilderInterface $builder)
{
}
/**
* Add a dependency description to the form builder.
* The associate form will be created as a hidden field and added later when all its dependencies were met.
*
* @param DependencyDescription $oDependencyDescription the dependency description
*
* @return void
*/
private function AddDependency(DependencyDescription $oDependencyDescription): void
{
if($this->dependencyHandler === null){
\IssueLog::Error('create dependency handler ' . $this->builder->getName());
$this->dependencyHandler = new DependencyHandler($this->builder);
}
$this->dependencyHandler->AddDependencyDescription($oDependencyDescription);
$this->InitFormBlocks();
}
public function add(string|FormBuilderInterface $child, ?string $type = null, array $options = []): static
{
if(!empty($options['bindings'])) {
$this->builder->add($child, HiddenType::class);
$this->AddDependency(new DependencyDescription($options['bindings'], $child, $type, $options));
}
else{
$this->builder->add($child, $type, $options);
}
$this->builder->add($child, $type, $options);
return $this;
}
public function addExpression(string $name, string $expression): static
private function InitFormBlocks()
{
$options['bindings'] = [$expression];
return $this->add($name, null, $options);
$oFormBlock = $this->builder->getOption('form_block');
if (is_null($oFormBlock)) {
return;
}
/** @var \Combodo\iTop\Forms\Block\FormBlock $oSubFormBlock */
foreach ($oFormBlock->GetSubFormBlocks() as $oSubFormBlock) {
if ($oSubFormBlock->HasConnections()) {
$this->builder->add($oSubFormBlock->GetName(), HiddenType::class);
} else {
$this->add($oSubFormBlock->GetName(), $oSubFormBlock->GetFormType(), $oSubFormBlock->getOptions());
}
}
}
// pure decoration of FormBuilderInterface

View File

@@ -6,8 +6,6 @@ use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FormTypeExtension extends AbstractTypeExtension
@@ -23,9 +21,7 @@ class FormTypeExtension extends AbstractTypeExtension
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefined([
'inputs',
'outputs',
'bindings',
'form_block',
'listener_callback',
]);
}

View File

@@ -0,0 +1,12 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms;
class FormsException extends \Exception
{
}