Code in der Datei functions.php Ihres WordPress-Themes organisieren?

92

Je mehr Anpassungen ich an WordPress vornehme, desto mehr überlege ich, ob ich diese Datei organisieren oder aufteilen soll.

Genauer gesagt, wenn ich eine Reihe von benutzerdefinierten Funktionen habe, die nur für den Admin-Bereich gelten, und andere, die nur für meine öffentliche Website gelten, gibt es einen Grund, alle Admin-Funktionen möglicherweise in ihre eigene Datei aufzunehmen oder zu einer Gruppe zusammenzufassen?

Würde das Aufteilen in separate Dateien oder Gruppieren diese möglicherweise eine WordPress-Website beschleunigen oder überspringt WordPress / PHP automatisch Funktionen, die ein is_admin-Code-Präfix haben?

Was ist der beste Weg, um mit einer großen Funktionsdatei umzugehen (meine ist 1370 Zeilen lang)?

NetConstructor.com
quelle

Antworten:

120

Wenn Sie den Punkt erreichen, an dem der Code in Ihrem functions.phpDesign Sie zu überfordern beginnt, würde ich definitiv sagen, dass Sie bereit sind, die Aufteilung in mehrere Dateien in Betracht zu ziehen. Ich neige dazu, das an diesem Punkt fast von Natur aus zu tun.

Verwenden Sie Include Files in der functions.phpDatei Ihres Themas

Ich erstelle ein Unterverzeichnis mit dem Namen "includes" unter meinem Themenverzeichnis und segmentiere meinen Code in Include-Dateien, die nach dem für mich zum jetzigen Zeitpunkt Sinnvollen geordnet sind (was bedeutet, dass ich ständig Code überarbeite und verschiebe, während sich eine Site weiterentwickelt). Ich auch selten Gib irgendeinen echten Code ein functions.php. Alles geht in die Include-Dateien; Nur meine Präferenz.

Um Ihnen ein Beispiel zu geben, hier ist meine Testinstallation, mit der ich meine Antworten auf Fragen hier auf WordPress Answers teste. Jedes Mal, wenn ich eine Frage beantworte, behalte ich den Code für den Fall, dass ich ihn erneut benötige. Dies ist nicht genau das, was Sie für eine Live-Site tun, aber es zeigt die Mechanismen der Aufteilung des Codes:

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

Oder Plugins erstellen

Eine weitere Möglichkeit ist es, den Code nach Funktionen zu gruppieren und eigene Plugins zu erstellen. Für mich fange ich an, in der functions.phpDatei des Themas zu codieren, und als der Code fertig ist, habe ich den größten Teil meines Codes in Plugins verschoben.

Jedoch KEINE signifikante Leistungssteigerung durch PHP Code Organization

Andererseits geht es bei der Strukturierung Ihrer PHP-Dateien zu 99% darum, Ordnung und Wartbarkeit zu schaffen, und zu 1% um die Leistung, wenn dies der Fall ist (Organisieren .jsund .cssAufrufen von Dateien durch den Browser über HTTP ist ein völlig anderer Fall und hat enorme Auswirkungen auf die Leistung.) Ihr PHP-Code auf dem Server spielt unter Performance-Gesichtspunkten keine Rolle.

Und Code-Organisation ist persönliche Präferenz

Und nicht zuletzt ist die Code-Organisation eine persönliche Präferenz. Einige Leute hassen es, wie ich Code organisiere, genauso wie ich es hasse, wie sie es auch tun. Finden Sie etwas, das Ihnen gefällt, und bleiben Sie dabei. Lassen Sie jedoch zu, dass sich Ihre Strategie im Laufe der Zeit weiterentwickelt, wenn Sie mehr lernen und sich damit vertraut machen.

MikeSchinkel
quelle
Schöne Antwort, ich bin gerade an dem Punkt angekommen, an dem ich die Funktionsdatei aufteilen muss. Wann denkst du, ist es praktisch, von frunctions.php zu einem Plugin zu wechseln? Sie sagten in Ihrer Antwort: Als ich den Code fertiggestellt habe, habe ich den größten Teil meines Codes in Plugins verschoben . Ich verstehe das nicht ganz, was meinst du mit konkretisiert.
Saif Bechan
5
+1 für "oder Plugins erstellen". Genauer gesagt, " Funktionalität Plugins "
Ian Dunn
3
Die Verwendung relativer Pfade ist möglicherweise nicht in allen Einstellungen zuverlässig. Stattdessen sollte immer der absolute Pfad verwendet werden
Mark Kaplun,
2
@ MarkKaplun - Sie sind absolut richtig. Seit ich diese Antwort geschrieben habe, habe ich diese Lektion auf die harte Tour gelernt. Ich werde meine Antwort aktualisieren. Vielen Dank für den Hinweis.
MikeSchinkel
Ich erhalte "Verwendung der undefinierten Konstanten DIR - angenommen ' DIR ' in C: \ wamp \ www \ site \ wp-content \ themes \ mytheme \ functions.php" - PHP v5.6.25 und PHP v7.0.10 - das kann ich nicht Formatieren Sie dieses DIR ordnungsgemäß in Kommentar (underscoreunderscoreDIRunderscoreunderscore), aber es funktioniert mit Dirname (underscoreunderscoreFILEunderscoreunderscore)
Marko
50

