Organizarea Codului în Fișierul functions.php al Temei WordPress
Cu cât fac mai multe personalizări în WordPress, cu atât încep să mă gândesc dacă ar trebui să organizez acest fișier sau să îl împart în mai multe părți.
Mai specific, dacă am o mulțime de funcții personalizate care se aplică doar zonei de administrare și altele care se aplică doar site-ului public, există vreun motiv pentru a include toate funcțiile de administrare în propriul lor fișier sau pentru a le grupa împreună?
Ar putea împărțirea lor în fișiere separate sau gruparea lor să mărească viteza unui site WordPress sau WordPress/PHP omite automat funcțiile care au un prefix de cod is_admin?
Care este cea mai bună modalitate de a gestiona un fișier functions mare (al meu are 1370 de linii).

Dacă ajungi în punctul în care codul din fișierul functions.php
al temei tale începe să te copleșească, aș spune cu siguranță că ești pregătit să îl împărți în mai multe fișiere. Eu tind să fac asta aproape instinctiv în acest moment.
Folosește Fișiere Include în Fișierul functions.php
al Temei Tale
Eu creez un subdirector numit "includes" în directorul temei și împart codul în fișiere include organizate după ceea ce are sens pentru mine la momentul respectiv (ceea ce înseamnă că refactorizez și mut codul constant pe măsură ce un site evoluează). De asemenea, rareori pun cod real în functions.php
; totul merge în fișierele include - pur și simplu preferința mea.
Pentru a-ți oferi un exemplu, iată instalarea mea de test pe care o folosesc pentru a verifica răspunsurile la întrebări de pe WordPress Answers. De fiecare dată când răspund la o întrebare, păstrez codul în caz că am nevoie de el din nou. Acesta nu este exact ceea ce vei face pentru un site live, dar arată mecanica de separare a codului:
<?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');
Sau Creează Plugin-uri
O altă opțiune este să începi să grupezi codul după funcție și să creezi propriile plugin-uri. Personal, încep să codez în fișierul functions.php
al temei, iar până când codul este complet, majoritatea codului meu este mutat în plugin-uri.
Totuși, Fără Beneficii Semnificative de Performanță din Organizarea Codului PHP
Pe de altă parte, structurarea fișierelor PHP este 99% despre crearea de ordine și menținere și 1% despre performanță, dacă chiar atât (organizarea fișierelor .js
și .css
apelate de browser prin HTTP este o situație complet diferită și are implicații mari de performanță). Dar modul în care organizezi codul PHP pe server nu contează prea mult din punct de vedere al performanței.
Și Organizarea Codului este o Preferință Personală
Și nu în ultimul rând, organizarea codului este o chestiune de preferință personală. Unii oameni ar detesta modul în care organizez eu codul, așa cum eu aș putea detesta modul în care o fac ei. Găsește ceva care îți place și ține-te de el, dar permite strategiei tale să evolueze în timp pe măsură ce înveți mai multe și devii mai confortabil.

Răspuns bun, tocmai am ajuns în punctul în care trebuie să separ fișierul de funcții. Când crezi că este util să treci de la functions.php la un plugin. Ai spus în răspunsul tău: până când finalizez codul, mut majoritatea codului în plugin-uri. Nu înțeleg pe deplin asta, ce vrei să spui cu "finalizez codul".

+1 pentru "sau crează plugin-uri". Mai exact, "plugin-uri de funcționalitate"

utilizarea căilor relative poate să nu fie de încredere în toate tipurile de setări, în schimb ar trebui să folosești întotdeauna calea absolută

@MarkKaplun - Ai absolut dreptate. De când am scris acest răspuns, am învățat această lecție pe pielea mea. Am să actualizez răspunsul meu. Mulțumesc că ai atras atenția asupra acestui lucru.

Primesc "Utilizare de constantă nedefinită DIR - presupus 'DIR' în C:\wamp\www\site\wp-content\themes\mytheme\functions.php" - PHP v5.6.25 și PHP v7.0.10 - Nu pot formata corect acest DIR în comentariu (underscoreunderscoreDIRunderscoreunderscore), dar funcționează cu dirname(underscoreunderscoreFILEunderscoreunderscore)

Atenție: ar trebui să folosești __DIR__
în loc de __DIR___
, altfel vei primi o Eroare Internă de Server (500).

