Cách tốt nhất để chuyển biến PHP giữa các partials?


16

Tôi có một biến trong header.php, chẳng hạn như:

$page_extra_title = get_post_meta($this_page->ID, "_theme_extra_title", true);

Khi tôi làm:

var_dump($page_extra_title);

Tôi luôn nhận được NULL bên ngoài tiêu đề.php (var_dump chỉ hoạt động đúng trong tiêu đề.php). Tôi đã dán cùng một biến ở mọi nơi tôi cần (page.php, post.php, footer.php, v.v.), nhưng điều đó thật điên rồ và khiến mọi thứ gần như không thể duy trì.

Tôi đang tự hỏi cách tốt nhất để chuyển một biến qua tất cả các tệp trong chủ đề của tôi là gì? Tôi đoán sử dụng hàm.php cùng với "get_post_meta" có thể không phải là ý tưởng tốt nhất? :)



Tôi nghĩ rằng biến là trong cùng một phạm vi, tôi cũng muốn tránh sử dụng GLOBAL vì những lý do rõ ràng.
Wordpressor

Tôi tin rằng bình luận của ialocin là tại chỗ. Một tập lệnh PHP không biết cái kia tồn tại và không thể truy cập vào các biến cục bộ hoặc các giá trị của chúng.
jdm2112

1
đầu trang và chân trang được bao gồm thông qua một hàm, vì vậy phạm vi của mọi thứ trong các tệp đó là phạm vi của các hàm đó.
Milo

4
Đừng bắn tin nhắn :) Điều duy nhất tôi nói là, đây thực sự là một vấn đề phạm vi. Có một cách, nó là global, phải không? Nhưng đó là ra khỏi câu hỏi vì lý do tốt. Bên cạnh đó, bạn cũng phải "gọi" các globalbiến, bằng cách sử dụng từ khóa để làm cho chúng có sẵn. Tùy thuộc vào các phiên trường hợp sử dụng có thể là một giải pháp. Mặt khác - như đã đề cập - Tôi nghĩ rằng một chức năng hoặc một lớp để thực hiện công việc cho bạn là con đường để đi.
Nicolai

Câu trả lời:


10

Cấu trúc dữ liệu tách biệt cơ bản

Để truyền dữ liệu, bạn thường sử dụng Mô hình (đó là "M" trong "MVC"). Hãy nhìn vào một giao diện rất đơn giản cho dữ liệu. Các giao diện chỉ được sử dụng làm "Bí quyết" cho các khối xây dựng của chúng tôi:

namespace WeCodeMore\Package\Models;
interface ArgsInterface
{
    public function getID();
    public function getLabel();
}

Trên đây là những gì chúng tôi vượt qua: ID chung và "Nhãn".

Hiển thị dữ liệu bằng cách kết hợp các mảnh nguyên tử

Tiếp theo, chúng tôi cần một số Chế độ xem có thể thương lượng giữa Mô hình của chúng tôi và ... mẫu của chúng tôi.

namespace WeCodeMore\Package;
interface PackageViewInterface
{
    /**
     * @param Models\ArgsInterface $args
     * @return int|void
     */
    public function render( Models\ArgsInterface $args );
}

Về cơ bản, Giao diện nói

"Chúng tôi có thể kết xuất một cái gì đó và một Mô hình là bắt buộc cho nhiệm vụ đó"

Cuối cùng chúng ta cần thực hiện ở trên và xây dựng View thực tế . Như bạn có thể thấy, hàm tạo cho biết rằng điều bắt buộc đối với chế độ xem của chúng tôi là Mẫu và chúng ta có thể kết xuất nó. Để dễ dàng phát triển, chúng tôi thậm chí kiểm tra xem tệp mẫu có thực sự hiện diện hay không để chúng tôi có thể làm cho các nhà phát triển khác sống (cũng như của chúng tôi) dễ dàng hơn nhiều và lưu ý rằng.

Trong bước thứ hai trong chức năng kết xuất, chúng tôi sử dụng một Đóng để xây dựng trình bao bọc mẫu thực tế vàbindTo() Mô hình cho mẫu.

namespace WeCodeMore\Package;

