Tout savoir sur WordPress

Démarrer une extension WordPress avec la Programmation Orientée Objet (POO) – Partie 1

Faire de la programmation orientée objet (POO) dans WordPress, ce n’est pas faire un bout de class que l’on met quelque part juste pour le plaisir. La POO à des principes, des règles et c’est presque une manière de penser. A nous de voir comment l’on peut être amené à l’utiliser dans WordPress avec comme exemple, le développement d’un plugin.

Quand on démarre une extension WordPress, on nous parle de notre hook favoris plugins_loaded sans pour autant nous expliquer quelle est la bonne manière de développer notre extension – surtout si l’on commence à faire de la POO. Ce tutoriel va donc vous amener à faire de la POO en partant des principes de bases de celle-ci. Cependant, vous ne devez pas vous attendre à ce que j’explique en détail ce qu’est exactement la programmation orientée objet (que je préfère, par ailleurs, appeler la programmation orientée service). Pour suivre convenablement ce tutoriel, il vous faudra donc le pré-requis minimum de savoir ce qu’est une class, une interface, connaître les mots-clés protectedpublicprivate, …

Avant même de commencer, une documentation sur ces différents sujets ne sera pas de trop. Je pourrais être amené à vous les expliquer plus en détail puisque nous allons baigner dedans :

Une todolist

Voici notre sujet ! A travers les différentes parties de ce tutoriel, nous allons développer une extension de todolist qui poutre les poneys que l’on va appeler : WP Todolist.

Pour démarrer, rien de plus simple, créons le dossier : wp-todolist dans wp-content/plugins/wp-todolist. Il nous faut ensuite le fichier de base qui va contenir notre en-tête afin que WordPress reconnaisse notre plugin :

<?php
// wp-content/plugins/wp-todolist/wp-todolist.php

/**
 * Plugin Name: WP Todolist
 * Description: Gestion d'une Todolist dans WordPress
 * Version: 1.0.0
 * Author: <a href="http://twitter.com/TDeneulin" target="_blank">Thomas DENEULIN</a>
 */

Et pour l’instant… c’est tout ! Ce fichier n’a qu’un rôle de bootstrap au niveau de l’extension. En effet, le fichier qui va exécuter l’extension se situe plus bas dans notre projet.

Une class = un rôle. C’est le principe de Single Responsability. On peut l’appliquer pour un fichier.

Le fichier de base n’est pas une class et permet uniquement de lancer l’exécution et les dépendances du plugin. Maintenant, il nous faut notre class qui va contenir l’exécution des différents hooks, des différents services et des différentes actions. Elle va contrôler la bonne exécution de l’extension. A partir de là, tout mon code va être géré par des class qui se situeront dans le dossier wp-todolist/src/Todolist.

<?php
// wp-todolist/src/Todolist/Todolist.php
namespace Todolist;

if ( ! defined( 'ABSPATH' ) ) exit;

use Todolist\Models\HooksInterface;
use Todolist\Models\HooksFrontInterface;
use Todolist\Models\HooksAdminInterface;

/**
 * Todolist
 *
 * @author Thomas DENEULIN
 * @version 1.0.0
 * @since 1.0.0
 */
class Todolist implements HooksInterface{

    protected $actions   = array();

    /**
     * @param array $actions
     */
    public function __construct($actions = array()){
        $this->actions = $actions;
    }

    /**
     * @return boolean
     */
    protected function canBeLoaded(){
        return true;
    }


    /**
     * Execute plugin
     */
    public function execute(){
        if ($this->canBeLoaded()){
            add_action( 'plugins_loaded' , array($this,'hooks'), 0);
        }
    }

    /**
     * @return array
     */
    public function getActions(){
        return $this->actions;
    }

    /**
     * Implements hooks from HooksInterface
     *
     * @see Todolist\Models\HooksInterface
     *
     * @return void
     */
    public function hooks(){
        foreach ($this->getActions() as $key => $action) {
            switch(true) {  // Cela m'évite de faire un if / else if
                case $action instanceof HooksAdminInterface:
                    if (is_admin()) {
                        $action->hooks();
                    }
                    break;
                case $action instanceof HooksFrontInterface:
                    if (!is_admin()) {
                        $action->hooks();
                    }
                    break;
                case $action instanceof HooksInterface:
                    $action->hooks();
                    break;
            }
        }
    }
}

