Организация кода в файле functions.php вашей темы WordPress?


92

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

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

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

Как лучше всего работать с большим файлом функций (у меня длина 1370 строк).

Ответы:


120

Если вы дошли до того, что код в вашей теме functions.phpначинает перегружать вас, я бы определенно сказал, что вы готовы рассмотреть возможность разбивки его на несколько файлов. Я склонен делать это почти второй натурой на данный момент.

Использование инклюдника в вашей темы functions.phpфайла

Я создаю подкаталог под названием « include » в моей директории темы и сегментирую свой код на включаемые файлы, организованные в соответствии с тем, что имеет для меня смысл в то время (что означает, что я постоянно реорганизую и перемещаю код по мере развития сайта). Я также редко положить любой реальный код в 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 за "или создать плагины". Более конкретно, « функциональные плагины »
Ян Данн

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

2
@MarkKaplun - Вы абсолютно правы. С тех пор как я написал этот ответ, я усвоил этот урок нелегким путем. Я собираюсь обновить свой ответ. Спасибо за указание на это.
MikeSchinkel

Я получаю «Использование неопределенной постоянной DIR - предполагается, что« DIR »в C: \ wamp \ www \ site \ wp-content \ themes \ mytheme \ functions.php» - PHP v5.6.25 и PHP v7.0.10 - я не могу правильно отформатируйте этот DIR в комментарии (underscoreunderscoreDIRunderscoreunderscore), но он работает с dirname (underscoreunderscoreFILEunderscoreunderscore)
Марко

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;

Изменить: ООП / 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 .

РЕДАКТИРОВАТЬ Очевидно, что правильный способ - использовать namespaced-код, подготовленный для автозагрузки PSR-4 , поместив все в соответствующий каталог, который уже определен через пространство имен. Затем просто используйте Composer и a composer.jsonдля управления вашими зависимостями и позвольте ему автоматически собрать ваш автозагрузчик PHP (который автоматически импортирует файл, просто вызывая use \<namespace>\ClassName). Это де-факто стандарт в мире PHP, самый простой способ, и еще более предварительно автоматизированный и упрощенный WP Starter .


5

с точки зрения его разбора, в своей рабочей области я использую пользовательскую функцию для поиска папки с именем functions в каталоге темы, если ее там нет, она создает ее. Затем он создает массив всех файлов .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

Мне нравится идея, но я согласен, что это может привести к ненужной загрузке для каждого запроса. Любая идея, если бы был простой способ, чтобы конечный генерируемый файл functions.php автоматически кэшировался с некоторым типом обновления, если / когда добавляются новые файлы или через определенный промежуток времени?
NetConstructor.com

Хорошо, но это приводит к недостаткам, а также, что произойдет, если злоумышленнику удастся добавить туда свой код? А что, если порядок включений важен?
Том Дж. Новелл

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

@NetConstructor: Было бы интересно и решение.
Кайзер

5

Мне нравится использовать функцию для файлов внутри папки. Такой подход позволяет легко добавлять новые функции при добавлении новых файлов. Но я пишу всегда в классе или с пространствами имен - дайте ему больше контроля над пространством имен функций, методов и т. Д.

Ниже небольшой пример; но также использование с соглашением о классе * .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 уникальных пользовательских типов страниц на нескольких языках по сетевой установке. Вместе с тонной плагинов.

Мы были вынуждены разделить все это в какой-то момент. Файл функций с 20-30 тыс. Строк кода совсем не смешной.

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

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

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

Например, наша страница участника:

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

functions-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.