use WeCodeMore\Package\Models\ArgsInterface;

/** @noinspection PhpInconsistentReturnPointsInspection */
class PackageView implements PackageViewInterface
{
    /** @var string|\WP_Error */
    private $template;
    /**
     * @param string $template
     */
    public function __construct( $template )
    {
        $this->template = ! file_exists( $template )
            ? new \WP_Error( 'wcm-package', 'A package view needs a template' )
            : $template;
    }
    /**
     * @param Models\ArgsInterface $args
     * @return int|void
     */
    public function render( Models\ArgsInterface $args )
    {
        if ( is_wp_error( $this->template ) )
            return print $this->template->get_error_message();

        /** @var $callback \Closure */
        $callback = function( $template )
        {
            extract( get_object_vars( $this ) );
            require $template;
        };
        call_user_func(
            $callback->bindTo( $args ),
            $this->template
        );
    }
}

Tách chế độ xem và kết xuất

Điều này có nghĩa là chúng ta có thể sử dụng một mẫu rất đơn giản như sau

<!--suppress HtmlFormInputWithoutLabel -->
<p><?= $label ?></p>

để hiển thị nội dung của chúng tôi. Đặt các mảnh lại với nhau, chúng ta sẽ có được thứ gì đó xung quanh các dòng sau (trong Người kiểm soát, Người hòa giải, v.v.):

namespace WeCodeMore\Package;

$view = new PackageView( plugin_dir_path( __FILE__ ).'tmpl/label.tmpl.php' );
$view->render( new Models\Args );

Chúng ta đã đạt được gì?

Bằng cách này chúng ta có thể

  1. Dễ dàng trao đổi mẫu mà không thay đổi cấu trúc dữ liệu
  2. Dễ đọc tempaltes
  3. Tránh phạm vi toàn cầu
  4. Có thể kiểm tra đơn vị
  5. Có thể trao đổi Mô hình / dữ liệu mà không làm hại các thành phần khác

Kết hợp OOP PHP với API WP

Tất nhiên điều này là khó có thể bằng cách sử dụng chức năng cơ bản như theming get_header(), get_footer()vv, phải không? Sai lầm. Chỉ cần gọi các lớp của bạn trong bất kỳ mẫu hoặc phần mẫu nào bạn muốn. Kết xuất nó, biến đổi dữ liệu, làm bất cứ điều gì bạn muốn. Nếu bạn thực sự tốt, bạn thậm chí chỉ cần thêm một loạt các bộ lọc tùy chỉnh của riêng mình và có một số nhà đàm phán để quan tâm đến những gì được hiển thị bởi bộ điều khiển nào trên tải tuyến đường / mẫu có điều kiện.

Phần kết luận?

Bạn có thể làm việc với những thứ như trên trong WP mà không gặp vấn đề gì và vẫn bám vào API cơ bản và sử dụng lại mã và dữ liệu mà không cần gọi một toàn cầu hoặc làm rối và làm ô nhiễm không gian tên toàn cầu.


3
Trông rất tuyệt! Tôi sẽ xem xét thêm về điều này, câu trả lời tốt đẹp!
marko

@kaiser gần 3 năm sau, có bất kỳ cập nhật nào cho suy nghĩ của bạn ở trên không? Tạo khuôn mẫu cốt lõi WP đã không thực sự tiến triển theo hướng tiên tiến hơn, vì vậy các giải pháp của bên thứ 3 vẫn là một điều.
lkraav

1
@Ikraav Tôi có thể sẽ không viết nó như thế này ngày nay, nhưng tôi vẫn chắc chắn rằng việc không sử dụng một cú pháp riêng biệt để xuất nội dung của các biến trong các thẻ HTML là cách để đi (và tránh một chi phí không cần thiết). Mặt khác, tôi hiếm khi viết các công cụ frontend trong PHP ngày nay, nhưng bằng JavaScript. Và tôi thực sự thích những gì VueJS và bạn bè đang mang lên bàn.
kaiser

11

