Code d'organisation dans le fichier functions.php de votre thème WordPress?

92

Plus je personnalise WordPress, plus je commence à me demander si je devrais organiser ce fichier ou le fractionner.

Plus précisément, si j’ai un ensemble de fonctions personnalisées qui s’appliquent uniquement à la zone administrative et à d’autres qui s’appliquent uniquement à mon site Web public, y at-il une raison pour éventuellement inclure toutes les fonctions administratives dans leur propre fichier ou les regrouper?

Est-ce que le fait de les séparer en fichiers séparés ou de les regrouper pourrait éventuellement accélérer un site Web WordPress ou WordPress / PHP ignore-t-il automatiquement les fonctions qui ont un préfixe is_admin?

Quelle est la meilleure façon de traiter un fichier de fonctions volumineuses (le mien a une longueur de 1370 lignes).

NetConstructor.com
la source

Réponses:

120

Si vous arrivez au point où le code de votre thème functions.phpcommence à vous submerger, je dirais certainement que vous êtes prêt à envisager de le scinder en plusieurs fichiers. J'ai tendance à le faire presque par seconde nature à ce stade.

Utilisez les fichiers d'inclusion dans le functions.phpfichier de votre thème

Je crée un sous-répertoire appelé "includes" dans mon répertoire de thèmes et segmente mon code en fichiers include organisés en fonction de ce qui a du sens pour moi à ce moment-là (ce qui signifie que je suis constamment en train de refactoriser et de déplacer le code au fur et à mesure que le site évolue.) mettre n'importe quel code réel dans functions.php; tout se passe dans les fichiers inclus; juste ma préférence.

Juste pour vous donner un exemple, voici mon installation de test que j'utilise pour tester mes réponses aux questions ici sur WordPress Answers. Chaque fois que je réponds à une question, je garde le code au cas où j'en aurais besoin à nouveau. Ce n'est pas exactement ce que vous ferez pour un site actif, mais cela montre la mécanique de la division du code:

<?php 
/*
 * functions.php
 * 
 */
require_once( __DIR__ . '/includes/null-meta-compare.php');
require_once( __DIR__ . '/includes/older-examples.php');
require_once( __DIR__ . '/includes/wp-admin-menu-classes.php');
require_once( __DIR__ . '/includes/admin-menu-function-examples.php');

// WA: Adding a Taxonomy Filter to Admin List for a Custom Post Type?
// http://wordpress.stackexchange.com/questions/578/
require_once( __DIR__ . '/includes/cpt-filtering-in-admin.php'); 
require_once( __DIR__ . '/includes/category-fields.php');
require_once( __DIR__ . '/includes/post-list-shortcode.php');
require_once( __DIR__ . '/includes/car-type-urls.php');
require_once( __DIR__ . '/includes/buffer-all.php');
require_once( __DIR__ . '/includes/get-page-selector.php');

// http://wordpress.stackexchange.com/questions/907/
require_once( __DIR__ . '/includes/top-5-posts-per-category.php'); 

// http://wordpress.stackexchange.com/questions/951/
require_once( __DIR__ . '/includes/alternate-category-metabox.php');  

// http://lists.automattic.com/pipermail/wp-hackers/2010-August/034384.html
require_once( __DIR__ . '/includes/remove-status.php');  

// http://wordpress.stackexchange.com/questions/1027/removing-the-your-backup-folder-might-be-visible-to-the-public-message-generate
require_once( __DIR__ . '/includes/301-redirects.php');  

Ou créer des plugins

Une autre option est de commencer à regrouper votre code par fonction et de créer vos propres plugins. Pour moi, je commence à coder dans le functions.phpfichier du thème et au moment où je complète le code, j'ai transféré la plupart de mon code dans des plugins.

Cependant, AUCUN gain de performance significatif de l'organisation du code PHP

D'autre part, structurer vos fichiers PHP concerne 99% de la création d'ordre et de maintenabilité et 1% de performances, si cela (l'organisation .jset les .cssfichiers appelés par le navigateur via HTTP est un cas complètement différent et a d'énormes implications en termes de performances.) Mais comment vous organisez votre code PHP sur le serveur n'a pas beaucoup d'importance du point de vue des performances.