J’ai mis en surbrillance un certains nombre de ligne et je vais vous les détailler.

  • Ligne 2-3 : ma class est définie dans le namespace Todolist. Plus besoin d’un class_exists pour éviter un conflit (que je trouve d’ailleurs aberrant de mettre… ! tout comme function_exists).
  • Ligne 6-9 : J’utilise un use sur les interfaces que nous allons devoir utiliser dans la class.
  • Ligne 15-16 : Ma class implémente HooksInterface qui contient la méthode hooks. Cela m’oblige à définir cette méthode qui va me permettre d’exécuter la liste des hooks que je souhaite utiliser. Cela équivaut à un contrat que l’on passe avec notre class.
  • Ligne 22-23 : Pour l’instant, le constructeur est vide et accepte un paramètres $actions qui doit être un tableau. Il va nous servir plus tard pour respecter le principe d’injection de dépendance.
  • Ligne 29-30 : Ici, je fais de la programmation par intention. Cette méthode va me retourner un booléen qui me permettra, plus tard, de savoir si je dois pouvoir charger le plugin ou non. C’est utile pour gérer les dépendances et les pré-requis du plugin (exemple : la version de PHP, l’utilisation de Curl, … ) Par défaut, je peux toujours charger mon plugin (pour l’instant) !
  • Ligne 37-38 : La méthode execute …. exécute mon plugin :) Elle va vérifier que j’ai bien le droit de charger mon plugin. Si c’est le cas, j’effectue l’action plugins_loaded sur ma méthode hooks.
  • Ligne 59-60 : Ma méthode hooks ne fait rien de plus que de boucler sur mes différents services que j’aurai chargé. Si ces derniers sont des instances de HooksInterface (donc, qui ont la méthode hooks) ou d’une autre interface en fonction de contexte où je me trouve, et bien… je hook !

Et voici mon HooksInterface :

<?php 
// wp-todolist/src/Todolist/Models/HooksInterface.php

namespace Todolist\Models;

/**
 *
 * @author Thomas DENEULIN
 * 
 */
interface HooksInterface{
    
    /**
     * @return void
     */
    public function hooks();
}

Nous sommes dans le principe de Ségrégation des Interfaces.

Puisqu’il arrive fréquemment de devoir exécuter des hooks uniquement côté admin ou, uniquement côté front, je crée alors deux autres interfaces qui héritent de HooksInterface. Cela n’a d’intérêt que de créer deux nouveaux « contrats » pour mes prochaines class / services.

<?php 
// wp-todolist/src/Todolist/Models/HooksAdminInterface.php

namespace Todolist\Models;

/**
 *
 * @author Thomas DENEULIN
 * 
 */
interface HooksAdminInterface extends HooksInterface {}

Préparation du fichier composer, de l’autoload et du fichier racine

Notre fichier racine va devoir utiliser et charger la class Todolist tout en lui donnant les dépendances requises. Je vais donc préparer mon fichier composer afin qu’il puisse me créer un autoloader. :

// wp-todolist/composer.json | PENSEZ A SUPPRIMER CETTE LIGNE.

{
    "name": "WP Todolist",
    "description": "Gestion d'une Todolist dans WordPress",
    "license": "GPL",
    "authors": [
        {
            "name": "Thomas DENEULIN",
            "email": "contact@wp-god.com"
        }
    ],
    "autoload": {
        "psr-4": {
            "Todolist\\": "src/Todolist/"
        }
    }
}

Ce qui nous intéresse dans cette configuration, c’est la configuration de l’autoload sur la norme psr-4. En clé, je met le nom du namespace et en valeur, la source du fichier racine dans lequel je vais utiliser le namespace Todolist. Cela veut dire que je ne pourrais pas autoloader des class dont le namespace est Todolist si elles ne se trouvent pas dans le dossier src/Todolist.

Dés lors que la configuration est fait, il ne me reste plus qu’à lancer la ligne de commande : composer dump. Cela suggère que vous ayez installé composer en tant que global sur votre environnement. Si vous n’avez que le fichier composer.phar, vous devez effectuez la commande : php composer.phar dump

Si vraiment vous n’y arrivez pas, téléchargez le tutoriel sur Github et vous aurez le fichier composer.phar

Un dossier vendor est alors créé. Il contient l’autoload dont nous allons avoir besoin ! Retournons donc du côté du fichier wp-todolist.php

<?php

/**
 * Plugin Name: WP Todolist
 * Description: Gestion d'une Todolist dans WordPress
 * Version: 1.0.0
 * Author: <a href="http://twitter.com/TDeneulin" target="_blank">Thomas DENEULIN</a>
 */

require_once( dirname( __FILE__ ) . '/vendor/autoload.php' );

use Todolist\Todolist;

$todolist = new Todolist();
$todolist->execute();

