Dans l’article précédent, on a vu comment gérer votre contenu avec WP CLI: gérer vos publications, vos utilisateurs et régénérer vos médias, entre autres.

Dans cette troisième et dernière partie, on va se concentrer sur ce qui à mon avis est la plus grande utilité de WP CLI. On va voir comment utiliser WP CLI pour accélérer tous vos développements et vraiment gagner du temps.

Lancez votre terminal et placez-vous dans votre installation de WordPress, et c’est parti.

Amorcer vos développements avec wp scaffold

wp scaffold est une des commandes les plus utiles. Elle permet de générer du code : des déclarations de taxonomies personnalisées et types de contenu personnalisés, des blocs et mêmes des thèmes basés sur Underscores et des extensions.

Générer une extension WordPress

wp scaffold plugin permet de générer tous les fichiers nécessaires pour une extension WordPress. Il suffit de lui passer un slug d’extension.

$ wp scaffold plugin my-plugin
Success: Created plugin files.
Success: Created test files.

Le dossier créé contient tout ce dont vous avez besoin.

Arborescence des fichiers créés par `wp scaffold plugin`
L’extension générée contient tout ce dont vous avez besoin pour bien démarrer.

Vous disposez d’un fichier racine, du fichier readme.txt ainsi que d’autres fichiers de développement courants comme package.json. Vous disposez aussi d’un dossier tests/ pour ranger vos tests et d’un script install-wp-tests pour installer un site WordPress pour les effectuer.

Selon les outils que vous utilisez vous allez peut-être avoir un peu de ménage à faire, mais globalement je trouve la commande très utile, ne serait-ce que pour .distignore, .gitignore, .phpcs.xml et .phpunit.xml.

Générer le code nécessaire pour déclarer un type de contenu personnalisé

Une fois votre extension en place, vous pouvez utiliser wp scaffold post-type et wp scaffold taxonomy pour générer le code nécessaire pour déclarer vos types de contenu et taxonomies personnalisées.

$ wp scaffold post-type projects --plugin=my-plugin
Success: Created '/home/vincent/Local Sites/example/app/public/wp-content/plugins/my-plugin/post-types/projects.php'.

La commande va générer le code nécessaire pour un type de contenu personnalisé projects. L’option --plugin=my-plugin permet de placer le code généré directement dans le plugin my-plugin, généré précédemment. Un fichier projects.php est crée et placé dans un dossier post-type/.

Le code généré ressemble à ceci :

<?php
/**
 * Registers the `projects` post type.
 */
function projects_init() {
  register_post_type(
    projects',
      [
        'labels'                => [
	  'name'                  => __( 'Projects', 'my-plugin' ),
	  'singular_name'         => __( 'Projects', 'my-plugin' ),
	  'all_items'             => __( 'All Projects', 'my-plugin' ),
	  'archives'              => __( 'Projects Archives', 'my-plugin' ),
	  'attributes'            => __( 'Projects Attributes', 'my-plugin' ),
	  'insert_into_item'      => __( 'Insert into projects', 'my-plugin' ),
	  'uploaded_to_this_item' => __( 'Uploaded to this projects', 'my-plugin' ),
	  'featured_image'        => _x( 'Featured Image', 'projects', 'my-plugin' ),
	  'set_featured_image'    => _x( 'Set featured image', 'projects', 'my-plugin' ),
	  'remove_featured_image' => _x( 'Remove featured image', 'projects', 'my-plugin' ),
	  'use_featured_image'    => _x( 'Use as featured image', 'projects', 'my-plugin' ),
	  'filter_items_list'     => __( 'Filter projects list', 'my-plugin' ),
	  'items_list_navigation' => __( 'Projects list navigation', 'my-plugin' ),
	  'items_list'            => __( 'Projects list', 'my-plugin' ),
	  'new_item'              => __( 'New Projects', 'my-plugin' ),
	  'add_new'               => __( 'Add New', 'my-plugin' ),
	  'add_new_item'          => __( 'Add New Projects', 'my-plugin' ),
	  'edit_item'             => __( 'Edit Projects', 'my-plugin' ),
	  'view_item'             => __( 'View Projects', 'my-plugin' ),
	  'view_items'            => __( 'View Projects', 'my-plugin' ),
	  'search_items'          => __( 'Search projects', 'my-plugin' ),
	  'not_found'             => __( 'No projects found', 'my-plugin' ),
	  'not_found_in_trash'    => __( 'No projects found in trash', 'my-plugin' ),
	  'parent_item_colon'     => __( 'Parent Projects:', 'my-plugin' ),
	  'menu_name'             => __( 'Projects', 'my-plugin' ),
	],
	'public'                => true,
	'hierarchical'          => false,
	'show_ui'               => true,
	'show_in_nav_menus'     => true,
	'supports'              => [ 'title', 'editor' ],
	'has_archive'           => true,
	'rewrite'               => true,
	'query_var'             => true,
	'menu_position'         => null,
	'menu_icon'             => 'dashicons-admin-post',
	'show_in_rest'          => true,
	'rest_base'             => 'projects',
	'rest_controller_class' => 'WP_REST_Posts_Controller',
      ]
  );
}