Et l'organisation du code est la préférence personnelle

Enfin, l'organisation du code est une préférence personnelle. Certaines personnes détesteraient la façon dont j'organise le code, tout comme je le ferais peut-être aussi. Trouvez quelque chose que vous aimez et respectez-le, mais laissez votre stratégie évoluer avec le temps, à mesure que vous en apprendrez plus et en deviendrez plus à l'aise.

Mike Schinkel
la source
Bonne réponse, je viens juste d'arriver à ce point où j'ai besoin de diviser le fichier de fonctions. Quand pensez-vous qu'il est utile de passer de frunctions.php à un plugin. Vous avez dit dans votre réponse: au moment où je complète le code, j'ai transféré la plupart de mon code dans des plugins . Je ne comprends pas tout à fait cela. Qu'entendez-vous par épanoui?
Saif Bechan
5
+1 pour "ou créer des plugins". Plus spécifiquement, " plugins de fonctionnalité "
Ian Dunn
3
l'utilisation de chemins relatifs peut ne pas être fiable dans tous les types de paramètres, mais le chemin absolu doit toujours être utilisé
Mark Kaplun
2
@MarkKaplun - Vous avez absolument raison. Depuis que j'ai écrit cette réponse, j'ai appris cette leçon à la dure. Je vais mettre à jour ma réponse. Merci de l'avoir signalé.
MikeSchinkel
J'obtiens "Utilisation de constante DIR non définie - supposée" DIR "dans C: \ wamp \ www \ site \ wp-content \ themes \ mytheme \ functions.php" - PHP v5.6.25 et PHP v7.0.10 - Je ne peux pas Formatez correctement cette DIR en commentaire (underscoreunderscoreDIRunderscoreunderscore), mais cela fonctionne avec dirname (underscoreunderscoreFILEunderscoreunderscore)
Marko
50

Réponse tardive

Comment inclure vos fichiers correctement:

function wpse1403_bootstrap()
{
    // Here we load from our includes directory
    // This considers parent and child themes as well    
    locate_template( array( 'inc/foo.class.php' ), true, true );
}
add_action( 'after_setup_theme', 'wpse1403_bootstrap' );

La même chose fonctionne aussi dans les plugins.

Comment obtenir le bon chemin ou URi

Jetez également un coup d'œil aux fonctions de l'API du système de fichiers telles que:

  • home_url()
  • plugin_dir_url()
  • plugin_dir_path()
  • admin_url()
  • get_template_directory()
  • get_template_directory_uri()
  • get_stylesheet_directory()
  • get_stylesheet_directory_uri()
  • etc.

Comment réduire le nombre de include/require

Si vous devez récupérer tous les fichiers d'un répertoire, allez avec

foreach ( glob( 'path/to/folder/*.php' ) as $file )
    include $file;

Gardez à l'esprit que cela ignore les échecs (peut-être bons pour une utilisation en production) / les fichiers non chargeables.

Pour modifier ce comportement, vous pouvez utiliser une configuration différente au cours du développement:

$files = ( defined( 'WP_DEBUG' ) AND WP_DEBUG )
    ? glob( 'path/to/folder/*.php', GLOB_ERR )
    : glob( 'path/to/folder/*.php' )

foreach ( $files as $file )
    include $file;

Edit: approche OOP / SPL

Alors que je revenais juste et que je voyais que cette réponse recevait de plus en plus de votes positifs, j'ai pensé montrer comment je le fais aujourd'hui - dans un monde PHP 5.3+. L'exemple suivant charge tous les fichiers d'un sous-dossier de thèmes nommé src/. C'est là que mes bibliothèques gèrent certaines tâches telles que les menus, les images, etc. Vous n'avez même pas à vous soucier du nom car chaque fichier est chargé. Si vous avez d'autres sous-dossiers dans ce répertoire, ils sont ignorés.

Le \FilesystemIteratorest du PHP de supercedor sur \DirectoryIterator. Les deux font partie du PHP SPL. Bien que PHP 5.2 ait permis de désactiver l'extension SPL intégrée (moins de 1% de toutes les installations l'ont fait), le SPL fait maintenant partie du noyau PHP.