On y trouve quelques nouveautés !

  • Ligne 9-10 : Il faut require le fichier d’autoload en tout premier afin de pouvoir utiliser toutes les class de notre projet
  • Ligne 11-12 : J’utilise la class Todolist que l’on a créée tout à l’heure afin de pouvoir l’utiliser

Et pour finir, j’instancie notre class Todolist puis, je l’exécute :) Notre plugin peut démarrer et exécuter nos hooks ! Et on est loin de poutrer les poneys aquatiques.

Créons un custom post type !

Intéressons-nous maintenant à la création du custom post type mais avant cela, établissons notre recette de cuisine afin de faire de la bonne POO ! Voici ce dont nous allons avoir besoin :

  • Une class qui gère le custom post type.
  • Un helper qui va gérer le nom du custom post type. Pourquoi ? Simplement car on en a toujours besoin partout quand on va s’amuser avec les différents hooks de WordPress.

A partir de là, nous sommes armés pour continuer notre route ! Mais attention, nous allons devoir être plus profond dans notre manière d’organiser notre code. En effet, nous attaquons une logique WordPress au niveau de notre code. Ce n’est pas une logique métier quelconque qui pourrait se retrouver dans n’importe quel projet. Je vais donc devoir créer de nouveaux dossiers :

  • Todolist/WordPress/Helpers : celui-ci va contenir mes Helpers liés à WordPress
  • Todolist/WordPress/PostType : ce dossier va contenir tous les customs post types que nous aurons à gérer au sein de notre plugin

Commençons par le fichier le plus simple, le helper :

<?php
// wp-todolist/src/Todolist/WordPress/Helpers/PostType.php

namespace Todolist\WordPress\Helpers;

/**
 *
 * @author Thomas DENEULIN
 * @version 1.0.0
 * @since 1.0.0
 */
abstract class PostType{

    const CPT_TODO = "td-todo";

}
  • Ligne 3-4 : je spécifie la profondeur du namespace en fonction de la profondeur du dossier
  • Ligne 11-12 : Cette class est abstraite afin que l’on ne puisse pas l’instancier. Son but est uniquement de contenir des constantes pouvant être utilisées dans mon plugin.

Jusque là, rien d’étonnant ! Allons du côté de notre class Todo :

<?php
// wp-todolist/src/Todolist/WordPress/PostType/Todo.php

namespace Todolist\WordPress\PostType;

use Todolist\Models\HooksInterface;

use Todolist\WordPress\Helpers\PostType;

/**
 * Todo
 *
 * @author Thomas DENEULIN
 * @version 1.0.0
 * @since 1.0.0
 */
class Todo implements HooksInterface{


    /**
     * @see Todolist\Models\HooksInterface
     */
    public function hooks(){

        add_action( "init", array($this, 'initPostType') );

    }



    /**
     * @filter todolist_rewrite_cpt_todo
     * @filter todolist_register_ PostType::CPT_TODO _post_type
     * @see Todolist\WordPress\Helpers\PostType
     */
    public function initPostType(){

        $labels = array(
            'name'               => __('Todos', 'td'),
            'singular_name'      => __('Todos', 'td'),
            'menu_name'          => __('Todos', 'td'),
            'name_admin_bar'     => __('Todos','td'),
            'view'               => __('View todo', 'td'),
            'all_items'          => __('All todos', 'td'),
            'search_items'       => __('Search todos', 'td'),
            'not_found'          => __('Todo not found', 'td'),
            'not_found_in_trash' => __('Todo not found', 'td')
        );

        $args = array(
            'labels'             => $labels,
            'public'             => true,
            'query_var'          => true,
            'rewrite'            => array( 'slug' => apply_filters("todolist_rewrite_cpt_todo", "todos") ),
            'capability_type'    => 'post',
            'has_archive'        => true,
            'hierarchical'       => false

        );

        register_post_type(PostType::CPT_TODO , apply_filters("todolist_register_" . PostType::CPT_TODO . "_post_type", $args) );
    }
}
  • Ligne 16-17 : Et oui ! il faut implémenter l’interface HooksInterface sinon, comment notre plugin va savoir qu’il faut exécuter nos hooks ?
  • Ligne 22-23 : L’implémentation de l’interface m’oblige donc à définir la méthode hooks. A l’intérieur de celle-ci, je vais lister tous (pour l’instant.. un seul !) les hooks qui vont interagir au niveau de mon custom post type.

J’ai également rajouté quelques commentaires au niveau de la PHPDocs pour informer qu’il existe deux filtres au niveau de ma méthode. Ceci est une convention personnelle et n’est pas reconnu par la PHPDocs. On retrouve :

  • todolist_rewrite_cpt_todo : pour changer éventuellement la partie rewrite du custom post type. (SEO ? :) )
  • todolist_register_ PostType::CPT_TODO _post_type : qui permet de retravailler les arguments du custom post type

