Mã tổ chức trong tệp tin.php của WordPress Theme của bạn?


92

Tôi càng tạo ra nhiều tùy chỉnh cho WordPress, tôi càng bắt đầu suy nghĩ về việc mình nên tổ chức tệp này hay chia nhỏ nó ra.

Cụ thể hơn, nếu tôi có một loạt các chức năng tùy chỉnh chỉ áp dụng cho khu vực quản trị viên và các chức năng khác chỉ áp dụng cho trang web công cộng của tôi thì có lý do gì để có thể bao gồm tất cả các chức năng quản trị trong tệp riêng của họ hoặc nhóm chúng lại với nhau không?

Việc chia chúng thành các tệp riêng biệt hoặc nhóm chúng lại với nhau có thể tăng tốc trang web WordPress hay WordPress / PHP sẽ tự động bỏ qua các chức năng có tiền tố mã is_admin?

Cách tốt nhất để xử lý một tệp chức năng lớn (của tôi dài 1370 dòng).

Câu trả lời:


120

Nếu bạn đang đi đến điểm mà mã trong chủ đề của bạn functions.phpbắt đầu áp đảo bạn, tôi chắc chắn sẽ nói rằng bạn đã sẵn sàng xem xét việc chia nó thành nhiều tệp. Tôi có xu hướng làm điều đó gần như bởi bản chất thứ hai vào thời điểm này.

Sử dụng Bao gồm tập tin trong Theme của bạn functions.phptập tin

Tôi tạo một thư mục con có tên "bao gồm" trong thư mục chủ đề của mình và phân đoạn mã của tôi thành các tệp được sắp xếp theo ý nghĩa của tôi tại thời điểm đó (có nghĩa là tôi liên tục tái cấu trúc và di chuyển mã xung quanh khi một trang web phát triển.) Tôi cũng hiếm khi đặt bất kỳ mã thực sự vào functions.php; tất cả mọi thứ đi trong các tập tin bao gồm; chỉ là sở thích của tôi

Chỉ để cung cấp cho bạn một ví dụ ở đây cài đặt thử nghiệm của tôi mà tôi sử dụng để kiểm tra câu trả lời của mình cho các câu hỏi ở đây trên Câu trả lời WordPress. Mỗi khi tôi trả lời một câu hỏi, tôi sẽ giữ mã xung quanh trong trường hợp tôi cần lại. Đây không phải là chính xác những gì bạn sẽ làm cho một trang web trực tiếp nhưng nó cho thấy các cơ chế phân tách mã:

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

Hoặc tạo plugin

Một tùy chọn khác để bắt đầu nhóm mã của bạn theo chức năng và tạo các plugin của riêng bạn. Đối với tôi, tôi bắt đầu mã hóa trong functions.phptệp của chủ đề và vào thời điểm tôi nhận được mã được bổ sung, tôi đã chuyển phần lớn mã của mình vào các plugin.

Tuy nhiên KHÔNG có hiệu suất đáng kể đạt được từ tổ chức mã PHP

Mặt khác, cấu trúc các tệp PHP của bạn là 99% về việc tạo thứ tự và khả năng duy trì và 1% về hiệu suất, nếu điều đó (tổ chức .js.csscác tệp được trình duyệt gọi qua HTTP là một trường hợp hoàn toàn khác và có ý nghĩa hiệu suất rất lớn.) Nhưng cách bạn tổ chức mã PHP của bạn trên máy chủ khá nhiều không quan trọng từ góc độ hiệu năng.

Và tổ chức mã là sở thích cá nhân

Và cuối cùng nhưng không kém phần quan trọng là tổ chức mã là sở thích cá nhân. Một số người sẽ ghét cách tôi tổ chức mã giống như tôi có thể ghét cách họ cũng làm như vậy. Tìm thứ gì đó bạn thích và gắn bó với nó, nhưng cho phép chiến lược của bạn phát triển theo thời gian khi bạn tìm hiểu thêm và thoải mái hơn với nó.