<?php

namespace Theme;

$files = new \FilesystemIterator( __DIR__.'/src', \FilesystemIterator::SKIP_DOTS );
foreach ( $files as $file )
{
    /** @noinspection PhpIncludeInspection */
    ! $files->isDir() and include $files->getRealPath();
}

Auparavant, alors que je soutenais encore PHP 5.2.x, j’utilisais la solution suivante: A \FilterIteratordans le src/Filtersrépertoire pour récupérer uniquement les fichiers (et non les pointeurs de dossiers) et a \DirectoryIteratorpour effectuer la mise en boucle et le chargement.

namespace Theme;

use Theme\Filters\IncludesFilter;

$files = new IncludesFilter( new \DirectoryIterator( __DIR__.'/src' ) );
foreach ( $files as $file )
{
    include_once $files->current()->getRealPath();
}

Le \FilterIteratorétait aussi simple que cela:

<?php

namespace Theme\Filters;

class IncludesFilter extends \FilterIterator
{
    public function accept()
    {
        return
            ! $this->current()->isDot()
            and $this->current()->isFile()
            and $this->current()->isReadable();
    }
}

En plus de PHP 5.2 mort / EOL maintenant (et 5.3 également), il y a plus de code et un fichier de plus dans le jeu, il n'y a donc aucune raison de continuer avec ce dernier et de supporter PHP 5.2.x.

Résumé

Un article encore plus détaillé peut être trouvé ici sur WPKrauts .

EDIT La manière évidente consiste à utiliser le namespacecode d, préparé pour le chargement automatique du PSR-4, en plaçant tout dans le répertoire approprié déjà défini via l’espace de nom. Ensuite, utilisez simplement Composer et a composer.jsonpour gérer vos dépendances et laissez-le construire automatiquement votre autochargeur PHP (qui importe automatiquement un fichier en appelant simplement use \<namespace>\ClassName). C'est le standard de facto dans le monde PHP, le moyen le plus simple et encore plus pré-automatisé et simplifié par WP Starter .

kaiser
la source
5

Pour ce qui est de le casser, dans ma plaque de chaudière, j'utilise une fonction personnalisée pour rechercher un dossier appelé fonctions dans le répertoire theme. S'il n'est pas présent, il le crée. Ensuite, crée un tableau de tous les fichiers .php trouvés dans ce dossier (le cas échéant) et exécute un include (); sur chacun d'eux.

Ainsi, chaque fois que je dois écrire de nouvelles fonctionnalités, j'ajoute simplement un fichier PHP au dossier des fonctions, sans avoir à vous soucier de le coder dans le site.

<?php
/* 
FUNCTIONS for automatically including php documents from the functions folder.
*/
//if running on php4, make a scandir functions
if (!function_exists('scandir')) {
  function scandir($directory, $sorting_order = 0) {
    $dh = opendir($directory);
    while (false !== ($filename = readdir($dh))) {
      $files[] = $filename;
    }
    if ($sorting_order == 0) {
      sort($files);
    } else {
      rsort($files);
    }
    return ($files);
  }
}
/*
* this function returns the path to the funtions folder.
* If the folder does not exist, it creates it.
*/
function get_function_directory_extension($template_url = FALSE) {
  //get template url if not passed
  if (!$template_url)$template_url = get_bloginfo('template_directory');


  //replace slashes with dashes for explode
  $template_url_no_slash = str_replace('/', '.', $template_url);

  //create array from URL
  $template_url_array = explode('.', $template_url_no_slash);

  //--splice array

  //Calculate offset(we only need the last three levels)
  //We need to do this to get the proper directory, not the one passed by the server, as scandir doesn't work when aliases get involved.
  $offset = count($template_url_array) - 3;

  //splice array, only keeping back to the root WP install folder (where wp-config.php lives, where the front end runs from)
  $template_url_array = array_splice($template_url_array, $offset, 3);
  //put back togther as string
  $template_url_return_string = implode('/', $template_url_array);
  fb::log($template_url_return_string, 'Template'); //firephp

  //creates current working directory with template extention and functions directory    
  //if admin, change out of admin folder before storing working dir, then change back again.
  if (is_admin()) {
    $admin_directory = getcwd();
    chdir("..");
    $current_working_directory = getcwd();
    chdir($admin_directory);
  } else {
    $current_working_directory = getcwd();
  }
  fb::log($current_working_directory, 'Directory'); //firephp

  //alternate method is chdir method doesn't work on your server (some windows servers might not like it)
  //if (is_admin()) $current_working_directory = str_replace('/wp-admin','',$current_working_directory);

  $function_folder = $current_working_directory . '/' . $template_url_return_string . '/functions';


  if (!is_dir($function_folder)) mkdir($function_folder); //make folder, if it doesn't already exist (lazy, but useful....ish)
  //return path
  return $function_folder;

}

