¿Organiza el código en el archivo functions.php de tu tema de WordPress?

92

Cuanta más personalización hago a WordPress, más empiezo a pensar si debería organizar este archivo o dividirlo.

Más específicamente, si tengo un montón de funciones personalizadas que solo se aplican al área de administración y otras que solo se aplican a mi sitio web público, ¿hay alguna razón para incluir todas las funciones de administrador dentro de su propio archivo o agruparlas?

¿Dividirlos en archivos separados o agruparlos podría acelerar un sitio web de WordPress o WordPress / PHP omite automáticamente las funciones que tienen un prefijo de código is_admin?

¿Cuál es la mejor manera de lidiar con un archivo de funciones grandes (el mío tiene 1370 líneas de largo)?

NetConstructor.com
fuente

Respuestas:

120

Si está llegando al punto en que el código de su tema functions.phpestá comenzando a abrumarlo, definitivamente diría que está listo para considerar dividirlo en varios archivos. Tiendo a hacer eso casi por segunda naturaleza en este punto.

Utilice Incluir archivos en el functions.phparchivo de su tema

Creo un subdirectorio llamado "incluye" en mi directorio de temas y segmento mi código en archivos de inclusión organizados por lo que tiene sentido para mí en ese momento (lo que significa que constantemente refactorizo ​​y muevo el código a medida que el sitio evoluciona). También rara vez poner cualquier código real en functions.php; todo va en los archivos de inclusión; solo mi preferencia

Solo para darle un ejemplo, aquí está mi instalación de prueba que uso para probar mis respuestas a las preguntas aquí en WordPress Answers. Cada vez que respondo una pregunta, mantengo el código en caso de que lo necesite nuevamente. Esto no es exactamente lo que harás para un sitio en vivo, pero muestra la mecánica de dividir el código:

<?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');  

O crear complementos

Otra opción es comenzar a agrupar su código por función y crear sus propios complementos. Para mí, empiezo a codificar en el functions.phparchivo del tema y cuando llego al código, he movido la mayor parte de mi código a complementos.

Sin embargo, NO hay ganancia de rendimiento significativa de la organización del código PHP

Por otro lado, estructurar sus archivos PHP es el 99% sobre la creación de orden y mantenimiento y el 1% sobre el rendimiento, si eso (la organización .jsy los .cssarchivos llamados por el navegador a través de HTTP es un caso completamente diferente y tiene enormes implicaciones de rendimiento). Pero cómo se organiza su código PHP en el servidor prácticamente no importa desde una perspectiva de rendimiento.

Y la organización del código es preferencia personal

Y por último, pero no menos importante, la organización del código es la preferencia personal. Algunas personas odiarían cómo organizo el código así como yo también odiaría cómo lo hacen. Encuentre algo que le guste y manténgalo, pero permita que su estrategia evolucione con el tiempo a medida que aprenda más y se sienta más cómodo con ella.

MikeSchinkel
fuente
Buena respuesta, acabo de llegar a este punto donde necesito dividir el archivo de funciones. ¿Cuándo crees que es útil pasar de frunctions.php a un complemento? Dijiste en tu respuesta: cuando llegué al código, moví la mayor parte de mi código a complementos . No entiendo eso completamente, ¿qué quieres decir con desarrollado?
Saif Bechan
55
+1 para "o crear complementos". Más específicamente, " complementos de funcionalidad "
Ian Dunn
3
el uso de rutas relativas puede no ser confiable en todo tipo de configuraciones, la ruta absoluta siempre debe usarse en su lugar
Mark Kaplun
2
@ MarkKaplun: tienes toda la razón. Desde que escribí esta respuesta, aprendí esa lección por las malas. Voy a actualizar mi respuesta. Gracias por señalar esto.
MikeSchinkel
Obtengo "Uso de DIR constante indefinido - asumido ' DIR ' en C: \ wamp \ www \ site \ wp-content \ themes \ mytheme \ functions.php" - PHP v5.6.25 y PHP v7.0.10 - No puedo formatee correctamente este DIR en el comentario (underscoreunderscoreDIRunderscoreunderscore), pero funciona con dirname (underscoreunderscoreFILEunderscoreunderscore)
Marko
50

