Khi bạn truy cập trang frontend, WordPress sẽ truy vấn cơ sở dữ liệu và nếu trang của bạn không tồn tại trong cơ sở dữ liệu, truy vấn đó không cần thiết và chỉ là một sự lãng phí tài nguyên.
May mắn thay, WordPress cung cấp một cách để xử lý các yêu cầu lối vào theo cách tùy chỉnh. Điều đó được thực hiện nhờ vào 'do_parse_request'
bộ lọc.
Quay trở lại false
với cái móc đó, bạn sẽ có thể ngăn WordPress xử lý các yêu cầu và thực hiện nó theo cách tùy chỉnh của riêng bạn.
Điều đó nói rằng, tôi muốn chia sẻ một cách để xây dựng một plugin OOP đơn giản có thể xử lý các trang ảo theo cách dễ sử dụng (và sử dụng lại).
Những gì chúng tôi cần
- Một lớp cho các đối tượng trang ảo
- Một lớp trình điều khiển, sẽ xem xét một yêu cầu và nếu nó dành cho một trang ảo, hãy hiển thị nó bằng cách sử dụng mẫu thích hợp
- Một lớp để tải mẫu
- Các tập tin plugin chính để thêm các hook sẽ làm cho mọi thứ hoạt động
Giao diện
Trước khi xây dựng các lớp, hãy viết các giao diện cho 3 đối tượng được liệt kê ở trên.
Đầu tiên giao diện trang (tệp PageInterface.php
):
<?php
namespace GM\VirtualPages;
interface PageInterface {
function getUrl();
function getTemplate();
function getTitle();
function setTitle( $title );
function setContent( $content );
function setTemplate( $template );
/**
* Get a WP_Post build using virtual Page object
*
* @return \WP_Post
*/
function asWpPost();
}
Hầu hết các phương pháp chỉ là getters và setters, không cần giải thích. Phương pháp cuối cùng nên được sử dụng để lấy một WP_Post
đối tượng từ một trang ảo.
Giao diện điều khiển (tệp ControllerInterface.php
):
<?php
namespace GM\VirtualPages;
interface ControllerInterface {
/**
* Init the controller, fires the hook that allows consumer to add pages
*/
function init();
/**
* Register a page object in the controller
*
* @param \GM\VirtualPages\Page $page
* @return \GM\VirtualPages\Page
*/
function addPage( PageInterface $page );
/**
* Run on 'do_parse_request' and if the request is for one of the registered pages
* setup global variables, fire core hooks, requires page template and exit.
*
* @param boolean $bool The boolean flag value passed by 'do_parse_request'
* @param \WP $wp The global wp object passed by 'do_parse_request'
*/
function dispatch( $bool, \WP $wp );
}
và giao diện trình tải mẫu (tệp TemplateLoaderInterface.php
):
<?php
namespace GM\VirtualPages;
interface TemplateLoaderInterface {
/**
* Setup loader for a page objects
*
* @param \GM\VirtualPagesPageInterface $page matched virtual page
*/
public function init( PageInterface $page );
/**
* Trigger core and custom hooks to filter templates,
* then load the found template.
*/
public function load();
}
Nhận xét phpDoc nên khá rõ ràng cho các giao diện này.
Kế hoạch
Bây giờ chúng ta có các giao diện và trước khi viết các lớp cụ thể, hãy xem lại quy trình làm việc của chúng tôi:
- Đầu tiên chúng ta khởi tạo một
Controller
lớp (thực hiện ControllerInterface
) và tiêm (có thể trong một hàm tạo) một thể hiện của TemplateLoader
lớp (triển khai TemplateLoaderInterface
)
- Khi
init
hook, chúng ta gọi ControllerInterface::init()
phương thức để thiết lập bộ điều khiển và kích hoạt hook mà mã người tiêu dùng sẽ sử dụng để thêm các trang ảo.
- Trên 'do_parse_Vquest', chúng tôi sẽ gọi
ControllerInterface::dispatch()
và ở đó chúng tôi sẽ kiểm tra tất cả các trang ảo được thêm vào và nếu một trong số chúng có cùng URL của yêu cầu hiện tại, hãy hiển thị nó; sau khi đã đặt tất cả các biến toàn cục cốt lõi ( $wp_query
, $post
). Chúng tôi cũng sẽ sử dụng TemplateLoader
lớp để tải đúng mẫu.
Trong thời gian này công việc chúng tôi sẽ kích hoạt một số móc cốt lõi, như wp
, template_redirect
, template_include
... để làm cho các plugin linh hoạt hơn và đảm bảo khả năng tương thích với lõi và các plugin khác, hoặc ít nhất là với một số lượng tốt của họ.
Ngoài quy trình làm việc trước đó, chúng tôi cũng sẽ cần:
- Dọn dẹp móc và biến toàn cục sau khi chạy vòng lặp chính, một lần nữa để cải thiện khả năng tương thích với mã bên thứ ba và bên thứ ba
- Thêm bộ lọc vào
the_permalink
để làm cho nó trả về đúng URL trang ảo khi cần.
Lớp bê tông
Bây giờ chúng ta có thể mã các lớp cụ thể của chúng tôi. Hãy bắt đầu với lớp trang (tệp Page.php
):
<?php
namespace GM\VirtualPages;
class Page implements PageInterface {
private $url;
private $title;
private $content;
private $template;
private $wp_post;
function __construct( $url, $title = 'Untitled', $template = 'page.php' ) {
$this->url = filter_var( $url, FILTER_SANITIZE_URL );
$this->setTitle( $title );
$this->setTemplate( $template);
}
function getUrl() {
return $this->url;
}
function getTemplate() {
return $this->template;
}
function getTitle() {
return $this->title;
}
function setTitle( $title ) {
$this->title = filter_var( $title, FILTER_SANITIZE_STRING );
return $this;
}
function setContent( $content ) {
$this->content = $content;
return $this;
}
function setTemplate( $template ) {
$this->template = $template;
return $this;
}
function asWpPost() {
if ( is_null( $this->wp_post ) ) {
$post = array(
'ID' => 0,
'post_title' => $this->title,
'post_name' => sanitize_title( $this->title ),
'post_content' => $this->content ? : '',
'post_excerpt' => '',
'post_parent' => 0,
'menu_order' => 0,
'post_type' => 'page',
'post_status' => 'publish',
'comment_status' => 'closed',
'ping_status' => 'closed',
'comment_count' => 0,
'post_password' => '',
'to_ping' => '',
'pinged' => '',
'guid' => home_url( $this->getUrl() ),
'post_date' => current_time( 'mysql' ),
'post_date_gmt' => current_time( 'mysql', 1 ),
'post_author' => is_user_logged_in() ? get_current_user_id() : 0,
'is_virtual' => TRUE,
'filter' => 'raw'
);
$this->wp_post = new \WP_Post( (object) $post );
}
return $this->wp_post;
}
}
Không có gì hơn là thực hiện giao diện.
Bây giờ lớp trình điều khiển (tệp Controller.php
):
<?php
namespace GM\VirtualPages;
class Controller implements ControllerInterface {
private $pages;
private $loader;
private $matched;
function __construct( TemplateLoaderInterface $loader ) {
$this->pages = new \SplObjectStorage;
$this->loader = $loader;
}
function init() {
do_action( 'gm_virtual_pages', $this );
}
function addPage( PageInterface $page ) {
$this->pages->attach( $page );
return $page;
}
function dispatch( $bool, \WP $wp ) {
if ( $this->checkRequest() && $this->matched instanceof Page ) {
$this->loader->init( $this->matched );
$wp->virtual_page = $this->matched;
do_action( 'parse_request', $wp );
$this->setupQuery();
do_action( 'wp', $wp );
$this->loader->load();
$this->handleExit();
}
return $bool;
}
private function checkRequest() {
$this->pages->rewind();
$path = trim( $this->getPathInfo(), '/' );
while( $this->pages->valid() ) {
if ( trim( $this->pages->current()->getUrl(), '/' ) === $path ) {
$this->matched = $this->pages->current();
return TRUE;
}
$this->pages->next();
}
}
private function getPathInfo() {
$home_path = parse_url( home_url(), PHP_URL_PATH );
return preg_replace( "#^/?{$home_path}/#", '/', esc_url( add_query_arg(array()) ) );
}
private function setupQuery() {
global $wp_query;
$wp_query->init();
$wp_query->is_page = TRUE;
$wp_query->is_singular = TRUE;
$wp_query->is_home = FALSE;
$wp_query->found_posts = 1;
$wp_query->post_count = 1;
$wp_query->max_num_pages = 1;
$posts = (array) apply_filters(
'the_posts', array( $this->matched->asWpPost() ), $wp_query
);
$post = $posts[0];
$wp_query->posts = $posts;
$wp_query->post = $post;
$wp_query->queried_object = $post;
$GLOBALS['post'] = $post;
$wp_query->virtual_page = $post instanceof \WP_Post && isset( $post->is_virtual )
? $this->matched
: NULL;
}
public function handleExit() {
exit();
}
}
Về cơ bản lớp tạo một SplObjectStorage
đối tượng trong đó tất cả các đối tượng trang được thêm vào được lưu trữ.
Bật 'do_parse_request'
, lớp trình điều khiển lặp vòng lưu trữ này để tìm kết quả khớp cho URL hiện tại trong một trong các trang được thêm.
Nếu nó được tìm thấy, lớp thực hiện chính xác những gì chúng ta đã lên kế hoạch: kích hoạt một số hook, thiết lập các biến và tải mẫu thông qua việc mở rộng lớp TemplateLoaderInterface
. Sau đó, chỉ exit()
.
Vì vậy, hãy viết lớp cuối cùng:
<?php
namespace GM\VirtualPages;
class TemplateLoader implements TemplateLoaderInterface {
public function init( PageInterface $page ) {
$this->templates = wp_parse_args(
array( 'page.php', 'index.php' ), (array) $page->getTemplate()
);
}
public function load() {
do_action( 'template_redirect' );
$template = locate_template( array_filter( $this->templates ) );
$filtered = apply_filters( 'template_include',
apply_filters( 'virtual_page_template', $template )
);
if ( empty( $filtered ) || file_exists( $filtered ) ) {
$template = $filtered;
}
if ( ! empty( $template ) && file_exists( $template ) ) {
require_once $template;
}
}
}
Các mẫu được lưu trữ trong trang ảo được hợp nhất trong một mảng với các giá trị mặc định page.php
và index.php
trước khi tải mẫu 'template_redirect'
được kích hoạt, để thêm tính linh hoạt và cải thiện khả năng tương thích.
Sau đó, mẫu tìm thấy sẽ chuyển qua các bộ lọc tùy chỉnh 'virtual_page_template'
và lõi 'template_include'
: một lần nữa để linh hoạt và tương thích.
Cuối cùng, tập tin mẫu vừa được tải.
Tệp plugin chính
Tại thời điểm này, chúng ta cần viết tệp với các tiêu đề plugin và sử dụng nó để thêm các hook sẽ làm cho quy trình công việc của chúng ta xảy ra:
<?php namespace GM\VirtualPages;
/*
Plugin Name: GM Virtual Pages
*/
require_once 'PageInterface.php';
require_once 'ControllerInterface.php';
require_once 'TemplateLoaderInterface.php';
require_once 'Page.php';
require_once 'Controller.php';
require_once 'TemplateLoader.php';
$controller = new Controller ( new TemplateLoader );
add_action( 'init', array( $controller, 'init' ) );
add_filter( 'do_parse_request', array( $controller, 'dispatch' ), PHP_INT_MAX, 2 );
add_action( 'loop_end', function( \WP_Query $query ) {
if ( isset( $query->virtual_page ) && ! empty( $query->virtual_page ) ) {
$query->virtual_page = NULL;
}
} );
add_filter( 'the_permalink', function( $plink ) {
global $post, $wp_query;
if (
$wp_query->is_page && isset( $wp_query->virtual_page )
&& $wp_query->virtual_page instanceof Page
&& isset( $post->is_virtual ) && $post->is_virtual
) {
$plink = home_url( $wp_query->virtual_page->getUrl() );
}
return $plink;
} );
Trong tệp thực, chúng tôi có thể sẽ thêm nhiều tiêu đề hơn, như liên kết plugin và tác giả, mô tả, giấy phép, v.v.
Plugin Gist
Ok, chúng tôi đã hoàn thành với plugin của chúng tôi. Tất cả các mã có thể được tìm thấy trong một Gist ở đây .
Thêm trang
Plugin đã sẵn sàng và hoạt động, nhưng chúng tôi chưa thêm bất kỳ trang nào.
Điều đó có thể được thực hiện bên trong chính plugin, bên trong chủ đề functions.php
, trong một plugin khác, v.v.
Thêm trang chỉ là vấn đề:
<?php
add_action( 'gm_virtual_pages', function( $controller ) {
// first page
$controller->addPage( new \GM\VirtualPages\Page( '/custom/page' ) )
->setTitle( 'My First Custom Page' )
->setTemplate( 'custom-page-form.php' );
// second page
$controller->addPage( new \GM\VirtualPages\Page( '/custom/page/deep' ) )
->setTitle( 'My Second Custom Page' )
->setTemplate( 'custom-page-deep.php' );
} );
Và như thế. Bạn có thể thêm tất cả các trang bạn cần, chỉ cần nhớ sử dụng URL tương đối cho các trang.
Bên trong tệp mẫu, bạn có thể sử dụng tất cả các thẻ mẫu WordPress và bạn có thể viết tất cả PHP và HTML bạn cần.
Đối tượng bài đăng toàn cầu chứa đầy dữ liệu đến từ trang ảo của chúng tôi. Các trang ảo có thể được truy cập thông qua $wp_query->virtual_page
biến.
Để có được URL cho một trang ảo dễ dàng như chuyển đến home_url()
cùng một đường dẫn được sử dụng để tạo trang:
$custom_page_url = home_url( '/custom/page' );
Lưu ý rằng trong vòng lặp chính trong mẫu đã tải, the_permalink()
sẽ trả lại permalink chính xác cho trang ảo.
Ghi chú về kiểu / tập lệnh cho trang ảo
Có lẽ khi các trang ảo được thêm vào, chúng ta cũng mong muốn có các kiểu / tập lệnh tùy chỉnh được yêu thích và sau đó chỉ sử dụng wp_head()
trong các mẫu tùy chỉnh.
Điều đó rất dễ dàng, bởi vì các trang ảo dễ dàng được nhận ra khi nhìn vào $wp_query->virtual_page
các trang biến và các trang ảo có thể được phân biệt giữa các trang này với các URL khác.
Chỉ là một ví dụ:
add_action( 'wp_enqueue_scripts', function() {
global $wp_query;
if (
is_page()
&& isset( $wp_query->virtual_page )
&& $wp_query->virtual_page instanceof \GM\VirtualPages\PageInterface
) {
$url = $wp_query->virtual_page->getUrl();
switch ( $url ) {
case '/custom/page' :
wp_enqueue_script( 'a_script', $a_script_url );
wp_enqueue_style( 'a_style', $a_style_url );
break;
case '/custom/page/deep' :
wp_enqueue_script( 'another_script', $another_script_url );
wp_enqueue_style( 'another_style', $another_style_url );
break;
}
}
} );
Ghi chú cho OP
Truyền dữ liệu từ trang này sang trang khác không liên quan đến các trang ảo này, mà chỉ là một nhiệm vụ chung.
Tuy nhiên, nếu bạn có một biểu mẫu trong trang đầu tiên và muốn chuyển dữ liệu từ đó sang trang thứ hai, chỉ cần sử dụng URL của trang thứ hai trong thuộc tính biểu mẫu action
.
Ví dụ: trong tệp mẫu trang đầu tiên bạn có thể:
<form action="<?php echo home_url( '/custom/page/deep' ); ?>" method="POST">
<input type="text" name="testme">
</form>
và sau đó trong tệp mẫu trang thứ hai:
<?php $testme = filter_input( INPUT_POST, 'testme', FILTER_SANITIZE_STRING ); ?>
<h1>Test-Me value form other page is: <?php echo $testme; ?></h1>