web-dev-qa-db-ja.com

合格の最良の方法 PHP パーシャル間で可変?

私はheader.phpに次のような変数があります。

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

私がしたら:

var_dump($page_extra_title);

私は常にNULLをheader.phpの外側に持ってきます(var_dumpはheader.phpでのみ正しく動作します)。私は必要なところに同じ変数(page.php、post.php、footer.phpなど)を貼り付けてきましたが、それは狂気であり、すべてを維持することはほとんど不可能です。

テーマ内のすべてのファイルに変数を渡すための最善の方法は何でしょうか。 "get_post_meta"と一緒にfunctions.phpを使うのは最善の方法ではないかもしれませんね。 :)

15
Wordpressor

基本的な分離データ構造

データをやり取りするには、通常、 Model を使用します(これは "MVC"の "M"です)。データ用の非常に単純なインターフェースを見てみましょう。 インタフェース は、ビルディングブロックの「レシピ」として使用されています。

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

上記が私たちが受け継ぐものです:共通のIDと "ラベル"。

原子片を組み合わせてデータを表示する

次に、私たちのモデルと...私たちのテンプレートとの間で交渉する View が必要です。

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

基本的には インターフェース と言う

「私たちは何かをレンダリングすることができ、そのタスクにはモデルが必須です」

最後に、上記を実装して実際の View を構築する必要があります。ご覧のとおり、コンストラクターは私たちの見解に必須のものは Template であること、そしてそれをレンダリングできることを伝えます。開発を容易にするために、テンプレートファイルが実際に存在するかどうかをチェックして、他の開発者(および私たちのものも)がずっと簡単になるようにしています。

レンダリング機能の2番目のステップでは、 Closure を使用して実際のテンプレートラッパーを作成し、bindTo()テンプレートをモデルにします。

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

ビューとレンダリングの分離

これは、次のような非常に単純なテンプレートを使用できることを意味します

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

コンテンツをレンダリングします。ピースをまとめると、次のようなものになります(Controller、Mediatorなど)。

namespace WeCodeMore\Package;

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

私たちは何を得ましたか?

こうすれば私達はできます

  1. データ構造を変更せずに簡単にテンプレートを交換
  2. 読みやすいテンプレートを持っている
  3. グローバルスコープを避ける
  4. ユニットテストできますか
  5. 他のコンポーネントに害を与えることなくモデル/データを交換できます

OOP PHPとWP AP​​Iの組み合わせ

もちろん、これはget_header()get_footer()などの基本的なテーマ機能を使用してもほとんど不可能です。違う。あなたが望むどんなテンプレートまたはテンプレート部分であなたのクラスを呼ぶだけです。それをレンダリングし、データを変換し、必要なことをします。あなたが本当にいい人であれば、あなたはあなた自身のカスタムフィルタの束を追加するだけで、どのコントローラがどのルート/条件付きテンプレートをロードするかによって何がレンダリングされるのかを気にする交渉人もいます。

結論?

あなたは問題なくWPで上記のようなものを扱うことができ、それでも基本的なAPIに固執し、単一のグローバルを呼び出すことやグローバルな名前空間をめちゃくちゃにすることなくコードとデータを再利用できます。

9
kaiser

これは @ kaiser 答えの代替アプローチであり、私はかなり良い(私から+1)を見つけましたが、コアWP関数で使用するには追加の作業が必要です。テンプレート階層と統合されたそれ自体。

私が共有したいアプローチは、テンプレートのレンダリングデータを処理する単一のクラス(私が取り組んでいるものから削除されたバージョンです)に基づいています。

いくつかの(IMO)興味深い機能があります。

  • テンプレートは標準のWordPressテンプレートファイル(single.php、page.php)で、もう少しpowerを取得します
  • 既存のテンプレートが機能するため、既存のテーマのテンプレートを簡単に統合できます。
  • @ kaiser アプローチとは異なり、テンプレートでは$thisキーワードを使用して変数にアクセスします。これにより、未定義の変数の場合に生産中の通知を回避できる可能性があります。

Engineクラス

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()
     );
  }
}

(ここでは Gist として利用可能です。)

使い方

必要なのは、おそらく'template_redirect'フックでEngine::init()メソッドを呼び出すことだけです。これは、テーマfunctions.phpまたはプラグインから実行できます。

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

それで全部です。

既存のテンプレートは期待どおりに機能します。ただし、カスタムテンプレートデータにアクセスできるようになりました。

カスタムテンプレートデータ

カスタムデータをテンプレートに渡すには、2つのフィルターがあります。

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

1つ目はすべてのテンプレートに対して起動され、2つ目はテンプレート固有です。実際、動的な部分{$type}は、ファイル拡張子のないテンプレートファイルのベース名です。

例えば。フィルター'gm_template_data_single'を使用して、データをsingle.phpテンプレートに渡すことができます。

これらのフックに接続されたコールバックは、配列を返す必要があります。キーは変数名です。

たとえば、次のようなテンプレートデータとしてメタデータを渡すことができます。

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

