Les formulaire (uiComponent form) du backend et le CRUD dans l'admin magento2

Ce tutoriel est le 8eme d’une longue série de tutoriel magento2. Il sera mis à jour au fur et à mesure des évolutions de la plate-forme. Dans le dernier épisode, vous avez appris à créer une grid (grille) dans l'admin de votre module magento2 et utiliser les actions de masse (MassAction). Si vous ne connaissez pas magento (v1) ce n’est pas grave, je vais en parler bien sur mais il n’est pas nécessaire de connaître la plate-forme magento (v1) pour maîtriser ce tutoriel sur magento2, assurez vous par contre d'avoir fait les 6 tutoriaux précédents. Donc comme je le disais, dans le tutoriel précédent nous avons vu comment ajouter une grid à son module d’administration (backend) magento2 ! Nous allons maintenant voir comment ajouter un formulaire et nos actions d'ajout / modification / Suppression (CRUD) dans ce module. Pour cela, on va repartir des fichiers de notre tutoriel précédent.

Afficher le formulaire de création via le contrôleur magento2

Pour créer notre Formulaire, on va premièrement créer l'action newAction dans notre contrôleur comme ceci : ( /Pfay/Contacts/Controller/Adminhtml/Contact/NewAction.php )

<?php
namespace Pfay\Contacts\Controller\Adminhtml\Test;
use Magento\Backend\App\Action;
use Pfay\Contacts\Model\Contact as Contact;

class NewAction extends \Magento\Backend\App\Action
{
    /**
     * Edit A Contact Page
     *
     * @return \Magento\Backend\Model\View\Result\Page|\Magento\Backend\Model\View\Result\Redirect
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function execute()
    {
        $this->_view->loadLayout();
        $this->_view->renderLayout();

        $contactDatas = $this->getRequest()->getParam('contact');
        if(is_array($contactDatas)) {
            $contact = $this->_objectManager->create(Contact::class);
            $contact->setData($contactDatas)->save();
            $resultRedirect = $this->resultRedirectFactory->create();
            return $resultRedirect->setPath('*/*/index');
        }
    }
}

On charge et on affiche ensuite le layout qui contiendra le formulaire. On vérifie si on recois le paramétre "contact" (si le formulaire est envoyé), si oui alors on sauvegarde le contacte dans la base de donnée magento2
. Logiquement, on va donc maintenant créer le layout (/Pfay/Contacts/view/adminhtml/layout/contacts_test_newaction.xml):

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="contacts_test_edit"/>
    <body/>
</page>

Dans ce layout, on lui dit juste d'aller chercher notre layout contacts_text_edit pour l'afficher. On crées donc le fichier de layout pour l'édition (/Pfay/Contacts/view/adminhtml/layout/contacts_test_edit.xml):

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="styles"/>
    <update handle="editor"/>
    <body>
        <referenceContainer name="content">
            <uiComponent name="pfay_contacts_form"/>
        </referenceContainer>
    </body>
</page>


Création du fichier uiComponent de type Form pour notre formulaire magento2

Ici on lui dit de charger un uiComponent qui s'apelle "pfay_contacts_form", comme on l'avait fait pour la grid dans le tutoriel précédent. On va donc créer le formulaire dans cet uiComponent de type form.
Créeons donc notre fichier uiComponent pour notre formulaire ( app/code/Pfay/Contacts/view/adminhtml/ui_component/pfay_contacts_form.xml ) :

    <?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">pfay_contacts_form.contacts_form_data_source</item>
            <item name="deps" xsi:type="string">pfay_contacts_form.contacts_form_data_source</item>
        </item>
        <item name="label" xsi:type="string" translate="true">Sample Form</item>
        <item name="layout" xsi:type="array">
            <item name="type" xsi:type="string">tabs</item>
        </item>

        <item name="buttons" xsi:type="array">
            <item name="back" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\BackButton</item>
            <item name="delete" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\DeleteButton</item>
            <item name="reset" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\ResetButton</item>
            <item name="save" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\SaveButton</item>
        </item>
    </argument>

    <dataSource name="contacts_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Pfay\Contacts\Model\Contact\DataProvider</argument>
            <argument name="name" xsi:type="string">contacts_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">pfay_contacts_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
    </dataSource>

    <fieldset name="contact">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="label" xsi:type="string" translate="true">Sample Fieldset</item>
            </item>
        </argument>

        <!-- This field represents form id and is hidden -->
        <field name="pfay_contact_id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="visible" xsi:type="boolean">false</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contact</item>
                </item>
            </argument>
        </field>

        <!-- This field has data type 'text' and standard 'input' form element and looks like input -->
        <field name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string">Name</item>
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contact</item>
                </item>
            </argument>
        </field>


        <!-- This field has data type 'text' and standard 'input' form element and looks like input -->
        <field name="email">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string">Email</item>
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">contact</item>
                </item>
            </argument>
        </field>

    </fieldset>
</form>