add_action( 'init', 'projects_init' );

/**
 * Sets the post updated messages for the `projects` post type.
 *
 * @param  array $messages Post updated messages.
 * @return array Messages for the `projects` post type.
 */
function projects_updated_messages( $messages ) {
  global $post;
  $permalink = get_permalink( $post );
  $messages['projects'] = [
	0  => '', // Unused. Messages start at index 1.
	/* translators: %s: post permalink */
	1  => sprintf( __( 'Projects updated. <a target="_blank" href="%s">View projects</a>', 'my-plugin' ), esc_url( $permalink ) ),
	2  => __( 'Custom field updated.', 'my-plugin' ),
	3  => __( 'Custom field deleted.', 'my-plugin' ),
	4  => __( 'Projects updated.', 'my-plugin' ),
	/* translators: %s: date and time of the revision */
	5  => isset( $_GET['revision'] ) ? sprintf( __( 'Projects restored to revision from %s', 'my-plugin' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false, // phpcs:ignore WordPress.Security.NonceVerification.Recommended
	/* translators: %s: post permalink */
	6  => sprintf( __( 'Projects published. <a href="%s">View projects</a>', 'my-plugin' ), esc_url( $permalink ) ),
	7  => __( 'Projects saved.', 'my-plugin' ),
	/* translators: %s: post permalink */
	8  => sprintf( __( 'Projects submitted. <a target="_blank" href="%s">Preview projects</a>', 'my-plugin' ), esc_url( add_query_arg( 'preview', 'true', $permalink ) ) ),
	/* translators: 1: Publish box date format, see https://secure.php.net/date 2: Post permalink */
	9  => sprintf( __( 'Projects scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview projects</a>', 'my-plugin' ), date_i18n( __( 'M j, Y @ G:i', 'my-plugin' ), strtotime( $post->post_date ) ), esc_url( $permalink ) ),
	/* translators: %s: post permalink */
	10 => sprintf( __( 'Projects draft updated. <a target="_blank" href="%s">Preview projects</a>', 'my-plugin' ), esc_url( add_query_arg( 'preview', 'true', $permalink ) ) ),
  ];
  return $messages;
}

add_filter( 'post_updated_messages', 'projects_updated_messages' );

/**
 * Sets the bulk post updated messages for the `projects` post type.
 *
 * @param  array $bulk_messages Arrays of messages, each keyed by the corresponding post type. Messages are
 *                              keyed with 'updated', 'locked', 'deleted', 'trashed', and 'untrashed'.
 * @param  int[] $bulk_counts   Array of item counts for each message, used to build internationalized strings.
 * @return array Bulk messages for the `projects` post type.
 */
function projects_bulk_updated_messages( $bulk_messages, $bulk_counts ) {
  global $post;
  $bulk_messages['projects'] = [
	/* translators: %s: Number of projects. */
	'updated'   => _n( '%s projects updated.', '%s projects updated.', $bulk_counts['updated'], 'my-plugin' ),
	'locked'    => ( 1 === $bulk_counts['locked'] ) ? __( '1 projects not updated, somebody is editing it.', 'my-plugin' ) :
		/* translators: %s: Number of projects. */
		_n( '%s projects not updated, somebody is editing it.', '%s projects not updated, somebody is editing them.', $bulk_counts['locked'], 'my-plugin' ),
	/* translators: %s: Number of projects. */
	'deleted'   => _n( '%s projects permanently deleted.', '%s projects permanently deleted.', $bulk_counts['deleted'], 'my-plugin' ),
		/* translators: %s: Number of projects. */
	'trashed'   => _n( '%s projects moved to the Trash.', '%s projects moved to the Trash.', $bulk_counts['trashed'], 'my-plugin' ),
		/* translators: %s: Number of projects. */
	'untrashed' => _n( '%s projects restored from the Trash.', '%s projects restored from the Trash.', $bulk_counts['untrashed'], 'my-plugin' ),
  ];
  return $bulk_messages;
}

add_filter( 'bulk_post_updated_messages', 'projects_bulk_updated_messages', 10, 2 );

Tous les domaines sont bons donc il vous suffit de vérifier les paramètres de la fonction register_post_type() et les chaines nécessaires, et d’inclure ce fichier dans le fichier racine de votre extension. Toutefois, la plupart des paramètres ont leur valeur par défaut et donc peuvent être omis.

Vous pouvez omettre le paramètre --plugin, mais le code sera généré dans votre terminal dans ce cas et vous devrez le copier manuellement.

Vous pouvez aussi utiliser le paramètre --theme=<slug> pour placer le code dans le thème correspondant au slug.

Générer le code pour déclarer une taxonomie personnalisée

Pour générer une taxonomie, la démarche est la même.

$ wp scaffold taxonomy type --post_types=projects --plugin=my-plugin
Success: Created '/home/vincent/Local Sites/example/app/public/wp-content/plugins/my-plugin/taxonomies/type.php'.

L’argument --post_types permet d’affecter automatiquement la taxonomie nouvellement créée aux types de contenu spécifiés. Séparez les slugs par des virgules s’il y en a plusieurs.

De la même façon, un fichier type.php a été créé dans un dossier taxonomies/. Il ne reste qu’à vérifier les paramètres passés à la fonction register_taxonomy() et à inclure le fichier.

Générer un thème ou thème enfant

Vous pouvez aussi utiliser WP CLI pour générer un thème basé sur Underscores.

Attention, Underscores est un starter thème pour le développement de thèmes classiques (thèmes en PHP). Si vous souhaitez créer un Block Theme, utilisez l’extension Create Block Theme.

$ wp scaffold _s my-theme --sassify --woocommerce
Success: Created theme 'My-theme'.

L’option --sassify permet de fournir les fichiers sass correspondant si vous utilisez Sass, et --woocommerce va générer les fichiers de compatibilité WooCommerce nécessaires.

En une seule commande, vous avez un starter classique basé sur Underscores, prêt à accueillir toutes vos personnalisations !

Vous pouvez tout de suite lui créer un thème enfant.

$ wp scaffold child-theme my-theme-child --parent_theme=my-theme
Success: Created '/home/vincent/Local Sites/example/app/public/wp-content/themes/my-theme-child'.

La commande wp scaffold child-theme va générer un thème enfant basé sur le thème passé via le paramètre --parent_theme.

Le thème est minimal et ne contient qu’un fichier style.css et functions.php. Aussi, le fichier functions.php contient une fonction hookée sur wp_enqueue_scripts qui va charger ses styles.

Mais attention, la fonction ne prend pas en considération ce que fait le parent et charge sa feuille de style. Vous pouvez donc avoir des styles chargés deux fois.

Pour comprendre comment fonctionnent les thèmes enfants, vous pouvez lire Créer un thème enfant ou le WPCookBook pour plus de détails.

Générer vos fichiers de traduction avec wp i18n

Vous pouvez utiliser WP CLI pour générer votre fichier POT en une seule commande. Placez-vous dans le dossier de votre thème ou extension, et utilisez la commande suivante :

$ wp i18n make-pot . languages/my-plugin.pot
Plugin file detected.
Success: POT file successfully generated.

Cela va scanner tout votre dossier (.) et va générer le fichier POT my-plugin.pot dans le dossier languages/.

Ce POT va même contenir les chaines de votre entête d’extension et les chaines de vos fichiers JavaScript si vous utilisez le package @wordpress/i18n dans ces scripts.

Pour exclure ou inclure des dossiers spécifiques lors du scan, vous pouvez utiliser les paramètres --exclude ou --include, respectivement.

Je vous recommande fortement de passer --exclude=node_modules/ si vous développez un bloc, par exemple.

Une fois votre fichier POT généré, vous pouvez créer vos fichiers de traductions PO en utilisant POEdit, par exemple.

Si vous utilisez le package @wordpress/i18n dans votre JavaScript, vous allez aussi avoir besoin de générer les fichiers JSON correspondants contenant les traductions de vos scripts.

Pour ce faire, utilisez wp i18n make-json.

$ wp i18n make-json languages/ --no-purge

La commande va scanner le dossier languages/ à la recherche de vos fichiers PO et va créer les fichiers JSON correspondants qui pourront être lus par vos scripts. L’option no-purge permet de laisser les POs intacts, sans supprimer les chaines présentes uniquement dans vos fichiers JS.

Pour plus de détails sur la gestion des traductions dans vos scripts, lisez Traduire le JavaScript dans WordPress.

Packager vos extensions avec wp dist-archive

Une fois vos développements terminés, vous allez avoir besoin de les livrer chez votre client, sur le dépôt officiel ou sur votre site en production.

En général, on ne livre pas les fichiers de développements comme package.json, les node_modules/, les tests et autres.

Pour cela, on va créer un fichier .distignore. Ce fichier va lister les fichiers et dossiers que l’on veut ignorer quand on va créer l’archive de notre thème ou extension.

Si vous avez généré votre thème ou extension avec wp scaffold, ce fichier est déjà crée pour vous et liste les éléments les plus courants.

# A set of files you probably don't want in your WordPress.org distribution
.babelrc
.deployignore
.distignore
.editorconfig
.eslintignore
.eslintrc
.git
.gitignore
.github
.gitlab-ci.yml
.travis.yml
.DS_Store
.*~
Thumbs.db
behat.yml
bitbucket-pipelines.yml
bin
.circleci/config.yml
composer.json
composer.lock
dependencies.yml
Gruntfile.js
package.json
package-lock.json
phpunit.xml
phpunit.xml.dist
multisite.xml
multisite.xml.dist
.phpcs.xml
phpcs.xml
.phpcs.xml.dist
phpcs.xml.dist
README.md
webpack.config.js
wp-cli.local.yml
yarn.lock
tests
vendor
node_modules
*.sql
*.tar.gz
*.zip

Une fois ce fichier en place, il suffit d’une commande pour générer un zip que vous pouvez uploader chez votre client. Placez-vous d’abord dans le dossier de votre extension ou thème.

$ wp dist-archive .
Success: Created my-plugin.0.1.0.zip

Cette commande a créé un zip dans le dossier wp-content/plugins. Ce zip peut être installé directement depuis l’interface d’administration de WordPress.

Cette commande va vraiment vous faire gagner un temps fou. Vous n’avez plus besoin de vous connecter via FTP et de transférer vos fichiers en prenant soin de ne pas pousser de fichiers inutiles. Générez le zip et installez-le comme n’importe quelle extension ou thème.

Gérer votre base de données

C’est une utilisation plus avancée, mais vous pouvez utiliser WP CLI pour gérer votre base de données : l’exporter, importer des données, l’optimiser et même la supprimer !

Vous pouvez aussi effectuer des requêtes directement mais honnêtement, un outil comme PHPmyAdmin est plus confortable pour cela.

Parmi les commandes utiles, nous avons :

$ wp db check
local.wp_postmeta OK
local.wp_posts OK
...
Success: Database checked.

$ wp db optimize
Success: Database optimized.

$ wp db export
Success: Exported to 'local-2025-02-14-aee932d.sql'.

$ wp db import local-2025-02-14-aee932d.sql
Success: Imported from 'local-2025-02-14-aee932d.sql'.

Toutes ces commandes lisent les informations du fichier wp-config.php pour se connecter à votre base de données.

wp db export permet de faire une sauvegarde de la base, et wp db import permet d’importer un fichier .sql, tout simplement. Si vous ne spécifiez pas de fichier à importer, WP CLI va chercher un fichier .sql du même nom que la base de données actuelle.

Quand vous migrez un site, vous aurez besoin d’effectuer des remplacements en base. Cela peut être fait simplement avec wp search-replace.

$ wp search-replace "//example.local" "//example.com" --dry-run
...
Success: 50 replacements to be made.
Sortie de la commande `wp search-replace`
La commande `wp search replace –dry-run` permet de lister les changements avant de les effectuer.

Ici, j’essaie de changer toutes les occurrences de “//example.local” en “//example.com”.

La commande va lister les changements à effectuer, mais ne va pas les effectuer, grâce au flag --dry-run.

Je vous recommande fortement de toujours utiliser ce flag avant d’effectuer vos changements. Cela vous permettra de voir quelles tables sont affectées et combien de lignes vont être affectées.

Autre astuce utile. WP CLI permet aussi de nettoyer vos transients en base de données. Plus généralement, il permet de manipuler chaque transient individuellement, mais c’est les nettoyer dont vous aurez le plus souvent besoin.

$ wp transient delete --expired
Success: No expired transients found.

$ wp transient delete --all
Success: 14 transients deleted from the database.

Utiliser WP CLI pour booster votre productivité

Dans ces trois articles, on a vu énormément de commandes différentes. Vous pouvez utiliser WP CLI pour tout ! Installer des extensions, mettre à jour votre installation, gérer vos publications, vos utilisateurs, etc…

Mais là où vous gagnerez le maximum de temps, ce sont sur les commandes pour développeurs que l’on a vues aujourd’hui.

  • wp scaffold plugin pour générer une extension
  • wp scaffold theme pour générer un thème
  • wp i18n make-pot pour générer votre POT
  • wp i18n make-json pour générer vos fichiers de traductions JavaScript
  • wp i18n dist-archive pour packager votre thème ou extension
  • wp db pour importer, exporter ou nettoyer votre base de données
  • wp search-replace pour effectuer des remplacements en base

Pour gagner un maximum de temps, je vous recommande fortement de vous créer des raccourcis. Cela peut-être des scripts bash ou des simples scripts npm.

Pour vous aider voici un exemple de scripts npm que je configure dans package.json.

"scripts": {
  "start": "sass --watch sass/style.scss:style.css sass/editor-style.scss:editor-style.css",
  "build": "npm run sass && npm run css && npm run terser && npm run make-pot && npm run archive",
  "sass": "sass sass/style.scss:style.css sass/editor-style.scss:editor-style.css",
  "css": "sass --style=compressed sass/style.scss:style.min.css sass/editor-style.scss:editor-style.min.css",
  "terser": "terser js/main.js -o js/main.min.js",
  "make-pot": "wp i18n make-pot . languages/kawi.pot",
  "archive": "wp dist-archive . kawi.zip"
}

Toutes ces commandes sont invokées tout simplement : npm run <command>, et chacune d’elle est responsable d’une tâche. Par exemple, npm run sass me permet de lancer le watcher Sass, npm run make-pot va me permettre de générer mon POT pour mon thème Kawi.

Quand j’ai fini de développer, un simple npm run build va régénérer mon CSS, le minifier, minifier mon JavaScript, créer mon POT et créer une archive de mon thème. Je n’ai plus qu’à uploader.

Si vous voulez en savoir plus sur comment j’utilise package.json, lisez Automatiser ses tâches de développement avec des scripts npm.

Utiliser WP CLI efficacement va vraiment vous faire gagner un temps précieux. Et nous n’avons que survolé toutes les commandes et options qu’il offre. Explorez ses possibilités et amusez-vous !

Apprenez à développer un Block Theme

Inscrivez-vous pour comprendre les Block Themes, prendre en main l'éditeur de site de WordPress, et développer votre premier Block Theme.