Організація коду у файлі function.php у темі WordPress Theme?


92

Чим більше я налаштовую на WordPress, тим більше я починаю замислюватися над тим, чи варто мені організовувати цей файл чи розбивати його.

Більш конкретно, якщо у мене є маса спеціальних функцій, які застосовуються лише до адміністративної області та інших, які просто застосовуються до мого загальнодоступного веб-сайту, чи є якась причина, можливо, включити всі функції адміністратора у свій власний файл або згрупувати їх разом?

Чи розділить їх на окремі файли або згрупує їх разом, можливо, пришвидшить веб-сайт WordPress або WordPress / PHP автоматично пропускає функції, які мають префікс коду is_admin?

Який найкращий спосіб працювати з великим файлом функцій (мій - 1370 рядків).

Відповіді:


120

Якщо ви доходите до того, що код у вашій темі functions.phpпочинає переповнювати вас, я б точно сказав, що ви готові розглянути питання про його поділ на кілька файлів. Я схильний робити це майже за другою природою в цей момент.

Використання інклюдніка в вашої теми functions.phpфайлу

Я створюю підкаталог під назвою "включає" в моєму каталозі тем і сегментую свій код, включаючи файли, впорядковані тим, що для мене є сенсом у той час (це означає, що я постійно переробляю і переміщую код по мірі розвитку сайту.) Я також рідко помістити будь-який реальний код functions.php; все йде у файлах включення; просто моє вподобання.

Просто для прикладу ось моя тестова установка, яку я використовую для тестування своїх відповідей на запитання тут у відповідях WordPress. Кожен раз, коли я відповідаю на запитання, я зберігаю код на випадок, якщо він мені знову потрібен. Це не зовсім те, що ви будете робити для прямого сайту, але воно показує механіку розбиття коду:

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

Або створити плагіни

Ще один варіант - почати групувати код за функціями та створювати власні плагіни. Для мене я починаю кодувати у functions.phpфайлі теми, і до моменту отримання коду я переніс більшу частину свого коду в плагіни.

Однак НІ суттєвих прибутків від організації коду PHP

З іншого боку, структурування файлів PHP - це 99% на створення порядку та ремонтопридатності та 1% на продуктивність, якщо це (організація .jsта .cssфайли, викликані браузером через HTTP - це зовсім інший випадок і має величезні наслідки для продуктивності.) Але як ви організуєте ваш PHP-код на сервері в значній мірі не має значення з точки зору продуктивності.

А організація коду - це особисті переваги

І останнє, але не в останню чергу організація коду - особисті переваги. Деякі люди ненавиджу, як я впорядковую код, так само як я можу ненавиджу, як вони це роблять. Знайдіть щось, що вам подобається, і дотримуйтесь цього, але дозвольте своїй стратегії розвиватися з часом, коли ви дізнаєтесь більше і отримуєте більше комфорту з цим.


Приємна відповідь, я щойно дійшов до цього моменту, коли мені потрібно розділити файл функцій. Коли ви вважаєте, що зручно переходити від frunctions.php до плагіна. Ви сказали у своїй відповіді: до моменту отримання коду я перемістив більшу частину свого коду в плагіни . Я не розумію це повністю, що ви маєте на увазі під плоттю.
Сайф Бечан

5
+1 для "або створити плагіни". Більш конкретно, « функціональні плагіни »
Ian Dunn

3
використання відносних шляхів може не бути надійним у всіх видах налаштувань, натомість завжди слід використовувати абсолютний шлях
Марк Каплун

2
@MarkKaplun - Ви абсолютно праві. Оскільки я написав цю відповідь, я навчився цього уроку важким шляхом. Я збираюся оновити свою відповідь. Дякуємо, що вказали на це.
MikeSchinkel