Đây là một cách tiếp cận khác với câu trả lời @kaiser , tôi thấy khá ổn (+1 từ tôi) nhưng yêu cầu công việc bổ sung phải được sử dụng với các chức năng WP cốt lõi và nó được tích hợp thấp với hệ thống phân cấp mẫu.

Cách tiếp cận tôi muốn chia sẻ dựa trên một lớp duy nhất (đó là phiên bản rút gọn từ thứ tôi đang làm) chăm sóc dữ liệu kết xuất cho các mẫu.

Nó có một số tính năng thú vị (IMO):

  • các mẫu là các tệp mẫu WordPress tiêu chuẩn (single.php, page.php), chúng có thêm một chút sức mạnh
  • các mẫu hiện có chỉ hoạt động, vì vậy bạn có thể tích hợp mẫu từ các chủ đề hiện có mà không cần nỗ lực
  • Không giống như cách tiếp cận @kaiser , trong các mẫu bạn truy cập các biến bằng $thistừ khóa: điều này mang đến cho bạn khả năng tránh các thông báo trong sản xuất trong trường hợp các biến không xác định

các EngineLớp

namespace GM\Template;

class Engine
{
    private $data;
    private $template;
    private $debug = false;

  /**
   * Bootstrap rendering process. Should be called on 'template_redirect'.
   */
  public static function init()
  {
      add_filter('template_include', new static(), 99, 1);
  }

  /**
   * Constructor. Sets debug properties.
   */
  public function __construct()
  {
      $this->debug =
          (! defined('WP_DEBUG') || WP_DEBUG)
          && (! defined('WP_DEBUG_DISPLAY') || WP_DEBUG_DISPLAY);
  }

  /**
   * Render a template.
   * Data is set via filters (for main template) or passed to method for partials.
   * @param string $template template file path
   * @param array  $data     template data
   * @param bool   $partial  is the template a partial?
   * @return mixed|void
   */
  public function __invoke($template, array $data = array(), $partial = false)
  {
      if ($partial || $template) {
          $this->data = $partial
              ? $data
              : $this->provide(substr(basename($template), 0, -4));
          require $template;
          $partial or exit;
      }

      return $template;
  }

  /**
   * Render a partial.
   * Partial-specific data can be passed to method.
   * @param string $template template file path
   * @param array  $data     template data
   * @param bool   $isolated when true partial has no access on parent template context
   */
  public function partial($partial, array $data = array(), $isolated = false)
  {
      do_action("get_template_part_{$partial}", $partial, null);
      $file = locate_template("{$partial}.php");
      if ($file) {
          $class = __CLASS__;
          $template = new $class();
          $template_data =  $isolated ? $data : array_merge($this->data, $data);
          $template($file, $template_data, true);
      } elseif ($this->debug) {
          throw new \RuntimeException("{$partial} is not a valid partial.");
      }
  }

  /**
   * Used in templates to access data.
   * @param string $name
   * @return string
   */
  public function __get($name)
  {
      if (array_key_exists($name, $this->data)) {
          return $this->data[$name];
      }
      if ($this->debug) {
          throw new \RuntimeException("{$name} is undefined.");
      }

      return '';
  }

  /**
   * Provide data to templates using two filters hooks:
   * one generic and another query type specific.
   * @param string $type Template file name (without extension, e.g. "single")
   * @return array
   */
  private function provide($type)
  {
      $generic = apply_filters('gm_template_data', array(), $type);
      $specific = apply_filters("gm_template_data_{$type}", array());

      return array_merge(
        is_array($generic) ? $generic : array(),
        is_array($specific) ? $specific : array()
     );
  }
}

(Có sẵn như là Gist ở đây.)

Cách sử dụng

Điều duy nhất cần thiết là gọi Engine::init()phương thức, có thể là trên 'template_redirect'hook. Điều đó có thể được thực hiện trong chủ đề functions.phphoặc từ một plugin.

require_once '/path/to/the/file/Engine.php';
add_action('template_redirect', array('GM\Template\Engine', 'init'), 99);

Đó là tất cả.

Các mẫu hiện có của bạn sẽ hoạt động như expcted. Nhưng bây giờ bạn có khả năng truy cập dữ liệu mẫu tùy chỉnh.

