Créez un thème wordpress (presque) comme si vous étiez sous symfony

C'est après quelques rudes soirées passées à maintenir et compléter un site créé il y a quelques années, bourré de mélanges illisibles, que j'ai décidé d'essayer.

Je suis régulièrement amené à développer des sites basés sous wordpress et je voulais me créer un modèle de base que je puisse mettre en place rapidement et surtout qui soit organisé et facilement maintenable.

Je me suis alors demandé s'il n'était pas possible de mettre en place une structure qui soit proche de celle de symfony. Il semble que cela soit possible. Je n'ai pas encore tout testé, mais Ce blog est développé avec ce modèle.

Voici les résultats de mes premières recherches, et vous allez voir, c'est assez simple et efficace.

Si vous êtres vraiment pressés, vous pouvez télécharger le zip du thème et une fois décompressé dans le dossier wp-content/themes, lancez dans ce dossier :
composer install
yarn install
yarn encore dev

Testez votre page d'accueil et vous pouvez directement vous rendre ici pour la suite

Au programme :

  • mise en place de la structure
  • installation de twig
  • préparation des fichiers de base et du fichier functions.php
  • installation de webpack pour les assets
  • mise en place de l'injection de dépendances
  • création d'un premier controlleur pour afficher les articles

Ça a l'air un peu long comme ça mais vous allez voir, en 15-20 minutes c'est plié et vous serez prêts à développer votre thème.

Préalable

Avant de vous lancer tout shuss dans le code, pensez qu'il y a quelques prérequis.

Il faut que composer et nodeJs soient installés sur votre machine

Si ce n'est pas le cas, je vous renvoie à ces deux sites pour les installer au préalable :

https://getcomposer.org/

https://nodejs.org/en/

Mise en place de la structure

Avant toutes choses, créons le dossier pour notre thème dans le dossier wp-content/themes

cd wp-content/themes
mkdir myTheme
cd myTheme

Dans le dossier "myTheme" créez les dossiers dont vous avez besoin. Pour infos la structure que j'ai utilisé est la suivante

myTheme
|_assets
   |__ js
        |__ app.js
   |__scss
        |__ app.scss
|_Controller
|_Model
|_public
|_Service
|_View

Initialiser composer

Vous êtes bien dans le dossier de votre thème hein ??
Sinon, cd myTheme puis ...

composer init

Suivez les étapes et répondez aux questions comme bon vous semble. Sinon, appuyez bêtement sur "entrée" à chaque fois. Une fois fini, ouvrez le fichier composer.json et ajoutez

"autoload": {
        "psr-4": {
            "App\\": ""
        }
    },

Du twig, du twig ...

Mettre en place ses vues avec twig est tellement plus agréable. Notre première action sera donc de l'installer

composer require twig/twig

et pi c'est tout :-)

Les fichiers de base pour Wordpress

Pour que notre thème soit reconnu par wordpress, il nous faut à minima à la racine du thème un fichier index.php, un fichier style.css et un fichier functions.php.

Le fichier index.php sera notre premier controlleur. Nous verrons cela plus tard.

Le fichier style.css va définir notre thème et on peut déja y inclure l'import du CSS qui sera généré par webpack par la suite :

/*
Theme Name: myTheme
Author: Prénom nom
Description: description of your theme
Version: 1.0.0
License: GNU General Public License v2 or later
*/
@import 'public/build/app.css';

Le fichier functions.php étant disponible partout dans notre thème, nous allons y préparer un certain nombre de variables qui nous serviront par la suite. On y ajoute également les fonctions globales de wordpress afin de les rendre disponibles partout dans nos futures vues twig :

<?php
require('vendor/autoload.php');

/**
 * Prepare twig renderer
 */
$loader = new Twig\Loader\FilesystemLoader(__DIR__ . '/View');
$twig   = new Twig\Environment($loader, [
    'debug' => true,
]);

/**
 * Add extension to make dump function operational in twig views
 */
$twig->addExtension(new \Twig\Extension\DebugExtension());

/**
 * Add function to twig to get global informations
 */

$twig->addFunction(
    new \Twig\TwigFunction('bloginfo', function ($param) {
        return get_bloginfo($param);
    })
);
$twig->addFunction(
    new \Twig\TwigFunction('footer', function () {
        return wp_footer();
    })
);

$twig->addFunction(
    new \Twig\TwigFunction('header', function () {
        return wp_head();
    })
);

