Commit 8e1a0931 authored by Laurent Lecluse's avatar Laurent Lecluse
Browse files

Trait spécifique pour que les fieldsets puissentutiliser les spec, comme les forms

parent 1612e884
Loading
Loading
Loading
Loading
+2 −20
Original line number Diff line number Diff line
@@ -2,29 +2,11 @@

namespace Application\Form;

use Application\Traits\FormFieldsetTrait;
use Laminas\Form\Fieldset;
use Laminas\InputFilter\InputFilterProviderInterface;

abstract class AbstractFieldset extends Fieldset implements InputFilterProviderInterface
{
    /**
     * Generates a url given the name of a route.
     *
     * @param string            $name               Name of the route
     * @param array             $params             Parameters for the link
     * @param array|Traversable $options            Options for the route
     * @param bool              $reuseMatchedParams Whether to reuse matched parameters
     *
     * @return string Url                         For the link href attribute
     * @see    \Laminas\Mvc\Router\RouteInterface::assemble()
     *
     */
    protected function getUrl($name = null, $params = [], $options = [], $reuseMatchedParams = false)
    {
        $url = \Application::$container->get('ViewHelperManager')->get('url');

        /* @var $url \Laminas\View\Helper\Url */
        return $url->__invoke($name, $params, $options, $reuseMatchedParams);
    }

    use FormFieldsetTrait;
}
 No newline at end of file
+2 −492
Original line number Diff line number Diff line
@@ -2,159 +2,17 @@

namespace Application\Form;

use Application\Constants;
use Application\Filter\FloatFromString;
use Application\Hydrator\GenericHydrator;
use Application\Interfaces\ParametreEntityInterface;
use Application\Service\AbstractEntityService;
use Application\Traits\TranslatorTrait;
use Doctrine\ORM\EntityManager;
use Laminas\Form\Element\Checkbox;
use Laminas\Form\Element\Csrf;
use Laminas\Form\Element\Number;
use Laminas\Form\Element\Select;
use Laminas\Form\Element\Text;
use Application\Traits\FormFieldsetTrait;
use Laminas\Form\Form;
use Laminas\Http\Request;
use Laminas\InputFilter\InputFilterProviderInterface;
use Laminas\Mvc\Plugin\FlashMessenger\FlashMessenger;
use Laminas\Stdlib\ArrayUtils;
use UnicaenApp\Entity\HistoriqueAwareInterface;
use UnicaenApp\Util;


abstract class AbstractForm extends Form implements InputFilterProviderInterface
{
    use TranslatorTrait;

    private ?FlashMessenger $controllerPluginFlashMessenger = null;

    private ?EntityManager  $entityManager                  = null;

    protected array         $spec                           = [];



    protected function getEntityManager(): EntityManager
    {
        if (!$this->entityManager) {
            $this->entityManager = \Application::$container->get(Constants::BDD);
        }

        return $this->entityManager;
    }



    private function getControllerPluginFlashMessenger(): FlashMessenger
    {
        if (!$this->controllerPluginFlashMessenger) {
            $this->controllerPluginFlashMessenger = \Application::$container->get('ControllerPluginManager')->get('flashMessenger');
        }

        return $this->controllerPluginFlashMessenger;
    }



    /**
     * Generates a url given the name of a route.
     *
     * @param string            $name               Name of the route
     * @param array             $params             Parameters for the link
     * @param array|Traversable $options            Options for the route
     * @param bool              $reuseMatchedParams Whether to reuse matched parameters
     *
     * @return string                         For the link href attribute
     * @see    \Laminas\Mvc\Router\RouteInterface::assemble()
     *
     */
    protected function getUrl($name = null, $params = [], $options = [], $reuseMatchedParams = false): string
    {
        $url = \Application::$container->get('ViewHelperManager')->get('url');

        /* @var $url \Laminas\View\Helper\Url */
        return $url->__invoke($name, $params, $options, $reuseMatchedParams);
    }



    /**
     * @return string
     */
    protected function getCurrentUrl()
    {
        return $this->getUrl(null, [], [], true);
    }



    public function spec(string|object|array $spec, array $ignore = [])
    {
        if (is_string($spec) && class_exists($spec)) {
            return $this->specFromClass($spec, $ignore);
        }
        if (is_object($spec)) {
            return $this->specFromObject($spec, $ignore);
        }
        if (is_array($spec)) {
            return $this->specFromArray($spec, $ignore);
        }

        throw new \Exception('La spécification fournie n\'est pas exploitable');
    }



    public function specDump()
    {
        echo '<pre>';
        foreach ($this->spec as $name => $spec) {

            echo '<h3>' . $name . '</h3>';
            phpDump($spec);
        }
        echo '</pre>';
    }



