Organizzare il Codice nel File functions.php del Tuo Tema WordPress

6 set 2010, 11:38:45
Visualizzazioni: 79.4K
Voti: 109

Più personalizzazioni faccio in WordPress, più inizio a chiedermi se dovrei organizzare questo file o suddividerlo.

Più specificamente, se ho un gruppo di funzioni personalizzate che si applicano solo all'area amministrativa e altre che si applicano solo al sito web pubblico, c'è qualche motivo per includere tutte le funzioni amministrative in un proprio file o raggrupparle insieme?

Dividere le funzioni in file separati o raggrupparle insieme potrebbe velocizzare un sito WordPress oppure WordPress/PHP salta automaticamente le funzioni che hanno un prefisso is_admin nel codice?

Qual è il modo migliore per gestire un file functions di grandi dimensioni (il mio è lungo 1370 righe).

0
Tutte le risposte alla domanda 8
7
142

Se stai arrivando al punto in cui il codice nel file functions.php del tuo tema sta iniziando a sopraffarti, direi senza dubbio che sei pronto a considerare di suddividerlo in più file. Ormai lo faccio quasi per istinto a questo punto.

Usa File di Include nel File functions.php del Tuo Tema

Creo una sottodirectory chiamata "includes" sotto la directory del mio tema e suddivido il codice in file di include organizzati in base a ciò che ha senso per me in quel momento (il che significa che ristrutturo e sposto continuamente il codice man mano che il sito evolve). Inoltre, raramente inserisco codice reale in functions.php; tutto va nei file di include; è semplicemente una mia preferenza.

Per darti un esempio, ecco la mia installazione di test che uso per verificare le mie risposte alle domande qui su WordPress Answers. Ogni volta che rispondo a una domanda, conservo il codice nel caso in cui ne avessi bisogno di nuovo. Non è esattamente ciò che farai per un sito live, ma mostra la meccanica della suddivisione del codice:

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

Oppure Crea Plugin

Un'altra opzione è iniziare a raggruppare il codice per funzionalità e creare i tuoi plugin. Personalmente, inizio a scrivere codice nel file functions.php del tema e, quando il codice è completo, ho già spostato la maggior parte del codice nei plugin.

Tuttavia, Nessun Vantaggio Significativo in Termini di Prestazioni dall'Organizzazione del Codice PHP

D'altra parte, strutturare i tuoi file PHP è per il 99% una questione di ordine e manutenibilità e per l'1% una questione di prestazioni, se non meno (organizzare i file .js e .css chiamati dal browser tramite HTTP è un caso completamente diverso e ha implicazioni enormi sulle prestazioni). Ma come organizzi il tuo codice PHP sul server non ha praticamente alcun impatto dal punto di vista delle prestazioni.

E l'Organizzazione del Codice è una Preferenza Personale

E, ultimo ma non meno importante, l'organizzazione del codice è una preferenza personale. Alcune persone potrebbero odiare il modo in cui organizzo il codice, proprio come potrei odiare il modo in cui lo fanno loro. Trova qualcosa che ti piace e attieniti a esso, ma consenti alla tua strategia di evolversi nel tempo man mano che impari di più e ti senti più a tuo agio.

6 set 2010 13:38:17
Commenti

Bella risposta, sono appena arrivato a questo punto in cui ho bisogno di dividere il file delle funzioni. Quando pensi che sia utile passare da functions.php a un plugin. Hai detto nella tua risposta: quando riesco a sviluppare completamente il codice, sposto la maggior parte del mio codice nei plugin. Non ho capito bene questa parte, cosa intendi con "sviluppare completamente".

Saif Bechan Saif Bechan
16 ago 2011 10:02:34

+1 per "o creare plugin". Più specificamente, "plugin di funzionalità"

Ian Dunn Ian Dunn
19 apr 2012 00:29:49

usare percorsi relativi potrebbe non essere affidabile in tutti i tipi di configurazioni, dovrebbe sempre essere usato invece il percorso assoluto

Mark Kaplun Mark Kaplun
22 set 2016 18:27:10

@MarkKaplun - Hai assolutamente ragione. Da quando ho scritto questa risposta ho imparato questa lezione a mie spese. Aggiornerò la mia risposta. Grazie per averlo fatto notare.

MikeSchinkel MikeSchinkel
23 set 2016 05:00:40

Ricevo "Use of undefined constant DIR - assumed 'DIR' in C:\wamp\www\site\wp-content\themes\mytheme\functions.php" - PHP v5.6.25 e PHP v7.0.10 - Non riesco a formattare correttamente questa DIR nel commento (underscoreunderscoreDIRunderscoreunderscore), ma funziona con dirname(underscoreunderscoreFILEunderscoreunderscore)

Marko Marko
1 nov 2016 10:08:45