Späte Antwort

So fügen Sie Ihre Dateien richtig ein:

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

Das gleiche funktioniert auch in Plugins.

Wie man den richtigen Weg oder URi findet

Schauen Sie sich auch die Funktionen der Dateisystem-API an:

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

So reduzieren Sie die Anzahl der include/require

Wenn Sie alle Dateien aus einem Verzeichnis holen müssen, gehen Sie mit

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

Beachten Sie, dass dies Fehler ignoriert (möglicherweise gut für die Produktion) / nicht ladbare Dateien.

Um dieses Verhalten zu ändern, möchten Sie möglicherweise während der Entwicklung eine andere Konfiguration verwenden:

$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: OOP / SPL-Ansatz

Als ich gerade zurückkam und sah, dass diese Antwort immer mehr positive Stimmen erhielt, dachte ich, ich könnte zeigen, wie ich es heutzutage mache - in einer PHP 5.3+ Welt. Im folgenden Beispiel werden alle Dateien aus einem Themenunterordner mit dem Namen geladen src/. Hier habe ich meine Bibliotheken, die bestimmte Aufgaben wie Menüs, Bilder usw. erledigen. Sie müssen sich nicht einmal um den Namen kümmern, wenn jede einzelne Datei geladen wird. Wenn Sie andere Unterordner in diesem Verzeichnis haben, werden diese ignoriert.

Das \FilesystemIteratorist der PHP 5.3+ Supercedor über dem \DirectoryIterator. Beide sind Teil der PHP SPL. Während PHP 5.2 es ermöglichte, die eingebaute SPL-Erweiterung zu deaktivieren (weniger als 1% aller Installationen haben dies getan), ist die SPL jetzt Teil des PHP-Kerns.

<?php

namespace Theme;

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

Während ich PHP 5.2.x noch unterstützte, verwendete ich die folgende Lösung: A \FilterIteratorim src/FiltersVerzeichnis, um nur Dateien (und keine Punktzeiger von Ordnern) abzurufen, und a \DirectoryIterator, um die Schleife zu bilden und zu laden.

namespace Theme;

use Theme\Filters\IncludesFilter;

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

Das \FilterIteratorwar so einfach:

<?php

namespace Theme\Filters;

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

Abgesehen davon, dass PHP 5.2 bereits tot / EOL ist (und auch 5.3), gibt es die Tatsache, dass es mehr Code und eine weitere Datei im Spiel gibt, so dass es keinen Grund gibt, sich für die spätere Version zu entscheiden und PHP 5.2.x zu unterstützen.

Zusammengefasst

Ein noch ausführlicherer Artikel ist hier auf WPKrauts zu finden .

BEARBEITEN Der offensichtlich richtige Weg ist die Verwendung von namespaced-Code, der für das automatische Laden von PSR-4 vorbereitet ist, indem alles in das entsprechende Verzeichnis gestellt wird, das bereits über den Namespace definiert ist. Verwenden Sie dann einfach Composer und a composer.json, um Ihre Abhängigkeiten zu verwalten, und lassen Sie Ihren PHP-Autoloader automatisch erstellen (der automatisch eine Datei importiert, indem er nur aufruft use \<namespace>\ClassName). Dies ist der De-facto-Standard in der PHP-Welt, der einfachste Weg und noch vorautomatisierter und durch WP Starter vereinfacht .

Kaiser
quelle
5

In Bezug auf die Aufteilung verwende ich in meinem Kessel eine benutzerdefinierte Funktion, um nach einem Ordner mit dem Namen "functions" im Themenverzeichnis zu suchen. Wenn dieser Ordner nicht vorhanden ist, wird er erstellt. Anschließend wird ein Array aller gefundenen .php-Dateien in diesem Ordner erstellt (falls vorhanden) und ein include () ausgeführt. auf jedem von ihnen.

Auf diese Weise füge ich jedes Mal, wenn ich neue Funktionen schreiben muss, eine PHP-Datei zum Funktionsordner hinzu und muss mich nicht darum kümmern, sie in die Site zu codieren.

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

}
Milder Flaum
quelle
5
@mildfuzz : Netter Trick. Ich persönlich würde es nicht für Produktionscode verwenden, da es bei jedem Seitenaufruf das tut, was wir beim Start der Site leicht tun könnten. Außerdem füge ich in gewisser Weise hinzu, dass Dateien weggelassen werden, zum Beispiel, dass nichts beginnend mit einem Unterstrich geladen wird, damit ich noch laufende Arbeiten im Themenverzeichnis speichern kann. Ansonsten schön!
MikeSchinkel
Ich liebe die Idee, aber ich stimme zu, dass dies möglicherweise zu unnötigem Laden für jede Anfrage führen kann. Gibt es eine Idee, wie die endgültige Datei functions.php auf einfache Weise automatisch zwischengespeichert werden kann, wenn neue Dateien hinzugefügt werden oder in einem bestimmten Zeitintervall?
NetConstructor.com
Schön, aber es führt zu Unflexibilität. Was passiert auch, wenn ein Angreifer seinen Code dort ablegt? Und wenn die Reihenfolge der Includes wichtig ist?
Tom J Nowell
1
@MikeSchinkel Ich rufe einfach meine Arbeitsdateien foo._php auf und lösche dann den _php, wenn ich möchte, dass er ausgeführt wird.
Mild Fuzz
@NetConstructor: Würde mich auch für eine Lösung interessieren.
Kaiser
5