Respuesta tardía

Cómo incluir sus archivos de la manera correcta:

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' );

Lo mismo funciona en complementos también.

Cómo obtener el camino correcto o URi

También eche un vistazo a las funciones API del sistema de archivos como:

  • 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.

Cómo reducir el número de include/require

Si necesita recuperar todos los archivos de un directorio, vaya con

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

Tenga en cuenta que esto ignora las fallas (quizás buenas para uso de producción) / archivos no cargables.

Para alterar este comportamiento, es posible que desee utilizar una configuración diferente durante el desarrollo:

$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;

Editar: enfoque OOP / SPL

Cuando acabo de regresar y vi que esta respuesta está recibiendo cada vez más votos positivos, pensé que podría mostrar cómo lo estoy haciendo hoy en día, en un mundo PHP 5.3+. El siguiente ejemplo carga todos los archivos de una subcarpeta de temas llamada src/. Aquí es donde tengo mis bibliotecas que manejan ciertas tareas como menús, imágenes, etc. Ni siquiera tiene que preocuparse por el nombre ya que cada archivo se carga. Si tiene otras subcarpetas en este directorio, se ignorarán.

El \FilesystemIteratores el PHP 5.3+ supercedor sobre el \DirectoryIterator. Ambos son parte del PHP SPL. Si bien PHP 5.2 hizo posible desactivar la extensión SPL integrada (menos del 1% de todas las instalaciones lo hicieron), el SPL ahora es parte del núcleo de PHP.

<?php

namespace Theme;

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

Anteriormente, aunque todavía soportaba PHP 5.2.x, usaba la siguiente solución: A \FilterIteratoren el src/Filtersdirectorio para recuperar solo archivos (y no punteros de punto de carpetas) y a \DirectoryIteratorpara hacer el bucle y la carga.

namespace Theme;

use Theme\Filters\IncludesFilter;

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

El \FilterIteratorfue tan fácil como eso:

<?php

namespace Theme\Filters;

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

Además de que PHP 5.2 está muerto / EOL por ahora (y 5.3 también), existe el hecho de que es más código y un archivo más en el juego, por lo que no hay razón para seguir con el último y admitir PHP 5.2.x.

Resumió

Un artículo aún más detallado se puede encontrar aquí en WPKrauts .

EDITAR La forma obviamente correcta es usar el namespacecódigo d, preparado para la carga automática de PSR-4 poniendo todo en el directorio apropiado que ya está definido a través del espacio de nombres. Luego, use Composer y a composer.jsonpara administrar sus dependencias y deje que cree automáticamente su autocargador PHP (que importa automáticamente un archivo simplemente llamando use \<namespace>\ClassName). Ese es el estándar de facto en el mundo PHP, el camino más fácil y aún más pre-automatizado y simplificado por WP Starter .

emperador
fuente
5

en términos de dividirlo, en mi placa de caldera uso una función personalizada para buscar una carpeta llamada funciones en el directorio de temas, si no está allí, la crea. Luego, crea una matriz de todos los archivos .php que encuentra en esa carpeta (si existe) y ejecuta un include (); en cada uno de ellos

De esa manera, cada vez que necesito escribir alguna funcionalidad nueva, solo agrego un archivo PHP a la carpeta de funciones y no tengo que preocuparme de codificarlo en el sitio.