//removed array elements that do not have extension .php
function only_php_files($scan_dir_list = false) {
  if (!$scan_dir_list || !is_array($scan_dir_list)) return false; //if element not given, or not array, return out of function.
  foreach ($scan_dir_list as $key => $value) {
    if (!strpos($value, '.php')) {

      unset($scan_dir_list[$key]);
    }
  }
  return $scan_dir_list;
}
//runs the functions to create function folder, select it,
//scan it, filter only PHP docs then include them in functions

add_action('wp_head', fetch_php_docs_from_functions_folder(), 1);
function fetch_php_docs_from_functions_folder() {

  //get function directory
  $functions_dir = get_function_directory_extension();
  //scan directory, and strip non-php docs
  $all_php_docs = only_php_files(scandir($functions_dir));

  //include php docs
  if (is_array($all_php_docs)) {
    foreach ($all_php_docs as $include) {
      include($functions_dir . '/' . $include);
    }
  }

}
Fuzz doux
la source
5
@ mildfuzz : bonne astuce. Personnellement, je ne l'emploierais pas pour le code de production, car il charge pour chaque page ce que nous pourrions facilement faire une fois lorsque nous lançons le site. De plus, j'ajouterais une sorte d'omission de fichiers, par exemple en ne chargeant rien qui commence par un trait de soulignement pour pouvoir toujours stocker les travaux en cours dans le répertoire du thème. Sinon, sympa!
MikeSchinkel
J'adore cette idée, mais je conviens que cela pourrait éventuellement entraîner un chargement inutile pour chaque demande. Avez-vous une idée de la possibilité de mettre en cache automatiquement le fichier final functions.php avec un type de mise à jour si / lorsque de nouveaux fichiers sont ajoutés ou à un intervalle de temps précis?
NetConstructor.com
Bien, mais cela conduit à des inflexibilités. Que se passe-t-il si un attaquant parvient à y déposer son code? Et si la commande d'inclus est importante?
Tom J Nowell
1
@ MikeSchinkel J'appelle juste mes fichiers de travail foo._php, puis laisse tomber le _php quand je veux qu'il s'exécute.
Doux Fuzz
@NetConstructor: Serait intéressé par une solution aussi.
Kaiser
5

J'aime utiliser une fonction pour les fichiers dans un dossier. Cette approche facilite l'ajout de nouvelles fonctionnalités lors de l'ajout de nouveaux fichiers. Mais j'écris toujours en classe ou avec des espaces de noms - donnez-lui plus de contrôle sur l'espace de noms des fonctions, méthodes, etc.

Ci-dessous un petit exemple; ut également utilisation avec l'accord sur la classe * .php

public function __construct() {

    $this->load_classes();
}

/**
 * Returns array of features, also
 * Scans the plugins subfolder "/classes"
 *
 * @since   0.1
 * @return  void
 */
protected function load_classes() {

    // load all files with the pattern class-*.php from the directory classes
    foreach( glob( dirname( __FILE__ ) . '/classes/class-*.php' ) as $class )
        require_once $class;

}