Ich verwende gerne eine Funktion für die Dateien in einem Ordner. Dieser Ansatz erleichtert das Hinzufügen neuer Funktionen, wenn neue Dateien hinzugefügt werden. Aber ich schreibe immer in der Klasse oder mit Namespaces - geben Sie ihm mehr Kontrolle über den Namespace von Funktionen, Methoden usw.

Unten ein kleines Beispiel; Nutze aber auch die Vereinbarung über die Klasse * .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;

}

In Themes verwende ich oft ein anderes Szenario. Ich definiere die Funktion der externen Datei in einer Support-ID, siehe Beispiel. Das ist nützlich, wenn ich die Funktion der externen Datei einfach deaktivieren möchte. Ich benutze die WP-Core-Funktion require_if_theme_supports()und er lade nur, wenn die Support-ID aktiv war. Im folgenden Beispiel habe ich diese unterstützte ID in der Zeile definiert, bevor ich die Datei geladen habe.

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

Sie können mehr davon im Repo dieses Themas sehen .

bueltge
quelle
4

Ich verwalte eine Site mit ungefähr 50 benutzerdefinierten Seitentypen in mehreren verschiedenen Sprachen über eine Netzwerkinstallation. Zusammen mit einer Menge Plugins.

Wir waren gezwungen, alles irgendwann aufzuteilen. Eine Funktionsdatei mit 20 bis 30.000 Codezeilen ist überhaupt nicht lustig.

Wir haben beschlossen, den gesamten Code komplett umzugestalten, um die Codebasis besser verwalten zu können. Die standardmäßige WordPress-Designstruktur eignet sich für kleine Websites, jedoch nicht für größere Websites.

Unsere neue functions.php enthält nur das, was zum Starten der Site notwendig ist, aber nichts, was zu einer bestimmten Seite gehört.

Das jetzt verwendete Themenlayout ähnelt dem MCV-Entwurfsmuster, weist jedoch einen prozeduralen Codierungsstil auf.

Zum Beispiel unsere Mitgliederseite:

page-member.php . Verantwortlich für die Initialisierung der Seite. Aufrufen der richtigen Ajax-Funktionen oder ähnliches. Entspricht möglicherweise dem Controller-Teil im MCV-Stil.

functions-member.php . Enthält alle Funktionen zu dieser Seite. Dies ist auch in mehreren anderen Seiten enthalten, die Funktionen für unsere Mitglieder benötigen.

content-member.php . Bereitet die Daten für HTML vor. Entspricht möglicherweise dem Modell in MCV.

layout-member.php . Der HTML-Teil.

Nachdem wir diese Änderungen vorgenommen haben, ist die Entwicklungszeit leicht um 50% gesunken, und jetzt hat der Produktbesitzer Probleme, uns neue Aufgaben zu geben. :)

Patrik Grinsvall
quelle
7
Um dies noch hilfreicher zu machen, sollten Sie sich überlegen, wie dieses MVC-Muster wirklich funktioniert.
Kaiser
Ich wäre auch gespannt auf ein Beispiel für Ihre Vorgehensweise, vorzugsweise mit einigen Details / verschiedenen Situationen. Der Ansatz klingt sehr interessant. Haben Sie die Serverauslastung / -leistung mit der Standardmethode verglichen, die andere verwenden? Geben Sie nach Möglichkeit ein Github-Beispiel an.
NetConstructor.com
3

Aus der Datei functions.php für untergeordnete Themen:

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

In functions.php wäre eine elegantere Art, eine erforderliche Datei aufzurufen:

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

Imperative Ideen
quelle
4
locate_template()hat einen dritten Parameter ...
fuxia
0

Ich habe die Antworten von @kaiser und @mikeschinkel kombiniert .

Ich habe alle meine Anpassungen an meinem Thema in einem /includesOrdner und in diesem Ordner habe ich alles in Unterordner aufgeteilt.

Ich möchte nur /includes/adminund seine Unterinhalte aufgenommen werden, wenntrue === is_admin()

Wenn ein Ordner iterator_check_traversal_callbackvon der Rückgabe ausgeschlossen wird, werden falseseine Unterverzeichnisse nicht iteriert (oder an ihn übergeben 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
quelle