Ici le fichier à l'air compliqué mais il faut le décomposer. vous inquietez pas c'est pas si compliqué que cela. Le fichier uiComponent de type form est composé de plusieurs balises :

  • argument : La base du fichier, qui permet de configurer la data_source à utiliser, le label, et l'affichage des boutons
    • data : permet de faire le liens avec la dataSource, attention à bien définirs vos "chemins nom du fichier"."nom de la datasource" en gros ;)
    • buttons: si vous êtes arrivés jusque la c'est que vous savez lire un xml normalement mais on va quand même précisé c'est ici qu'on ajoute les bouttons. Pour chaque bouton on déini un nom et une classe à utiliser. On va y revenir plus tard.
  • dataSource : La configuration de la dataSource elle meme, le liens entre la base de donnée et notre formulaire.
    La datasource défini le liens entre la base de donnée et votre formualire, ici on lui donne le nom de "contacts_form_data_source" et la classe "Pfay\Contacts\Model\Contact\DataProvider". on défini que l'id dans notre table sera "pfay_contacts_id" mais qu'il sera renommé "id" dans notre formulaire.
  • fieldset : Le fieldset permet de définir les champs de votre formulaire.
    • field : représente un field de notre formulaire, ils peuvent être de plusieurs types que je vous invite à rechercher sur la documentation de magento2. Ici on a utilisé uniquement des champs de type text pour faciliter notre travail. On défini quand meme que le cahmps pour l'id sera invisible.

La DataSource de notre formulaire magento2

La datasource de notre formulaire magento2 reprend ici notre classe "Pfay\Contacts\Model\Contact\DataProvider" on va donc la créer.
Créez donc le fichier app/code/Pfay/Contacts/Model/Contact/DataProvider.php comme ceci :

<?php
namespace Pfay\Contacts\Model\Contact;
use Pfay\Contacts\Model\ResourceModel\Contact\CollectionFactory;
class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
{
    /**
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param CollectionFactory $contactCollectionFactory
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $contactCollectionFactory,
        array $meta = [],
        array $data = []
    ) {
        $this->collection = $contactCollectionFactory->create();
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
    }

    public function getData()
    {
        if (isset($this->loadedData)) {
            return $this->loadedData;
        }

        $items = $this->collection->getItems();
        $this->loadedData = array();
        /** @var Customer $customer */
        foreach ($items as $contact) {
            // notre fieldset s'apelle "contact" d'ou ce tableau pour que magento puisse retrouver ses datas :
            $this->loadedData[$contact->getId()]['contact'] = $contact->getData();
        }


        return $this->loadedData;

    }
}

C'est ce fichier qui va permettre à notre champs d'etre mappé automatiquement avec la base de donnée. ce qu'on appelle ailleur "dataBinding". Ici on a appelé notre fieldset "contact", on passe donc notre objet Contact dans le tableau loadedData qu'on retourne.

Configuration de nos bouttons de formulaire

Sous magento2, dans les formulaires, on défini des classes pour les boutons comme ce qu'on a fait dans notre uiComponent :

        <item name="buttons" xsi:type="array">
            <item name="back" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\BackButton</item>
            <item name="delete" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\DeleteButton</item>
            <item name="reset" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\ResetButton</item>
            <item name="save" xsi:type="string">Pfay\Contacts\Block\Adminhtml\Contact\Edit\SaveButton</item>
        </item>
On va donc créer nos fichiers :

Créer un GenericButton :

le GenericButton est l'lément qui permet aux autres boutons d'exister. C'est la base qu'étend le autres bouttons.
app/code/Pfay/Contacts/Block/Adminhtml/Contact/Edit/GenericButton.php

<?php
namespace Pfay\Contacts\Block\Adminhtml\Contact\Edit;

use Magento\Search\Controller\RegistryConstants;

/**
 * Class GenericButton
 */
class GenericButton
{
    /**
     * Url Builder
     *
     * @var \Magento\Framework\UrlInterface
     */
    protected $urlBuilder;

    /**
     * Registry
     *
     * @var \Magento\Framework\Registry
     */
    protected $registry;

    /**
     * Constructor
     *
     * @param \Magento\Backend\Block\Widget\Context $context
     * @param \Magento\Framework\Registry $registry
     */
    public function __construct(
        \Magento\Backend\Block\Widget\Context $context,
        \Magento\Framework\Registry $registry
    ) {
        $this->urlBuilder = $context->getUrlBuilder();
        $this->registry = $registry;
    }

    /**
     * Return the synonyms group Id.
     *
     * @return int|null
     */
    public function getId()
    {
        $contact = $this->registry->registry('contact');
        return $contact ? $contact->getId() : null;
    }

    /**
     * Generate url by route and parameters
     *
     * @param   string $route
     * @param   array $params
     * @return  string
     */
    public function getUrl($route = '', $params = [])
    {
        return $this->urlBuilder->getUrl($route, $params);
    }
}

Créer un bouton pour sauvegarder notre formulaire magento2

