Formation Magento 2 : Chapitre 6 – Partie B

Rappelez-vous, nous avons créé une collection dans l’article précédent :

$jobCollection = $this->_job->getCollection()
                ->addFieldToSelect('*')
                ->addFieldToFilter('status', $this->_job->getEnableStatus())
                ->join(
                    array('department' => $this->_department->getResource()->getMainTable()),
                    'main_table.department_id = department.'.$this->_job->getIdFieldName(),
                    array('department_name' => 'name')
                );

Nous allons nous attarder sur la méthode getCollection qui permet de faire plein de choses !
Avant de commencer reprenez le fichier :
app/code/Maxime/Jobs/Block/Job/ListJob.php

Et changer la récupération de la collection en une seule ligne :

            $jobCollection = $this->_job->getCollection()->addStatusFilter($this->_job, $this->_department);

Vous aurez le PHP complet suivant :

Afficher

<?php
namespace Maxime\Jobs\Block\Job;
class ListJob extends \Magento\Framework\View\Element\Template
{

    protected $_job;

    protected $_department;

    protected $_resource;

    protected $_jobCollection = null;

    /**
     * @param \Magento\Framework\View\Element\Template\Context $context
     * @param \Maxime\Jobs\Model\Job $job
     * @param \Maxime\Jobs\Model\Department $department
     * @param \Magento\Framework\App\ResourceConnection $resource
     * @param array $data
     */
    public function __construct(
        \Magento\Framework\View\Element\Template\Context $context,
        \Maxime\Jobs\Model\Job $job,
        \Maxime\Jobs\Model\Department $department,
        \Magento\Framework\App\ResourceConnection $resource,
        array $data = []
    ) {
        $this->_job = $job;
        $this->_department = $department;
        $this->_resource = $resource;

        parent::__construct(
            $context,
            $data
        );
    }

    /**
     * @return $this
     */
    protected function _prepareLayout()
    {
        parent::_prepareLayout();


        // You can put these informations editable on BO
        $title = __('We are hiring');
        $description = __('Look at the jobs we have got for you');
        $keywords = __('job,hiring');

        $this->getLayout()->createBlock('Magento\Catalog\Block\Breadcrumbs');

        if ($breadcrumbsBlock = $this->getLayout()->getBlock('breadcrumbs')) {
            $breadcrumbsBlock->addCrumb(
                'jobs',
                [
                    'label' => $title,
                    'title' => $title,
                    'link' => false // No link for the last element
                ]
            );
        }

        $this->pageConfig->getTitle()->set($title);
        $this->pageConfig->setDescription($description);
        $this->pageConfig->setKeywords($keywords);


        $pageMainTitle = $this->getLayout()->getBlock('page.main.title');
        if ($pageMainTitle) {
            $pageMainTitle->setPageTitle($title);
        }

        return $this;
    }

    protected function _getJobCollection()
    {
        if ($this->_jobCollection === null) {

            $jobCollection = $this->_job->getCollection()->addStatusFilter($this->_job, $this->_department);

            $this->_jobCollection = $jobCollection;
        }
        return $this->_jobCollection;
    }


    public function getLoadedJobCollection()
    {
        return $this->_getJobCollection();
    }

    public function getJobUrl($job){
        if(!$job->getId()){
            return '#';
        }

        return $this->getUrl('jobs/job/view', ['id' => $job->getId()]);
    }

    public function getDepartmentUrl($job){
        if(!$job->getDepartmentId()){
            return '#';
        }

        return $this->getUrl('jobs/department/view', ['id' => $job->getDepartmentId()]);
    }
}

[collapse]

Nous allons gérer nos données via la classe de notre collection. Après tout elle existe, donc autant s’en servir !

Et dans la classe :
app/code/Maxime/Jobs/Model/ResourceModel/Job/Collection.php

Créez la méthode suivante avec laquelle nous allons travailler :

public function addStatusFilter($job, $department){
        $this->addFieldToSelect('*')
            ->addFieldToFilter('status', $job->getEnableStatus())
            ->join(
                array('department' => $department->getResource()->getMainTable()),
                'main_table.department_id = department.'.$department->getIdFieldName(),
                array('department_name' => 'name')
            );

        return $this;
    }

Vous remarquerez que j’ai mis en paramètre mes objets Department et Job pour les avoir sous la main lors de nos tests.

Afficher le SQL d’une collection

Pour afficher le SQL qui sera lancé par Magento lors de la création de la collection, vous pouvez lancer le code suivant juste après celle-ci :

var_dump($this->getSelect().'')

ou

var_dump($this->getSelect()->__toString();)

En vous rendant sur la page d’affichage des jobs en front, vous verrez votre requête.

Appliquer un filtre

Vous avez certainement remarqué que dans notre collection, nous créons un filtre sur le champs « status » avec la méthode addFieldToFilter.
Notez que lancer ce code :

$this->addFieldToSelect('*')
     ->addFieldToFilter('status', $job->getEnableStatus());