Я отримую "Використання невизначеного постійного DIR - передбачається" DIR "в C: \ wamp \ www \ site \ wp-content \ themes \ mytheme \ функции.php" - PHP v5.6.25 та PHP v7.0.10 - не можу форматуйте цей DIR у коментарі (підкреслюйте підсумкиDIRunderscoreunderscore), але він працює з dirname (underscoreunderscoreFILEunderscoreunderscore)
Marko

50

Пізня відповідь

Як правильно включити свої файли:

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

Те саме працює і в плагінах.

Як дістати правильний шлях або URi

Також подивіться на такі функції API файлової системи, як:

  • home_url()
  • plugin_dir_url()
  • plugin_dir_path()
  • admin_url()
  • get_template_directory()
  • get_template_directory_uri()
  • get_stylesheet_directory()
  • get_stylesheet_directory_uri()
  • тощо.

Як зменшити кількість include/require

Якщо вам потрібно отримати всі файли з каталогу, перейдіть з

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

Майте на увазі, що це ігнорує збої (можливо, добре для виробництва) / не завантажувані файли.

Щоб змінити таку поведінку, можливо, ви хочете використовувати інший конфігурацію під час розробки:

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

Редагувати: підхід OOP / SPL

Щойно я повернувся і побачив, що ця відповідь набирає все більшої зміни, я подумав, що можу показати, як це роблю в наш час - у світі PHP 5.3+. У наступному прикладі завантажуються всі файли з підтеки теми з назвою src/. Тут я маю свої бібліотеки, які займаються певними завданнями, такими як меню, зображення тощо. Вам навіть не потрібно піклуватися про ім’я, оскільки кожен файл завантажується. Якщо в цьому каталозі є інші підпапки, вони ігноруються.

\FilesystemIteratorЦе в PHP 5.3 + supercedor над \DirectoryIterator. Обидва є частиною PHP SPL. У той час як PHP 5.2 дозволив вимкнути вбудований розширення SPL (менше 1% усіх встановлень), SPL тепер є частиною PHP.

<?php

namespace Theme;

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

Раніше, поки я ще підтримував PHP 5.2.x, я використовував таке рішення: A \FilterIteratorу src/Filtersкаталозі, щоб отримати лише файли (а не крапки вказівників папок) та a \DirectoryIteratorдля виконання циклічного та завантажувального завантаження.

namespace Theme;

use Theme\Filters\IncludesFilter;

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

Це \FilterIteratorбуло так само просто:

<?php

namespace Theme\Filters;

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

На додаток до того, що PHP 5.2 вже є мертвим / EOL (і 5.3), є факт, що це більше коду та ще один файл у грі, тому немає ніяких причин піти з пізнішою та підтримувати PHP 5.2.x.

Підведені

Ще більш глибоку статтю можна знайти тут на WPKrauts .

EDIT Очевидно правильним способом є використання namespaced-коду, підготовленого для автоматичного завантаження PSR-4 , помістивши все у відповідний каталог, який уже визначений через простір імен. Тоді просто використовуйте Composer та a, composer.jsonщоб керувати своїми залежностями, і дозвольте йому автоматично створити автозавантажувач PHP (який автоматично імпортує файл, просто зателефонувавши use \<namespace>\ClassName). Це стандарт де-факто у світі PHP, найпростіший шлях та ще більш попередньо автоматизований та спрощений WP Starter .


5

з точки зору її розбиття, я використовую власну функцію, щоб шукати папку з назвою функцій у тематичному каталозі, якщо її немає, вона створює її. Потім створює масив усіх .php-файлів, які він знаходить у цій папці (якщо такі є) і запускає include (); на кожному з них.

Таким чином, кожного разу, коли мені потрібно написати якийсь новий функціонал, я просто додаю PHP-файл до папки функцій, і не потрібно турбуватися про його кодування на сайті.

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

}

5
@mildfuzz : Хороший трюк. Я особисто не використовував би його для виробничого коду, оскільки він робить для кожної сторінки завантаження того, що ми могли легко зробити один раз, коли ми запускаємо сайт. Крім того, я б додав якимось чином пропускати файли, як, наприклад, не завантажувати нічого, починаючи з підкреслення, щоб я міг зберігати незавершені роботи в каталозі тем. Інакше приємно!
MikeSchinkel

