パーシャル間でPHP変数を渡す最良の方法は?


16

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

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

一度したら:

var_dump($page_extra_title);

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

私のテーマのすべてのファイルに変数を渡す最良の方法は何ですか?functions.phpを「get_post_meta」と一緒に使用するのは最良のアイデアではないでしょうか?:)



変数は同じスコープ内にあると思いますが、明白な理由でGLOBALを使用することも避けたいです。
Wordpressor

ialocinのコメントはスポットライトだと思います。1つのPHPスクリプトは、他のPHPスクリプトが存在することを知らず、ローカル変数またはその値にアクセスできません。
jdm2112

1
ヘッダーとフッターは関数を介して含まれているため、これらのファイル内のすべてのスコープはそれらの関数のスコープです。
ミロ

4
メッセンジャーを撃たないでください:)私が言った唯一のことは、それは確かにスコープの問題です。方法があります、そうglobalですよね?しかし、それは正当な理由で問題外です。globalキーワードを使用して変数を使用可能にすることで、変数も「呼び出す」必要があります。ユースケースによっては、セッションが解決策になる場合があります。そうでなければ-前述のように-私はあなたのために仕事をする関数またはクラスが行く方法だと思います。
ニコライ

回答:


10

基本的な分離データ構造

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

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

上記は、共通IDと「ラベル」です。

アトミックピースを組み合わせてデータを表示する

次に、モデルとテンプレートの間でネゴシエートするビューが必要です。

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

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

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

最後に、上記を実装し、実際のViewを構築する必要があります。ご覧のとおり、コンストラクターは、ビューに必須のものはテンプレートであり、レンダリングできることを示しています。開発を簡単にするために、テンプレートファイルが実際に存在するかどうかを確認して、他の開発者(および私たちも)をより簡単にし、それに注意できるようにします。

レンダリング関数の2番目のステップでは、クロージャーを使用して、実際のテンプレートラッパーと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>

コンテンツをレンダリングします。ピースをまとめると、次の行(コントローラー、メディエーターなど)の周りに何かが表示されます。

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 APIの組み合わせ

もちろん、このような基本的なテーマ設定機能を使用して、ほとんど不可能であるget_header()get_footer()右、など、?違う。必要なテンプレートまたはテンプレートパーツでクラスを呼び出すだけです。レンダリングし、データを変換し、必要なことを行います。あなたが本当に素晴らしいなら、あなたはあなた自身のカスタムフィルターの束を追加するだけで、どのルート/条件付きテンプレートロードでどのコントローラーによってレンダリングされるかを世話するための交渉者を持っています。

結論?

WPで上記のようなものを問題なく操作でき、単一のグローバルを呼び出したり、グローバルネームスペースを混乱させたり汚染したりすることなく、基本的なAPIに固執し、コードとデータを再利用できます。


3
素晴らしく見える!これについてもっと詳しく見ていきましょう。
マルコ

@kaiserほぼ3年後、上記のあなたの考えの更新はありますか?WPコアテンプレートはこれ以上高度な方向に進んでいません。そのため、サードパーティのソリューションは依然として重要です。
lkraav

1
@Ikraav最近ではこのようには書かないでしょうが、HTMLタグ内の変数のコンテンツを出力するために別の構文を使用しないことは進むべき方法であると確信しています(そして不必要なオーバーヘッドを回避します)。一方で、最近ではPHPでフロントエンドを書くことはめったにありませんが、JavaScriptで書いています。そして、私はVueJSと友人たちがテーブルに持ってきているものが本当に好きです。
カイザー

11

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

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

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

  • テンプレートは標準のWordPressテンプレートファイル(single.php、page.php)であり、もう少し力があります
  • 既存のテンプレートが機能するため、既存のテーマのテンプレートを簡単に統合できます。
  • @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()
     );
  }
}

(ここで要点として利用できます。)

使い方