Dans les thèmes, j'utilise souvent un autre scénario. Je définis la fonction du fichier externel dans un ID de support, voir l'exemple. C’est utile si je vais facilement désactiver la fonction du fichier externel. J'utilise la fonction principale de WP require_if_theme_supports()et il ne charge que si l'ID de support était actif. Dans l'exemple suivant, j'ai défini cet ID pris en charge dans la ligne avant de charger le fichier.

    /**
     * Add support for Theme Customizer
     * 
     * @since  09/06/2012
     */
    add_theme_support( 'documentation_customizer', array( 'all' ) );
    // Include the theme customizer for options of theme options, if theme supported
    require_if_theme_supports( 
        'documentation_customizer',
        get_template_directory() . '/inc/theme-customize.php'
    );

Vous pouvez en voir plus dans le repo de ce thème .

Bueltge
la source
4

Je gère un site avec environ 50 types de page personnalisés uniques dans plusieurs langues sur une installation réseau. Avec une tonne de plugins.

Nous avons été obligés de tout séparer à un moment donné. Un fichier de fonctions avec 20-30k lignes de code n'est pas drôle du tout.

Nous avons décidé de complètement refactoriser tout le code afin de mieux gérer la base de code. La structure de thème wordpress par défaut convient aux petits sites, mais pas aux plus grands.

Notre nouveau functions.php ne contient que ce qui est nécessaire pour démarrer le site, mais rien qui appartient à une page spécifique.

La disposition de thème que nous utilisons maintenant est similaire au modèle de conception MCV, mais dans un style de codage procédural.

Par exemple notre page membre:

page-member.php . Responsable de l'initialisation de la page. Appeler les fonctions ajax correctes ou similaires. Pourrait être équivalent à la partie contrôleur dans le style MCV.

functions-member.php . Contient toutes les fonctions liées à cette page. Ceci est également inclus dans plusieurs autres pages qui ont besoin de fonctions pour nos membres.

content-member.php . Prépare les données pour HTML Peut être équivalent au modèle dans MCV.

layout-member.php . La partie HTML.

Après ces modifications, le temps de développement a facilement diminué de 50% et le responsable du produit a maintenant du mal à nous attribuer de nouvelles tâches. :)

Patrik Grinsvall
la source
7
Pour rendre cela plus utile, vous pourriez montrer comment ce motif MVC fonctionne réellement.
Kaiser
Je serais également curieux de voir un exemple de votre approche, de préférence avec quelques détails / diverses situations. L’approche semble très intéressante. Avez-vous comparé la charge / les performances du serveur à la méthodologie standard utilisée par d’autres? fournissez un exemple de github si possible.
NetConstructor.com 10/10/12
3

A partir du fichier functions.php des thèmes enfants:

    require_once( get_stylesheet_directory() . '/inc/custom.php' );
Brad Dalton
la source
0

Dans functions.php, un moyen plus élégant d’appeler un fichier requis serait:

require_once Locate_template ('/ inc / functions / shortcodes.php');

Idées impératives
la source
4
locate_template()a un troisième paramètre…
fuxia
0

J'ai combiné les réponses de @kaiser et de @mikeschinkel .

Toutes mes personnalisations pour mon thème sont placées dans un /includesdossier et dans ce dossier, tout est divisé en sous-dossiers.

Je veux seulement que /includes/adminses sous-contenus soient inclus quandtrue === is_admin()

Si un dossier est exclu en iterator_check_traversal_callbackretournant falsealors ses sous-répertoires ne seront pas itérés (ou passés à iterator_check_traversal_callback)

/**
 *  Require all customizations under /includes
 */
$includes_import_root = 
    new \RecursiveDirectoryIterator( __DIR__ . '/includes', \FilesystemIterator::SKIP_DOTS );

function iterator_check_traversal_callback( $current, $key, $iterator ) {
    $file_name = $current->getFilename();

    // Only include *.php files
    if ( ! $current->isDir() ) {
        return preg_match( '/^.+\.php$/i', $file_name );
    }

    // Don't include the /includes/admin folder when on the public site
    return 'admin' === $file_name
        ? is_admin()
        : true;
}

$iterator_filter = new \RecursiveCallbackFilterIterator(
    $includes_import_root, 'iterator_check_traversal_callback'
);

foreach ( new \RecursiveIteratorIterator( $iterator_filter ) as $file ) {
    include $file->getRealPath();
}
Seangwright
la source