On donne à la class ce dont elle a besoin. C’est le principe d’injection de dépendance.

Enfin, pour que nos hooks soient exécutés, il faut bien que notre class Todolist ait connaissance de cette class qui va effectuer des actions. Afin de respecter le principe d’injection de dépendance, nous devons donner à notre class ce dont elle a besoin pour travailler et pas l’inverse ! Rappelez-vous, nous avions un paramètre dans notre constructeur qui acceptait des actions ; nous allons nous en servir ! Repartons du côté de notre fichier de base afin de donner à la class ce dont elle a besoin.

<?php

/**
 * Plugin Name: WP Todolist
 * Description: Gestion d'une Todolist dans WordPress
 * Version: 1.0.0
 * Author: <a href="http://twitter.com/TDeneulin" target="_blank">Thomas DENEULIN</a>
 */

require_once( dirname( __FILE__ ) . '/vendor/autoload.php' );

use Todolist\Todolist;
use Todolist\WordPress\PostType\Todo;

$actions = array(
    new Todo()
);


$todolist = new Todolist($actions);
$todolist->execute();

It’s work ! Si vous lancez votre plugin, vous venez alors de créer un nouveau custom post type. Nous voilà dans notre version 0.0.0.0.1 de notre plugin. Non ? Pourtant si je crée un post de type todo, je peux très bien sauvegarder ma petite todo… Certes, ça ne sert pas à grand chose et autant prendre un post-it pour l’instant :)

Vous pouvez retrouver chaque partie du tutoriel sur mon repo Github. Il existe une branch par partie (part-1, part-2, … ) et la branch master contiendra la dernière version à jour.

Quelques questions / réponses que je n’ai pas abordé durant le tutoriel

Pourquoi utiliser une méthode « hooks » pour définir mes actions et mes filtres plutôt que de le faire dans le constructeur ?

Un constructeur est là pour construire la class. Il n’existe pas pour exécuter des actions qui ont une logique indépendante de la construction de la class. De plus, la méthode hooks est gérer par une interface ce qui me permet de boucler dessus et d’établir des contrats au sein de mes class comme nous avons pu le voir.

Est-ce que ce n’est pas une usine à gaz juste pour un custom post type ?

Non ? Pensez que c’est la première étape d’un long chemin. Le plugin sera bien plus maintenable plus tard lorsqu’il va devoir grossir. [spoiler] La suite des évènements va comprendre du ReactJS, de l’héritage de template, des meta données et bien plus encore.. ! Nous serons dans une « usine à gaz » alors autant que celle-ci soit lisible, maintenable et performante [/spoiler]

Par contre, je ne vous invite pas à faire ceci si ce n’est QUE pour créer un custom post type.

Que faire de la compatibilité de PHP 5.2 en faisant de la POO?

Je la met aux amendes :) Nous démarrons l’air de PHP 7. Cela fait 5ans et 4mois à l’heure où j’écris ces lignes que la version 5.2 n’est plus maintenu par PHP. Je ne vais pas attendre 2022 pour arrêter la compatibilité de la version 5.2 et passer sur PHP 7.

Cet article a été mis à jour il y a 552 jours - Il n'est peut être plus à jour !

Article écrit par Thomas

Software Engineer chez YurPlan et auteur de WP GoD

Grand passionné des technologies web et de la science de l’informatique, je ne me restreins pas à un seul langage / une seule technologie. Je passe d’abord par une approche conceptuelle des projets avant d’admettre quel langage faut-il utiliser.

Développeur PHP, JavaScript et Python. J’utilise AngularJS, ReactJS, Django, Symfony1-2-3 et WordPress. Je m’amuse avec BabylonJS. Je versionne avec Git. Je compile avec BrunchJS et je déploie avec Capistrano.

3 Commentaires

Laisser un commentaire

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

  1. Bonjour
    désoler de sortir du sujet du poste mais j’ais une petite question s’il vous plait.

    dans vos article sur le site quand un rédacteur ou l’auteur de l’article comment il y a mentionner devant sont pseudo « auteur de la’article » ou bien  » rédacteur a geekpress ».
    s’il vous lait comment je peut faire sa sur mon site ?
    j’ai essayer plusieur fois mais j’ais pas resusi donc je le suie que c’est pas possible et du coup je la trouve sur votre blog .
    merci de votre reponse

    • Tout se passe en CSS avec une pseudo class :after. Je cible en fait la classe auteur qui est créée par WordPress pour distinguer l’auteur des autres commentaires

4a1830417dcaeb690065b7dfe48abb3e__________________________