Organizujesz kod w pliku funkcji pliku WordPress Theme.php?

92

Im więcej dostosowań wprowadzam w WordPressie, tym bardziej zastanawiam się, czy powinienem organizować ten plik, czy go dzielić.

Mówiąc dokładniej, jeśli mam kilka niestandardowych funkcji, które dotyczą tylko obszaru administracyjnego i innych, które dotyczą tylko mojej publicznej strony internetowej, czy istnieje jakiś powód, aby ewentualnie uwzględnić wszystkie funkcje administracyjne w ich własnym pliku lub zgrupować je razem?

Czy podzielenie ich na osobne pliki lub grupowanie ich może przyspieszy stronę internetową WordPress, czy też WordPress / PHP automatycznie pominie funkcje, które mają prefiks kodu is_admin?

Jaki jest najlepszy sposób radzenia sobie z plikiem dużych funkcji (mój ma długość 1370 linii).

NetConstructor.com
źródło

Odpowiedzi:

120

Jeśli dochodzisz do punktu, w którym kod w twoim motywie functions.phpzaczyna się przytłaczać, zdecydowanie powiedziałbym, że jesteś gotowy rozważyć podzielenie go na wiele plików. W tym momencie staram się to robić niemal z drugiej natury.

Użyj opcji Dołącz pliki do functions.phppliku motywu

Tworzę podkatalog o nazwie „obejmuje” w moim katalogu motywów i dzielę mój kod na pliki dołączone uporządkowane według tego, co ma dla mnie sens w danym momencie (co oznacza, że ​​ciągle zmieniam kod i zmieniam kod w miarę ewolucji strony). Również rzadko wstaw dowolny prawdziwy kod functions.php; wszystko idzie w plikach dołączanych; tylko moje preferencje.

Oto przykład mojej instalacji testowej, której używam do testowania odpowiedzi na pytania tutaj na WordPress Answers. Za każdym razem, gdy odpowiadam na pytanie, trzymam kod na wszelki wypadek, aby go znowu potrzebować. Nie jest to dokładnie to, co zrobisz dla witryny na żywo, ale pokazuje ona mechanizmy dzielenia kodu:

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

Lub utwórz wtyczki

Inną opcją jest grupowanie kodu według funkcji i tworzenie własnych wtyczek. Dla mnie zaczynam kodować w functions.phppliku motywu i do czasu, gdy otrzymam kod, przeniosłem większość mojego kodu do wtyczek.

Jednak NIE znaczący wzrost wydajności dzięki organizacji kodu PHP

Z drugiej strony struktura twoich plików PHP polega na 99% na tworzeniu porządku i łatwości konserwacji, a 1% na wydajności, jeśli to (organizowanie .jsi .csspliki wywoływane przez przeglądarkę przez HTTP to zupełnie inny przypadek i ma to ogromny wpływ na wydajność.) Ale jak organizujesz twój kod PHP na serwerze nie ma większego znaczenia z punktu widzenia wydajności.

A organizacja kodów jest osobistą preferencją

I wreszcie organizacja kodu to osobiste preferencje. Niektórzy nienawidzą tego, jak organizuję kod, tak samo jak ja nienawidzę tego, jak to robią. Znajdź coś, co ci się podoba i trzymaj się tego, ale pozwól, aby twoja strategia ewoluowała w czasie, gdy uczysz się więcej i czujesz się bardziej komfortowo.

MikeSchinkel
źródło
Dobra odpowiedź, właśnie doszedłem do tego miejsca, w którym muszę podzielić plik funkcji. Kiedy uważasz, że warto przejść z frunctions.php do wtyczki. Powiedziałeś w swojej odpowiedzi: zanim dostałem kod, przeniosłem większość mojego kodu do wtyczek . Nie rozumiem tego w pełni, co masz na myśli, mówiąc o rozwinięciu.
Saif Bechan
5
+1 za „lub utwórz wtyczki”. Mówiąc dokładniej, „ wtyczki funkcjonalne
Ian Dunn
3
używanie ścieżek względnych może nie być wiarygodne we wszystkich rodzajach ustawień, zamiast tego należy zawsze używać ścieżki bezwzględnej
Mark Kaplun,
2
@MarkKaplun - Masz całkowitą rację. Odkąd napisałem tę odpowiedź, nauczyłem się tej lekcji na własnej skórze. Mam zamiar zaktualizować swoją odpowiedź. Dzięki za zwrócenie na to uwagi.
MikeSchinkel
Pojawia się komunikat „Zastosowanie niezdefiniowanego stałego DIR - przyjęto„ DIR ”w C: \ wamp \ www \ site \ wp-content \ themes \ mytheme \ functions.php” - PHP v5.6.25 i PHP v7.0.10 - Nie mogę poprawnie sformatuj ten DIR w komentarzu (underscoreunderscoreDIRunderscoreunderscore), ale działa on z dirname (underscoreunderscoreFILEunderscoreunderscore)
Marko,
50