Revient à faire ceci :

$this->addFieldToSelect('*')
      ->addFieldToFilter('status', array('eq' => $job->getEnableStatus()));

Comme vous pouvez vous en douter, on peut alors mettre d’autres opérateurs que « eq » dans notre indice de tableau !

Opérateur Action
eq Egal à
gteq Supérieur ou égal à
gt Supérieur à
lteq Inférieur ou égal à
lt Inférieur à
neq Non égal à
like Like SQL (ne pas oublier les %)
nlike Not Like SQL (ne pas oublier les %)
in Parmi
nin Non parmi
null Est null (qu’importe la valeur assigné au tableau en paramètre)
notnull Non null (qu’importe la valeur assigné au tableau en paramètre)
finset FIND_IN_SET MySQL, pour les colonnes en BDD du type « valeur1,valeur2,valeurX ». Ex : Dont la valeur « 100 » est présente dans la chaîne « 76,82,100,628 »

Voici un exemple des différents opérateurs en SQL (notez que c’est juste à titre d’exemple et que ces requêtes n’ont pas vraiment lieu d’être utiles) :
requetessqlcollection
find_in_set

Condition ET en SQL avec les collections

Pour faire un ET en SQL grâce aux méthodes des collections, il vous faut mettre à la suite les addFieldToFilter.
Ex :

$this->addFieldToSelect('*')
            ->addFieldToFilter('status', array('eq' => $job->getEnableStatus()))
            ->addFieldToFilter('date', array('gt' => date('Y-m-d')));

Ce qui donnera : SELECT `main_table`.* FROM `maxime_job` AS `main_table` WHERE (`status` = 1) AND (`date` > '2016-03-01')

On peut en mettre autant que l’on souhaite !

Condition OU en SQL avec les collections

Plus complexe à faire, la condition OU. En effet, les appels consécutifs à la méthode addFieldToFilter génèrent des ET.
Il faut mettre en premier paramètre un tableau de colonnes, et en deuxième, un tableau avec leurs conditions respectives.
Voici donc comment procéder :

$this->addFieldToSelect('*')
            ->addFieldToFilter(
                array(
                    'status',
                    'date'
                ),
                array(
                    array('eq' => $job->getEnableStatus()),
                    array('gt' => date('Y-m-d'))
                )
            );

Cela transforme notre ET de tout à l’heure en OU :
SELECT `main_table`.* FROM `maxime_job` AS `main_table` WHERE ((`status` = 1) OR (`date` > '2016-03-01'))

Ce sont des conditions simples sur une seule colonne, pour des conditions plus complexes, il faudra passer par du SQL via les méthodes Zend. Mais avant cela, nous allons voir comment faire une jointure.

Les jointures avec les collections

Nous allons faire une jointure avec la table des departments.
Je veux tous mes « jobs » activés avec le nom du département :

$this->addFieldToSelect('*')
            ->addFieldToFilter('status', $job->getEnableStatus())
            ->join(
                array('department' => $department->getResource()->getMainTable()),
                'main_table.department_id = department.'.$department->getIdFieldName(),
                array('department_name' => 'name')
            );

Notre méthode join prend 3 paramètres :
– Le premier c’est le nom de la table sur laquelle on fait la jointure, on la récupère avec une méthode Magento getTableName
La clé du tableau correspond à l’alias de notre table (AS)
– Le deuxième c’est la condition de notre jointure, ici on joint l’ID du départment
– Le troisième, c’est les colonnes que le met dans notre SELECT. Si rien n’est renseignés, on SELECT * sur notre table department.
La clé du tableau est l’alias de la colonne, la valeur est la colonne de la table jointe à sélectionner.
Si aucune clé n’est renseigné, juste une ou des valeurs, les colonnes ne seront pas renommées (pas de AS).

Ici notre requête fait un INNER JOIN :
SELECT `main_table`.*, `department`.`name` AS `department_name` FROM `maxime_job` AS `main_table`
INNER JOIN `maxime_department` AS `department` ON main_table.department_id = department.entity_id WHERE (`status` = '1')

Si l’on veut faire d’autres jointures, il faut faire un getSelect() avant le join :

$this->addFieldToSelect('*')
            ->addFieldToFilter('status', $job->getEnableStatus())
            ->getSelect()
            ->joinLeft(
                array('department' => $department->getResource()->getMainTable()),
                'main_table.department_id = department.'.$department->getIdFieldName(),
                array('department_name' => 'name')
            );

L’avantage d’utiliser le getSelect() dans notre objet collection, c’est que dans notre block, nous aurons toujours un objet de type « Collection ».
Si nous avions fait notre getSelect() directement dans le block, nous n’aurions plus une collection, mais un objet de type « Select ».

Des conditions complexes avec des collections

Petit exercice pratique !
Je souhaite avoir tous les objets Jobs (avec le nom du départment) avec les conditions suivantes :
– Son statut est égal à 1
– Et son nom est du type « %sample% » ou sa date est supérieure ou égale à la date actuelle