Dữ liệu mẫu tùy chỉnh

Để truyền dữ liệu tùy chỉnh cho các mẫu, có hai bộ lọc:

  • 'gm_template_data'
  • 'gm_template_data_{$type}'

Phần đầu tiên được kích hoạt cho tất cả các mẫu, phần thứ hai là mẫu cụ thể, trên thực tế, phần dymamic {$type}là tên cơ sở của tệp mẫu không có phần mở rộng tệp.

Ví dụ: bộ lọc 'gm_template_data_single'có thể được sử dụng để truyền dữ liệu đến single.phpmẫu.

Các cuộc gọi lại được gắn vào các hook này phải trả về một mảng , trong đó các khóa là tên biến.

Ví dụ: bạn có thể truyền dữ liệu meta dưới dạng lượt thích dữ liệu mẫu:

add_filter('gm_template_data', function($data) {
    if (is_singular()) {
        $id = get_queried_object_id();
        $data['extra_title'] = get_post_meta($id, "_theme_extra_title", true);
    }

    return $data;
};

Và sau đó, bên trong mẫu bạn chỉ có thể sử dụng:

<?= $this->extra_title ?>

Chế độ kiểm tra sửa lỗi

Khi cả hai hằng WP_DEBUGWP_DEBUG_DISPLAYđều đúng, lớp hoạt động ở chế độ gỡ lỗi. Nó có nghĩa là nếu một biến không được xác định, một ngoại lệ được đưa ra.

Khi lớp không ở chế độ gỡ lỗi (có thể trong sản xuất) truy cập vào một biến không xác định sẽ tạo ra một chuỗi rỗng.

Mô hình dữ liệu

Một cách hay và dễ bảo trì để sắp xếp dữ liệu của bạn là sử dụng các lớp mô hình.

Chúng có thể là các lớp rất đơn giản, trả về dữ liệu bằng các bộ lọc được mô tả ở trên. Không có giao diện cụ thể để theo dõi, họ có thể được tổ chức accordi theo sở thích của bạn.

Dưới đây, chỉ có một ví dụ, nhưng bạn có thể tự do làm theo cách của riêng bạn.

class SeoModel
{
  public function __invoke(array $data, $type = '')
  {
      switch ($type) {
          case 'front-page':
          case 'home':
            $data['seo_title'] = 'Welcome to my site';
            break;
          default:
            $data['seo_title'] = wp_title(' - ', false, 'right');
            break;
      }

      return $data;
  }
}

add_filter('gm_template_data', new SeoModel(), 10, 2);

Các __invoke()phương pháp (mà chạy khi một lớp được sử dụng như một callback) trả về một chuỗi được sử dụng cho các <title>thẻ của mẫu.

Nhờ thực tế là đối số thứ hai được truyền qua 'gm_template_data'là tên mẫu, phương thức trả về một tiêu đề tùy chỉnh cho trang chủ.

Có mã ở trên, sau đó có thể sử dụng một cái gì đó như

 <title><?= $this->seo_title ?></title>

trong <head>phần của trang.

Hạt

WordPress có các chức năng như get_header()hoặc get_template_part()có thể được sử dụng để tải các phần vào mẫu chính.

Các hàm này, giống như tất cả các hàm WordPress khác, có thể được sử dụng trong các mẫu khi sử dụng Enginelớp.

Vấn đề duy nhất là bên trong các phần được tải bằng các chức năng cốt lõi của WordPress không thể sử dụng tính năng nâng cao để nhận dữ liệu mẫu tùy chỉnh bằng cách sử dụng $this.

Vì lý do này, Enginelớp có một phương thức partial()cho phép tải một phần (theo cách tương thích hoàn toàn với chủ đề con) và vẫn có thể sử dụng trong các phần dữ liệu mẫu tùy chỉnh.

Cách sử dụng khá đơn giản.

Giả sử có một tệp có tên partials/content.phptrong thư mục theme (hoặc theme theme), nó có thể được bao gồm bằng cách sử dụng:

<?php $this->partial('partials/content') ?>

Bên trong một phần đó sẽ có thể truy cập tất cả dữ liệu chủ đề gốc là cùng một cách.

Không giống như các chức năng của WordPress, Engine::partial()phương thức cho phép truyền dữ liệu cụ thể cho các phần, chỉ cần truyền một mảng dữ liệu làm đối số thứ hai.

<?php $this->partial('partials/content', array('greeting' => 'Welcome!')) ?>

Theo mặc định, các phần có quyền truy cập vào dữ liệu có sẵn trong chủ đề gốc và dữ liệu được thông qua.

Nếu một số biến được truyền rõ ràng cho một phần có cùng tên của biến chủ đề gốc, thì biến đó được truyền rõ ràng sẽ thắng.

Tuy nhiên, cũng có thể bao gồm một phần trong chế độ cách ly , tức là một phần không có quyền truy cập vào dữ liệu chủ đề gốc. Để làm điều đó, chỉ cần chuyển truelàm đối số thứ ba cho partial():

<?php $this->partial('partials/content', array('greeting' => 'Welcome!'), true) ?>

Phần kết luận

Ngay cả khi khá đơn giản, Enginelớp học khá hoàn chỉnh, nhưng chắc chắn có thể được cải thiện hơn nữa. Ví dụ, không có cách nào để kiểm tra xem một biến có được xác định hay không.

Nhờ khả năng tương thích 100% với các tính năng WordPress và phân cấp mẫu, bạn có thể tích hợp nó với mã bên thứ ba và bên thứ ba mà không gặp vấn đề gì.

Tuy nhiên, lưu ý rằng chỉ được thử nghiệm một phần, vì vậy có thể có những vấn đề tôi chưa phát hiện ra.

Năm điểm dưới "Chúng ta đã đạt được gì?" trong câu trả lời @kaiser :

  1. Dễ dàng trao đổi mẫu mà không thay đổi cấu trúc dữ liệu
  2. Dễ đọc tempaltes
  3. Tránh phạm vi toàn cầu
  4. Có thể kiểm tra đơn vị
  5. Có thể trao đổi Mô hình / dữ liệu mà không làm hại các thành phần khác

tất cả đều hợp lệ cho lớp học của tôi là tốt.


1
Hehe. Làm tốt lắm, bạn đời :) +1
kaiser