Spóźniona odpowiedź

Jak dołączyć swoje pliki we właściwy sposób:

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

To samo działa również we wtyczkach.

Jak uzyskać właściwą ścieżkę lub URi

Zobacz także funkcje interfejsu API systemu plików, takie jak:

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

Jak zmniejszyć liczbę include/require

Jeśli chcesz pobrać wszystkie pliki z katalogu, idź z

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

Należy pamiętać, że ignoruje to awarie (być może dobre do użytku produkcyjnego) / nieładowalne pliki.

Aby zmienić to zachowanie, możesz chcieć użyć innej konfiguracji podczas programowania:

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

Edycja: podejście OOP / SPL

Gdy właśnie wróciłem i zobaczyłem, że ta odpowiedź zyskuje coraz więcej głosów, pomyślałem, że mogę pokazać, jak to robię w dzisiejszych czasach - w świecie PHP 5.3+. Poniższy przykład ładuje wszystkie pliki z podfolderu tematów o nazwie src/. Tutaj mam swoje biblioteki, które obsługują niektóre zadania, takie jak menu, obrazy itp. Nie musisz nawet przejmować się nazwą, gdy ładuje się każdy plik. Jeśli masz inne podfoldery w tym katalogu, zostaną one zignorowane.

\FilesystemIteratorJest PHP 5.3+ supercedor nad \DirectoryIterator. Oba są częścią PHP SPL. Podczas gdy PHP 5.2 umożliwiło wyłączenie wbudowanego rozszerzenia SPL (zrobiło to mniej niż 1% wszystkich instalacji), SPL jest teraz częścią rdzenia PHP.

<?php

namespace Theme;

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

Wcześniej, podczas gdy nadal obsługiwałem PHP 5.2.x, użyłem następującego rozwiązania: A \FilterIteratorw src/Filterskatalogu, aby pobrać tylko pliki (a nie kropki wskaźników folderów) oraz a, \DirectoryIteratoraby wykonać zapętlenie i ładowanie.

namespace Theme;

use Theme\Filters\IncludesFilter;

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

To \FilterIteratorbyło tak proste:

<?php

namespace Theme\Filters;

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

Poza tym, że PHP 5.2 jest już martwy / EOL (i 5.3 również), istnieje fakt, że jest to więcej kodu i jeszcze jeden plik w grze, więc nie ma powodu, aby iść z późniejszym i wspierać PHP 5.2.x.

Podsumował

Jeszcze bardziej szczegółowy artykuł można znaleźć tutaj na WPKrauts .

EDYCJA Oczywiście poprawnym sposobem jest użycie namespacekodu d, przygotowanego do automatycznego ładowania PSR-4, poprzez umieszczenie wszystkiego w odpowiednim katalogu, który jest już zdefiniowany przez przestrzeń nazw. Następnie użyj Composer i a composer.jsondo zarządzania zależnościami i pozwól mu automatycznie zbudować autoloader PHP (który automatycznie importuje plik po prostu wywołując use \<namespace>\ClassName). To de facto standard w świecie PHP, najłatwiejsza droga, jeszcze bardziej zautomatyzowana i uproszczona przez WP Starter .

kajzer
źródło
5

jeśli chodzi o rozbijanie, na płycie kotła używam funkcji niestandardowej, aby wyszukać folder o nazwie funkcje w katalogu motywów, jeśli go nie ma, tworzy go. Następnie tworzy tablicę wszystkich plików .php, które znajdzie w tym folderze (jeśli istnieją) i uruchamia funkcję include (); na każdym z nich.

W ten sposób za każdym razem, gdy muszę napisać nową funkcjonalność, po prostu dodaję plik PHP do folderu funkcji i nie muszę się martwić kodowaniem go na stronie.

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

}
Łagodny Fuzz
źródło
5
@mildfuzz : Nice trick. Ja osobiście nie używałbym tego do kodu produkcyjnego, ponieważ robi to przy każdym ładowaniu strony, co moglibyśmy łatwo zrobić raz po uruchomieniu witryny. Dodałbym też w jakiś sposób pominięcie plików, na przykład nie ładowanie niczego zaczynającego się od znaku podkreślenia, aby móc nadal przechowywać prace w toku w katalogu motywów. W przeciwnym razie miło!
MikeSchinkel,
podoba mi się ten pomysł, ale zgadzam się, że może to prowadzić do niepotrzebnego ładowania dla każdego żądania. Masz pomysł, czy istnieje prosty sposób na automatyczne generowanie końcowego pliku functions.php w pamięci podręcznej z pewnym rodzajem aktualizacji, jeśli / kiedy zostaną dodane nowe pliki lub w określonym przedziale czasu?
NetConstructor.com,
Fajnie, ale prowadzi to do braku elastyczności, a co się stanie, jeśli atakującemu uda się tam upuścić swój kod? A jeśli ważna jest kolejność dołączeń?
Tom J Nowell
1
@MikeSchinkel Po prostu nazywam moje działające pliki foo._php, a następnie upuszczam _php, kiedy chcę, aby działało.
Łagodny Fuzz,
@NetConstructor: Zainteresowałby mnie też trochę sollution.
kaiser
5