Créez le fichier app/code/Pfay/Contacts/Block/Adminhtml/Contact/Edit/BackButton.php
dans lequel on configure juste le label du boutton, l'event associé et le form-role qui déclenche automatiquement des évenements js pour magento2.

<?php
namespace Pfay\Contacts\Block\Adminhtml\Contact\Edit;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

/**
 * Class BackButton
 */
class BackButton extends GenericButton implements ButtonProviderInterface
{
    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Back'),
            'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()),
            'class' => 'back',
            'sort_order' => 10
        ];
    }

    /**
     * Get URL for back (reset) button
     *
     * @return string
     */
    public function getBackUrl()
    {
        return $this->getUrl('*/*/');
    }
}

Créer un bouton pour supprimer un objet via notre formulaire

Créez ensuite le fichier pour supprimer un élément depuis notre formualaire
app/code/Pfay/Contacts/Block/Adminhtml/Contact/Edit/DeleteButton.php ici c'est plus ou moins pariel mais pour la suppression :

<?php
namespace Pfay\Contacts\Block\Adminhtml\Contact\Edit;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

/**
 * Class DeleteButton
 */
class DeleteButton extends GenericButton implements ButtonProviderInterface
{
    /**
     * @return array
     */
    public function getButtonData()
    {
        $data = [];
        if ($this->getId()) {
            $data = [
                'label' => __('Delete Contact'),
                'class' => 'delete',
                'on_click' => 'deleteConfirm(\''
                    . __('Are you sure you want to delete this contact ?')
                    . '\', \'' . $this->getDeleteUrl() . '\')',
                'sort_order' => 20,
            ];
        }
        return $data;
    }

    /**
     * @return string
     */
    public function getDeleteUrl()
    {
        return $this->getUrl('*/*/delete', ['pfay_contacts_id' => $this->getId()]);
    }
}

Créer un reset button pour réinitialiser notre formulaire magento2

app/code/Pfay/Contacts/Block/Adminhtml/Contact/Edit/ResetButton.php

<?php
namespace Pfay\Contacts\Block\Adminhtml\Contact\Edit;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

/**
 * Class ResetButton
 */
class ResetButton implements ButtonProviderInterface
{
    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Reset'),
            'class' => 'reset',
            'on_click' => 'location.reload();',
            'sort_order' => 30
        ];
    }
}

Créer un reset button pour sauvegarder notre formulaire

app/code/Pfay/Contacts/Block/Adminhtml/Contact/Edit/SaveButton.php

<?php
namespace Pfay\Contacts\Block\Adminhtml\Contact\Edit;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

/**
 * Class SaveButton
 */
class SaveButton extends GenericButton implements ButtonProviderInterface
{
    /**
     * @return array
     */
    public function getButtonData()
    {
        return [
            'label' => __('Save Contact'),
            'class' => 'save primary',
            'data_attribute' => [
                'mage-init' => ['button' => ['event' => 'save']],
                'form-role' => 'save',
            ],
            'sort_order' => 90,
        ];
    }
}

Et voilà vos bouttons sont créer et théoriquement tout devrait fonctionner :)

Créer notre action delete

On crée notre action delete pour supprimer un objet depuis la grid magento comme ceci :

<?php
namespace Pfay\Contacts\Controller\Adminhtml\Test;
use Pfay\Contacts\Model\Contact as Contact;
use Magento\Backend\App\Action;

class Delete extends \Magento\Backend\App\Action
{
    public function execute()
    {
        $id = $this->getRequest()->getParam('id');

        if (!($contact = $this->_objectManager->create(Contact::class)->load($id))) {
            $this->messageManager->addError(__('Unable to proceed. Please, try again.'));
            $resultRedirect = $this->resultRedirectFactory->create();
            return $resultRedirect->setPath('*/*/index', array('_current' => true));
        }
        try{
            $contact->delete();
            $this->messageManager->addSuccess(__('Your contact has been deleted !'));
        } catch (Exception $e) {
            $this->messageManager->addError(__('Error while trying to delete contact: '));
            $resultRedirect = $this->resultRedirectFactory->create();
            return $resultRedirect->setPath('*/*/index', array('_current' => true));
        }

        $resultRedirect = $this->resultRedirectFactory->create();
        return $resultRedirect->setPath('*/*/index', array('_current' => true));
    }
}
Félicitation à vous qui avez suivi ce tutoriel jusqu’au bout. Ces 2 derniers tutoriels ne sont pas les articles les plus facile de la formation. C'est assez long et compliqué mais ca vaut le coup de passer un peu de temps pour comprendre cela car les formulaire d'admin et les grid vous allez tout le temps les utiliser sous magento2.
Si ce tutoriel vous à plus ou que vous avez une question, partagez cet article sur twitter s’il vous plait ! Merci à ceux qui le feront. Vous pouvez retrouver les sources de ce tutoriel juste en dessous de cet article.
Documents disponibles pour cet article :
Questions sur cette leçon
Pas de questions pour cette leçon. Soyez le premier !

Vous devez etre connecté pour demander de l'aide sur une leçon.