N°8771 - Add Symfony form component to iTop core

- IO debug
This commit is contained in:
Benjamin Dalsass
2025-10-28 15:33:28 +01:00
parent 5d335b39d2
commit 6952bfa978
25 changed files with 397 additions and 226 deletions

View File

@@ -22,4 +22,6 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Component:Input:ChangeNotAllowed' => 'This change is not allowed',
'UI:Component:Input:Password:DoesNotMatch' => 'Passwords do not match',
'UI:Component:Input:Set:MinimumItems' => 'Minimum %1$s item(s) required',
'UI:Component:Input:Select:Select_item' => 'Select an item...'
));

View File

@@ -13,4 +13,6 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:Component:Input:ChangeNotAllowed' => 'Cette modification n\'est pas autorisée',
'UI:Component:Input:Password:DoesNotMatch' => 'Les mots de passe ne correspondent pas',
'UI:Component:Input:Set:MinimumItems' => 'Minimum %1$s élément(s) requis',
'UI:Component:Input:Select:Select_item' => 'Sélectionnez un élément...'
));

View File

@@ -475,6 +475,7 @@ return array(
'Combodo\\iTop\\Form\\Validator\\SelectObjectValidator' => $baseDir . '/sources/Form/Validator/SelectObjectValidator.php',
'Combodo\\iTop\\Forms\\Block\\AbstractFormBlock' => $baseDir . '/sources/Forms/Block/AbstractFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFormBlock' => $baseDir . '/sources/Forms/Block/Base/ChoiceFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\CollectionBlock' => $baseDir . '/sources/Forms/Block/Base/CollectionBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\FormBlock' => $baseDir . '/sources/Forms/Block/Base/FormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\TextAreaFormBlock' => $baseDir . '/sources/Forms/Block/Base/TextAreaFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\TextFormBlock' => $baseDir . '/sources/Forms/Block/Base/TextFormBlock.php',
@@ -483,6 +484,11 @@ return array(
'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/OqlFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\FormBlockException' => $baseDir . '/sources/Forms/Block/FormBlockException.php',
'Combodo\\iTop\\Forms\\Block\\FormBlockIOException' => $baseDir . '/sources/Forms/Block/IO/FormBlockIOException.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\AttributeFormType' => $baseDir . '/sources/Forms/Block/FormType/AttributeFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\AttributeValueFormType' => $baseDir . '/sources/Forms/Block/FormType/AttributeValueFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\ChoiceFormType' => $baseDir . '/sources/Forms/Block/FormType/ChoiceFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\CollectionFormType' => $baseDir . '/sources/Forms/Block/FormType/CollectionFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\OqlFormType' => $baseDir . '/sources/Forms/Block/FormType/OqlFormType.php',
'Combodo\\iTop\\Forms\\Block\\IO\\AbstractFormIO' => $baseDir . '/sources/Forms/Block/IO/AbstractFormIO.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\AbstractConverter' => $baseDir . '/sources/Forms/Block/IO/Converter/AbstractConverter.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\OqlToClassConverter' => $baseDir . '/sources/Forms/Block/IO/Converter/OqlToClassConverter.php',
@@ -501,10 +507,6 @@ return array(
'Combodo\\iTop\\Forms\\FormBuilder\\FormTypeExtension' => $baseDir . '/sources/Forms/FormBuilder/FormTypeExtension.php',
'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormType' => $baseDir . '/sources/Forms/FormBuilder/ResolvedFormType.php',
'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormTypeFactory' => $baseDir . '/sources/Forms/FormBuilder/ResolvedFormTypeFactory.php',
'Combodo\\iTop\\Forms\\FormType\\AttributeFormType' => $baseDir . '/sources/Forms/FormType/AttributeFormType.php',
'Combodo\\iTop\\Forms\\FormType\\AttributeValueFormType' => $baseDir . '/sources/Forms/FormType/AttributeValueFormType.php',
'Combodo\\iTop\\Forms\\FormType\\ChoiceFormType' => $baseDir . '/sources/Forms/FormType/ChoiceFormType.php',
'Combodo\\iTop\\Forms\\FormType\\OqlFormType' => $baseDir . '/sources/Forms/FormType/OqlFormType.php',
'Combodo\\iTop\\Forms\\Forms' => $baseDir . '/sources/Forms/Forms.php',
'Combodo\\iTop\\Forms\\FormsException' => $baseDir . '/sources/Forms/FormsException.php',
'Combodo\\iTop\\Forms\\Twig\\Extension\\FormCompatibilityExtension' => $baseDir . '/sources/Forms/Twig/Extension/FormCompatibilityExtension.php',

View File

@@ -856,6 +856,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Form\\Validator\\SelectObjectValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/SelectObjectValidator.php',
'Combodo\\iTop\\Forms\\Block\\AbstractFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/AbstractFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/ChoiceFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\CollectionBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/CollectionBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\FormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/FormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\TextAreaFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/TextAreaFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\TextFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/TextFormBlock.php',
@@ -864,6 +865,11 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/OqlFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\FormBlockException' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockException.php',
'Combodo\\iTop\\Forms\\Block\\FormBlockIOException' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/FormBlockIOException.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\AttributeFormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/AttributeFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\AttributeValueFormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/AttributeValueFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\ChoiceFormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/ChoiceFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\CollectionFormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/CollectionFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\OqlFormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/OqlFormType.php',
'Combodo\\iTop\\Forms\\Block\\IO\\AbstractFormIO' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/AbstractFormIO.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\AbstractConverter' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Converter/AbstractConverter.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\OqlToClassConverter' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Converter/OqlToClassConverter.php',
@@ -882,10 +888,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Forms\\FormBuilder\\FormTypeExtension' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/FormTypeExtension.php',
'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormType' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/ResolvedFormType.php',
'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormTypeFactory' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/ResolvedFormTypeFactory.php',
'Combodo\\iTop\\Forms\\FormType\\AttributeFormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/AttributeFormType.php',
'Combodo\\iTop\\Forms\\FormType\\AttributeValueFormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/AttributeValueFormType.php',
'Combodo\\iTop\\Forms\\FormType\\ChoiceFormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/ChoiceFormType.php',
'Combodo\\iTop\\Forms\\FormType\\OqlFormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/OqlFormType.php',
'Combodo\\iTop\\Forms\\Forms' => __DIR__ . '/../..' . '/sources/Forms/Forms.php',
'Combodo\\iTop\\Forms\\FormsException' => __DIR__ . '/../..' . '/sources/Forms/FormsException.php',
'Combodo\\iTop\\Forms\\Twig\\Extension\\FormCompatibilityExtension' => __DIR__ . '/../..' . '/sources/Forms/Twig/Extension/FormCompatibilityExtension.php',

View File

@@ -6,6 +6,8 @@
namespace Combodo\iTop\Forms\Block;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\Block\IO\Converter\AbstractConverter;
use Combodo\iTop\Forms\Block\IO\FormInput;
use Combodo\iTop\Forms\Block\IO\FormOutput;
use IssueLog;
@@ -21,14 +23,17 @@ use Symfony\Component\Filesystem\Exception\IOException;
*/
abstract class AbstractFormBlock
{
/** @var null|FormBlock */
private ?FormBlock $oParent = null;
/** @var array form block inputs */
private array $aFormInputs = [];
/** @var array form block outputs */
private array $aFormOutputs = [];
/** @var bool flag */
private bool $bIsAdded = false;
/** @var bool flag indicating the form insertion */
private bool $bIsAddedToForm = false;
/**
* Return the form type.
@@ -50,7 +55,7 @@ abstract class AbstractFormBlock
* @param string $sName
* @param array $aOptions
*/
public function __construct(private readonly string $sName, private array $aOptions = [])
public function __construct(private readonly string $sName, protected array $aOptions = [])
{
// Attach the form block
$this->aOptions['form_block'] = $this;
@@ -65,6 +70,28 @@ abstract class AbstractFormBlock
$this->InitOutputs();
}
/**
* Set the parent block.
*
* @param FormBlock $oParent
*
* @return void
*/
public function SetParent(FormBlock $oParent): void
{
$this->oParent = $oParent;
}
/**
* Get the parent block.
*
* @return FormBlock
*/
public function GetParent(): FormBlock
{
return $this->oParent;
}
/**
* Return the form block name.
*
@@ -100,12 +127,14 @@ abstract class AbstractFormBlock
/**
* Add an input.
*
* @param FormInput $oFormInput
* @param string $sName the input name
* @param string $sType the type of the input
*
* @return void
*/
public function AddInput(FormInput $oFormInput): void
public function AddInput(string $sName, string $sType): void
{
$oFormInput = new FormInput($sName, $sType);
$oFormInput->SetOwnerBlock($this);
$this->aFormInputs[$oFormInput->GetName()] = $oFormInput;
}
@@ -130,12 +159,15 @@ abstract class AbstractFormBlock
/**
* Add an output.
*
* @param FormOutput $oFormOutput
* @param string $sName
* @param string $sType
* @param AbstractConverter|null $oConverter
*
* @return void
*/
public function AddOutput(FormOutput $oFormOutput): void
public function AddOutput(string $sName, string $sType, AbstractConverter $oConverter = null): void
{
$oFormOutput = new FormOutput($sName, $sType, $oConverter);
$oFormOutput->SetOwnerBlock($this);
$this->aFormOutputs[$oFormOutput->GetName()] = $oFormOutput;
}
@@ -143,7 +175,7 @@ abstract class AbstractFormBlock
/**
* Get an output.
*
* @param string $sName
* @param string $sName output name
*
* @return FormOutput
* @throws FormBlockException
@@ -151,7 +183,7 @@ abstract class AbstractFormBlock
public function GetOutput(string $sName): FormOutput
{
if(!array_key_exists($sName, $this->aFormOutputs)) {
throw new FormBlockException('Missing ouput ' . $sName . ' for ' . $this->sName);
throw new FormBlockException('Missing output ' . $sName . ' for ' . $this->sName);
}
return $this->aFormOutputs[$sName];
@@ -180,19 +212,20 @@ abstract class AbstractFormBlock
/**
* Attach an input to a block output.
*
* @param string $sInputName
* @param AbstractFormBlock $oOutputBlock
* @param string $sOutputName
* @param string $sInputName the input name
* @param string $sOutputBlockName the dependency block name
* @param string $sOutputName the dependency output name
*
* @return $this
* @throws FormBlockException
* @throws FormBlockIOException
*/
public function DependsOnBlockOutput(string $sInputName, AbstractFormBlock $oOutputBlock, string $sOutputName): AbstractFormBlock
public function DependsOn(string $sInputName, string $sOutputBlockName, string $sOutputName): AbstractFormBlock
{
$oOutputBlock = $this->GetParent()->Get($sOutputBlockName);
$oFormInput = $this->GetInput($sInputName);
$oFormOutput = $oOutputBlock->GetOutput($sOutputName);
$oFormInput->BindFromOutput($oFormOutput);
$oFormOutput->BindToInput($oFormInput);
return $this;
}
@@ -200,47 +233,46 @@ abstract class AbstractFormBlock
/**
* Attach an input to a parent block input.
*
* @param string $sInputName
* @param AbstractFormBlock $oParentBlock
* @param string $sParentInputName
* @param string $sInputName input name
* @param string $sParentInputName parent input name
*
* @return $this
* @throws FormBlockException
* @throws FormBlockIOException
*/
public function DependsOnParentBlockInput(string $sInputName, AbstractFormBlock $oParentBlock, string $sParentInputName): AbstractFormBlock
public function DependsOnParent(string $sInputName, string $sParentInputName): AbstractFormBlock
{
$oFormInput = $this->GetInput($sInputName);
$oParentFormInput = $oParentBlock->GetInput($sInputName);
$oFormInput->BindFromInput($oParentFormInput);
$oParentFormInput = $this->GetParent()->GetInput($sParentInputName);
$oParentFormInput->BindToInput($oFormInput);
return $this;
}
/**
* Attach an output to a parent block outpu.
* Attach an output to a parent block output.
*
* @param string $sOutputName
* @param AbstractFormBlock $oParentBlock
* @param string $sParentInputName
* @param string $sOutputName output name
* @param string $sParentOutputName parent output name
*
* @return $this
* @throws FormBlockException
* @throws FormBlockIOException
*/
public function BindOutputToParentBlockOutput(string $sOutputName, AbstractFormBlock $oParentBlock, string $sParentOutputName): AbstractFormBlock
public function ImpactParent(string $sOutputName, string $sParentOutputName): AbstractFormBlock
{
$oFormOutput = $this->GetOutput($sOutputName);
$oParentFormOutput = $oParentBlock->GetOutput($sParentOutputName);
$oParentFormOutput->BindFromOutput($oFormOutput);
$oParentFormOutput = $this->GetParent()->GetOutput($sParentOutputName);
$oFormOutput->BindToOutput($oParentFormOutput);
return $this;
}
/**
* Check existence of one or more dependencies.
*
* @return bool
*/
public function HasAtLeastOneBoundInput(): bool
public function HasDependenciesBlocks(): bool
{
foreach ($this->aFormInputs as $oFormInput) {
if ($oFormInput->IsBound()) {
@@ -251,9 +283,11 @@ abstract class AbstractFormBlock
}
/**
* Check existence of one or more dependents blocks.
*
* @return bool
*/
public function HasAtLeastOneBoundOutput(): bool
public function ImpactDependentsBlocks(): bool
{
/** @var FormOutput $oFormOutput */
foreach ($this->aFormOutputs as $oFormOutput) {
@@ -265,6 +299,8 @@ abstract class AbstractFormBlock
}
/**
* Get bindings on inputs.
*
* @return array
*/
public function GetInputsBindings(): array
@@ -281,6 +317,8 @@ abstract class AbstractFormBlock
}
/**
* Get bindings on outputs.
*
* @return array
*/
public function GetOutputBindings(): array
@@ -297,9 +335,11 @@ abstract class AbstractFormBlock
}
/**
* Inputs data ready.
*
* @return bool
*/
public function IsInputsReady(): bool
public function IsInputsDataReady(): bool
{
foreach ($this->aFormInputs as $oFormInput) {
if ($oFormInput->IsBound()) {
@@ -313,24 +353,30 @@ abstract class AbstractFormBlock
}
/**
* The block has been added to its parent.
*
* @return bool
*/
public function IsAdded(): bool
{
return $this->bIsAdded;
return $this->bIsAddedToForm;
}
/**
* Indicate that the block has been added to its parent.
*
* @param bool $bIsAdded
*
* @return void
*/
public function SetAdded(bool $bIsAdded): void
{
$this->bIsAdded = $bIsAdded;
$this->bIsAddedToForm = $bIsAdded;
}
/**
* Compute outputs values.
*
* @param string $sEventType
* @param mixed $oData
*
@@ -341,8 +387,8 @@ abstract class AbstractFormBlock
/** Iterate throw output @var FormOutput $oFormOutput */
foreach ($this->aFormOutputs as $oFormOutput) {
// Compute the output value
try{
// Compute the output value
$oFormOutput->ComputeValue($sEventType, $oData);
}
catch(IOException $oException){

View File

@@ -7,7 +7,7 @@
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Combodo\iTop\Forms\Block\FormType\ChoiceFormType;
/**
* Form block for choices.
@@ -18,16 +18,13 @@ class ChoiceFormBlock extends AbstractFormBlock
/** @inheritdoc */
public function GetFormType(): string
{
return ChoiceType::class;
return ChoiceFormType::class;
}
/** @inheritdoc */
public function InitOptions(): array
{
return [
// debug purpose
'required' => false,
'placeholder' => 'Select an element...',
];
}
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
/**
* Collection form type.
*
*/
class CollectionBlock extends AbstractFormBlock
{
public function __construct(string $sName, array $aOptions = [])
{
parent::__construct($sName, $aOptions);
}
/** @inheritdoc */
public function GetFormType(): string
{
return CollectionType::class;
}
/** @inheritdoc */
public function InitOptions(): array
{
$sBlockEntryType = $this->GetOptions()['block_entry_type'];
$sBlockEntryOptions = $this->GetOptions()['block_entry_options'];
$this->aOptions = [];
$oBlock = new ($sBlockEntryType)('prototype', $sBlockEntryOptions);
return [
'entry_type' => $oBlock->GetFormType()
];
}
}

View File

@@ -7,6 +7,7 @@
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\FormsException;
use Symfony\Component\Form\Extension\Core\Type\FormType;
/**
@@ -15,21 +16,28 @@ use Symfony\Component\Form\Extension\Core\Type\FormType;
*/
class FormBlock extends AbstractFormBlock
{
/** @var array form sub blocks */
private array $aSubFormBlocks = [];
/** @var array children blocks */
private array $aChildrenBlocks = [];
/**
* Constructor.
*
* @param string $sName
* @param array $aOptions
* @param string $sName block name
* @param array $aOptions options
*
* @throws FormsException
*/
public function __construct(string $sName, array $aOptions = [])
{
parent::__construct($sName, $aOptions);
// Build the form
$this->BuildForm();
try {
// Build the form
$this->BuildForm();
}
catch (Exception $ex) {
throw new FormsException('Unable to construct demonstrator form.', 0, $ex);
}
}
/** @inheritdoc */
@@ -47,26 +55,42 @@ class FormBlock extends AbstractFormBlock
}
/**
* Add a sub form.
* Add a child form.
*
* @param AbstractFormBlock $oSubFormBlock
* @param string $sType block class name
* @param string $sName block name
* @param array $aOptions options
*
* @return $this
*/
public function AddSubFormBlock(AbstractFormBlock $oSubFormBlock): AbstractFormBlock
public function Add(string $sType, string $sName, array $aOptions): AbstractFormBlock
{
$this->aSubFormBlocks[] = $oSubFormBlock;
return $this;
$oSubFormBlock = new ($sType)($sName, $aOptions);
$this->aChildrenBlocks[$sName] = $oSubFormBlock;
$oSubFormBlock->SetParent($this);
return $oSubFormBlock;
}
/**
* Get the sub forms.
* Get the children forms.
*
* @return array
*/
public function GetSubFormBlocks(): array
public function GetChildren(): array
{
return $this->aSubFormBlocks;
return $this->aChildrenBlocks;
}
/**
* Return a child block.
*
* @param string $sName name of the block
*
* @return AbstractFormBlock
*/
public function Get(string $sName): AbstractFormBlock
{
return $this->aChildrenBlocks[$sName];
}
/**

View File

@@ -10,8 +10,6 @@ use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock;
use Combodo\iTop\Forms\Block\FormBlockException;
use Combodo\iTop\Forms\Block\IO\Format\AttributeIOFormat;
use Combodo\iTop\Forms\Block\IO\Format\ClassIOFormat;
use Combodo\iTop\Forms\Block\IO\FormInput;
use Combodo\iTop\Forms\Block\IO\FormOutput;
use Combodo\iTop\Forms\Block\IO\Converter\StringToAttributeConverter;
use Combodo\iTop\Forms\FormType\AttributeFormType;
use CoreException;
@@ -34,7 +32,7 @@ class AttributeChoiceFormBlock extends ChoiceFormBlock
public function InitOptions(): array
{
$aOptions = parent::InitOptions();
$aOptions['placeholder'] = 'Select an attribute...';
// $aOptions['placeholder'] = 'Select an attribute...';
return $aOptions;
}
@@ -42,14 +40,14 @@ class AttributeChoiceFormBlock extends ChoiceFormBlock
public function InitInputs(): void
{
parent::InitInputs();
$this->AddInput(new FormInput(self::INPUT_CLASS_NAME, ClassIOFormat::class));
$this->AddInput(self::INPUT_CLASS_NAME, ClassIOFormat::class);
}
/** @inheritdoc */
public function InitOutputs(): void
{
parent::InitOutputs();
$this->AddOutput(new FormOutput(self::OUTPUT_ATTRIBUTE, AttributeIOFormat::class, new StringToAttributeConverter()));
$this->AddOutput(self::OUTPUT_ATTRIBUTE, AttributeIOFormat::class, new StringToAttributeConverter());
}
/** @inheritdoc
@@ -79,9 +77,4 @@ class AttributeChoiceFormBlock extends ChoiceFormBlock
return $aOptions;
}
/** @inheritdoc */
public function GetFormType(): string
{
return AttributeFormType::class;
}
}

View File

@@ -8,12 +8,9 @@ namespace Combodo\iTop\Forms\Block\DataModel;
use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock;
use Combodo\iTop\Forms\Block\FormBlockException;
use Combodo\iTop\Forms\Block\IO\Converter\StringToAttributeConverter;
use Combodo\iTop\Forms\Block\IO\Format\AttributeIOFormat;
use Combodo\iTop\Forms\Block\IO\Format\ClassIOFormat;
use Combodo\iTop\Forms\Block\IO\Format\RawFormat;
use Combodo\iTop\Forms\Block\IO\FormInput;
use Combodo\iTop\Forms\Block\IO\FormOutput;
use Combodo\iTop\Forms\FormType\AttributeValueFormType;
use Exception;
use MetaModel;
@@ -48,15 +45,15 @@ class AttributeValueChoiceFormBlock extends ChoiceFormBlock
public function InitInputs(): void
{
parent::InitInputs();
$this->AddInput(new FormInput(self::INPUT_CLASS_NAME, ClassIOFormat::class));
$this->AddInput(new FormInput(self::INPUT_ATTRIBUTE, AttributeIOFormat::class));
$this->AddInput(self::INPUT_CLASS_NAME, ClassIOFormat::class);
$this->AddInput(self::INPUT_ATTRIBUTE, AttributeIOFormat::class);
}
/** @inheritdoc */
public function InitOutputs(): void
{
parent::InitOutputs();
$this->AddOutput(new FormOutput(self::OUTPUT_VALUE, RawFormat::class));
$this->AddOutput(self::OUTPUT_VALUE, RawFormat::class);
}
/** @inheritdoc
@@ -103,10 +100,4 @@ class AttributeValueChoiceFormBlock extends ChoiceFormBlock
return $aOptions;
}
/** @inheritdoc */
public function GetFormType(): string
{
return AttributeValueFormType::class;
}
}

View File

@@ -8,9 +8,8 @@ namespace Combodo\iTop\Forms\Block\DataModel;
use Combodo\iTop\Forms\Block\Base\TextAreaFormBlock;
use Combodo\iTop\Forms\Block\IO\Format\ClassIOFormat;
use Combodo\iTop\Forms\Block\IO\FormOutput;
use Combodo\iTop\Forms\Block\IO\Converter\OqlToClassConverter;
use Combodo\iTop\Forms\FormType\OqlFormType;
use Combodo\iTop\Forms\Block\FormType\OqlFormType;
/**
* Form block for oql expression.
@@ -32,7 +31,7 @@ class OqlFormBlock extends TextAreaFormBlock
public function InitOutputs(): void
{
parent::InitOutputs();
$this->AddOutput(new FormOutput(self::OUTPUT_SELECTED_CLASS, ClassIOFormat::class, new OqlToClassConverter()));
$this->AddOutput(self::OUTPUT_SELECTED_CLASS, ClassIOFormat::class, new OqlToClassConverter());
}
/** @inheritdoc */

View File

@@ -4,7 +4,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\FormType;
namespace Combodo\iTop\Forms\Block\FormType;
use Symfony\Component\Form\AbstractType;

View File

@@ -4,7 +4,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\FormType;
namespace Combodo\iTop\Forms\Block\FormType;
use Symfony\Component\Form\AbstractType;

View File

@@ -4,9 +4,10 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\FormType;
namespace Combodo\iTop\Forms\Block\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Event\PreSetDataEvent;
use Symfony\Component\Form\Event\PreSubmitEvent;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -27,6 +28,20 @@ class ChoiceFormType extends AbstractType
/** @inheritdoc */
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// on preset data
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (PreSetDataEvent $event) use ($options){
if($options['multiple'] === false && $options['required'] === true) {
if ($event->getData() === null) {
$FirstElement = array_shift($options['choices']);
if($FirstElement !== null){
$event->setData($FirstElement);
}
}
}
});
// on pre submit
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event) use ($options){
@@ -48,7 +63,7 @@ class ChoiceFormType extends AbstractType
private function CheckValue($oValue, $options): bool
{
// Check multi selection values
if(is_array($oValue)){
if($options['multiple'] === true){
foreach ($oValue as $v){
if(!in_array($v, $options['choices'])){
return false;

View File

@@ -0,0 +1,42 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Event\PreSetDataEvent;
use Symfony\Component\Form\Event\PreSubmitEvent;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
*
*/
class CollectionFormType extends AbstractType
{
/** @inheritdoc */
public function getParent(): string
{
return CollectionType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefined([
'block_entry_type',
'block_entry_options',
]);
}
}

View File

@@ -4,7 +4,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\FormType;
namespace Combodo\iTop\Forms\Block\FormType;
use Combodo\iTop\Forms\Block\IO\Converter\OqlToClassConverter;
use Exception;

View File

@@ -9,6 +9,9 @@ namespace Combodo\iTop\Forms\Block\IO;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Symfony\Component\Form\FormEvents;
/**
*
*/
class AbstractFormIO
{
/** @var AbstractFormBlock The owner block */
@@ -23,11 +26,17 @@ class AbstractFormIO
/** @var array Stored values */
private array $aValues = [];
/** @var FormBinding|null */
private FormBinding|null $oBinding = null;
/** @var array bindings pointing to other inputs */
protected array $aBindingsToInputs = [];
/**
* Constructor.
*
* @param string $sName
* @param string $sType
* @param string $sName name of the IO
* @param string $sType type of the IO
*/
public function __construct(string $sName, string $sType)
{
@@ -35,6 +44,29 @@ class AbstractFormIO
$this->sType = $sType;
}
/**
* Get the owner block.
*
* @return AbstractFormBlock
*/
public function GetOwnerBlock(): AbstractFormBlock
{
return $this->oOwnerBlock;
}
/**
* Set the owner block.
*
* @param AbstractFormBlock $oOwnerBlock
*
* @return $this
*/
public function SetOwnerBlock(AbstractFormBlock $oOwnerBlock): self
{
$this->oOwnerBlock = $oOwnerBlock;
return $this;
}
/**
* Get the IO name.
*
@@ -68,29 +100,6 @@ class AbstractFormIO
return $this->sType;
}
/**
* Get the owner block.
*
* @return AbstractFormBlock
*/
public function GetOwnerBlock(): AbstractFormBlock
{
return $this->oOwnerBlock;
}
/**
* Set the owner block.
*
* @param AbstractFormBlock $oOwnerBlock
*
* @return $this
*/
public function SetOwnerBlock(AbstractFormBlock $oOwnerBlock): self
{
$this->oOwnerBlock = $oOwnerBlock;
return $this;
}
/**
* Set the IO value.
*
@@ -170,5 +179,65 @@ class AbstractFormIO
return null;
}
/**
* Bind to input.
*
* @param FormInput $oDestinationIO
*
* @return FormBinding
*/
public function BindToInput(FormInput $oDestinationIO): FormBinding
{
$oBinding = new FormBinding($this, $oDestinationIO);
$this->aBindingsToInputs[] = $oBinding;
$oDestinationIO->Attach($oBinding);
return $oBinding;
}
/**
* Attach a binding.
*
* @param FormBinding $oFormBinding
*
* @return void
*/
public function Attach(FormBinding $oFormBinding)
{
$this->oBinding = $oFormBinding;
}
/**
* Indicate IO is bound.
*
* @return bool
*/
public function IsBound(): bool
{
return $this->oBinding !== null;
}
/**
* Return the binding.
*
* @return FormBinding|null
*/
public function GetBinding(): ?FormBinding
{
return $this->oBinding;
}
/**
* Indicated inputs data is ready.
*
* @return bool
*/
public function IsDataReady(): bool
{
return $this->HasValue();
}
}

View File

@@ -6,13 +6,25 @@
namespace Combodo\iTop\Forms\Block\IO;
/**
*
*/
class FormBinding
{
/**
* @param AbstractFormIO $oSourceIO
* @param AbstractFormIO $oDestinationIO
*/
public function __construct(public readonly AbstractFormIO $oSourceIO, public readonly AbstractFormIO $oDestinationIO)
{
}
/**
* Propagate binding values.
*
* @return void
*/
public function PropagateValues(): void
{
$this->oDestinationIO->SetValues($this->oSourceIO->GetValues());

View File

@@ -6,70 +6,27 @@
namespace Combodo\iTop\Forms\Block\IO;
use Combodo\iTop\Forms\Block\FormBlockIOException;
/**
*
*/
class FormInput extends AbstractFormIO
{
private FormBinding|null $oBinding = null;
private array $aBindingsToInputs = [];
/**
* @throws FormBlockIOException
* @return bool
*/
public function BindFromOutput(FormOutput $oSourceIO): void
{
if($this->GetDataType() !== $oSourceIO->GetDataType()){
throw new FormBlockIOException('Cannot connect input types incompatibles ' . $this->GetName() . ' from ' . $oSourceIO->GetOwnerBlock()->GetName() . ' ' . $oSourceIO->GetName());
}
$this->oBinding = $oSourceIO->BindToInput($this);
}
/**
* @throws FormBlockIOException
*/
public function BindFromInput(FormInput $oSourceIO): void
{
if($this->GetDataType() !== $oSourceIO->GetDataType()){
throw new FormBlockIOException('Cannot connect input types incompatibles ' . $this->GetName() . ' from ' . $oSourceIO->GetOwnerBlock()->GetName() . ' ' . $oSourceIO->GetName());
}
$this->oBinding = $oSourceIO->BindToInput($this);
}
/**
* @throws FormBlockIOException
*/
public function BindToInput(FormInput $oDestinationIO): FormBinding
{
if($this->GetDataType() !== $oDestinationIO->GetDataType()){
throw new FormBlockIOException('Cannot connect input types incompatibles ' . $this->GetName() . ' from ' . $oDestinationIO->GetOwnerBlock()->GetName() . ' ' . $oDestinationIO->GetName());
}
$oBinding = new FormBinding($this, $oDestinationIO);
$this->aBindingsToInputs[] = $oBinding;
return $oBinding;
}
public function GetBinding(): ?FormBinding
{
return $this->oBinding;
}
public function IsDataReady(): bool
{
return $this->HasValue();
}
public function IsBound(): bool
{
return $this->oBinding !== null;
}
/**
* Set the values of the input.
*
* @param array $aValues
*
* @return AbstractFormIO
*/
public function SetValues(array $aValues): AbstractFormIO
{
parent::SetValues($aValues);

View File

@@ -6,18 +6,16 @@
namespace Combodo\iTop\Forms\Block\IO;
use Combodo\iTop\Forms\Block\FormBlockIOException;
use Combodo\iTop\Forms\Block\IO\Converter\AbstractConverter;
/**
*
*/
class FormOutput extends AbstractFormIO
{
/** @var AbstractConverter|null */
private null|AbstractConverter $oConverter;
private FormBinding|null $oBinding = null;
/** @var array */
private array $aBindingsToInputs = [];
/** @var array */
private array $aBindingsToOutputs = [];
@@ -84,21 +82,7 @@ class FormOutput extends AbstractFormIO
}
}
/**
* Bind to input.
*
* @param FormInput $oDestinationIO
*
* @return FormBinding
*/
public function BindToInput(FormInput $oDestinationIO): FormBinding
{
$oBinding = new FormBinding($this, $oDestinationIO);
$this->aBindingsToInputs[] = $oBinding;
return $oBinding;
}
/**
* Bind to output.
@@ -113,30 +97,12 @@ class FormOutput extends AbstractFormIO
$this->aBindingsToOutputs[] = $oBinding;
$oDestinationIO->Attach($oBinding);
return $oBinding;
}
public function GetBinding(): ?FormBinding
{
return $this->oBinding;
}
/**
* @throws FormBlockIOException
*/
public function BindFromOutput(FormOutput $oSourceIO): void
{
if($this->GetDataType() !== $oSourceIO->GetDataType()){
throw new FormBlockIOException('Cannot connect input types incompatibles ' . $this->GetName() . ' from ' . $oSourceIO->GetOwnerBlock()->GetName() . ' ' . $oSourceIO->GetName());
}
$this->oBinding = $oSourceIO->BindToOutput($this);
}
public function IsBound(): bool
{
return $this->oBinding !== null;
}
/**
* Get the bindings.

View File

@@ -40,7 +40,7 @@ class DependencyHandler
public function __construct(private readonly string $sName, private readonly AbstractFormBlock $oFormBlock, private readonly FormBuilder $oFormBuilder, private readonly array $aSubBlocks, private readonly array $aDependentBlocks)
{
// dependencies map
$this->oDependenciesMap = new DependencyMap($this->aDependentBlocks);
$this->oDependenciesMap = new DependencyMap($aDependentBlocks);
// Add form ready listener
$this->AddFormReadyListener();
@@ -48,6 +48,7 @@ class DependencyHandler
// Check the dependencies
$this->CheckDependencies($this->oFormBuilder);
// Store the dependency handler
self::$aDependencyHandlers[] = $this;
}
@@ -86,7 +87,7 @@ class DependencyHandler
'builder' => $this->oFormBuilder->getName(),
'event' => 'form.listen',
'form' => $sOutputBlockName,
'value' => null
'value' => 'NA'
];
// Listen the output block POST_SET_DATA & POST_SUBMIT
@@ -144,7 +145,7 @@ class DependencyHandler
foreach ($this->aDependentBlocks as $qBlockName => $oDependentBlock)
{
// When dependencies met, add the dependent field if not already done
if(!$oDependentBlock->IsAdded() && $oDependentBlock->IsInputsReady()) {
if(!$oDependentBlock->IsAdded() && $oDependentBlock->IsInputsDataReady()) {
// Get the dependent field options
$aOptions = $oDependentBlock->UpdateOptions();
@@ -164,7 +165,15 @@ class DependencyHandler
'builder' => $this->oFormBuilder->getName(),
'event' => 'form.add',
'form' => $oDependentBlock->getName(),
'value' => null
'value' => 'NA'
];
if(array_key_exists('builder_listener', $aOptions))
$this->aDebugData[] = [
'builder' => $this->oFormBuilder->getName(),
'event' => 'form.listen',
'form' => $oDependentBlock->getName(),
'value' => 'NA'
];
// Mark the dependency as added
@@ -177,7 +186,7 @@ class DependencyHandler
}
if($oDependentBlock->IsAdded() && !$oDependentBlock->IsInputsReady()) {
if($oDependentBlock->IsAdded() && !$oDependentBlock->IsInputsDataReady()) {
$oForm->add($oDependentBlock->GetName(), HiddenType::class, [
'form_block' => $oDependentBlock,
'prevent_form_build' => true,

View File

@@ -47,7 +47,7 @@ class DependencyMap
/** Iterate throw blocks with dependencies... @var FormBlock $oDependentBlock */
foreach ($this->aDependentBlocks as $sBlockName => $oDependentBlock) {
/** Iterate throw the block inputs connections... @var FormBinding $oBinding**/
/** Iterate throw the block inputs bindings... @var FormBinding $oBinding**/
foreach ($oDependentBlock->GetInputsBindings() as $oBinding) {
// Output to inputs map
@@ -60,11 +60,6 @@ class DependencyMap
&& $oBinding->oDestinationIO instanceof FormInput){
$this->AddBindingToMap($this->aInputToInputsMap, $oBinding);
}
// Output to outputs map
if($oBinding->oSourceIO instanceof FormOutput
&& $oBinding->oDestinationIO instanceof FormOutput){
$this->AddBindingToMap($this->aOutputToOutputsMap, $oBinding);
}
}

View File

@@ -67,7 +67,7 @@ class FormBuilder implements FormBuilderInterface, \IteratorAggregate
$aDependentBlocks = [];
/** Iterate throw the form sub blocks... @var FormBlock $oSubFormBlock */
foreach ($oFormBlock->GetSubFormBlocks() as $oSubFormBlock) {
foreach ($oFormBlock->GetChildren() as $oSubFormBlock) {
// Add to the sub blocks array
$this->aSubFormBlocks[$oSubFormBlock->getName()] = $oSubFormBlock;
@@ -98,8 +98,8 @@ class FormBuilder implements FormBuilderInterface, \IteratorAggregate
private function HandleSubBlock(AbstractFormBlock $oSubFormBlock): bool
{
// Has at least one bounded input ?
if ($oSubFormBlock->HasAtLeastOneBoundInput()) {
// Has dependencies blocks
if ($oSubFormBlock->HasDependenciesBlocks()) {
// Insert a hidden type to save the place
$this->builder->add($oSubFormBlock->GetName(), HiddenType::class, [

View File

@@ -57,7 +57,7 @@ class FormTypeExtension extends AbstractTypeExtension
$view->vars['form_block'] = $options['form_block'];
$oFormBlock = $options['form_block'];
$view->vars['trigger_form_submit_on_modify'] = $oFormBlock->HasAtLeastOneBoundOutput();
$view->vars['trigger_form_submit_on_modify'] = $oFormBlock->ImpactDependentsBlocks();
}
}

View File

@@ -6,10 +6,11 @@
namespace Combodo\iTop\Forms;
use Exception;
use IssueLog;
use Throwable;
class FormsException extends \Exception
class FormsException extends Exception
{
public function __construct(string $message = '', int $code = 0, ?Throwable $previous = null, array $aContext = [])
{