/**
* Make {{ asset() }} functionnal
*/
$twig->addFunction(new \Twig\TwigFunction('asset', function ($asset) {
    return sprintf(get_bloginfo('template_directory') . '/public/build/%s', ltrim($asset, '/'));
}));

Nous utiliserons webpack pour les assets. Il faut donc au préalable que nodeJs soit installé sur votre machine.

Installation de webpack

composer require symfony/webpack-encore-bundle
yarn add @symfony/webpack-encore --dev
yarn install

A la racine de votre thème, créez un fichier webpack.config.js

var Encore = require('@symfony/webpack-encore');

// Manually configure the runtime environment if not already configured yet by the "encore" command.
// It's useful when you use tools that rely on webpack.config.js file.
if (!Encore.isRuntimeEnvironmentConfigured()) {
    Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}

Encore
    // directory where compiled assets will be stored
    .setOutputPath('public/build/')
    // public path used by the web server to access the output path
    .setPublicPath('/build')
    // only needed for CDN's or sub-directory deploy
    //.setManifestKeyPrefix('build/')

    /*
     * ENTRY CONFIG
     *
     * Add 1 entry for each "page" of your app
     * (including one that's included on every page - e.g. "app")
     *
     * Each entry will result in one JavaScript file (e.g. app.js)
     * and one CSS file (e.g. app.css) if your JavaScript imports CSS.
     */
    .addEntry('app', './assets/js/app.js')
    //.addEntry('page1', './assets/js/page1.js')
    //.addEntry('page2', './assets/js/page2.js')

    // When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
    .splitEntryChunks()

    // will require an extra script tag for runtime.js
    // but, you probably want this, unless you're building a single-page app
    .enableSingleRuntimeChunk()

    /*
     * FEATURE CONFIG
     *
     * Enable & configure other features below. For a full
     * list of features, see:
     * https://symfony.com/doc/current/frontend.html#adding-more-features
     */
    .cleanupOutputBeforeBuild()
    .enableBuildNotifications()
    .enableSourceMaps(!Encore.isProduction())
    // enables hashed filenames (e.g. app.abc123.css)
    .enableVersioning(Encore.isProduction())

    // enables @babel/preset-env polyfills
    .configureBabelPresetEnv((config) => {
        config.useBuiltIns = 'usage';
        config.corejs = 3;
    })

    // enables Sass/SCSS support
    //.enableSassLoader()

    // uncomment if you use TypeScript
    //.enableTypeScriptLoader()

    // uncomment to get integrity="..." attributes on your script & link tags
    // requires WebpackEncoreBundle 1.4 or higher
    //.enableIntegrityHashes(Encore.isProduction())

    // uncomment if you're having problems with a jQuery plugin
    //.autoProvidejQuery()

    // uncomment if you use API Platform Admin (composer require api-admin)
    //.enableReactPreset()
    //.addEntry('admin', './assets/js/admin.js')
;

module.exports = Encore.getWebpackConfig();

Si vous souhaitez utiliser le SCSS plutot que le CSS, renommez le dossier CSS en SCSS et le fichier app.css en app.scss. Pensez aussi à décommenter la ligne suivante dans le fichier webpack.config.js

// enables Sass/SCSS support
.enableSassLoader()