Câu trả lời hay, tôi vừa mới đến điểm này, nơi tôi cần phải chia các tập tin chức năng. Khi nào bạn nghĩ sẽ thuận tiện khi chuyển từ frifts.php sang plugin. Bạn đã nói trong câu trả lời của mình: vào thời điểm tôi nhận được mã xác thực, tôi đã chuyển phần lớn mã của mình vào các plugin . Tôi không hiểu điều đó một cách đầy đủ, bạn có ý nghĩa gì với xác thịt.
Saif Bechan

5
+1 cho "hoặc tạo plugin". Cụ thể hơn, " plugin chức năng "
Ian Dunn

3
sử dụng các đường dẫn tương đối có thể không đáng tin cậy trong tất cả các loại cài đặt, thay vào đó, đường dẫn tuyệt đối phải luôn được sử dụng
Mark Kaplun

2
@MarkKaplun - Bạn hoàn toàn chính xác. Kể từ khi tôi viết câu trả lời này, tôi đã học được bài học đó một cách khó khăn. Tôi sẽ cập nhật câu trả lời của tôi. Cảm ơn đã chỉ ra điều này.
MikeSchinkel

Tôi nhận được "Sử dụng hằng số không xác định DIR - giả sử ' DIR ' trong C: \ wamp \ www \ site \ wp-content \ Themes \ huyền thoại \ tests.php" - PHP v5.6.25 và PHP v7.0.10 - Tôi không thể định dạng đúng TRỰC TIẾP này trong nhận xét (gạch dưới dấu gạch ngangDIRunderscoreunderscore), nhưng nó hoạt động với dirname (gạch dưới dấu gạch dướiFILEunderscoreunderscore)
Marko

50

Câu trả lời muộn

Cách bao gồm các tệp của bạn đúng cách:

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

Các plugin cũng vậy.

Làm thế nào để đi đúng đường hoặc URi

Ngoài ra hãy xem các chức năng API hệ thống tệp như:

  • home_url()
  • plugin_dir_url()
  • plugin_dir_path()
  • admin_url()
  • get_template_directory()
  • get_template_directory_uri()
  • get_stylesheet_directory()
  • get_stylesheet_directory_uri()
  • Vân vân.

Làm thế nào để giảm số lượng include/require

Nếu bạn cần tìm nạp tất cả các tệp từ một thư mục, hãy đi với

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

Hãy nhớ rằng điều này bỏ qua các lỗi (có thể tốt cho sử dụng sản xuất) / không thể tải các tệp.

Để thay đổi hành vi này, bạn có thể muốn sử dụng một cấu hình khác trong quá trình phát triển:

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

Chỉnh sửa: Cách tiếp cận OOP / SPL

Khi tôi vừa trở lại và thấy rằng câu trả lời này ngày càng được nâng cao hơn, tôi nghĩ rằng tôi có thể cho thấy cách mà tôi đang làm ngày nay - trong một thế giới PHP 5.3+. Ví dụ sau đây tải tất cả các tệp từ thư mục con chủ đề có tên src/. Đây là nơi tôi có các thư viện của mình xử lý một số tác vụ nhất định như menu, hình ảnh, v.v. Thậm chí bạn không cần phải quan tâm đến tên khi mỗi tệp được tải. Nếu bạn có các thư mục con khác trong thư mục này, chúng sẽ bị bỏ qua.

Các \FilesystemIteratorlà PHP 5.3+ supercedor qua \DirectoryIterator. Cả hai đều là một phần của SPL PHP. Trong khi PHP 5.2 cho phép tắt phần mở rộng SPL tích hợp (dưới 1% trong số tất cả các cài đặt đã làm điều đó), SPL bây giờ là một phần của lõi PHP.

<?php

namespace Theme;

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

Trước đây trong khi tôi vẫn hỗ trợ PHP 5.2.x, tôi đã sử dụng giải pháp sau: A \FilterIteratortrong src/Filtersthư mục để chỉ truy xuất tệp (và không chấm con trỏ của thư mục) và a \DirectoryIteratorđể thực hiện lặp và tải.

