# Comportement des Smart Structures

L'association d'une classe de comportement permet d'enrichir les fonctionnalités d'un Smart Element. Elle permet d'ajouter des fonctions supplémentaires pour calculer des valeurs ou pour réaliser des traitements spécifiques. Elle permet aussi de modifier le comportement des fonctions standards en enregistrant des hooks sur ces fonctions standards telle que la modification ou la création de Smart Element.

# Déclarer une classe de comportement

La classe de comportement doit être déclarée dans la balise structure-configuration/class/. Cette référence indique le nom complet de la classe avec son namespace.

Le fichier décrivant la classe doit être placé sous le répertoire vendor en respectant la norme PSR-4.

Si la structure définie n'a pas de parent alors la classe doit étendre la classe Anakeen\SmartElement. Si la structure contient un héritage, alors la classe doit étendre la classe du parent soit : SmartStructure\<Structureparentname>

WARNING

Le nom de classe héritée à mettre le fichier PHP référence la structure parente. Elle doit être en minuscules sauf la première lettre qui doit être en majuscule.

Système

Pour chaque Smart Structure enregistrée, une classe PHP est construite. Cette classe PHP est nommée SmartStructure\<Structurename>. Structurename est le nom de la structure en minuscules avec la première lettre en majuscule. Cette classe est enregistrée dans le répertoire <contextInstall>/var/SmartClasses/SmartStructure. Le répertoire <contextInstall>/var/SmartClasses/ est déclaré comme répertoire racine de PSR-4. Le fichier de cette classe est nommé Structurename.php.

De plus, une classe contenant les noms des Smart Fields sous forme de constantes est aussi construite. Cette classe PHP est nommée SmartStructure\Fields\<Structurename>. Elle est enregistrée dans le répertoire <contextInstall>/var/SmartClasses/SmartStructure/Fields

# Exemple sans héritage :

<smart:config xmlns:smart="https://platform.anakeen.com/4/schemas/smart/1.0">
    <smart:structure-configuration name="MY_STRUCTURE"  label="Ma structure">
        <smart:class>My\SmartStructures\MyStructure\MyStructureBehaviour</smart:class>
        <smart:fields>
            <smart:field-set name="my_fr_info" type="frame" label="Information" access="ReadWrite">
                <smart:field-text name="my_title" label="Titre" access="ReadWrite" needed="true" is-title="true"/>
            </smart:field-set>
        </smart:fields>
    </smart:structure-configuration>
</smart:config>

Fichier de comportement vendor/My/SmartStructures/MyStructure/MyStructureBehaviour.php

namespace My\SmartStructures\MyStructure;

class MyStructureBehaviour extends \Anakeen\SmartElement
{
}

# Exemple avec héritage :

<smart:config xmlns:smart="https://platform.anakeen.com/4/schemas/smart/1.0">
    <smart:structure-configuration name="MY_BIGSTRUCTURE"  label="Ma structure">
    <smart:extends ref="MY_STRUCTURE"/>
        <smart:class>My\SmartStructures\MyBigStructure\MyBigStructureBehaviour</smart:class>
        <smart:fields>
            <smart:field-set name="my_fr_info" extended="true">
                <smart:field-date name="my_date" label="Date" access="Read" />
            </smart:field-set>
        </smart:fields>
    </smart:structure-configuration>
</smart:config>

Fichier de comportement vendor/My/SmartStructures/MyBigStructure/MyBigStructureBehaviour.php

namespace My\SmartStructures\MyBigStructure;

class MyBigStructureBehaviour extends \SmartStructure\My_structure
{
}

Ici, c'est bien \SmartStructure\My_structure qui est utilisé pour indiquer l'héritage de classe vers la structure dont le nom est MY_STRUCTURE.

# Les hooks des Smart Elements

Les principales méthodes d'un Smart Element déclenchent du code spécifique qui est enregistré dans la classe de comportement de la Smart Structure.