Attenzione: devi usare __DIR__ invece di __DIR___, altrimenti riceverai un Errore Interno del Server (500).

robro robro
13 feb 2017 16:01:46

@robro - Ah, grazie per averlo notato! (Maledetti errori di battitura. Grrr...) :-)

MikeSchinkel MikeSchinkel
14 feb 2017 00:47:34
Mostra i restanti 2 commenti
0
58

Risposta tardiva

Come includere i tuoi file nel modo giusto:

function wpse1403_bootstrap()
{
    // Qui carichiamo dalla nostra directory includes
    // Questo considera sia i temi genitori che i temi figli    
    locate_template( array( 'inc/foo.class.php' ), true, true );
}
add_action( 'after_setup_theme', 'wpse1403_bootstrap' );

Lo stesso funziona anche nei plugin.

Come ottenere il percorso o l'URI corretto

Dai anche un'occhiata alle funzioni dell'API del filesystem come:

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

Come ridurre il numero di include/require

Se hai bisogno di recuperare tutti i file da una directory usa

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

Tieni presente che questo ignora i fallimenti (forse buono per l'uso in produzione)/file non caricabili.

Per modificare questo comportamento potresti voler usare una configurazione diversa durante lo sviluppo:

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

Modifica: approccio OOP/SPL

Dato che sono tornato e ho visto che questa risposta sta ricevendo sempre più voti, ho pensato di mostrare come lo faccio oggi - in un mondo PHP 5.3+. Il seguente esempio carica tutti i file da una sottocartella del tema chiamata src/. Qui è dove ho le mie librerie che gestiscono determinati compiti come menu, immagini, ecc. Non devi nemmeno preoccuparti del nome poiché ogni singolo file viene caricato. Se hai altre sottocartelle in questa directory, vengono ignorate.

Il \FilesystemIterator è il successore di PHP 5.3+ rispetto al \DirectoryIterator. Entrambi fanno parte della SPL di PHP. Mentre PHP 5.2 permetteva di disattivare l'estensione SPL integrata (meno dell'1% delle installazioni lo faceva), la SPL ora fa parte del core di PHP.

<?php

namespace Theme;

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

In precedenza, mentre supportavo ancora PHP 5.2.x, usavo la seguente soluzione: un \FilterIterator nella directory src/Filters per recuperare solo i file (e non i puntatori delle cartelle) e un \DirectoryIterator per fare il ciclo e il caricamento.

namespace Theme;

use Theme\Filters\IncludesFilter;

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

Il \FilterIterator era semplice così:

<?php

namespace Theme\Filters;

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

Oltre al fatto che PHP 5.2 è morto/EOL da tempo (e anche 5.3), c'è il fatto che è più codice e un file in più in gioco, quindi non c'è motivo di usare il secondo e supportare PHP 5.2.x.

Riassunto

MODIFICA Il modo ovviamente corretto è usare codice con namespace, preparato per l'autoloading PSR-4 mettendo tutto nella directory appropriata già definita dal namespace. Poi basta usare Composer e un composer.json per gestire le tue dipendenze e lasciare che costruisca automaticamente il tuo autoloader PHP (che importa automaticamente un file semplicemente chiamando use \<namespace>\ClassName). Questo è lo standard de-facto nel mondo PHP, il modo più semplice per procedere e ancora più pre-automatizzato e semplificato da WP Starter.

13 ott 2012 16:53:12
0

Mi piace utilizzare una funzione per i file all'interno di una cartella. Questo approccio rende semplice aggiungere nuove funzionalità quando si aggiungono nuovi file. Ma scrivo sempre in classi o con namespace - questo dà più controllo sul Namespace di funzioni, metodi ecc.

Di seguito un piccolo esempio; mostra anche l'utilizzo con l'accordo riguardo ai file class*.php

public function __construct() {

    $this->load_classes();
}

/**
 * Restituisce un array di funzionalità,
 * Scansiona la sottocartella "/classes" del plugin
 *
 * @since   0.1
 * @return  void
 */
protected function load_classes() {

    // carica tutti i file con il pattern class-*.php dalla directory classes
    foreach( glob( dirname( __FILE__ ) . '/classes/class-*.php' ) as $class )
        require_once $class;

}

Nei temi uso spesso un altro scenario. Definisco la funzione del file esterno in un ID di supporto, vedi l'esempio. Questo è utile se voglio disattivare facilmente la funzionalità del file esterno. Uso la funzione core di WP require_if_theme_supports() che carica solo se l'ID di supporto è attivo. Nell'esempio seguente ho definito questo ID di supporto nella riga prima del caricamento del file.

    /**
     * Aggiungi supporto per il Personalizzatore del Tema
     * 
     * @since  09/06/2012
     */
    add_theme_support( 'documentation_customizer', array( 'all' ) );
    // Includi il personalizzatore del tema per le opzioni del tema, se supportato
    require_if_theme_supports( 
        'documentation_customizer',
        get_template_directory() . '/inc/theme-customize.php'
    );