Răspuns întârziat
Cum să includ fișierele corect:
function wpse1403_bootstrap()
{
// Aici încărcăm din directorul nostru de includes
// Acest lucru ia în considerare atât tema părinte cât și tema copil
locate_template( array( 'inc/foo.class.php' ), true, true );
}
add_action( 'after_setup_theme', 'wpse1403_bootstrap' );
Același lucru funcționează și în plugin-uri.
Cum să obții calea sau URL-ul corect
De asemenea, aruncați o privire la funcțiile API-ului pentru sistemul de fișiere, cum ar fi:
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.
Cum să reduci numărul de include/require
Dacă trebuie să încarci toate fișierele dintr-un director, folosește
foreach ( glob( 'path/to/folder/*.php' ) as $file )
include $file;
Ține minte că această metodă ignoră erorile (poate fi bună pentru producție)/fișierele care nu pot fi încărcate.
Pentru a modifica acest comportament, poți folosi o configurație diferită în timpul dezvoltării:
$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;
Editare: Abordare OOP/SPL
Revenind și văzând că acest răspuns primește din ce în ce mai multe voturi, m-am gândit să arăt cum fac asta în prezent - într-o lume cu PHP 5.3+. Următorul exemplu încarcă toate fișierele dintr-un subfolder al temei numit src/
. Aici am bibliotecile care se ocupă de anumite sarcini precum meniuri, imagini, etc. Nu trebuie să te preocupi de nume, deoarece fiecare fișier este încărcat. Dacă ai alte subfoldere în acest director, acestea sunt ignorate.
\FilesystemIterator
este înlocuitorul PHP 5.3+ pentru \DirectoryIterator
. Ambele fac parte din PHP SPL. În timp ce PHP 5.2 permitea dezactivarea extensiei SPL (sub 1% din instalații făceau asta), acum SPL face parte din nucleul PHP.
<?php
namespace Theme;
$files = new \FilesystemIterator( __DIR__.'/src', \FilesystemIterator::SKIP_DOTS );
foreach ( $files as $file )
{
/** @noinspection PhpIncludeInspection */
! $files->isDir() and include $files->getRealPath();
}
Anterior, când încă suportam PHP 5.2.x, foloseam următoarea soluție: Un \FilterIterator
în directorul src/Filters
pentru a prelua doar fișierele (și nu pointerii de foldere) și un \DirectoryIterator
pentru buclă și încărcare.
namespace Theme;
use Theme\Filters\IncludesFilter;
$files = new IncludesFilter( new \DirectoryIterator( __DIR__.'/src' ) );
foreach ( $files as $file )
{
include_once $files->current()->getRealPath();
}
\FilterIterator
era la fel de simplu:
<?php
namespace Theme\Filters;
class IncludesFilter extends \FilterIterator
{
public function accept()
{
return
! $this->current()->isDot()
and $this->current()->isFile()
and $this->current()->isReadable();
}
}
Pe lângă faptul că PHP 5.2 este mort/EOL acum (și 5.3 de asemenea), mai este și faptul că este mai mult cod și încă un fișier în joc, așa că nu există niciun motiv să folosești versiunea mai veche și să suporți PHP 5.2.x.
Rezumat
EDITARE Modul evident corect este să folosești cod cu namespace
, pregătit pentru încărcarea automată PSR-4 prin plasarea tuturor în directorul corespunzător definit prin namespace. Apoi folosește Composer și un composer.json
pentru a gestiona dependențele și lasă-l să construiască automat încărcătorul PHP (care importă automat un fișier prin simplul apel use \<namespace>\ClassName
). Acesta este standardul de facto în lumea PHP, cel mai simplu mod de a proceda și chiar mai automatizat și simplificat de WP Starter.

Îmi place să folosesc o funcție pentru fișierele dintr-un folder. Această abordare facilitează adăugarea de noi funcționalități atunci când adaug fișiere noi. Dar scriu întotdeauna în clase sau cu namespace-uri - oferă mai mult control asupra Namespace-ului funcțiilor, metodelor etc.
Mai jos un mic exemplu; dar folosesc și acordul despre class*.php
public function __construct() {
$this->load_classes();
}
/**
* Returnează un array de funcționalități, de asemenea
* Scanează subfolderul pluginului "/classes"
*
* @since 0.1
* @return void
*/
protected function load_classes() {
// încarcă toate fișierele cu modelul class-*.php din directorul classes
foreach( glob( dirname( __FILE__ ) . '/classes/class-*.php' ) as $class )
require_once $class;
}
În teme folosesc adesea un alt scenariu. Definești funcția fișierului extern într-un ID de suport, vezi exemplul. Acest lucru este util dacă vreau să dezactivez ușor funcționalitatea fișierului extern. Folosesc funcția WP core require_if_theme_supports()
și se încarcă doar dacă ID-ul de suport este activ. În exemplul următor am definit acest ID de suport în linia dinainte de încărcarea fișierului.
/**
* Adaugă suport pentru Theme Customizer
*
* @since 09/06/2012
*/
add_theme_support( 'documentation_customizer', array( 'all' ) );
// Include theme customizer pentru opțiunile temei, dacă tema are suport
require_if_theme_supports(
'documentation_customizer',
get_template_directory() . '/inc/theme-customize.php'
);
Puteți vedea mai multe despre acest lucru în repo-ul acestei teme.