Le SQL final sera :
SELECT `main_table`.*, `department`.`name` AS `department_name` FROM `maxime_job` AS `main_table`
LEFT JOIN `maxime_department` AS `department` ON main_table.department_id = department.entity_id WHERE (`status` = '1') AND ((`name` LIKE '%Sample%') OR (`date` >= '2016-03-02'))

Vous avez deviné ?
Voici ce qu’il faut faire :

$this->addFieldToSelect('*')
            ->addFieldToFilter('status', $job->getEnableStatus())
            ->addFieldToFilter(
                array(
                    'name',
                    'date'
                ),
                array(
                    array('like' => '%Sample%'),
                    array('gteq' => date('Y-m-d'))
                )
            )
            ->getSelect()
            ->joinLeft(
                array('department' => $department->getResource()->getMainTable()),
                'main_table.department_id = department.'.$department->getIdFieldName(),
                array('department_name' => 'name')
            );

Dernier exercice beaucoup plus difficile car les conditions OU sont dominantes :
– Son statut est égal à 1
– Ou son statut est égal à 0 et sa date est supérieure ou égale à la date actuelle
– Ou son id est supérieur ou égal à 0 et, soit sa date actuelle est inférieure à la date actuelle, ou le nom de son department est du type « %mar% »

Pas vraiment de logique mais c’est pour l’exercice, voici le SQL final que vous devez avoir :
SELECT `main_table`.*, `department`.`name` AS `department_name`
FROM `maxime_job` AS `main_table`
LEFT JOIN `maxime_department` AS `department` ON main_table.department_id = department.entity_id
WHERE (main_table.status = 1)
OR (main_table.status = 0 AND main_table.date >= 2016-03-02)
OR (main_table.entity_id > 0 && (main_table.date < 2016-03-02 || department.name LIKE "%mar%"))

Nous ne pouvons pas appeler des where et des orwhere à la suite dans ce cas, car nous avons des conditions imbriquées dans d’autres.

Avec les méthodes utilisées précédemment nous ne pouvons avoir qu’un seul niveau, donc nous allons procéder de la sorte :

$this->addFieldToSelect('*')
            ->getSelect()
            ->joinLeft(
                array('department' => $department->getResource()->getMainTable()),
                'main_table.department_id = department.'.$department->getIdFieldName(),
                array('department_name' => 'name')
            )
            ->where(CONDITION1)
            ->orWhere(CONDITION2)
            ->orWhere(CONDITION3);



Alors qu’avez-vous fait ?
Je vous donne ma version :

Afficher

$this->addFieldToSelect('*')
            ->getSelect()
            ->joinLeft(
                array('department' => $department->getResource()->getMainTable()),
                'main_table.department_id = department.'.$department->getIdFieldName(),
                array('department_name' => 'name')
            )
            ->where('main_table.status = ?', $job->getEnableStatus())
            ->orWhere('main_table.status = ? AND main_table.date >= '.date('Y-m-d'), $job->getDisableStatus())
            ->orWhere('main_table.'.$job->getIdFieldName().' > 0 && (main_table.date < '.date('Y-m-d').' || department.name LIKE "%mar%")');

[collapse]

Pourquoi doit-on faire ça ? Un simple sql serait suffisant non ?
Tout est question de SECURITÉ. Pour éviter les injections SQL, Magento utilise la méthode quoteInto pour « sécuriser » la chaîne SQL qui est traitée. De plus, on récupère une collection à la fin, ce qui nous facilite grandement le traitement des données dans notre template pour l’affichage.
C’est pour ça qu’il faut passer pour ces méthodes. N’hésitez pas à consulter la classe Zend_Db_Select qui contient pas mal de méthodes et à vous de faire quelques tests de requêtes 😉

Avant de continuer, n’hésitez pas à remettre votre classe au propre :
app/code/Maxime/Jobs/Model/ResourceModel/Job/Collection.php

Voici le contenu complet :

<?php
namespace Maxime\Jobs\Model\ResourceModel\Job;

use \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;

class Collection extends AbstractCollection
{

    protected $_idFieldName = \Maxime\Jobs\Model\Job::JOB_ID;

    /**
     * Define resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('Maxime\Jobs\Model\Job', 'Maxime\Jobs\Model\ResourceModel\Job');
    }

    public function addStatusFilter($job, $department){
        $this->addFieldToSelect('*')
            ->addFieldToFilter('status', $job->getEnableStatus())
            ->join(
                array('department' => $department->getResource()->getMainTable()),
                'main_table.department_id = department.'.$department->getIdFieldName(),
                array('department_name' => 'name')
            );
        
        return $this;
    }
}

Lors du prochain article, nous allons mettre en place notre premier CSS !

Continuer la formation
Revenir à la partie précédente
Manipuler des collections avec Magento 2
Taggé sur :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous utilisons des cookies afin de nous assurer de vous proposer la meilleure expérience sur ce site.
Ok