namespace Theme;

use Theme\Filters\IncludesFilter;

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

Các \FilterIteratorlà dễ dàng như vậy:

<?php

namespace Theme\Filters;

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

Ngoài việc PHP 5.2 đã chết / EOL cho đến bây giờ (và cả 5.3), có một thực tế là có thêm mã và một tệp nữa trong trò chơi, vì vậy không có lý do gì để đi sau và hỗ trợ PHP 5.2.x.

Tóm tắt

Một bài viết chuyên sâu hơn nữa có thể được tìm thấy ở đây trên WPKrauts .

EDIT Cách rõ ràng chính xác là sử dụng namespacemã d, được chuẩn bị cho tự động tải PSR-4 bằng cách đặt mọi thứ vào thư mục thích hợp đã được xác định thông qua không gian tên. Sau đó, chỉ cần sử dụng Trình soạn thảo và a composer.jsonđể quản lý các phụ thuộc của bạn và để nó tự động xây dựng trình tải tự động PHP của bạn (nhập tự động một tệp chỉ bằng cách gọi use \<namespace>\ClassName). Đó là tiêu chuẩn thực tế trong thế giới PHP, cách dễ nhất để đi và thậm chí còn được tự động hóa và đơn giản hóa hơn bởi WP Starter .


5

về mặt phá vỡ nó, trong tấm nồi hơi của tôi, tôi sử dụng một chức năng tùy chỉnh để tìm kiếm một thư mục có tên là các chức năng trong thư mục chủ đề, nếu nó không ở đó thì nó tạo ra nó. Sau đó, tạo một mảng gồm tất cả các tệp .php mà nó tìm thấy trong thư mục đó (nếu có) và chạy tệp bao gồm (); trên mỗi người trong số họ.

Theo cách đó, mỗi lần tôi cần viết một số chức năng mới, tôi chỉ cần thêm một tệp PHP vào thư mục hàm và không phải lo lắng về việc mã hóa nó vào trang web.

<?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 : Thủ thuật hay. Cá nhân tôi sẽ không sử dụng nó cho mã sản xuất bởi vì nó làm cho mỗi trang tải những gì chúng ta có thể dễ dàng làm một lần khi chúng tôi khởi chạy trang web. Ngoài ra, tôi sẽ thêm một số cách để bỏ qua các tệp, như không tải bất cứ thứ gì bắt đầu bằng dấu gạch dưới để tôi vẫn có thể lưu trữ các tác phẩm đang diễn ra trong thư mục chủ đề. Nếu không, tốt đẹp!
MikeSchinkel

thích ý tưởng này nhưng tôi đồng ý rằng điều này có thể dẫn đến tải không cần thiết cho mỗi yêu cầu. Bất kỳ ý tưởng nào sẽ có một cách đơn giản để tệp tin.php cuối cùng được tạo tự động lưu trữ với một số loại cập nhật nếu / khi các tệp mới được thêm vào hoặc trong một khoảng thời gian cụ thể?
NetConstructor.com

Đẹp nhưng nó dẫn đến sự không linh hoạt, còn điều gì xảy ra nếu kẻ tấn công quản lý để thả mã của họ vào đó? Và nếu thứ tự bao gồm là quan trọng?
Tom J Nowell

1
@MikeSchinkel Tôi chỉ cần gọi các tệp làm việc của mình là foo._php, sau đó thả _php khi tôi muốn nó chạy.
Fuzz nhẹ

@NetConstructor: Cũng sẽ quan tâm đến một số giải pháp.
kaiser

5

Tôi thích sử dụng một chức năng cho các tập tin trong một thư mục. Cách tiếp cận này giúp dễ dàng thêm các tính năng mới khi thêm tệp mới. Nhưng tôi viết luôn trong lớp hoặc với các không gian tên - cung cấp cho nó nhiều quyền kiểm soát hơn về Không gian tên của các hàm, phương thức, v.v.