Code - Hook Déclenché par la méthode
Anakeen\SmartHooks\PRESTORE Anakeen\SmartElement::store()
Anakeen\SmartHooks\POSTSTORE Anakeen\SmartElement::store()
Anakeen\SmartHooks\PRECREATED Anakeen\SmartElement::store()
Anakeen\SmartHooks\POSTCREATED Anakeen\SmartElement::store()
Anakeen\SmartHooks\PREIMPORT ImportSingleDocument::import()
Anakeen\SmartHooks\POSTIMPORT ImportSingleDocument::import()
Anakeen\SmartHooks\PREREVISE Anakeen\SmartElement::revise()
Anakeen\SmartHooks\POSTREVISE Anakeen\SmartElement::revise()
Anakeen\SmartHooks\PREUNDELETE Anakeen\SmartElement::undelete()
Anakeen\SmartHooks\POSTUNDELETE Anakeen\SmartElement::undelete()
Anakeen\SmartHooks\PREDELETE Anakeen\SmartElement::delete()
Anakeen\SmartHooks\POSTDELETE Anakeen\SmartElement::delete()
Anakeen\SmartHooks\PREDUPLICATE Anakeen\SmartElement::duplicate()
Anakeen\SmartHooks\POSTDUPLICATE Anakeen\SmartElement::duplicate()
Anakeen\SmartHooks\PREAFFECT Anakeen\SmartElement::affect()
Anakeen\SmartHooks\POSTAFFECT Anakeen\SmartElement::affect()
Anakeen\SmartHooks\PREREFRESH Anakeen\SmartElement::refresh()
Anakeen\SmartHooks\POSTREFRESH Anakeen\SmartElement::refresh()
Anakeen\SmartHooks\POSTSTRUCTUREIMPORT Anakeen\SmartStructure::hook::postImport()

# Création de Smart Element

Lors de la création avec Anakeen\SmartElement::store(), les hooks appelés sont :

  1. Anakeen\SmartHooks::PRESTORE
  2. Anakeen\SmartHooks::PRECREATED
  3. Anakeen\SmartHooks::PREAFFECT
  4. Anakeen\SmartHooks::POSTAFFECT
  5. Anakeen\SmartHooks::POSTCREATED
  6. Anakeen\SmartHooks::PREREFRESH
  7. Anakeen\SmartHooks::POSTREFRESH
  8. Anakeen\SmartHooks::POSTSTORE

# Modification de Smart Element

