เมื่อคุณเยี่ยมชมหน้าส่วนหน้า WordPress จะทำการสืบค้นฐานข้อมูลและหากหน้าของคุณไม่มีอยู่ในฐานข้อมูลการสืบค้นนั้นไม่จำเป็นและเป็นทรัพยากรที่สิ้นเปลือง
โชคดีที่ WordPress เสนอวิธีจัดการคำขอส่วนหน้าด้วยวิธีที่กำหนดเอง เสร็จแล้วต้องขอบคุณ'do_parse_request'
ตัวกรอง
false
เมื่อกลับมาที่ตะขอคุณจะสามารถหยุด WordPress จากการประมวลผลคำขอและทำมันในแบบที่คุณกำหนดเอง
ที่กล่าวว่าฉันต้องการแบ่งปันวิธีการสร้างปลั๊กอิน OOP แบบง่ายที่สามารถจัดการหน้าเสมือนในวิธีที่ใช้งานง่าย (และใช้ซ้ำ)
สิ่งที่เราต้องการ
- คลาสสำหรับวัตถุหน้าเสมือน
- คลาสตัวควบคุมที่จะตรวจสอบคำขอและหากเป็นหน้าเสมือนให้แสดงโดยใช้เทมเพลตที่เหมาะสม
- คลาสสำหรับการโหลดเทมเพลต
- ไฟล์ปลั๊กอินหลักเพื่อเพิ่ม hooks ที่จะทำให้ทุกอย่างทำงาน
อินเตอร์เฟซ
ก่อนที่จะสร้างคลาสให้เขียนอินเตอร์เฟสสำหรับวัตถุ 3 รายการที่ระบุไว้ด้านบน
ก่อนอินเตอร์เฟซหน้า (ไฟล์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();
}
วิธีการส่วนใหญ่เป็นเพียงแค่ getters และ setters ไม่จำเป็นต้องอธิบาย วิธีสุดท้ายควรใช้เพื่อรับWP_Post
วัตถุจากหน้าเสมือน
อินเทอร์เฟซตัวควบคุม (ไฟล์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 );
}
และเทมเพลตอินเตอร์เฟสโหลดเดอร์ (ไฟล์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();
}
ความคิดเห็น phpDoc ควรมีความชัดเจนสำหรับอินเตอร์เฟสเหล่านี้
แผนการ
ตอนนี้เรามีอินเทอร์เฟซและก่อนที่จะเขียนคลาสที่เป็นรูปธรรมมาตรวจสอบเวิร์กโฟลว์ของเรา:
- ครั้งแรกที่เราสร้างอินสแตนซ์ของ
Controller
คลาส (การนำไปใช้ControllerInterface
) และฉีด (อาจเป็นในตัวสร้าง) เป็นตัวอย่างของTemplateLoader
คลาส (นำไปใช้TemplateLoaderInterface
)
- ที่
init
hook เราเรียกControllerInterface::init()
วิธีการตั้งค่าคอนโทรลเลอร์และใช้ hook ที่รหัสผู้ใช้จะใช้เพื่อเพิ่มหน้าเสมือน
- ใน'do_parse_request'เราจะโทรหา
ControllerInterface::dispatch()
และที่นั่นเราจะตรวจสอบหน้าเสมือนทั้งหมดที่เพิ่มเข้ามาและหากหนึ่งในนั้นมี URL เดียวกันกับคำขอปัจจุบันให้แสดง; หลังจากตั้งค่าตัวแปรโกลบอลหลักทั้งหมด ( $wp_query
, $post
) แล้ว เราจะใช้TemplateLoader
คลาสเพื่อโหลดเทมเพลตที่ถูกต้อง
ในระหว่างขั้นตอนการทำงานนี้เราจะเรียกตะขอหลักบางอย่างเช่นwp
, template_redirect
, template_include
... ที่จะทำให้ปลั๊กอินที่มีความยืดหยุ่นมากขึ้นและให้แน่ใจว่าเข้ากันได้กับหลักและอื่น ๆ ปลั๊กอินหรืออย่างน้อยมีจำนวนดีของพวกเขา
นอกเหนือจากเวิร์กโฟลว์ก่อนหน้าเราจะต้อง:
- ทำความสะอาด hooks และตัวแปรส่วนกลางหลังจากลูปหลักทำงานอีกครั้งเพื่อปรับปรุงความเข้ากันได้กับคอร์และรหัสบุคคลที่สาม
- เพิ่มตัวกรอง
the_permalink
เพื่อให้คืน URL หน้าเสมือนจริงที่ถูกต้องเมื่อจำเป็น
คลาสคอนกรีต
ตอนนี้เราสามารถเขียนโค้ดคลาสของเราได้ มาเริ่มด้วยคลาสเพจ (ไฟล์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;
}
}
ไม่มีอะไรมากไปกว่าการใช้อินเทอร์เฟซ
ตอนนี้คลาสคอนโทรลเลอร์ (ไฟล์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();
}
}
โดยพื้นฐานแล้วคลาสจะสร้างSplObjectStorage
ออบเจ็กต์ที่จัดเก็บออบเจ็กต์หน้าที่เพิ่มไว้ทั้งหมด
เปิด'do_parse_request'
คลาสตัวควบคุมจะวนลูปของหน่วยเก็บนี้เพื่อค้นหารายการที่ตรงกันสำหรับ URL ปัจจุบันในหนึ่งในหน้าที่เพิ่ม
หากพบชั้นไม่ตรงกับสิ่งที่เราวางแผนไว้: TemplateLoaderInterface
ไกตะขอตัวแปรการติดตั้งบางส่วนและโหลดแม่แบบผ่านการเรียนการขยาย exit()
หลังจากนั้นเพียง
ดังนั้นเรามาเขียนคลาสสุดท้ายกัน:
<?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;
}
}
}
เทมเพลตที่เก็บไว้ในหน้าเสมือนจะรวมอยู่ในอาร์เรย์ด้วยค่าเริ่มต้นpage.php
และindex.php
ก่อนที่จะโหลดเทมเพลต'template_redirect'
จะเริ่มทำงานเพื่อเพิ่มความยืดหยุ่นและปรับปรุงความเข้ากันได้
หลังจากนั้นเทมเพลตที่พบจะผ่านตัวกรองแบบกำหนดเอง'virtual_page_template'
และตัวหลัก'template_include'
: อีกครั้งเพื่อความยืดหยุ่นและความเข้ากันได้
ในที่สุดไฟล์เทมเพลตก็จะถูกโหลด
ไฟล์ปลั๊กอินหลัก
ณ จุดนี้เราจำเป็นต้องเขียนไฟล์ด้วยส่วนหัวของปลั๊กอินและใช้มันเพื่อเพิ่ม hooks ที่จะทำให้เวิร์กโฟลว์ของเราเกิดขึ้น:
<?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;
} );
ในไฟล์จริงเราอาจเพิ่มส่วนหัวเพิ่มเติมเช่นปลั๊กอินและลิงก์ผู้เขียนคำอธิบายใบอนุญาตเป็นต้น
ส่วนสำคัญของปลั๊กอิน
ตกลงเราทำกับปลั๊กอินของเรา ทุกรหัสที่สามารถพบได้ในกระทู้ที่นี่
การเพิ่มหน้า
ปลั๊กอินพร้อมใช้งานแล้ว แต่เรายังไม่ได้เพิ่มหน้าใด ๆ
ที่สามารถทำได้ภายในปลั๊กอินตัวเองภายในชุดรูปแบบfunctions.php
ในปลั๊กอินอื่น ๆ
เพิ่มหน้าเป็นเพียงเรื่องของ:
<?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' );
} );
และอื่น ๆ คุณสามารถเพิ่มหน้าทั้งหมดที่คุณต้องการเพียงจำไว้ว่าให้ใช้ URL สัมพัทธ์สำหรับหน้า
ภายในไฟล์เทมเพลตคุณสามารถใช้แท็กเทมเพลต WordPress ทั้งหมดและคุณสามารถเขียน PHP และ HTML ทั้งหมดที่คุณต้องการ
วัตถุโพสต์ทั่วโลกเต็มไปด้วยข้อมูลที่มาจากหน้าเสมือน หน้าเสมือนจริงสามารถเข้าถึงได้ผ่าน$wp_query->virtual_page
ตัวแปร
ในการรับ URL สำหรับหน้าเสมือนนั้นง่ายเหมือนการส่งผ่านไปhome_url()
ยังเส้นทางเดียวกับที่ใช้ในการสร้างหน้า:
$custom_page_url = home_url( '/custom/page' );
โปรดทราบว่าในลูปหลักในเทมเพลตที่โหลดthe_permalink()
จะส่งคืนค่าลิงก์ที่ถูกต้องไปยังหน้าเสมือน
หมายเหตุเกี่ยวกับสไตล์ / สคริปต์สำหรับหน้าเสมือน
อาจเป็นเมื่อมีการเพิ่มหน้าเสมือนเป็นสิ่งที่พึงปรารถนาที่จะจัดรูปแบบ / สคริปต์ที่กำหนดเองจากนั้นจึงใช้wp_head()
ในเทมเพลตที่กำหนดเอง
นั่นเป็นเรื่องง่ายมากเนื่องจากหน้าเสมือนจะจดจำได้ง่ายเมื่อมอง$wp_query->virtual_page
ตัวแปรและหน้าเสมือนสามารถแยกแยะหน้ากันได้โดยดูจาก URL
เป็นเพียงตัวอย่าง:
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;
}
}
} );
หมายเหตุถึง OP
การส่งผ่านข้อมูลจากหน้าหนึ่งไปอีกหน้าไม่เกี่ยวข้องกับหน้าเสมือนจริงเหล่านี้ แต่เป็นเพียงงานทั่วไป
อย่างไรก็ตามหากคุณมีแบบฟอร์มในหน้าแรกและต้องการส่งข้อมูลจากที่นั่นไปยังหน้าสองให้ใช้ URL ของหน้าสองในaction
คุณสมบัติของแบบฟอร์ม
เช่นในไฟล์เทมเพลตหน้าแรกคุณสามารถ:
<form action="<?php echo home_url( '/custom/page/deep' ); ?>" method="POST">
<input type="text" name="testme">
</form>
จากนั้นในไฟล์เทมเพลตหน้าสอง:
<?php $testme = filter_input( INPUT_POST, 'testme', FILTER_SANITIZE_STRING ); ?>
<h1>Test-Me value form other page is: <?php echo $testme; ?></h1>