Puoi vedere di più su questo nel repository di questo tema.

4 ott 2012 12:16:58
9

Per quanto riguarda la suddivisione, nel mio boilerplate utilizzo una funzione personalizzata per cercare una cartella chiamata "functions" nella directory del tema. Se non è presente, la crea. Poi crea un array di tutti i file .php che trova in quella cartella (se presenti) ed esegue un include(); per ciascuno di essi.

In questo modo, ogni volta che devo scrivere una nuova funzionalità, aggiungo semplicemente un file PHP alla cartella functions, senza dovermi preoccupare di integrarlo manualmente nel sito.

<?php
/* 
FUNZIONI per includere automaticamente i documenti PHP dalla cartella functions.
*/
//se si utilizza php4, crea una funzione scandir
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);
  }
}
/*
* Questa funzione restituisce il percorso della cartella functions.
* Se la cartella non esiste, la crea.
*/
function get_function_directory_extension($template_url = FALSE) {
  //ottieni l'URL del template se non è passato
  if (!$template_url)$template_url = get_bloginfo('template_directory');


  //sostituisce gli slash con trattini per l'explode
  $template_url_no_slash = str_replace('/', '.', $template_url);

  //crea un array dall'URL
  $template_url_array = explode('.', $template_url_no_slash);

  //--scompone l'array

  //Calcola l'offset (ci servono solo gli ultimi tre livelli)
  //Necessario per ottenere la directory corretta, non quella passata dal server, poiché scandir non funziona con gli alias.
  $offset = count($template_url_array) - 3;

  //scompone l'array, mantenendo solo fino alla cartella radice di WP (dove si trova wp-config.php, da cui viene eseguito il front-end)
  $template_url_array = array_splice($template_url_array, $offset, 3);
  //ricompone in stringa
  $template_url_return_string = implode('/', $template_url_array);
  fb::log($template_url_return_string, 'Template'); //firephp

  //crea la directory di lavoro corrente con l'estensione del template e la directory functions    
  //se in admin, cambia dalla cartella admin prima di salvare la directory di lavoro, poi torna indietro.
  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

  //metodo alternativo se chdir non funziona sul tuo server (alcuni server Windows potrebbero non supportarlo)
  //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); //crea la cartella se non esiste già (pigro, ma utile... più o meno)
  //restituisce il percorso
  return $function_folder;

}

//rimuove gli elementi dell'array che non hanno estensione .php
function only_php_files($scan_dir_list = false) {
  if (!$scan_dir_list || !is_array($scan_dir_list)) return false; //se l'elemento non è fornito o non è un array, esci dalla funzione.
  foreach ($scan_dir_list as $key => $value) {
    if (!strpos($value, '.php')) {

      unset($scan_dir_list[$key]);
    }
  }
  return $scan_dir_list;
}
//esegue le funzioni per creare la cartella functions, selezionarla,
//scansionarla, filtrare solo i documenti PHP e includerli nelle funzioni

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

  //ottieni la directory functions
  $functions_dir = get_function_directory_extension();
  //scansiona la directory e rimuovi i documenti non PHP
  $all_php_docs = only_php_files(scandir($functions_dir));

  //includi i documenti PHP
  if (is_array($all_php_docs)) {
    foreach ($all_php_docs as $include) {
      include($functions_dir . '/' . $include);
    }
  }

}
6 set 2010 16:50:06
Commenti

@mildfuzz: Bel trucco. Personalmente non lo userei per codice in produzione perché esegue ad ogni caricamento di pagina ciò che potremmo facilmente fare una sola volta al momento del lancio del sito. Inoltre, aggiungerei un modo per escludere alcuni file, come non caricare nulla che inizi con un underscore, così da poter comunque conservare lavori in corso nella directory del tema. Per il resto, ottimo!

MikeSchinkel MikeSchinkel
6 set 2010 22:55:15

Adoro l'idea ma concordo sul fatto che potrebbe portare a un caricamento non necessario per ogni richiesta. Qualcuno ha idea se ci sarebbe un modo semplice per far sì che il file functions.php finale venga generato automaticamente in cache con qualche tipo di aggiornamento quando vengono aggiunti nuovi file o a intervalli di tempo specifici?

NetConstructor.com NetConstructor.com
7 set 2010 07:27:51

Interessante, ma introduce rigidità. Inoltre, cosa succede se un malintenzionato riesce a inserire il proprio codice lì dentro? E se l'ordine degli include è importante?

Tom J Nowell Tom J Nowell
7 set 2010 15:09:23

@MikeSchinkel Io chiamo semplicemente i miei file di lavoro foo._php, poi rimuovo _php quando voglio che venga eseguito.

