在WordPress主题的functions.php文件中组织代码?


92

我对WordPress进行的自定义设置越多,我就开始考虑是否应该组织此文件或将其拆分。

更具体地说,如果我有一堆仅适用于管理区域的自定义功能,而其他仅适用于我的公共网站的自定义功能,是否有任何理由可能将所有管理功能包含在自己的文件中或将它们组合在一起?

将它们拆分成单独的文件或将它们分组在一起可能会加快WordPress网站的速度,还是WordPress / PHP是否会自动跳过具有is_admin代码前缀的功能?

处理大型函数文件(最小长度为1370行)的最佳方法是什么。

Answers:


120

如果您到了主题的代码functions.php开始不知所措的地步,那么我肯定会说您已经准备好考虑将其拆分为多个文件。在这一点上,我倾向于几乎是天生的。

在主题文件中使用包含functions.php文件

我在主题目录下创建一个名为“ includes”的子目录,并将代码按当时对我有意义的方式组织到包含文件中(这意味着我会随着网站的发展不断地重构和移动代码。)我也很少放入任何真实代码functions.php; 一切都在包含文件中;只是我的偏爱。

仅举一个例子,这是我的测试安装,用于测试WordPress Answers上的问题答案。每当我回答一个问题时,我都会保留代码以防万一我再次需要它。这并不完全是您对实时站点所做的工作,但是它显示了拆分代码的机制:

<?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%涉及性能(如果是这样的话)(浏览器通过HTTP调用的组织.js.css文件是完全不同的情况,并且对性能有巨大影响。)但是,如何组织从性能的角度来看,服务器上的PHP代码几乎无关紧要。

代码组织是个人偏好

最后但并非最不重要的代码组织是个人喜好。某些人会讨厌我如何组织代码,就像我可能会讨厌他们也是如此。找到喜欢的东西并坚持下去,但是随着您学到更多并逐渐适应它,策略会随着时间的推移而发展。


好的答案,我刚到这里需要分割功能文件。您何时认为从frunctions.php转到插件很方便。您在回答中说:到我充实代码的时候,我已经将大部分代码移到了plugins中。我不完全理解,充血是什么意思。
Saif Bechan

5
为“或创建插件” +1。更具体地说,“ 功能插件
Ian Dunn

3
使用相对路径可能不是各种设置可靠,绝对路径应该始终用来代替
马克·卡普伦

2
@MarkKaplun-你是绝对正确的。自从我写了这个答案以来,我就很难学到这一课。我将更新我的答案。感谢您指出了这一点。
MikeSchinkel '16

我收到“使用未定义的常量DIR-在C:\ wamp \ www \ site \ wp-content \ themes \ mytheme \ functions.php中假定为' DIR '”-PHP v5.6.25和PHP v7.0.10-我无法正确格式化此DIR的注释格式(underscoreunderscoreDIRunderscoreunderscore),但可与目录名一起使用(underscoreunderscoreFILEunderscoreunderscore)
Marko 2016年

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,我用了以下解决方案:一个\FilterIteratorsrc/Filters目录中只检索文件(而不是点文件夹的指针)和\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到现在已经死/停产(以及5.3)之外,还有一个事实,那就是游戏中包含了更多的代码和一个文件,因此没有理由去支持后者并支持PHP5.2.x。

总结

在WPKrauts上可以找到更深入的文章。

编辑显然,正确的方法是使用namespaced代码,将其放置在已经通过名称空间定义的适当目录中,从而为PSR-4自动加载做好了准备。然后,只需使用Composer和a composer.json来管理您的依赖项,并使其自动构建您的PHP自动加载器(通过调用即可自动导入文件use \<namespace>\ClassName)。这是PHP世界中的事实上的标准,这是最简单的方法,并且由WP Starter进行了更加自动化和简化。


5

关于分解,在样板中,我使用一个自定义函数在主题目录中查找一个名为functions的文件夹,如果没有,它将创建它。然后,创建一个在该文件夹(如果有)中找到的所有.php文件的数组,并运行一个include();。在每个。

这样,每次我需要编写一些新功能时,我只需将PHP文件添加到functions文件夹,而不必担心将其编码到站点中。

<?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 2010年

喜欢这个主意,但我同意这可能会导致每个请求不必要的加载。是否知道是否有一种简单的方法可以使最终生成的functions.php文件在添加新文件时或在特定时间间隔自动缓存并以某种类型的更新进行缓存?
NetConstructor.com 2010年

很好,但这会导致灵活性不足,如果攻击者设法将其代码放在那里,该怎么办?而且,如果包含的顺序很重要怎么办?
汤姆·J·诺维尔

1
@MikeSchinkel我只是将工作文件命名为foo._php,然后在希望运行时删除_php。
轻微的绒毛2010年

@NetConstructor:也会对某些解决方案感兴趣。
kaiser

5

我喜欢对文件夹内的文件使用函数。这种方法使添加新文件时轻松添加新功能。但是我总是用类或命名空间编写-给它更多关于函数,方法等的命名空间的控制权。

下面是一个小例子;ut也可以使用有关class * .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;

}

在主题中,我经常使用其他方案。我在支持ID中定义了外部文件的功能,请参见示例。如果我将轻松停用外部文件的功能,那将很有用。我使用WP核心功能,require_if_theme_supports()并且仅在支持ID处于活动状态时才加载。在以下示例中,我在加载文件之前在行中定义了此受支持的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-30k行代码的函数文件一点都不有趣。

我们决定完成对所有代码的重构,以便更好地管理代码库。默认的wordpress主题结构适用于小型网站,但不适用于大型网站。

我们的新functions.php仅包含启动站点所需的内容,但不包含属于特定页面的内容。

我们现在使用的主题布局类似于MCV设计模式,但是采用过程编码样式。

例如我们的会员页面:

page-member.php。负责初始化页面。调用正确的ajax函数或类似函数。可能与MCV样式的Controller零件等价。

functions-member.php。包含与此页面相关的所有功能。这也包含在其他需要我们成员功能的页面中。

content-member.php。为HTML准备数据可以等同于MCV中的模型。

layout-member.php。HTML部分。

我们进行了这些更改后,开发时间轻松减少了50%,现在产品负责人无法给我们新任务。:)


7
为了使此功能更有用,您可以考虑显示此MVC模式是如何工作的。
kaiser 2012年

我也很急于看到您的方法的示例,最好提供一些细节/各种情况。这个方法听起来很有趣。您是否将服务器负载/性能与其他人使用的标准方法进行了比较?尽可能提供一个github示例。
NetConstructor.com 2012年


0

在functions.php中,调用所需文件的一种更优雅的方法是:

require_oncelocate_template('/ inc / functions / shortcodes.php');


4
locate_template()有第三个参数…
fuxia

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();
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.