次に、テンプレート内で次のように使用できます。

<?= $this->extra_title ?>

デバッグモード

定数WP_DEBUGWP_DEBUG_DISPLAYの両方がtrueの場合、クラスはデバッグモードで動作します。変数が定義されていない場合、例外がスローされることを意味します。

クラスがデバッグモードでない場合(おそらく本番環境)、未定義の変数にアクセスすると、空の文字列が出力されます。

データモデル

データを整理するためのメンテナンスが容易な方法は、モデルクラスを使用することです。

これらは、上記と同じフィルターを使用してデータを返す非常に単純なクラスにすることができます。従う特定のインターフェイスはありません。好みに応じて整理できます。

以下に、ほんの一例がありますが、独自の方法で自由に行うことができます。

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

__invoke()メソッド(クラスがコールバックのように使用されるときに実行されます)は、テンプレートの<title>タグに使用される文字列を返します。

'gm_template_data'によって渡される2番目の引数がテンプレート名であるという事実のおかげで、このメソッドはホームページのカスタムタイトルを返します。

上記のコードを持つと、次のようなものを使用することが可能になります

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

ページの<head>セクション。

パーシャル

WordPressには、get_header()get_template_part()などの関数があり、これらを使用してメインテンプレートにパーシャルをロードできます。

これらの関数は、他のすべてのWordPress関数と同様に、Engineクラスを使用するときにテンプレートで使用できます。

唯一の問題は、コアWordPress関数を使用して読み込まれたパーシャル内では、$thisを使用してカスタムテンプレートデータを取得するadvanced機能を使用できないことです。

このため、Engineクラスにはメソッドpartial()があり、パーシャルを(完全に子テーマと互換性のある方法で)ロードし、パーシャルでカスタムテンプレートデータを使用することができます。

使い方はとても簡単です。

テーマ(または子テーマ)フォルダー内にpartials/content.phpという名前のファイルがあると仮定すると、それを使用して含めることができます:

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

そのパーシャル内では、すべての親テーマデータにアクセスできますが、同じ方法です。

WordPress関数とは異なり、Engine::partial()メソッドでは、特定のデータをパーシャルに渡すことができ、データの配列を2番目の引数として渡すだけです。

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

デフォルトでは、パーシャルは親テーマで利用可能なデータおよび渡されたデータの明示性にアクセスできます。

Partialに明示的に渡された変数が親テーマ変数と同じ名前を持つ場合、明示的に渡された変数が優先されます。

ただし、isolatedモードにパーシャルを含めることもできます。つまり、パーシャルは親テーマデータにアクセスできません。そのためには、partial()の3番目の引数としてtrueを渡すだけです。

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

結論

たとえ非常に単純であっても、Engineクラスはかなり完全ですが、確実にさらに改善することができます。例えば。変数が定義されているかどうかを確認する方法はありません。

WordPress機能およびテンプレート階層との100%の互換性により、問題なく既存およびサードパーティのコードと統合できます。

ただし、これは部分的にしかテストされていないため、まだ発見していない問題がある可能性があります。

「何が得られたのか?」の下の5つのポイント @ kaiseranswer

  1. データ構造を変更せずにテンプレートを簡単に交換
  2. 読みやすいテンプレート
  3. グローバルスコープを避ける
  4. 単体テスト可能
  5. 他のコンポーネントを損なうことなくモデル/データを交換できます

私のクラスでもすべて有効です。

11
gmazzap

簡単な答え、それは悪いことであるグローバル変数を使うことの臭いがするので、どこにも変数を渡さないでください。

あなたの例から、あなたは初期の最適化をやろうとしているように思えますが、それでもまた別の悪があります;)

DBに格納されているデータを取得するにはワードプレスAPIを使用します。APIは値を取得するだけでなくフィルタやアクションをアクティブにするので、使用率を最大限に高めて使用を最適化しません。 API呼び出しを削除すると、他の開発者がコードの動作を変更せずに変更できなくなります。

5
Mark Kaplun

カイザーの答えは技術的には正しいですが、あなたにとって最良の答えだとは思いません。

独自のテーマを作成している場合は、クラスを使用して何らかのフレームワークを設定するのに最適な方法だと思います(名前空間とインターフェイスも多分ですが、WPテーマ)。

一方、既存のテーマを拡張/調整するだけで、1つまたはいくつかの変数のみを渡す必要がある場合は、globalを使用する必要があります。 header.phpは関数内に含まれているため、そのファイルで宣言した変数はそのファイルでのみ使用できます。 globalを使用すると、WPプロジェクト全体でアクセス可能になります。

header.php ::

global $page_extra_title;

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

single.php(例):

global $page_extra_title;

var_dump( $page_extra_title );
2
redelschaap

簡単な解決策は、追加のタイトルを取得する関数を書くことです。データベース呼び出しを1つだけにするために静的変数を使用します。これをあなたのfunctions.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;
}

Header.phpの外側で、値を取得するために関数を呼び出します。

var_dump(get_extra_title($post->ID));
1
pbd