    public function build()
    {
        if (!$this->hasAttribute('action')) {
            $this->setAttribute('action', $this->getCurrentUrl());
        }

        if (!$this->hydrator) {
            $hydratorElements = [];
            foreach ($this->spec as $name => $spec) {
                if (isset($spec['hydrator'])) {
                    $hydratorElements[$name] = $spec['hydrator'];
                }
            }
            $hydrator = new GenericHydrator($this->getEntityManager(), $hydratorElements);
            $this->setHydrator($hydrator);
        }

        foreach ($this->spec as $elName => $elSpec) {
            if (isset($elSpec['input'])) {
                unset($elSpec['input']);
            }
            if (isset($elSpec['hydrator'])) {
                unset($elSpec['hydrator']);
            }
            if (!empty($elSpec)) {
                try {
                    $this->add($elSpec);
                } catch (\Throwable $e) {
                    throw new \Exception('L\'élément de formulaire "' . $elName . '" n\'a pas pu être généré depuis sa spécification', 0, $e);
                }
            }
        }

        $this->add(new Csrf('security'));
    }

    use FormFieldsetTrait;


    public function addSubmit(string $value = 'Enregistrer'): self
@@ -173,201 +31,6 @@ abstract class AbstractForm extends Form implements InputFilterProviderInterface



    public function setLabels(array $labels): self
    {
        foreach ($labels as $element => $label) {
            if ($this->has($element)) {
                $this->get($element)->setLabel($label);
            }
        }

        return $this;
    }



    /**
     * Permet de peupler facilement une liste d'options
     * Accepte pour $collection :
     * - Un tableau d'entités
     * - Une requête DQL
     * - Un tableau associatif ([value => label, ...])
     *
     * $params sert à fournir d'éventuels paramètres si $collection est une requête DQL
     *
     * @param string       $name
     * @param string|array $collection
     * @param              $params
     *
     * @return $this
     * @throws \Exception
     */
    public function setValueOptions(string $name, string|array $collection, $params = null): self
    {
        if (!$this->has($name)) {
            throw new \Exception('Elément ' . $name . ' non trouvé');
        }
        $element = $this->get($name);
        if (!method_exists($element, 'setValueOptions')) {
            throw new \Exception('L\élément ' . $name . ' ne peut pas se voir associer de listes d\'options');
        }

        if (is_string($collection)) {
            $query = $this->getEntityManager()->createQuery($collection);
            if (is_array($params)) {
                $query->setParameters($params);
            }
            $element->setValueOptions(Util::collectionAsOptions($query->getResult()));
        }
        if (is_array($collection)) {
            $element->setValueOptions(Util::collectionAsOptions($collection));
        }

        return $this;
    }



    /**
     * Permet de peupler facilement une liste d'options à l'aide d'une requête SQL
     * la clé doit correspondre à la colonne VALUE et à défaut ID ou CODE ou SOURCE_CODE
     * la valeur doit correspondre à la colonne LABEL et à défaut LIBELLE ou LIBELLE_COURT
     * Si la clé est introuvable, alors la ligne est ignorée
     *
     * @param string $name
     * @param string $query
     * @param array  $params
     *
     * @return $this
     * @throws \Doctrine\DBAL\Exception
     */
    public function setValueOptionsSql(string $name, string $query, array $params = []): self
    {
        $res     = $this->getEntityManager()->getConnection()->fetchAllAssociative($query, $params);
        $options = [];
        foreach ($res as $r) {
            $value = $r['VALUE'] ?? $r['ID'] ?? $r['CODE'] ?? $r['SOURCE_CODE'] ?? null;
            $label = $r['LABEL'] ?? $r['LIBELLE'] ?? $r['LIBELLE_COURT'] ?? null;
            if ($value && $label) {
                $options[$value] = $label;
            }
        }
        asort($options);

        return $this->setValueOptions($name, $options);
    }