În ceea ce privește organizarea codului, în șablonul meu folosesc o funcție personalizată pentru a căuta un director numit "functions" în directorul temei. Dacă acesta nu există, îl creează. Apoi creează un array cu toate fișierele .php găsite în acel folder (dacă există) și rulează un include(); pentru fiecare dintre ele.
Astfel, de fiecare dată când am nevoie să adaug o nouă funcționalitate, pur și simplu adaug un fișier PHP în folderul "functions" și nu trebuie să mă preocup să-l integrez manual în site.
<?php
/*
FUNCȚII pentru includerea automată a documentelor PHP din folderul functions.
*/
//dacă rulează pe php4, creează o funcție 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);
}
}
/*
* Această funcție returnează calea către folderul functions.
* Dacă folderul nu există, îl creează.
*/
function get_function_directory_extension($template_url = FALSE) {
//obține URL-ul șablonului dacă nu este pasat
if (!$template_url)$template_url = get_bloginfo('template_directory');
//înlocuiește slash-urile cu puncte pentru explode
$template_url_no_slash = str_replace('/', '.', $template_url);
//creează array din URL
$template_url_array = explode('.', $template_url_no_slash);
//--taie array-ul
//Calculează offset (avem nevoie doar de ultimele trei niveluri)
//Este necesar pentru a obține directorul corect, nu cel pasat de server, deoarece scandir nu funcționează când sunt implicate alias-uri.
$offset = count($template_url_array) - 3;
//taie array-ul, păstrând doar până la folderul rădăcină WP (unde se află wp-config.php, de unde rulează front-end-ul)
$template_url_array = array_splice($template_url_array, $offset, 3);
//reconstruiește ca string
$template_url_return_string = implode('/', $template_url_array);
fb::log($template_url_return_string, 'Șablon'); //firephp
//creează directorul curent de lucru cu extensia șablonului și directorul functions
//dacă e în admin, iese din folderul admin înainte de a stoca directorul de lucru, apoi revine
if (is_admin()) {
$admin_directory = getcwd();
chdir("..");
$current_working_directory = getcwd();
chdir($admin_directory);
} else {
$current_working_directory = getcwd();
}
fb::log($current_working_directory, 'Director'); //firephp
//metodă alternativă dacă chdir nu funcționează pe serverul tău (unele servere Windows s-ar putea să nu o suporte)
//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); //creează folderul dacă nu există deja (lazy, dar util... oarecum)
//returnează calea
return $function_folder;
}
//elimină elementele din array care nu au extensia .php
function only_php_files($scan_dir_list = false) {
if (!$scan_dir_list || !is_array($scan_dir_list)) return false; //dacă elementul nu este dat sau nu este array, iese din funcție.
foreach ($scan_dir_list as $key => $value) {
if (!strpos($value, '.php')) {
unset($scan_dir_list[$key]);
}
}
return $scan_dir_list;
}
//rulează funcțiile pentru a crea folderul functions, îl selectează,
//îl scanează, filtrează doar documentele PHP apoi le include în functions
add_action('wp_head', fetch_php_docs_from_functions_folder(), 1);
function fetch_php_docs_from_functions_folder() {
//obține directorul functions
$functions_dir = get_function_directory_extension();
//scanează directorul și elimină documentele non-PHP
$all_php_docs = only_php_files(scandir($functions_dir));
//include documentele PHP
if (is_array($all_php_docs)) {
foreach ($all_php_docs as $include) {
include($functions_dir . '/' . $include);
}
}
}

@mildfuzz: Truc drăguț. Personal, nu l-aș folosi pentru cod de producție, deoarece face pentru fiecare încărcare de pagină ceea ce am putea face ușor o singură dată la lansarea site-ului. De asemenea, aș adăuga o modalitate de a omite anumite fișiere, cum ar fi să nu încărcați nimic care începe cu un underscore, astfel încât să pot stoca în continuare lucrări în curs în directorul temei. În rest, frumos!