Lors de la modification avec Anakeen\SmartElement::store(), les hooks appelés sont :

  1. Anakeen\SmartHooks::PRESTORE
  2. Anakeen\SmartHooks::PREREFRESH
  3. Anakeen\SmartHooks::POSTREFRESH
  4. Anakeen\SmartHooks::POSTSTORE {#core-ref:10c4622a-8834-4b9b-94ba-93f278d9bde0}

# Suppression de Smart Element

Lors de la suppression avec Anakeen\SmartElement::delete(), les hooks appelés sont :

  1. Anakeen\SmartHooks::PREDELETE
  2. Anakeen\SmartHooks::POSTDELETE
  3. Anakeen\SmartHooks::PREAFFECT
  4. Anakeen\SmartHooks::POSTAFFECT

Lors de la restauration avec Anakeen\SmartElement::undelete(), les hooks appelés sont :

  1. Anakeen\SmartHooks::PREUNDELETE
  2. Anakeen\SmartHooks::POSTUNDELETE
  3. Anakeen\SmartHooks::PREAFFECT
  4. Anakeen\SmartHooks::POSTAFFECT

# Duplication de Smart Element

Lors de la duplication avec Anakeen\SmartElement::duplicate(), les hooks appelés sont :

  1. Anakeen\SmartHooks::PREDUPLICATE
  2. Anakeen\SmartHooks::PRECREATED
  3. Anakeen\SmartHooks::PREAFFECT
  4. Anakeen\SmartHooks::POSTAFFECT
  5. Anakeen\SmartHooks::POSTCREATED
  6. Anakeen\SmartHooks::POSTDUPLICATE

# Révision d'un Smart Element

Lors de la modification avec Anakeen\SmartElement::revise(), les hooks appelés sont :

  1. Anakeen\SmartHooks::PREREVISE
  2. Anakeen\SmartHooks::PRECREATED
  3. Anakeen\SmartHooks::PREAFFECT
  4. Anakeen\SmartHooks::POSTAFFECT
  5. Anakeen\SmartHooks::PREREFRESH
  6. Anakeen\SmartHooks::POSTREFRESH
  7. Anakeen\SmartHooks::POSTREVISE

# Importation d'un Smart Element

Les hooks appelés lors de l'importation d'un Smart Element sont :

Avec création de Smart Element

  1. Anakeen\SmartHooks::PREREFRESH
  2. Anakeen\SmartHooks::POSTREFRESH
  3. Anakeen\SmartHooks::PREIMPORT
  4. Anakeen\SmartHooks::PRECREATED
  5. Anakeen\SmartHooks::PREAFFECT
  6. Anakeen\SmartHooks::POSTAFFECT
  7. Anakeen\SmartHooks::POSTCREATED
  8. Anakeen\SmartHooks::PRESTORE
  9. Anakeen\SmartHooks::PREREFRESH
  10. Anakeen\SmartHooks::POSTREFRESH
  11. Anakeen\SmartHooks::POSTSTORE
  12. Anakeen\SmartHooks::POSTIMPORT

Avec mise à jour de Smart Element

  1. Anakeen\SmartHooks::PREREFRESH
  2. Anakeen\SmartHooks::POSTREFRESH
  3. Anakeen\SmartHooks::PREAFFECT
  4. Anakeen\SmartHooks::POSTAFFECT
  5. Anakeen\SmartHooks::PREIMPORT
  6. Anakeen\SmartHooks::PRESTORE
  7. Anakeen\SmartHooks::PREREFRESH
  8. Anakeen\SmartHooks::POSTREFRESH
  9. Anakeen\SmartHooks::POSTSTORE
  10. Anakeen\SmartHooks::POSTIMPORT

# Enregistrer des méthodes sur les hooks des Smart Elements

L'enregistrement des hooks se fait dans la classe de comportement en surchargeant la méthode registerHooks. Dans cette méthode, l'appel à la méthode ->getHooks()->addListener() permet d'enregistrer de nouvelles callbacks.

L'appel à la méthode parent peut être fait pour ne pas perdre les éventuels hooks définis dans les structures parentes.

L'objet de la classe SmartElementHooks fournie par la méthode getHooks() fournit aussi les méthodes suivantes pour gérer des cas particuliers.

  • SmartElementHooks::getListeners() : liste toutes les callback déjà enregistrés (indexé par le nom du hook)
  • SmartElementHooks::removeListeners($hookName) : supprime les callback déjà enregistrés pour le hook donné
  • SmartElementHooks::resetListeners() : supprime tous les callback déjà enregistrés
  • SmartElementHooks::trigger($hookName) : déclenche les callback enregistrés pour le hook donné

Les callback sont exécutées dans l'ordre où elles ont été enregistrées.

# Exemple calcul d'un Smart Field :

Le Smart Field my_date contiendra la date du jour à chaque enregistrement.

<smart:config xmlns:smart="https://platform.anakeen.com/4/schemas/smart/1.0">
    <smart:structure-configuration name="MY_STRUCTURE"  label="Ma structure">
        <smart:class>My\SmartStructures\MyStructure\MyStructureBehaviour</smart:class>
        <smart:fields>
            <smart:field-set name="my_fr_info" type="frame" label="Information" access="ReadWrite">
                <smart:field-text name="my_title" label="Titre" access="ReadWrite" needed="true" is-title="true"/>
                <smart:field-date name="my_date" label="Date" access="Read" />
            </smart:field-set>
        </smart:fields>
    </smart:structure-configuration>
</smart:config>

Fichier de comportement vendor/My/SmartStructures/MyStructure/MyStructureBehaviour.php

namespace My\SmartStructures\MyStructure;

use SmartStructure\Fields\MyStructure as MyStructureFields;
use Anakeen\SmartHooks;
class MyStructureBehaviour extends \Anakeen\SmartElement
{
  public function registerHooks()
  {
    parent::registerHooks();
    $this->getHooks()->addListener(SmartHooks::PRESTORE, function () {
      $this->setValue(MyStructureFields::my_date, date("Y-m-d"));
    });
  }
}

# Les hooks des Smart Fields

Il est possible de mettre en place, au travers des Hooks, des méthodes permettant de contrôler les données d'un Smart Element afin de valider ou non l'enregistrement, on parle alors de contrainte ou bien de calculer la valeur d'un Smart Field à partir d'autres données, on parle alors d'un Smart Field calculé.

La déclaration des hooks des Smart Fields est faite dans la balise structure-configuration/hooks/field-hook.

L'attribut structure-configuration/hooks/field-hook/field-callable/@function peut référencer :

  • une méthode statique (Namespace\Class::method)
  • une classe avec une méthode __invoke statique (Namespace\Class)
  • une méthode de la classe de comportement (::method)

Cette méthode callable est appelée au déclenchement de l'évènement du hook défini.

L'attribut structure-configuration/hooks/field-hook/field-argument/@type peut avoir les valeurs suivantes :

  • string : la valeur de l'argument est le contenu de la balise field-argument
  • field : le contenu de la balise field-argument référence un Smart Field, la valeur de l'argument est la valeur du Smart Field
  • property : le contenu de la balise field-argument référence une propriété, la valeur de l'argument est la valeur de la propriété.
  • index : Dans le cas où le Smart Field est dans un tableau, cela donne la rangée dans le tableau (0 étant la première rangée). Le contenu de la balise field-argument est vide.
  • this : Retourne l'objet PHP "SmartElement" courant. Le contenu de la balise field-argument est vide.

# Exemple d'une contrainte de Smart Field

Une contrainte permet d'empêcher l'enregistrement d'un Smart Element si les conditions fonctionnelles ne sont pas réunies. Les contraintes sont vérifiées lors de l'appel la méthode :

public function store(&$info = null, $skipConstraint = false)

Le deuxième argument, s'il est positionné à true permet d'enregistrer sans vérification de contrainte.

Une méthode de contrainte callable doit être définie et doit retourner null, chaîne vide "" ou true pour indiquer que la contrainte est respectée. La méthode retourne une chaîne de caractères non vide pour indiquer la raison de l'échec. La méthode peut retourner false pour mettre en échec sans donner de raison.

Fichier PHP de contrainte vendor/My/Utils/Mail.php

namespace My\Utils;

class Mail
{
  public static function checkMailAddress(string $email): string
  {
    if ($email) {
      if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        return "The email address is not valid";
      }
    }
    return "";
  }
}

