Đến đây đúng 2 năm sau khi câu hỏi ban đầu được hỏi, có một vài điều tôi muốn chỉ ra. (Đừng yêu cầu tôi chỉ ra nhiều thứ , bao giờ).
Móc đúng
Để khởi tạo một lớp plugin, nên sử dụng hook thích hợp . Không có một quy tắc chung nào, bởi vì nó phụ thuộc vào những gì lớp học làm.
Việc sử dụng một hook rất sớm "plugins_loaded"
thường không có ý nghĩa gì vì một hook như thế được kích hoạt cho các yêu cầu của admin, frontend và AJAX, nhưng thường thì hook sau đó tốt hơn nhiều vì nó chỉ cho phép khởi tạo các lớp plugin khi cần.
Ví dụ, một lớp làm công cụ cho các mẫu có thể được khởi tạo "template_redirect"
.
Nói chung, rất hiếm khi một lớp cần phải được khởi tạo trước khi "wp_loaded"
bị sa thải.
Không có lớp Chúa
Hầu hết tất cả các lớp được sử dụng làm ví dụ trong các câu trả lời cũ hơn đều sử dụng một lớp có tên như "Prefix_Example_Plugin"
hoặc "My_Plugin"
... Điều này cho thấy có lẽ có một lớp chính cho plugin.
Chà, trừ khi một plugin được tạo bởi một lớp duy nhất (trong trường hợp đó, đặt tên theo tên plugin là hoàn toàn hợp lý), để tạo một lớp quản lý toàn bộ plugin (ví dụ: thêm tất cả các hook mà plugin cần hoặc khởi tạo tất cả các lớp plugin khác ) có thể được coi là một thực hành xấu, như một ví dụ về một đối tượng thần .
Trong mã lập trình hướng đối tượng nên có xu hướng RẮN trong đó "S" là viết tắt của "Nguyên tắc trách nhiệm đơn lẻ" .
Nó có nghĩa là mỗi lớp nên làm một điều duy nhất. Trong phát triển plugin WordPress, điều đó có nghĩa là các nhà phát triển nên tránh sử dụng một hook duy nhất để khởi tạo một lớp plugin chính , nhưng các hook khác nhau nên được sử dụng để khởi tạo các lớp khác nhau, theo trách nhiệm của lớp.
Tránh móc trong constructor
Lập luận này đã được giới thiệu trong các câu trả lời khác ở đây, tuy nhiên tôi muốn nhận xét khái niệm này và liên kết câu trả lời khác này , nơi nó đã được giải thích khá rộng rãi trong mục đích thử nghiệm đơn vị.
Gần như 2015: PHP 5.2 dành cho zombie
Kể từ ngày 14 tháng 8 năm 2014, PHP 5.3 đã kết thúc vòng đời . Nó chắc chắn đã chết. PHP 5.4 sẽ được hỗ trợ cho cả năm 2015, điều đó có nghĩa là một năm nữa tại thời điểm tôi đang viết.
Tuy nhiên, WordPress vẫn hỗ trợ PHP 5.2, nhưng không ai nên viết một dòng mã hỗ trợ phiên bản đó, đặc biệt nếu mã là OOP.
Có nhiều lý do khác nhau:
- PHP 5.2 đã chết cách đây rất lâu, không có bản sửa lỗi bảo mật nào được phát hành cho nó, điều đó có nghĩa là nó không an toàn
- PHP 5.3 đã thêm rất nhiều tính năng cho PHP, các hàm ẩn danh và các không gian tên über alles
- các phiên bản mới hơn của PHP nhanh hơn rất nhiều . PHP là miễn phí. Cập nhật nó là miễn phí. Tại sao sử dụng phiên bản chậm hơn, không an toàn nếu bạn có thể sử dụng phiên bản nhanh hơn, an toàn hơn miễn phí?
Nếu bạn không muốn sử dụng mã PHP 5.4+, hãy sử dụng ít nhất 5,3+
Thí dụ
Tại thời điểm này là thời gian để xem xét các câu trả lời cũ hơn dựa trên những gì tôi đã nói cho đến đây.
Khi chúng ta không phải quan tâm đến 5.2 nữa, chúng ta có thể và nên sử dụng các không gian tên.
Để giải thích rõ hơn về nguyên tắc trách nhiệm duy nhất, ví dụ của tôi sẽ sử dụng 3 lớp, một lớp thực hiện điều gì đó ở ngoại vi, một lớp trên phụ trợ và lớp thứ ba được sử dụng trong cả hai trường hợp.
Lớp quản trị viên:
namespace GM\WPSE\Example;
class AdminStuff {
private $tools;
function __construct( ToolsInterface $tools ) {
$this->tools = $tools;
}
function setup() {
// setup class, maybe add hooks
}
}
Lớp trưởng:
namespace GM\WPSE\Example;
class FrontStuff {
private $tools;
function __construct( ToolsInterface $tools ) {
$this->tools = $tools;
}
function setup() {
// setup class, maybe add hooks
}
}
Giao diện công cụ:
namespace GM\WPSE\Example;
interface ToolsInterface {
function doSomething();
}
Và một lớp Công cụ, được sử dụng bởi hai người kia:
namespace GM\WPSE\Example;
class Tools implements ToolsInterface {
function doSomething() {
return 'done';
}
}
Có các lớp này, tôi có thể khởi tạo chúng bằng cách sử dụng các hook thích hợp. Cái gì đó như:
require_once plugin_dir_path( __FILE__ ) . 'src/ToolsInterface.php';
require_once plugin_dir_path( __FILE__ ) . 'src/Tools.php';
add_action( 'admin_init', function() {
require_once plugin_dir_path( __FILE__ ) . 'src/AdminStuff.php';
$tools = new GM\WPSE\Example\Tools;
global $admin_stuff; // this is not ideal, reason is explained below
$admin_stuff = new GM\WPSE\Example\AdminStuff( $tools );
} );
add_action( 'template_redirect', function() {
require_once plugin_dir_path( __FILE__ ) . 'src/FrontStuff.php';
$tools = new GM\WPSE\Example\Tools;
global $front_stuff; // this is not ideal, reason is explained below
$front_stuff = new GM\WPSE\Example\FrontStuff( $tools );
} );
Phụ thuộc đảo ngược & tiêm phụ thuộc
Trong ví dụ trên, tôi đã sử dụng các không gian tên và các hàm ẩn danh để khởi tạo các lớp khác nhau ở các hook khác nhau, đưa vào thực tế những gì tôi đã nói ở trên.
Lưu ý cách không gian tên cho phép tạo các lớp được đặt tên mà không có bất kỳ tiền tố nào.
Tôi đã áp dụng một khái niệm khác đã được đề cập gián tiếp ở trên: Dependency Injection , đó là một phương pháp để áp dụng Nguyên tắc đảo ngược phụ thuộc , "D" trong từ viết tắt RẮN.
Các Tools
lớp được "tiêm" trong hai lớp khác khi chúng được khởi tạo, vì vậy theo cách này nó có thể tách rời trách nhiệm.
Ngoài ra, AdminStuff
và FrontStuff
các lớp sử dụng gợi ý kiểu để khai báo họ cần một lớp thực hiện ToolsInterface
.
Theo cách này, bản thân chúng tôi hoặc người dùng sử dụng mã của chúng tôi có thể sử dụng các triển khai khác nhau của cùng một giao diện, làm cho mã của chúng tôi không được kết hợp với một lớp cụ thể mà là một sự trừu tượng: đó chính xác là Nguyên tắc đảo ngược phụ thuộc.
Tuy nhiên, ví dụ trên có thể được cải thiện hơn nữa. Chúng ta hãy xem làm thế nào.
Trình tải tự động
Một cách tốt để viết mã OOP dễ đọc hơn là không trộn lẫn các định nghĩa (Giao diện, Lớp) với mã khác và đặt mọi loại trong tệp riêng của nó.
Quy tắc này cũng là một trong những tiêu chuẩn mã hóa PSR-1 1 .
Tuy nhiên, làm như vậy, trước khi có thể sử dụng một lớp, người ta cần phải có tệp chứa nó.
Điều này có thể áp đảo, nhưng PHP cung cấp các hàm tiện ích để tự động tải một lớp khi được yêu cầu, sử dụng một hàm gọi lại tải một tệp dựa trên tên của nó.
Sử dụng không gian tên nó trở nên rất dễ dàng, bởi vì bây giờ có thể khớp cấu trúc thư mục với cấu trúc không gian tên.
Điều đó không chỉ có thể, mà còn là một tiêu chuẩn PSR khác (hoặc tốt hơn là 2: PSR-0 hiện không được chấp nhận và PSR-4 ).
Theo các tiêu chuẩn đó, có thể sử dụng các công cụ khác nhau để xử lý tự động tải mà không cần phải mã hóa trình tải tự động tùy chỉnh.
Tôi phải nói rằng các tiêu chuẩn mã hóa WordPress có các quy tắc khác nhau để đặt tên tệp.
Vì vậy, khi viết mã cho lõi WordPress, các nhà phát triển phải tuân theo các quy tắc WP, nhưng khi viết mã tùy chỉnh thì đó là lựa chọn của nhà phát triển, nhưng sử dụng tiêu chuẩn PSR thì dễ sử dụng hơn các công cụ đã viết 2 .
Các mẫu định vị truy cập, đăng ký và dịch vụ toàn cầu.
Một trong những vấn đề lớn nhất khi khởi tạo các lớp plugin trong WordPress, là làm thế nào để truy cập chúng từ các phần khác nhau của mã.
Bản thân WordPress sử dụng cách tiếp cận toàn cầu : các biến được lưu trong phạm vi toàn cầu, khiến chúng có thể truy cập ở mọi nơi. Mỗi nhà phát triển WP gõ từ global
hàng ngàn lần trong sự nghiệp của họ.
Đây cũng là cách tiếp cận tôi sử dụng cho ví dụ trên, nhưng nó là xấu xa .
Câu trả lời này đã quá dài để cho phép tôi giải thích thêm tại sao, nhưng đọc kết quả đầu tiên trong SERP cho "biến toàn cầu xấu" là điểm khởi đầu tốt.
Nhưng làm thế nào có thể tránh các biến toàn cầu?
Có nhiều cách khác nhau.
Một số câu trả lời cũ hơn ở đây sử dụng cách tiếp cận tĩnh .
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self;
}
return self::$instance;
}
Thật dễ dàng và khá ổn, nhưng nó buộc phải triển khai mô hình cho mọi lớp chúng ta muốn truy cập.
Hơn nữa, rất nhiều lần cách tiếp cận này đặt ra vấn đề về lớp thần, bởi vì các nhà phát triển tạo ra một lớp chính có thể truy cập bằng phương thức này, và sau đó sử dụng nó để truy cập vào tất cả các lớp khác.
Tôi đã giải thích mức độ tệ của một lớp thần, vì vậy cách tiếp cận thể hiện tĩnh là một cách tốt để đi khi một plugin chỉ cần làm cho một hoặc hai lớp có thể truy cập được.
Điều này không có nghĩa là nó chỉ có thể được sử dụng cho các plugin chỉ có một vài lớp, trên thực tế, khi nguyên tắc tiêm phụ thuộc được sử dụng đúng cách, có thể tạo các ứng dụng khá phức tạp mà không cần phải truy cập toàn cầu với số lượng lớn của các đối tượng.
Tuy nhiên, đôi khi các plugin cần làm cho một số lớp có thể truy cập được và trong trường hợp đó, cách tiếp cận thể hiện tĩnh là quá mức.
Một cách tiếp cận khác có thể là sử dụng mẫu đăng ký .
Đây là một thực hiện rất đơn giản của nó:
namespace GM\WPSE\Example;
class Registry {
private $storage = array();
function add( $id, $class ) {
$this->storage[$id] = $class;
}
function get( $id ) {
return array_key_exists( $id, $this->storage ) ? $this->storage[$id] : NULL;
}
}
Sử dụng lớp này có thể lưu trữ các đối tượng trong đối tượng đăng ký theo id, vì vậy có quyền truy cập vào sổ đăng ký, có thể có quyền truy cập vào tất cả các đối tượng. Tất nhiên khi một đối tượng được tạo lần đầu tiên, nó cần được thêm vào sổ đăng ký.
Thí dụ:
global $registry;
if ( is_null( $registry->get( 'tools' ) ) ) {
$tools = new GM\WPSE\Example\Tools;
$registry->add( 'tools', $tools );
}
if ( is_null( $registry->get( 'front' ) ) ) {
$front_stuff = new GM\WPSE\Example\FrontStuff( $registry->get( 'tools' ) );
$registry->add( 'front', front_stuff );
}
add_action( 'wp', array( $registry->get( 'front' ), 'wp' ) );
Ví dụ trên cho thấy rõ rằng để hữu ích, sổ đăng ký cần có thể truy cập được trên toàn cầu. Một biến toàn cục cho sổ đăng ký duy nhất không phải là rất xấu, tuy nhiên, đối với những người theo chủ nghĩa thuần túy toàn cầu, có thể thực hiện cách tiếp cận cá thể tĩnh cho một sổ đăng ký, hoặc có thể là một hàm có biến tĩnh:
function gm_wpse_example_registry() {
static $registry = NULL;
if ( is_null( $registry ) ) {
$registry = new GM\WPSE\Example\Registry;
}
return $registry;
}
Lần đầu tiên hàm được gọi, nó sẽ khởi tạo registry, trong các cuộc gọi tiếp theo, nó sẽ trả về nó.
Một phương thức dành riêng cho WordPress khác để tạo một lớp có thể truy cập toàn cầu là trả về một thể hiện đối tượng từ bộ lọc. Một cái gì đó như thế này:
$registry = new GM\WPSE\Example\Registry;
add_filter( 'gm_wpse_example_registry', function() use( $registry ) {
return $registry;
} );
Sau đó, mọi nơi đăng ký là cần thiết:
$registry = apply_filters( 'gm_wpse_example_registry', NULL );
Một mẫu khác có thể được sử dụng là mẫu định vị dịch vụ . Nó tương tự như mẫu đăng ký, nhưng các bộ định vị dịch vụ được chuyển đến các lớp khác nhau bằng cách sử dụng phép nội xạ phụ thuộc.
Vấn đề chính với mẫu này là nó ẩn các lớp phụ thuộc làm cho mã khó duy trì và đọc hơn.
DI container
Bất kể phương thức được sử dụng để làm cho đăng ký hoặc định vị dịch vụ có thể truy cập toàn cầu, các đối tượng phải được lưu trữ ở đó và trước khi được lưu trữ, chúng cần phải được khởi tạo.
Trong các ứng dụng phức tạp, nơi có khá nhiều lớp và nhiều lớp trong số chúng có một số phụ thuộc, các lớp khởi tạo đòi hỏi rất nhiều mã, vì vậy khả năng lỗi tăng lên: mã không tồn tại không có lỗi.
Trong những năm trước, đã xuất hiện một số thư viện PHP giúp các nhà phát triển PHP dễ dàng khởi tạo và lưu trữ các thể hiện của các đối tượng, tự động giải quyết các phụ thuộc của chúng.
Các thư viện này được gọi là Container phụ thuộc vì chúng có khả năng khởi tạo các lớp giải quyết các phụ thuộc và cũng để lưu trữ các đối tượng và trả lại chúng khi cần, hoạt động tương tự như một đối tượng đăng ký.
Thông thường, khi sử dụng các thùng chứa DI, các nhà phát triển phải thiết lập các phụ thuộc cho mỗi lớp của ứng dụng, và sau đó, lần đầu tiên một lớp là cần thiết trong mã, nó được khởi tạo với các phụ thuộc thích hợp và cùng một trường hợp được trả lại lần nữa cho các yêu cầu tiếp theo .
Một số bộ chứa DI cũng có khả năng tự động khám phá các phụ thuộc mà không cần cấu hình, nhưng sử dụng phản chiếu PHP .
Một số container DI nổi tiếng là:
và nhiều người khác.
Tôi muốn chỉ ra rằng đối với các plugin đơn giản, chỉ liên quan đến một vài lớp và các lớp không có nhiều phụ thuộc, có lẽ nó không đáng để sử dụng các thùng chứa DI: phương thức cá thể tĩnh hoặc một sổ đăng ký có thể truy cập toàn cầu là những giải pháp tốt, nhưng đối với các plugin phức tạp lợi ích của một container DI trở nên rõ ràng.
Tất nhiên, ngay cả các đối tượng chứa DI phải có thể truy cập được để sử dụng trong ứng dụng và với mục đích đó, có thể sử dụng một trong các phương thức được thấy ở trên, biến toàn cục, biến đối tượng tĩnh, trả về đối tượng qua bộ lọc, v.v.
Nhà soạn nhạc
Để sử dụng DI container thường có nghĩa là sử dụng mã bên thứ 3. Ngày nay, trong PHP, khi chúng ta cần sử dụng lib bên ngoài (vì vậy không chỉ các thùng chứa DI, mà bất kỳ mã nào không phải là một phần của ứng dụng), chỉ cần tải xuống và đưa nó vào thư mục ứng dụng của chúng tôi không được coi là một cách làm tốt. Ngay cả khi chúng tôi là tác giả của đoạn mã khác.
Việc tách mã ứng dụng khỏi các phụ thuộc bên ngoài là dấu hiệu của tổ chức tốt hơn, độ tin cậy tốt hơn và độ tinh khiết của mã tốt hơn .
Trình soạn thảo , là tiêu chuẩn thực tế trong cộng đồng PHP để quản lý các phụ thuộc PHP. Cũng là một xu hướng chính trong cộng đồng WP, đó là một công cụ mà mọi nhà phát triển PHP và WordPress ít nhất nên biết, nếu không sử dụng.
Câu trả lời này đã có kích thước cuốn sách để cho phép thảo luận thêm, và thảo luận về Nhà soạn nhạc ở đây có lẽ không có chủ đề, nó chỉ được đề cập vì sự hoàn chỉnh.
Để biết thêm thông tin truy cập vào trang Composer và nó cũng có giá trị cho một đọc này minisite giám tuyển bởi @Rarst .
1 PSR là các quy tắc tiêu chuẩn PHP được phát hành bởi Nhóm khung công tác PHP
2 Trình soạn thảo (một thư viện sẽ được đề cập trong câu trả lời này) trong số những thứ khác cũng chứa tiện ích trình tải tự động.