îmi place ideea, dar sunt de acord că ar putea duce la încărcări inutile pentru fiecare cerere. Aveți vreo idee dacă ar exista o modalitate simplă de a avea fișierul final functions.php generat automat și stocat în cache cu un tip de actualizare atunci când sunt adăugate fișiere noi sau la un anumit interval de timp?

Frumos, dar duce la inflexibilități, de asemenea, ce se întâmplă dacă un atacator reușește să-și introducă codul acolo? Și dacă ordinea includerilor este importantă?

@MikeSchinkel Eu pur și simplu numesc fișierele mele de lucru foo._php, apoi elimin _php când vreau să ruleze.

@kaiser, presupun că ai putea face asta cu scripturi cron care să ruleze o funcție care să execute căutarea în folderul menționat mai sus, dar să scrie rezultatele într-o bază de date/fișier text, apoi să bazeze încărcările pe acea funcție. Asta ar putea duce potențial și la încărcarea unor lucrări neterminate.

@MildFuzz: Mike tocmai mi-a arătat API-ul pentru transiente. Poate că asta ar putea face parte dintr-un fel de soluție...

bazat pe soluția prezentată de @mildfuzz - care credeți că este cea mai bună metodă de a exclude automat orice fișier sau folder (și subfișierele/subfolderele acestuia) din includerea automată conform abordării sale? Ideea mea ar fi să folosim prefixul cu underscore. Ce cod propriu ar fi cea mai bună abordare pentru a include astfel de capabilități?

Administrez un site cu aproximativ 50 de tipuri unice de pagini personalizate în mai multe limbi diferite, instalat pe o rețea. Împreună cu o TONĂ de plugin-uri.
Am fost nevoiți să împărțim totul la un moment dat. Un fișier functions.php cu 20-30k de linii de cod nu este deloc amuzant.
Am decis să refactorizăm complet tot codul pentru a gestiona mai bine baza de cod. Structura implicită a temei WordPress este bună pentru site-uri mici, dar nu și pentru cele mai mari.
Noul nostru functions.php conține doar ceea ce este necesar pentru a porni site-ul, dar nimic care aparține unei anumite pagini.
Layout-ul temei pe care îl folosim acum este similar cu modelul de design MCV, dar într-un stil de codare procedural.
De exemplu, pagina noastră pentru membri:
page-member.php. Responsabilă pentru inițializarea paginii. Apelarea funcțiilor corecte AJAX sau similare. Ar putea fi echivalentă cu partea de Controller în stilul MCV.
functions-member.php. Conține toate funcțiile legate de această pagină. Acesta este inclus și în alte pagini care au nevoie de funcții pentru membrii noștri.
content-member.php. Pregătește datele pentru HTML. Ar putea fi echivalentă cu Modelul în MCV.
layout-member.php. Partea HTML.
După ce am făcut aceste modificări, timpul de dezvoltare a scăzut cu ușurință cu 50%, iar acum proprietarul produsului are probleme să ne dea noi task-uri. :)

Pentru a face acest lucru mai util, ai putea lua în considerare să arăți cum funcționează cu adevărat acest model MVC.

Aș fi, de asemenea, curios să văd un exemplu al abordării tale, de preferință cu câteva detalii/diverse situații. Abordarea sună foarte interesantă. Ai comparat încărcarea/performanța serverului cu metodologia standard pe care o folosesc alții? Dacă este posibil, oferă un exemplu pe GitHub.

locate_template()
are al treilea parametru …

Am combinat răspunsurile lui @kaiser și @mikeschinkel.
Am toate personalizările pentru tema mea într-un folder /includes
, iar în interiorul acestui folder am totul organizat în subfoldere.
Vreau ca doar /includes/admin
și conținutul său să fie inclus când true === is_admin()
.
Dacă un folder este exclus în iterator_check_traversal_callback
prin returnarea valorii false
, atunci subdirectoarele sale nu vor fi iterate (sau transmise către iterator_check_traversal_callback
).
/**
* Încarcă toate personalizările din /includes
*/
$includes_import_root =
new \RecursiveDirectoryIterator( __DIR__ . '/includes', \FilesystemIterator::SKIP_DOTS );
function iterator_check_traversal_callback( $current, $key, $iterator ) {
$file_name = $current->getFilename();
// Include doar fișierele *.php
if ( ! $current->isDir() ) {
return preg_match( '/^.+\.php$/i', $file_name );
}
// Nu include folderul /includes/admin când este site-ul public
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();
}
