SDK Form demonstrator introduce field dependency graph

This commit is contained in:
Eric Espie
2025-04-17 15:58:51 +02:00
parent c91efa53bc
commit 81a256aa0e
6 changed files with 205 additions and 6 deletions

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\Dependency;
class DependencyException extends \Exception
{
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Dependency;
class DependencyGraph implements \Iterator
{
use GraphTrait;
private array $aFieldNameToPosition = [];
/**
* @param string $sName
* @param string $sType
* @param array $aDependencies
* @param array $aUserOptions
*
* @return void
* @throws \Combodo\iTop\Forms\Dependency\DependencyException
*/
public function Add(string $sName, string $sType, array $aDependencies = [], array $aUserOptions = []): void
{
$oNode = new DependencyNode($sName, $sType, $aUserOptions);
// Store field position
$this->aFieldNameToPosition[$sName] = count($this->aFieldNameToPosition);
if (empty($aDependencies)) {
$this->AddChild($oNode);
return;
}
// Search the last added dependency
$oParentNode = null;
$iParentPosition = -1;
foreach ($aDependencies as $sNodeName) {
if ($sNodeName === $sName) {
throw new DependencyException("Form field '$sName' cannot reference itself");
}
if (!isset($this->aFieldNameToPosition[$sNodeName])) {
throw new DependencyException("Form field '$sNodeName' is not existing");
}
if ($this->aFieldNameToPosition[$sNodeName] > $iParentPosition) {
$oParentNode = $this->SearchNode($sNodeName);
$iParentPosition = $this->aFieldNameToPosition[$sNodeName];
}
}
if (is_null($oParentNode)) {
throw new DependencyException("Could not find dependency for field '$sName'");
}
// Add to the latest dependency
$oParentNode->AddChild($oNode);
}
private function SearchNode(string $sName) : ?DependencyNode
{
foreach($this as $oChildNode) {
$oNode = $oChildNode->SearchNode($sName);
if ($oNode instanceof DependencyNode) {
return $oNode;
}
}
return null;
}
public function __toString(): string
{
$sResult = "\n";
foreach ($this as $oNode) {
$sResult .= $oNode->Display();
}
return $sResult;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Dependency;
class DependencyNode implements \Iterator
{
use GraphTrait;
public function __construct(private string $sName, private string $sType, private array $aUserOptions = [])
{
}
public function GetType(): string
{
return $this->sType;
}
public function GetName() : string
{
return $this->sName;
}
public function GetUserOptions(): array
{
return $this->aUserOptions;
}
public function SearchNode(string $sName) : ?DependencyNode
{
if ($sName === $this->GetName()) {
return $this;
}
foreach ($this as $oChildNode) {
$oNode = $oChildNode->SearchNode($sName);
if ($oNode instanceof DependencyNode) {
return $oNode;
}
}
return null;
}
public function Display(int $iDepth = 1)
{
$sResult = str_repeat(' ', $iDepth).$this->GetName()."\n";
foreach ($this as $oNode) {
$sResult .= $oNode->Display($iDepth + 1);
}
return $sResult;
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Dependency;
trait GraphTrait
{
private array $aChildren = [];
private int $iPosition = 0;
public function AddChild(DependencyNode $node) {
$this->aChildren[] = $node;
}
public function IsLast(): bool
{
return $this->iPosition === count($this->aChildren) - 1;
}
/*
* Iterator interface
*/
public function current(): mixed
{
return $this->aChildren[$this->iPosition];
}
public function next(): void
{
$this->iPosition++;
}
public function key(): mixed
{
return $this->iPosition;
}
public function valid(): bool
{
return isset($this->aChildren[$this->iPosition]);
}
public function rewind(): void
{
$this->iPosition = 0;
}
}

View File

@@ -6,6 +6,7 @@
namespace Combodo\iTop\Forms\FormType\Base;
use Combodo\iTop\Forms\Dependency\DependencyGraph;
use Combodo\iTop\Forms\FormType\Orm\AttCodeGroupByType;
use Combodo\iTop\Forms\FormType\Orm\ValuesFromAttcodeType;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -26,9 +27,12 @@ use Symfony\Component\PropertyAccess\PropertyPathInterface;
class FormBuilder implements FormBuilderInterface, \IteratorAggregate
{
public array $aModelData = [];
private DependencyGraph $oDependencies;
public function __construct(private FormBuilderInterface $builder)
{
$this->oDependencies = new DependencyGraph();
$this->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$this->aModelData = [];
});
@@ -36,6 +40,8 @@ class FormBuilder implements FormBuilderInterface, \IteratorAggregate
public function Finalize(): void
{
\IssueLog::Info($this->oDependencies);
$aCallbacks['query'] = function (FormEvent $event) {
if ($event instanceof PostSubmitEvent) {
$this->aModelData['query'] = $event->getForm()->getData();
@@ -102,13 +108,10 @@ class FormBuilder implements FormBuilderInterface, \IteratorAggregate
$oType = new $type();
$aPrerequisites = $oType->GetPrerequisites($options);
if (is_null($aPrerequisites)) {
$this->oDependencies->Add($child, $type);
$this->builder->add($child, $type, $options);
} else {
$this->aDynamicFields[$child] = [
'type' => $type,
'prerequisites' => $aPrerequisites,
'user_options' => $options,
];
$this->oDependencies->Add($child, $type, $aPrerequisites, $options);
$this->builder->add($child, HiddenType::class, ['mapped' => false]);
}
return $this;

View File

@@ -55,8 +55,8 @@ class ValuesFromAttcodeType extends AbstractType
public function GetPrerequisites(array $aUserOptions): ?array
{
return [
$aUserOptions['source_class'],
$aUserOptions['source_attcode'],
$aUserOptions['source_class'],
];
}
}