<?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 suave
fuente
55
@mildfuzz : Buen truco. Personalmente, no lo usaría para el código de producción porque hace para cada carga de página lo que podríamos hacer fácilmente una vez que lancemos el sitio. Además, agregaría de alguna manera para omitir archivos, como no cargar nada que comience con un guión bajo para poder seguir almacenando trabajos en progreso en el directorio de temas. De lo contrario, bien!
MikeSchinkel
Me encanta la idea, pero estoy de acuerdo en que esto podría conducir a una carga innecesaria para cada solicitud. ¿Alguna idea de si habría una manera simple de generar el archivo functions.php final en caché automáticamente con algún tipo de actualización si / cuando se agregan nuevos archivos o en un intervalo de tiempo específico?
NetConstructor.com
Agradable, pero conduce a inflexibilidades, ¿qué sucede también si un atacante logra colocar su código allí? ¿Y qué pasa si es importante ordenar?
Tom J Nowell
1
@ MikeSchinkel Acabo de llamar a mis archivos de trabajo foo._php, luego dejo caer el _php cuando quiero que se ejecute.
Mild Fuzz
@NetConstructor: También estaría interesado en alguna solución.
Kaiser
5

Me gusta usar una función para los archivos dentro de una carpeta. Este enfoque facilita la adición de nuevas funciones al agregar nuevos archivos. Pero siempre escribo en clase o con espacios de nombres: le doy más control sobre el espacio de nombres de funciones, métodos, etc.

Debajo de un pequeño ejemplo; ut también uso con el acuerdo sobre la clase * .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;

}

En Temas uso a menudo otro escenario. Defino la función del archivo externel en una ID de soporte, vea el ejemplo. Eso es útil si desactivé fácilmente el feture del archivo externel. Uso la función principal de WP require_if_theme_supports()y solo carga, si la ID de soporte estaba activa. En el siguiente ejemplo, definí esta ID compatible en la línea antes de cargar el archivo.

    /**
     * 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'
    );

Puedes ver más de esto en el repositorio de este tema .

bueltge
fuente
4

Administro un sitio con aproximadamente 50 tipos de páginas personalizadas únicas en varios idiomas diferentes a través de una instalación de red. Junto con una TONELADA de complementos.

Nos obligaron a dividirlo todo en algún momento. Un archivo de funciones con 20-30k líneas de código no es divertido en absoluto.

Decidimos refactorizar completamente todo el código para administrar mejor la base de código. La estructura predeterminada del tema de WordPress es buena para sitios pequeños, pero no para sitios más grandes.

Nuestras nuevas funciones.php solo contienen lo necesario para iniciar el sitio, pero nada que pertenezca a una página específica.

El diseño del tema que usamos ahora es similar al patrón de diseño MCV, pero en un estilo de codificación de procedimiento.

Por ejemplo nuestra página de miembro:

page-member.php . Responsable de inicializar la página. Llamando a las funciones correctas de ajax o similar. Podría ser equivalente a la parte del controlador en el estilo MCV.

funciones-member.php . Contiene todas las funciones relacionadas con esta página. Esto también se incluye en varias otras páginas que necesitan funciones para nuestros miembros.

content-member.php . Prepara los datos para HTML. Puede ser equivalente al Modelo en MCV.

layout-member.php . La parte HTML.

Después de que hicimos estos cambios, el tiempo de desarrollo se ha reducido fácilmente en un 50% y ahora el propietario del producto tiene problemas para darnos nuevas tareas. :)

Patrik Grinsvall
fuente
77
Para que esto sea más útil, puede considerar mostrar cómo funciona realmente este patrón MVC.
kaiser
También me gustaría ver un ejemplo de su enfoque, preferiblemente con algunos detalles / diversas situaciones. El enfoque suena muy interesante. ¿Ha comparado la carga / rendimiento del servidor con la metodología estándar que usan otros? proporcione un ejemplo de github si es posible.
NetConstructor.com
3

Desde el archivo de temas infantiles functions.php:

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

En functions.php, una forma más elegante de llamar a un archivo requerido sería:

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

Ideas imperativas
fuente
44
locate_template()tiene un tercer parámetro ...
fuxia
0

Combiné @kaiser 's y @mikeschinkel respuestas' s.

Tengo todas mis personalizaciones para mi tema en una /includescarpeta y dentro de esa carpeta tengo todo dividido en subcarpetas.

Solo quiero /includes/adminque se incluyan sus sub-contenidos cuandotrue === is_admin()

Si se excluye una carpeta iterator_check_traversal_callbackal regresar, falseentonces sus subdirectorios no se iterarán (o pasarán a 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
fuente