дуже подобається ідея, але я згоден, це може призвести до зайвого завантаження кожного запиту. Будь-яка ідея, чи існував би простий спосіб автоматичного кешування кінцевого файлу function.php з певним оновленням, якщо / коли нові файли додаються або через певний проміжок часу?
NetConstructor.com

Приємно, але це призводить до гнучкості, а що станеться, якщо зловмиснику вдасться скинути там свій код? А що робити, якщо впорядкування включає важливе значення?
Том Дж. Ноуелл

1
@MikeSchinkel Я просто називаю свої робочі файли foo._php, а потім кидаю _php, коли я хочу, щоб він працював.
Легкий фузз

@NetConstructor: Був би зацікавлений і якийсь забруднення.
кайзер

5

Мені подобається використовувати функцію для файлів всередині папки. Цей підхід дозволяє легко додавати нові функції при додаванні нових файлів. Але я пишу завжди в класі або з просторами імен - надайте йому більше контролю над простором імен функцій, методом тощо.

Нижче невеликий приклад; ut також useage з домовленістю про клас * .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;

}

У Темах я часто використовую інший сценарій. Я визначаю функцію зовнішнього файлу в ідентифікаторі підтримки, див. Приклад. Це корисно, якщо я легко деактивую зовнішній вигляд файлу зовнішньої програми. Я використовую основну функцію WP, require_if_theme_supports()і він завантажується тільки, якщо підтримка ID була активною. У наступному прикладі я розробив цей підтримуваний ідентифікатор у рядку перед завантаженням файлу.

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

Більше цього ви можете побачити в репо-темі цієї теми .


4

Я керую сайтом з приблизно 50 унікальними типовими сторінками сторінок на різних мовах через мережу. Поряд з TON плагінів.

Ми десь змушені було це розділити в якийсь момент. Файл функцій з 20-30k рядків коду зовсім не смішний.

Ми вирішили виконати повний рефакторний код, щоб краще керувати базою кодів. Структура теми WordPress за замовчуванням хороша для невеликих сайтів, але не для великих сайтів.

Наш новий function.php містить лише те, що необхідно для запуску сайту, але нічого, що належить певній сторінці.

Макет теми, який ми зараз використовуємо, схожий на модель дизайну MCV, але в стилі процедурного кодування.

Наприклад, наша сторінка:

page-member.php . Відповідальний за ініціалізацію сторінки. Виклик правильних функцій ajax або подібних. Може бути еквівалентним частині контролера в стилі MCV.

Функції-member.php . Містить усі функції, пов’язані з цією сторінкою. Це також включено до інших інших сторінок, які потребують функцій для наших членів.

content-member.php . Підготовка даних для HTML може бути еквівалентною Моделі в MCV.

layout-member.php . Частина HTML.

Якщо ми зробили ці зміни, час розробки легко впав на 50%, і тепер у власника продукту виникають проблеми з завданням нових завдань. :)


7
Щоб зробити це більш корисним, ви можете розглянути можливість показу цього способу MVC.
кайзер

мені також було б цікаво побачити приклад вашого підходу, бажано з деякими деталями / різними ситуаціями. Підхід звучить дуже цікаво. Чи порівняли ви завантаження / продуктивність сервера зі стандартною методологією, яку використовують інші? надайте приклад github, якщо це можливо.
NetConstructor.com



0

Я поєднав відповіді @kaiser та @mikeschinkel .

У мене всі налаштування моєї теми в /includesпапці, і всередині цієї папки я все розбила на підпапки.

Я хочу лише, щоб /includes/adminйого підміст було включено, колиtrue === is_admin()

Якщо папка, яка iterator_check_traversal_callbackповертається, виключена , falseїї підкаталоги не будуть повторені (або передані 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();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.