Il vous faudra probablement installer sassLoader (suivez les consignes que la console vous donne en lançant la commande yarn encore dev

yarn add sass-loader@^9.0.1 node-sass --dev
yarn add webpack-notifier@^1.6.0 --dev

puis (re)faîtes un petit yarn encore dev pour être sur que tout est ok.

Ouvrez ensuite le fichier app.js

// assets/js/app.js
/*
 * Welcome to your app's main JavaScript file!
 *
 * We recommend including the built version of this JavaScript file
 * (and its CSS file) in your base layout (base.html.twig).
 */

// any CSS you import will output into a single css file (app.css in this case)
import '../css/app.css';
// ou import '../scss/app.scss'; 
// si vous utiliser sass

// Need jQuery? Install it with "yarn add jquery", then uncomment to import it.
// import $ from 'jquery';

Et le fichier app.scss / ou app.css

body {
   background: #ccc;
}

Attention, pour inclure les fichiers JS compilés par webpack dans vos vues, comme nous ne sommes pas dans symfony, il vous faudra inclure également le fichier runtime.js généré lors du build, par exemple dans votre fichier base.html.twig.
<script src="{{ asset('runtime.js') }}"></script>
<script src="{{ asset('app.js') }}"></script>

Injection de dépendances

Pour mettre en place l'injection de dépendances, nous allons utiliser le bundle Auryn (https://github.com/rdlowrey/auryn )

composer require rdlowrey/auryn

Une fois installé, nous allons faire en sorte de disposer de l'injecteur partout dans le thème.

Ouvrez le fichier functions.php et ajoutez :

/**
 * Init dependencies injections
 */
$injector = new Auryn\Injector;

Et voila, on dispose d'un système d'injections de dépendances.

Création de notre premier controlleur

Ouvrons le fichier index.php

<?php
/**
 * @package myTheme
 */

echo $twig->render('index.html.twig', [
]);

Dans le dossier view, créez un fichier index.html.twig, ouvrez votre navigateur préféré et rendez vous à la page de votre blog...

Miracle.

Allons plus loin

Nous allons créer notre premier controlleur destiné à afficher les pages en utilisant un service avec injection d'un repository.

  1. Création du repository
  2. Création du service
  3. Création du controlleur
  4. Création de la vue

C'est parti.

Dans le dossier Model, créez un fichier PostRepository.php qui va nous servir à récupérer les 5 derniers articles (pour l'exemple parce que sinon, ça wordpress le fait tout seul :-) )

namespace App\Model;

class PostRepository
{
    public function __construct()
    {
        global $wpdb;
        $this->db = $wpdb;
    }

    public function findFiveLastPosts() {
        $query = 'SELECT * FROM wp_posts WHERE post_type = "POST"  ORDER BY post_date DESC' limit 5;
        return $this->db->get_results($query);
    }
    
}

On va ensuite créer un service qui trie ces informations. Oui je sais, on en a pas besoin mais c'est pour l'exemple. Donc dans le dossier Service, créez un fichier HomeService.php

<?php
namespace App\Service;

use App\Model\PostRepository;

class HomeService
{
    private $postRepository;
    
    public function __construct(PostRepository $postRepository)
    {
        $this->postRepository = $postRepository;
    }

    public function getDataForPage()
    {
        $posts = [];
        $data = $this->postRepository->findFiveLastPosts();
        foreach ($data as $post) {
            $posts[] = [
                'id'            => $post->id,
                'title'        => $post->post_title,
                'content' => $post->post_content,
                'url'         => $post->guid,
            ];
        }
        return $posts;
    }

}

Notez que l'on s'est injecté le repository dans le constructeur du service.

Il ne reste plus qu'à créer un controlleur. Les controlleurs seront en fait vos "templates"

Dans le dossier Controller, créez un fichier PageController.php

<?php
namespace App\Controller;
/*
Template Name: pages normales
*/

/**
 * On instancie notre service grace à $injector
 */
$homeService = $injector->make('App\Service\HomeService');

/**
 * On peut utiliser les fonctions natives de Wordpress
 */
$page = [
    'id'      => get_the_id(),
    'title'   => get_the_title(),
    'content' => get_the_content(),
];

/**
 * On utilise notre service
 */
$lastPosts = $homeService->getDataForPage();

/**
 * Et on fait le rendu dans twig
 */
echo $twig->render('pages/pages.html.twig', [
    'page' => $page,
    'latest_posts' => $lastPosts
]);

Il ne vous reste plus qu'à créer une nouvelle page dans le back en utilisant votre nouveau template de page "Pages normales"....

... et à créer la vue twig comme dans l'exemple ci-dessous :

    <div class="row container">
        <div class="col l3 hide-on-small-only">
            <ul>
            {% for post in latest_posts %}
                <li><a href="{{ post.url }}">{{ post.title }}</a></li>
            {% endfor %}
            </ul>
        </div>
        <div class="col s12 l9">
            <div class="col s12">
                <h2 class="left-align">{{ page.title }}</h2>
            </div>
            <div class="col s12">
                <p>{{ page.content | raw }}</p>
            </div>
        </div>
    </div>

Notez que les fonctions twig {{header()}} et {{footer()}} que nous avons instancié dans functions.php vous seront certainement utiles, notamment si vous utilisez des plugins qui utilisent javascript.

Voila pour l'essentiel. Il est donc assez facile en suivant ce modèle de créer des templates pour ses articles, ses pages ... En tous cas c'est le modèle que j'expérimente avec ce blog.

La prochaine étape sera d'implémenter les formulaires de commentaires, et de créer des entités spécifiques avec des custom posts et ACF.

A suivre ...

Laisser un commentaire

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

textsms
le 15/01/2021 par Fred.stp

Super article, merci beaucoup. Ce blog va devenir une vraie mine d'or.

textsms
le 05/10/2020 par Guillaume

Merci François pour ce partage ! ça m'a l'air excellent :)