@gmazzap gần 3 năm sau, có bất kỳ cập nhật nào cho suy nghĩ của bạn ở trên không? Tạo khuôn mẫu cốt lõi WP đã không thực sự tiến triển theo hướng tiên tiến hơn, vì vậy các giải pháp của bên thứ 3 vẫn là một điều.
lkraav

1
Tôi không làm nhiều chủ đề làm việc những ngày này. Gần đây, cách của tôi là kết hợp github.com/Brain-WP/Context + github.com/Brain-WP/HVELy để xây dựng dữ liệu và chuyển đến các mẫu. Đối với bản thân công cụ mẫu, tôi đã sử dụng các cách tiếp cận khác nhau, Lá (tất nhiên), Mustache, nhưng cả Twig (chỉ khi tôi có quyền kiểm soát trên toàn bộ webiste để tránh địa ngục phụ thuộc) @lkraav
gmazzap

5

Câu trả lời đơn giản, đừng chuyển các biến ở bất cứ đâu vì nó không sử dụng các biến toàn cục là xấu.

Từ ví dụ của bạn, có vẻ như bạn đang cố gắng tối ưu hóa sớm, nhưng lại là một tội ác khác;)

Sử dụng API wordpress để lấy dữ liệu được lưu trữ trong DB và không cố gắng vượt quá và tối ưu hóa việc sử dụng nó khi API làm nhiều hơn sau đó chỉ cần truy xuất các giá trị và nó kích hoạt các bộ lọc và hành động. Bằng cách loại bỏ lệnh gọi API, bạn loại bỏ khả năng các nhà phát triển khác thay đổi hành vi của mã của bạn mà không sửa đổi nó.


2

Mặc dù câu trả lời của kaiser là đúng về mặt kỹ thuật, tôi nghi ngờ đây là câu trả lời tốt nhất cho bạn.