    private function specFromClass(string $class, array $ignore): self
    {
        $elements = [];
        $rc       = new \ReflectionClass($class);
        $methods  = $rc->getMethods();

        if ($rc->implementsInterface(HistoriqueAwareInterface::class)) {
            $ignore[] = 'histoCreation';
            $ignore[] = 'histoCreateur';
            $ignore[] = 'histoModification';
            $ignore[] = 'histoModificateur';
            $ignore[] = 'histoDestruction';
            $ignore[] = 'histoDestructeur';
        }
        if ($rc->implementsInterface(ParametreEntityInterface::class)) {
            $ignore[] = 'annee';
        }

        foreach ($methods as $method) {
            $property = null;
            if (str_starts_with($method->name, 'get')) {
                $property = substr($method->name, 3);
            } elseif (str_starts_with($method->name, 'is')) {
                $property = substr($method->name, 2);
            }

            if ($property) {
                if (!$rc->hasMethod('set' . $property)) {
                    $property = null;
                }
            }

            if ($property) {
                $elKey = lcfirst($property);
                if (!in_array($elKey, $ignore)) {
                    $element = [
                        'hydrator' => [
                            'getter' => $method->name,
                            'setter' => 'set' . $property,
                        ],
                    ];
                    if ($method->hasReturnType()) {
                        $rt = $method->getReturnType();
                        if ($rt instanceof \ReflectionNamedType) {
                            $element['hydrator']['type'] = $rt->getName();
                        } elseif ($rt instanceof \ReflectionUnionType) {
                            $element['hydrator']['type'] = $rt->getTypes()[0]->getName();
                        }
                    }
                    $elements[$elKey] = $element;
                }
            }
        }

        /* Si c'est une entité Doctrine, on récupère les infos du mapping */
        try {
            $cmd = $this->getEntityManager()->getClassMetadata($class);
        } catch (\Exception $e) {
            $cmd = null;
        }
        if (!empty($elements) && !empty($cmd)) {
            foreach ($elements as $property => $element) {
                if ($cmd->hasField($property)) {
                    $mapping = $cmd->getFieldMapping($property);
                    $this->elementAddMapping($elements[$property], $mapping);
                }
            }
        }

        /* Ajout d'un élément caché pour l'ID */
        if ($cmd && $cmd->hasField('id')) {
            $elements['id'] = ['type' => 'Hidden', 'name' => 'id'];
        }

        /* Construction des éléments de formulaires */
        if (!empty($elements)) {
            foreach ($elements as $property => $element) {
                $this->makeElement($property, $elements[$property]);
            }
        }

        $this->specFromArray($elements, []);

        return $this;
    }



    private function specFromObject(object $object, array $ignore): self
    {
        return $this->specFromClass(get_class($object), $ignore);
    }



    private function specFromArray(array $spec, array $ignore): self
    {
        foreach ($spec as $k => $v) {
            if (in_array($k, $ignore)) {
                unset($spec[$k]);
            }
        }
        $this->spec = ArrayUtils::merge($this->spec, $spec);

        return $this;
    }



    /**
     * Exécute la sauvegarde d'un formulaire à partir des données Request
     *
@@ -451,157 +114,4 @@ abstract class AbstractForm extends Form implements InputFilterProviderInterface
        return true;
    }



    public function readOnly(bool $readOnly = true)
    {
        /** @var $element \Laminas\Form\Element */
        foreach ($this->getElements() as $element) {
            switch (get_class($element)) {
                case Number::class:
                case Text::class:
                    $element->setAttribute('readonly', $readOnly);
                break;
                case Select::class:
                case Checkbox::class:
                    $element->setAttribute('disabled', $readOnly);
                break;
            }
        }
    }



    public function getInputFilterSpecification()
    {
        $filters = [];

        foreach ($this->spec as $name => $elSpec) {
            if (isset($elSpec['input'])) {
                $filters[$name] = $elSpec['input'];
            }
        }

        return $filters;
    }



    private function elementAddMapping(array &$element, array $mapping)
    {
        /* Gestion du Required */
        if (isset($mapping['nullable'])) {
            if (!isset($element['input'])) {
                $element['input'] = [];
            }
            $element['input']['required'] = !$mapping['nullable'];
        }

        /* Gestion des length */
        if (($mapping['type'] ?? '') == 'string' && isset($mapping['length']) && $mapping['length']) {
            if (!isset($element['input'])) {
                $element['input'] = [];
            }
            if (!isset($element['input']['validators'])) {
                $element['input']['validators'] = [];
            }
            $element['input']['validators'][] = [
                'name'    => 'StringLength',
                'options' => ['max' => $mapping['length']],
            ];
        }
    }



    protected function makeElement(string $property, array &$element)
    {
        $spec = [];
        switch ($element['hydrator']['type'] ?? '') {
            case 'string':
                $spec = [
                    'type'    => 'Text',
                    'name'    => $property,
                    'options' => [
                        'label' => ucfirst($property),
                    ],
                ];
            break;
            case 'bool':
            case 'boolean':
                $spec = [
                    'type'    => 'Checkbox',
                    'name'    => $property,
                    'options' => [
                        'label'           => ucfirst($property),
                        'checked_value'   => '1',
                        'unchecked_value' => '0',
                    ],
                ];
            break;
            case 'float':
                $spec = [
                    'type'    => 'Text',
                    'name'    => $property,
                    'options' => [
                        'label' => ucfirst($property),
                    ],
                    'input'   => [
                        'filters' => [
                            ['name' => 'Laminas\Filter\StringTrim'],
                            ['name' => FloatFromString::class],
                        ],
                    ],
                ];

            break;
            case 'int':
                $spec = [
                    'type'    => 'Text',
                    'name'    => $property,
                    'options' => [
                        'label' => ucfirst($property),
                    ],
                    'input'   => [
                        'filters' => [
                            ['name' => 'Laminas\Filter\StringTrim'],
                        ],
                    ],
                ];
            break;
            case \DateTime::class:
                $spec = [
                    'type'       => 'DateTime',
                    'name'       => $property,
                    'options'    => [
                        'label'         => ucfirst($property),
                        'format'        => Constants::DATE_FORMAT,
                        'label_options' => [
                            'disable_html_escape' => true,
                        ],
                    ],
                    'attributes' => [
                        'placeholder' => "jj/mm/aaaa",
                    ],
                ];
            break;
        }

        /* Si c'est une entité Doctrine, alors on présuppose qu'on a affaire à un Select */
        try {
            $this->getEntityManager()->getClassMetadata($element['hydrator']['type'] ?? '');
            $spec = [
                'type'    => 'Select',
                'name'    => $property,
                'options' => [
                    'label' => ucfirst($property),
                ],
            ];
        } catch (\Exception $e) {
        }

        if (!empty($spec)) {
            $element = ArrayUtils::merge($element, $spec);
        }
    }
}
 No newline at end of file