La déclatation de la contrainte doit être effectuée dans la balise field-hook, les attributs à définir sont :

  • type : égal à constraint
  • event : égal à onPreStore
  • field : nom du Smart Field où se porte la contrainte
<smart:config xmlns:smart="https://platform.anakeen.com/4/schemas/smart/1.0">
    <smart:structure-configuration name="MY_STRUCTURE"  label="Ma structure">
        <smart:class>My\SmartStructures\MyStructure\MyStructureBehaviour</smart:class>
        <smart:fields>
            <smart:field-set name="my_fr_info" type="frame" label="Information" access="ReadWrite">
                <smart:field-text name="my_title" label="Titre" access="ReadWrite" needed="true" is-title="true"/>
                <smart:field-text name="my_email" label="Adresse courriel" access="ReadWrite" />
            </smart:field-set>
        </smart:fields>
        <smart:hooks>
            <smart:field-hook type="constraint" event="onPreStore" field="my_email">
                <smart:field-callable function="My\Utils\Mail::checkMailAddress"/>
                <smart:field-argument type="field">my_email</smart:field-argument>
            </smart:field-hook>
        </smart:hooks>
    </smart:structure-configuration>
</smart:config>

# Exemple d'une contrainte sur une colonne de tableau

Il est possible d'utiliser une contrainte sur une colonne de tableau. Dans ce cas, la contrainte sera déclenchée une fois par ligne.