Dưới một ví dụ nhỏ; ut cũng useage với thỏa thuận về lớp * .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;

}

Trong Chủ đề tôi thường sử dụng một kịch bản khác. Tôi xác định chức năng của tệp externel trong ID hỗ trợ, xem ví dụ. Điều đó là hữu ích nếu tôi sẽ dễ dàng hủy kích hoạt feture của tệp externel. Tôi sử dụng chức năng lõi WP require_if_theme_supports()và anh ta chỉ tải, nếu ID hỗ trợ đã hoạt động. Trong ví dụ sau, tôi đã hủy ID được hỗ trợ này trong dòng trước khi tải tệp.

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

Bạn có thể thấy nhiều hơn về điều này trong repo của chủ đề này .


4

Tôi quản lý một trang web với khoảng 50 loại trang tùy chỉnh duy nhất bằng các ngôn ngữ khác nhau của máy chủ qua cài đặt mạng. Cùng với một TẤN các plugin.

Chúng tôi buộc phải chia tách tất cả tại một số điểm. Một tệp chức năng với 20-30k dòng mã không hài hước chút nào.

Chúng tôi quyết định hoàn thành cấu trúc lại tất cả các mã để quản lý cơ sở mã tốt hơn. Cấu trúc chủ đề wordpress mặc định là tốt cho các trang web nhỏ, nhưng không phải cho các trang web lớn hơn.

Hàm mới.php của chúng tôi chỉ chứa những gì cần thiết để bắt đầu trang web, nhưng không có gì thuộc về một trang cụ thể.

Bố cục chủ đề chúng ta sử dụng bây giờ tương tự như mẫu thiết kế MCV, nhưng theo kiểu mã hóa thủ tục.

Ví dụ trang thành viên của chúng tôi :

trang-thành viên.php . Chịu trách nhiệm khởi tạo trang. Gọi các hàm ajax chính xác hoặc tương tự. Có thể tương đương với phần Bộ điều khiển theo kiểu MCV.

Hàm-thành viên.php . Chứa tất cả các chức năng liên quan đến trang này. Điều này cũng được bao gồm trong các trang khác của máy chủ cần chức năng cho các thành viên của chúng tôi.

nội dung-thành viên.php . Chuẩn bị dữ liệu cho HTML có thể tương đương với Mô hình trong MCV.

bố trí thành viên.php . Phần HTML.

Sau khi chúng tôi thực hiện những thay đổi này, thời gian phát triển đã dễ dàng giảm 50% và bây giờ chủ sở hữu sản phẩm gặp khó khăn khi giao cho chúng tôi các nhiệm vụ mới. :)


7
Để làm cho điều này hữu ích hơn, bạn có thể xem xét hiển thị cách mô hình MVC này thực sự hoạt động.
kaiser

tôi cũng sẽ rất vui khi thấy một ví dụ về cách tiếp cận của bạn, tốt nhất là với một số chi tiết / tình huống khác nhau. Cách tiếp cận nghe có vẻ rất can thiệp. Bạn đã so sánh tải / hiệu suất của máy chủ với phương pháp chuẩn mà người khác sử dụng chưa? không cung cấp một ví dụ github nếu có thể.
NetConstructor.com

3

Từ tập tin chủ đề con.php.

    require_once( get_stylesheet_directory() . '/inc/custom.php' );


0

Tôi kết hợp @kaiser 's và @mikeschinkel câu trả lời' s.

Tôi có tất cả các tùy chỉnh cho chủ đề của mình trong một /includesthư mục và trong thư mục đó, tôi có mọi thứ được chia thành các thư mục phụ.

Tôi chỉ muốn /includes/adminvà nội dung phụ của nó được bao gồm khitrue === is_admin()

Nếu một thư mục được loại trừ iterator_check_traversal_callbackbằng cách trả về falsethì thư mục con của nó sẽ không bị lặp lại (hoặc được chuyển đến 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();
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.