+1 −4
Original line number Diff line number Diff line
@@ -5,8 +5,6 @@ namespace Application\Form\OffreFormation;
use Application\Entity\Db\ElementPedagogique;
use Application\Form\AbstractFieldset;
use Application\Service\Traits\ContextServiceAwareTrait;
use UnicaenApp\Service\EntityManagerAwareInterface;
use UnicaenApp\Service\EntityManagerAwareTrait;
use UnicaenApp\Traits\SessionContainerTrait;
use Doctrine\ORM\QueryBuilder;
use Application\Service\Traits\ElementPedagogiqueServiceAwareTrait;
@@ -17,9 +15,8 @@ use Laminas\Hydrator\HydratorInterface;
 * Description of ElementPedagogiqueRechercheFieldset
 *
 */
class ElementPedagogiqueRechercheFieldset extends AbstractFieldset implements EntityManagerAwareInterface
class ElementPedagogiqueRechercheFieldset extends AbstractFieldset
{
    use EntityManagerAwareTrait;
    use ContextServiceAwareTrait;
    use SessionContainerTrait;
    use ElementPedagogiqueServiceAwareTrait;
+1 −4
Original line number Diff line number Diff line
@@ -12,8 +12,6 @@ use Application\Service\Traits\FonctionReferentielServiceAwareTrait;
use Application\Service\Traits\LocalContextServiceAwareTrait;
use Application\Service\Traits\StructureServiceAwareTrait;
use UnicaenApp\Form\Element\SearchAndSelect;
use UnicaenApp\Service\EntityManagerAwareInterface;
use UnicaenApp\Service\EntityManagerAwaretrait;
use UnicaenApp\Util;
use Laminas\Filter\PregReplace;
use Laminas\Validator\Callback;
@@ -28,13 +26,12 @@ use Laminas\Hydrator\HydratorInterface;
 *
 * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr>
 */
class SaisieFieldset extends AbstractFieldset implements EntityManagerAwareInterface
class SaisieFieldset extends AbstractFieldset
{
    use ContextServiceAwareTrait;
    use LocalContextServiceAwareTrait;
    use StructureServiceAwareTrait;
    use FonctionReferentielServiceAwareTrait;
    use EntityManagerAwareTrait;

    /**
     * @var Structure[]
+2 −1
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@

namespace Application\Form\TypeIntervention;

use Application\Entity\Db\Traits\TypeInterventionStructureAwareTrait;
use Application\Form\AbstractForm;
use Application\Service\Traits\TypeInterventionServiceAwareTrait;
use Application\Service\Traits\TypeInterventionStructureServiceAwareTrait;
@@ -19,7 +20,7 @@ use UnicaenApp\Util;
 */
class TypeInterventionStructureSaisieForm extends AbstractForm
{
    use \Application\Entity\Db\Traits\TypeInterventionStructureAwareTrait;
    use TypeInterventionStructureAwareTrait;
    use StructureServiceAwareTrait;
    use ContextServiceAwareTrait;
    use AnneeServiceAwareTrait;
Loading