必要なのはEngine::init()、おそらく'template_redirect'オンフックでメソッドを呼び出すことだけです。これは、テーマ内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_DEBUGとの両方WP_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>テンプレートのタグ。

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

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

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

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

パーシャル

WordPressには、パーシャルをメインテンプレートに読み込むために使用できる、get_header()またはget_template_part()使用できる機能があります。

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

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

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

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

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

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

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

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

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

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

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

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

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

結論

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

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

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

「何が得られたのか」の5つの​​ポイント 中@kaiserの 答え

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

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


1
へへ。よくやった、仲間:) +1
kaiser

@gmazzapほぼ3年後、上記のあなたの考えの更新はありますか?WPコアのテンプレート化は、これ以上進んだ方向に進んでいないため、サードパーティのソリューションは依然として重要です。
lkraav

1
最近は多くのテーマが機能しません。最近行った方法は、github.com / Brain - WP / Context + github.com/Brain-WP/Hierarchyを組み合わせてデータを構築し、テンプレートに渡すことでした。テンプレートエンジン自体には、さまざまなアプローチ、フォイル(もちろん)、口ひげだけでなく、Twig(依存関係の地獄を避けるためにWebiste全体を制御できる場合のみ)を使用しました。@ lkraav
gmazzap

5

簡単な答えは、変数をどこにも渡さないでください。グローバル変数を使用するのは悪ですから。

あなたの例から、あなたは早期の最適化を試みているように見えますが、さらに別の悪;)

wordpress APIを使用して、DBに保存されているデータを取得します。APIが値を取得するだけでなく、フィルターとアクションをアクティブにするため、DBの使用を最適化しようとしないでください。API呼び出しを削除すると、他の開発者がコードを変更せずにコードの動作を変更できなくなります。


2

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

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

一方、既存のテーマを拡張/調整するだけで、1つまたはいくつかの変数のみを渡す必要がある場合は、に固執する必要があると思いますglobalheader.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 );

3
失礼なことなどしたくありませんが、グローバルな範囲に飛び込むのは本当に悪い習慣です。グローバルスコープに完全に追加することは避けてください。ここでの命名規則には本当に注意する必要があり、他の人がそのような名前を再現できないように、変数名が一意になるようにする必要があります。@kaiserアプローチは、あなたにとっては過剰なものに思えるかもしれませんが、それは間違いなく最高で安全です。これに取り組む方法を説明することはできませんが、グローバルな範囲から外れるようにアドバイスします:
ピーターグーセン

3
もちろん、他の変数を上書きしないように注意する必要があります。あなたのカスタム変数、とのユニークな接頭辞や配列を使用しているに取り組むことができます$wp_theme_vars_page_extra_titleまたは$wp_theme_vars['page_extra_title']たとえば。ここでグローバルが機能する理由は説明に過ぎません。OPは、すべてのファイルに変数を渡す方法を求めましたglobal
redelschaap

2
いいえ、グローバルはそれを行う方法ではありません。グローバルを使用せずに同じことを達成するはるかに良い方法があります。前に言ったように、そして@kaiserが彼の答えで述べたように、グローバルな範囲を避けて、そこから離れてください。ほんの一例として、この非常に簡単な代替案を採用し、コードを関数でラップし、必要に応じて関数を呼び出します。このように、グローバルを設定または使用する必要はありません。
ピーターグーセン

3
はい、そうです。それは最善の方法ではないかもしれませんが、間違いなく方法です。
redelschaap

2
but it is really bad practice diving into the global scopeWPコア開発者にそれを誰かに伝えてほしい。Wordpressコアにグラバル変数(例:ウィジェット)のような不適切なコーディングプラクティスが散らばっている場合、Wordpress 向けに記述されたコードで名前空間、データ抽象化、デザインパターン、単体テスト、およびその他のプログラミングのベストプラクティス/テクニックを使用することのポイントが本当にわかりませんコード)。
エハズ

1

簡単な解決策は、追加のタイトルを取得する関数を作成することです。静的変数を使用して、データベース呼び出しを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));
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.