Nếu bạn đang tạo chủ đề của riêng mình, thì tôi nghĩ đó thực sự là cách tốt nhất để thiết lập một số loại khung bằng cách sử dụng các lớp (và có thể cả không gian tên và giao diện, mặc dù điều đó có thể hơi quá đối với chủ đề WP).

Mặt khác, nếu bạn chỉ mở rộng / điều chỉnh một chủ đề hiện có và chỉ cần vượt qua một hoặc một vài biến, tôi nghĩ bạn nên gắn bó global. Vì header.phpđược bao gồm trong một hàm, các biến bạn khai báo trong tệp đó chỉ có thể sử dụng được trong tệp đó. Với globalbạn làm cho chúng có thể truy cập được trong toàn bộ dự án WP:

Trong header.php:

global $page_extra_title;

$page_extra_title = get_post_meta($this_page->ID, "_theme_extra_title", true);

Trong single.php(ví dụ):

global $page_extra_title;

var_dump( $page_extra_title );

3
Tôi không muốn thô lỗ hay bất cứ điều gì, nhưng thực sự rất tệ khi đi sâu vào phạm vi toàn cầu. Bạn nên tránh thêm vào phạm vi toàn cầu. Bạn phải thực sự cẩn thận với các quy ước đặt tên ở đây và bạn cần đảm bảo rằng tên biến của bạn sẽ là duy nhất theo cách mà không ai có thể sao chép tên đó. Cách tiếp cận @kaiser có vẻ quá sức đối với bạn, nhưng cho đến nay là tốt nhất và an toàn nhất. Tôi không thể cho bạn biết làm thế nào để giải quyết vấn đề này, nhưng tôi thực sự khuyên bạn nên tránh xa phạm vi toàn cầu :-)
Pieter Goosen

3
Tất nhiên bạn cần cẩn thận rằng bạn không ghi đè lên các biến khác. Bạn có thể giải quyết điều đó bằng cách sử dụng một tiền tố duy nhất hoặc một mảng với các biến tùy chỉnh $wp_theme_vars_page_extra_titlehoặc $wp_theme_vars['page_extra_title']ví dụ. Đó chỉ là một lời giải thích tại sao toàn cầu sẽ làm việc ở đây. OP đã hỏi một cách chuyển một biến qua tất cả các tệp, sử dụng globallà một cách để làm điều đó.
redelschaap

2
Không, toàn cầu không phải là một cách để làm điều đó. Có nhiều cách tốt hơn để đạt được điều tương tự mà không cần sử dụng toàn cầu. Như tôi đã nói trước đây, và như @kaiser đã nói trong câu trả lời của mình, hãy tránh phạm vi toàn cầu và tránh xa nó. Chỉ là một ví dụ, lấy phương án thay thế rất dễ dàng này, bọc mã của bạn trong một hàm và gọi hàm khi cần. Bằng cách này, bạn không cần phải thiết lập hoặc sử dụng toàn cầu.
Pieter Goosen

3
Vâng, đúng vậy. Nó có thể không phải là cách tốt nhất, nhưng nó chắc chắn là một cách.
redelschaap

2
but it is really bad practice diving into the global scopeTôi muốn ai đó nói với các nhà phát triển cốt lõi WP. Tôi thực sự không hiểu quan điểm của việc sử dụng không gian tên, trừu tượng hóa dữ liệu, mẫu thiết kế, kiểm thử đơn vị và các thực tiễn / kỹ thuật tốt nhất về lập trình khác được viết cho Wordpress khi lõi Wordpress bị vấy bẩn bởi các thực tiễn mã hóa xấu như các biến glabal (ví dụ: các widget mã).
Ejaz

1

Một giải pháp dễ dàng là viết một hàm để có thêm tiêu đề. Tôi sử dụng một biến tĩnh để chỉ giữ các cuộc gọi cơ sở dữ liệu. Đặt cái này trong hàm của bạn.php.

function get_extra_title($post_id) {
    static $title = null;
    if ($title === null) {
        $title = get_post_meta($post_id, "_theme_extra_title", true)
    }
    return $title;
}

Bên ngoài header.php, gọi hàm để nhận giá trị:

var_dump(get_extra_title($post->ID));
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.