Mild Fuzz Mild Fuzz
10 set 2010 18:25:32

@NetConstructor: Sarei interessato anche io a una soluzione.

kaiser kaiser
1 feb 2011 09:35:25

@kaiser, suppongo che potresti farlo con script cron che eseguono una funzione che esegue la ricerca delle cartelle come sopra ma scrive i risultati in un DB/file di testo, poi basa i caricamenti su quella funzione. Questo potrebbe potenzialmente portare a lavori incompleti che vengono inclusi nel caricamento.

Mild Fuzz Mild Fuzz
1 feb 2011 11:19:33

@MildFuzz: Mike mi ha appena indicato l'API dei transients. Forse potrebbe far parte di una sorta di soluzione...

kaiser kaiser
1 feb 2011 20:52:51

basandoci sulla soluzione delineata da @mildfuzz - qual è secondo voi il metodo migliore per escludere automaticamente qualsiasi file o cartella (e i suoi sottofile/sottocartelle) dall'essere inclusi nel suo approccio di auto-inclusione? La mia idea sarebbe usare l'approccio dell'underscore come prefisso. Quale codice appropriato sarebbe il miglior approccio per includere tali capacità?

NetConstructor.com NetConstructor.com
10 ott 2012 14:10:37

hai solo bisogno di cambiare if (!strpos($value, '.php')) per includere qualsiasi sistema tu scelga.

Mild Fuzz Mild Fuzz
10 ott 2012 19:13:14
Mostra i restanti 4 commenti
2

Gestisco un sito con circa 50 tipi di pagine personalizzate uniche in diverse lingue su un'installazione multisito. Insieme a una MONTAGNA di plugin.

Ad un certo punto siamo stati costretti a dividere tutto. Un file functions con 20-30 mila righe di codice non è affatto divertente.

Abbiamo deciso di rifattorizzare completamente tutto il codice per gestire meglio la codebase. La struttura predefinita dei temi WordPress va bene per siti piccoli, ma non per siti più grandi.

Il nostro nuovo functions.php contiene solo ciò che è necessario per avviare il sito, ma niente che appartenga a una pagina specifica.

Il layout del tema che usiamo ora è simile al pattern di design MVC, ma in uno stile di codifica procedurale.

Per esempio la nostra pagina membri:

page-member.php. Responsabile dell'inizializzazione della pagina. Chiama le corrette funzioni AJAX o simili. Potrebbe essere equivalente alla parte Controller nello stile MVC.

functions-member.php. Contiene tutte le funzioni relative a questa pagina. È incluso anche in diverse altre pagine che necessitano di funzioni per i nostri membri.

content-member.php. Prepara i dati per l'HTML. Potrebbe essere equivalente al Model in MVC.

layout-member.php. La parte HTML.

Dopo aver apportato questi cambiamenti, il tempo di sviluppo è diminuito facilmente del 50% e ora il product owner ha difficoltà ad assegnarci nuovi compiti. :)

4 ott 2012 12:37:17
Commenti

Per renderlo più utile potresti considerare di mostrare come funziona realmente questo pattern MVC.

kaiser kaiser
5 ott 2012 13:57:01

Sarei anche curioso di vedere un esempio del tuo approccio, preferibilmente con alcuni dettagli/vari scenari. L'approccio sembra molto interessante. Hai confrontato il carico del server/le prestazioni con la metodologia standard utilizzata da altri? Fornisci un esempio su GitHub se possibile.

NetConstructor.com NetConstructor.com
10 ott 2012 14:29:52
0

Dal file functions.php del tema figlio:

    require_once( get_stylesheet_directory() . '/inc/custom.php' );
20 ott 2013 21:34:23
1

Nel file functions.php, un modo più elegante per richiamare un file richiesto sarebbe:

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

4 ott 2012 10:23:45
Commenti

locate_template() ha un terzo parametro …

fuxia fuxia
4 ott 2012 10:38:12
0

Ho combinato le risposte di @kaiser e @mikeschinkel.

Tengo tutte le personalizzazioni del mio tema in una cartella /includes e all'interno di questa cartella ho tutto suddiviso in sottocartelle.

Voglio che solo /includes/admin e i suoi contenuti vengano inclusi quando true === is_admin()

Se una cartella viene esclusa in iterator_check_traversal_callback restituendo false, allora le sue sottodirectory non verranno iterate (né passate a iterator_check_traversal_callback)

/**
 *  Richiede tutte le personalizzazioni sotto /includes
 */
$includes_import_root = 
    new \RecursiveDirectoryIterator( __DIR__ . '/includes', \FilesystemIterator::SKIP_DOTS );

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

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

    // Non includere la cartella /includes/admin quando sei nel sito pubblico
    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();
}
19 ago 2018 22:02:26