Lubię używać funkcji do plików w folderze. Takie podejście ułatwia dodawanie nowych funkcji podczas dodawania nowych plików. Ale piszę zawsze w klasie lub w przestrzeniach nazw - dajmy większą kontrolę nad przestrzenią nazw funkcji, metody itp.

Poniżej mały przykład; ut wykorzystanie również z umową o klasie * .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;

}

W Motywach często używam innego scenariusza. Funkcję pliku zewnętrznego definiuję w identyfikatorze wsparcia, patrz przykład. Jest to przydatne, jeśli w łatwy sposób dezaktywuję kasowanie pliku zewnętrznego. Używam podstawowej funkcji WP, require_if_theme_supports()a on ładuje się tylko, jeśli identyfikator wsparcia był aktywny. W poniższym przykładzie zdefiniowałem ten obsługiwany identyfikator w wierszu przed załadowaniem pliku.

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

Możesz zobaczyć więcej tego w repozytorium tego tematu .

bueltge
źródło
4

Zarządzam witryną z około 50 unikalnymi niestandardowymi typami stron w różnych językach serwerowych za pośrednictwem instalacji sieciowej. Wraz z TONĄ wtyczek.

W pewnym momencie byliśmy zmuszeni to wszystko rozdzielić. Plik funkcji zawierający 20-30 000 wierszy kodu wcale nie jest śmieszny.

Postanowiliśmy zakończyć refaktoryzację całego kodu, aby lepiej zarządzać bazą kodów. Domyślna struktura motywów Wordpress jest dobra dla małych witryn, ale nie dla większych witryn.

Nasze nowe funkcje.php zawierają tylko to, co jest konieczne do uruchomienia strony, ale nic, co należy do określonej strony.

Układ motywu, którego teraz używamy, jest podobny do wzorca projektowego MCV, ale w proceduralnym stylu kodowania.

Na przykład nasza strona członkowska:

page-member.php . Odpowiedzialny za inicjowanie strony. Wywoływanie poprawnych funkcji ajax lub podobnych. Może być równoważne części kontrolera w stylu MCV.

functions-member.php . Zawiera wszystkie funkcje związane z tą stroną. Jest to również zawarte na innych serwerowych stronach, które potrzebują funkcji dla naszych członków.

content-member.php . Przygotowuje dane dla HTML Może być równoważne z modelem w MCV.

layout-member.php . Część HTML.

Po wprowadzeniu tych zmian czas programowania łatwo spadł o 50%, a teraz właściciel produktu ma problem z powierzeniem nam nowych zadań. :)

Patrik Grinsvall
źródło
7
Aby uczynić to bardziej pomocnym, możesz rozważyć pokazanie, jak naprawdę działa ten wzorzec MVC.
kaiser
Byłbym również ciekawy zobaczyć przykład twojego podejścia, najlepiej z pewnymi szczegółami / różnymi sytuacjami. Podejście to brzmi bardzo interesująco. Czy porównałeś obciążenie / wydajność serwera ze standardową metodologią stosowaną przez innych? podaj przykład github, jeśli to w ogóle możliwe.
NetConstructor.com,
3

Z pliku motywów potomnych functions.php:

    require_once( get_stylesheet_directory() . '/inc/custom.php' );
Brad Dalton
źródło
0

W funkcji.php bardziej eleganckim sposobem wywołania wymaganego pliku byłoby:

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

Imperatywne pomysły
źródło
4
locate_template()ma trzeci parametr…
fuxia
0

Połączyłem odpowiedzi @kaiser i @mikeschinkel .

Mam wszystkie dostosowania mojego motywu w /includesfolderze, aw tym folderze mam wszystko podzielone na podfoldery.

Chcę tylko, aby /includes/adminwraz z jego podtekstem były uwzględniane kiedytrue === is_admin()

Jeśli folder zostanie wykluczony iterator_check_traversal_callbackpoprzez powrót, falsejego podkatalogi nie będą iterowane (ani przekazywane do 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
źródło