WARNING

Il est impossible d'utiliser un tableau en argument d'une contrainte.

Fichier PHP de contrainte vendor/My/Utils/Number.php

namespace My\Utils;

class Number
{
  public static function checkAscendingNumbers(int number_1, int number_2): string
  {
    if ($number_1 > $number_2) {
        return "Le nombre 1 est supérieur au nombre 2";
    }
    return "";
  }
}
<smart:config xmlns:smart="https://platform.anakeen.com/4/schemas/smart/1.0">
    <smart:structure-configuration name="MY_STRUCTURE"  label="Ma structure">
        <smart:class>My\SmartStructures\MyStructure\MyStructureBehaviour</smart:class>
        <smart:fields>
            <smart:field-set name="numbers_frame" type="frame" access="ReadWrite" label="Des nombres">
                <smart:field-set name="numbers_array" type="array" access="ReadWrite">
                    <smart:field-int access="ReadWrite" name="number_1" label="Nombre 1"/>
                    <smart:field-int access="ReadWrite" name="number_2" label="Nombre 2"/>
                </smart:field-set>
            </smart:field-set>
        </smart:fields>
        <smart:hooks>
            <smart:field-hook type="constraint" event="onPreStore" field="number_1">
                <smart:field-callable function="My\Utils\Number::checkAscendingNumbers"/>
                <smart:field-argument type="field">number_1</smart:field-argument>
                <smart:field-argument type="field">number_2</smart:field-argument>
            </smart:field-hook>
        </smart:hooks>
    </smart:structure-configuration>
</smart:config>

# Exemple d'un Smart Field calculé

Un Smart Field calculé est un Smart Field dont la valeur est calculée par Anakeen Platform. Ce calcul est effectué par l'intermédiaire d'une méthode callable appelée suite au déclenchement d'un hook spécifique, défini dans la configuation de la Smart Structure

La déclaration est faite dans la balise structure-configuration/hooks/field-hook.

<smart:config xmlns:smart="https://platform.anakeen.com/4/schemas/smart/1.0">
    <smart:structure-configuration name="MY_STRUCTURE"  label="Ma structure">
        <smart:class>My\SmartStructures\MyStructure\MyStructureBehaviour</smart:class>
        <smart:fields>
            <smart:field-set name="my_fr_info" type="frame" label="Information" access="ReadWrite">
                <smart:field-text name="my_title" label="Titre" access="ReadWrite" needed="true" is-title="true"/>
                <smart:field-text name="my_email" label="Adresse courriel" access="ReadWrite" />
                <smart:field-text name="my_first_name" label="Adresse courriel" access="ReadWrite" />
                <smart:field-text name="my_last_name" label="Adresse courriel" access="ReadWrite" />
                <smart:field-text name="my_complete_name" label="Adresse courriel" access="ReadWrite" />
            </smart:field-set>
        </smart:fields>
        <smart:hooks>
            <smart:field-hook event="onPreRefresh" field="my_complete_name">
                <smart:field-callable function="::calculateCompleteName"/>
              </smart:field-hook>
        </smart:hooks>
    </smart:structure-configuration>
</smart:config>

Méthode PHP définie dans la classe définissant le comportement My\SmartStructures\MyStructure\MyStructureBehaviour

public function calculateCompleteName(): float
  {
    $fname = $this->getAttributeValue(MyStructureFields::my_first_name);
    $lname = $this->getAttributeValue(MyStructureFields::my_last_name);
    $completeName = $fname . ' '. $lname;

    return $completeName;
  }