Cara terbaik untuk melewatkan variabel PHP antar parsial?

16

Saya memiliki variabel di header.php, seperti:

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

Setelah saya lakukan:

var_dump($page_extra_title);

Saya selalu NULLkeluar dari header.php (var_dump hanya berfungsi dengan baik di header.php). Saya telah menempelkan variabel yang sama di mana pun saya membutuhkannya (page.php, post.php, footer.php, dll.), Tapi ini gila dan membuat semuanya hampir mustahil untuk dipertahankan.

Saya bertanya-tanya apa cara terbaik untuk melewatkan variabel melalui semua file di tema saya? Saya kira menggunakan functions.php bersama dengan "get_post_meta" mungkin bukan ide terbaik? :)

Wordpressor
sumber
Saya pikir variabelnya berada dalam cakupan yang sama, juga saya ingin menghindari penggunaan GLOBAL untuk alasan yang jelas.
Wordpressor
Saya percaya komentar ialocino tepat. Satu skrip PHP tidak tahu yang lain ada dan tidak dapat mengakses variabel lokal atau nilainya.
jdm2112
1
header dan footer dimasukkan melalui suatu fungsi, jadi ruang lingkup dari semua file itu adalah cakupan fungsi tersebut.
Milo
4
Jangan tembak pembawa pesan :) Satu-satunya hal yang saya katakan adalah, itu memang masalah ruang lingkup. Ada cara, ya globalkan? Tapi itu tidak mungkin untuk alasan yang baik. Selain itu Anda harus "memanggil" global-variables juga, dengan menggunakan kata kunci untuk membuatnya tersedia. Tergantung pada sesi penggunaan kasus mungkin menjadi solusi. Kalau tidak - seperti yang disebutkan - saya pikir fungsi atau kelas untuk melakukan pekerjaan untuk Anda adalah cara untuk pergi.
Nicolai

Jawaban:

10

Struktur data terpisah dasar

Untuk meneruskan data, Anda biasanya menggunakan Model (itulah "M" dalam "MVC"). Mari kita lihat antarmuka data yang sangat sederhana. Antarmuka hanya digunakan sebagai "Resep" untuk blok bangunan kami:

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

Di atas adalah apa yang kami sebarkan: ID umum dan "Label".

Menampilkan data dengan menggabungkan potongan atom

Selanjutnya kita memerlukan beberapa Tampilan yang bernegosiasi antara Model kita dan ... template kita.

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

Pada dasarnya itu kata Antarmuka

"Kita dapat membuat sesuatu dan Model wajib untuk tugas itu"

Akhirnya kita perlu menerapkan di atas dan membangun View yang sebenarnya . Seperti yang Anda lihat, konstruktor memberi tahu bahwa hal wajib untuk tampilan kami adalah Templat dan kami dapat membuatnya. Demi pengembangan mudah, kami bahkan memeriksa apakah file template benar-benar ada sehingga kami dapat membuat pengembang lain hidup (dan juga kami) jauh lebih mudah dan perhatikan itu.

Pada langkah kedua dalam fungsi render kami menggunakan Penutupan untuk membangun pembungkus template aktual dan bindTo()Model untuk templat.

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

Memisahkan View dan Rendering

Ini artinya kita bisa menggunakan templat yang sangat sederhana seperti berikut ini

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

untuk membuat konten kami. Menyatukan potongan-potongan kita akan mendapatkan sesuatu di sekitar baris berikut (di Controller, Mediator, dll):

namespace WeCodeMore\Package;

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

Apa yang kita dapatkan?

Dengan cara ini kita bisa

  1. Tukar templat dengan mudah tanpa mengubah struktur data
  2. Memiliki tempalt mudah dibaca
  3. Hindari ruang lingkup global
  4. Dapat Unit-Test
  5. Dapat bertukar Model / data tanpa merusak komponen lain

Menggabungkan OOP PHP dengan WP API

Tentu saja hal ini hampir tidak mungkin dengan menggunakan fungsi theming dasar seperti get_header(), get_footer(), dll, kan? Salah. Panggil saja kelas Anda di bagian templat apa pun yang Anda inginkan. Render, ubah data, lakukan apa pun yang Anda inginkan. Jika Anda benar-benar baik, Anda bahkan hanya menambahkan banyak filter kustom Anda sendiri dan memiliki beberapa negosiator untuk mengurus apa yang akan diberikan oleh pengontrol mana pada rute / beban template bersyarat mana.

Kesimpulan?

Anda dapat bekerja dengan hal-hal seperti di atas di WP tanpa masalah dan masih tetap menggunakan API dasar dan menggunakan kembali kode dan data tanpa memanggil satu pun global atau mengacaukan dan mencemari ruang nama global.

kaisar
sumber
3
Tampak hebat! Saya akan melihat lebih dalam, jawaban yang bagus!
marko
@kaiser hampir 3 tahun kemudian, apakah ada pembaruan untuk pemikiran Anda di atas? Templating inti WP belum benar-benar berkembang ke arah yang lebih maju, sehingga solusi pihak ketiga masih menjadi masalah.
lkraav
1
@ Ikraav Saya mungkin tidak akan menulis seperti ini saat ini, tetapi saya masih yakin bahwa tidak menggunakan sintaks yang terpisah untuk menampilkan isi variabel dalam tag HTML adalah cara untuk pergi (dan menghindari overhead yang tidak perlu). Di sisi lain, saya jarang menulis hal-hal frontend di PHP hari ini, tetapi dalam JavaScript. Dan saya sangat suka apa yang VueJS dan teman-teman bawa ke meja.
kaiser
11

Ini adalah pendekatan alternatif untuk @kaiser jawaban , yang saya temukan cukup baik (+1 dari saya) tetapi membutuhkan pekerjaan tambahan untuk digunakan dengan fungsi-fungsi WP inti dan itu per-se terintegrasi rendah dengan hierarki templat.

Pendekatan yang ingin saya bagikan didasarkan pada satu kelas (ini adalah versi singkat dari sesuatu yang saya kerjakan) yang menangani render data untuk templat.

Ini memiliki beberapa (IMO) fitur menarik:

  • templat adalah file templat WordPress standar (single.php, page.php) mereka mendapatkan sedikit lebih banyak kekuatan
  • template yang ada hanya berfungsi, jadi Anda dapat mengintegrasikan template dari tema yang ada tanpa usaha
  • tidak seperti pendekatan @iser , dalam templat Anda mengakses variabel menggunakan $thiskata kunci: ini memberi Anda kemungkinan untuk menghindari pemberitahuan dalam produksi jika ada variabel yang tidak ditentukan

The EngineKelas

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

(Tersedia sebagai Intisari di sini.)

Cara Penggunaan

Satu-satunya hal yang diperlukan adalah memanggil Engine::init()metode, mungkin dengan cara apa pun 'template_redirect'. Itu bisa dilakukan dalam tema functions.phpatau dari sebuah plugin.

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

Itu saja.

Template Anda yang ada akan berfungsi seperti yang diharapkan. Tetapi sekarang Anda memiliki kemungkinan untuk mengakses data templat khusus.

Data Template Kustom

Untuk meneruskan data khusus ke template ada dua filter:

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

Yang pertama dipecat untuk semua templat, yang kedua adalah templat khusus, pada kenyataannya, bagian dymamic {$type}adalah nama dasar dari file templat tanpa ekstensi file.

Misalnya filter 'gm_template_data_single' dapat digunakan untuk meneruskan data kesingle.php templat.

Callback terlampir pada kait ini ini harus mengembalikan array , di mana kunci adalah nama variabel.

Misalnya, Anda dapat meneruskan data meta sebagai data templat suka seperti itu:

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

Dan kemudian, di dalam templat Anda cukup menggunakan:

<?= $this->extra_title ?>

Mode Debug

Ketika kedua konstanta WP_DEBUGdanWP_DEBUG_DISPLAY itu benar, kelas bekerja dalam mode debug. Ini berarti bahwa jika suatu variabel tidak didefinisikan pengecualian dilemparkan.

Ketika kelas tidak dalam mode debug (mungkin dalam produksi) mengakses variabel yang tidak terdefinisi akan menghasilkan string kosong.

Model Data

Cara yang bagus dan dapat dikelola untuk mengatur data Anda adalah dengan menggunakan kelas model.

Mereka bisa menjadi kelas yang sangat sederhana, yang mengembalikan data menggunakan filter yang sama yang dijelaskan di atas. Tidak ada antarmuka khusus untuk diikuti, mereka dapat diatur sesuai keinginan Anda.

Di bawah ini, hanya ada sebuah contoh, tetapi Anda bebas melakukannya dengan cara Anda sendiri.

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

The __invoke()metode (yang berjalan ketika kelas digunakan seperti callback) mengembalikan string yang akan digunakan untuk <title>tag template.

Berkat fakta bahwa argumen kedua yang dilewati 'gm_template_data'adalah nama templat, metode ini mengembalikan judul khusus untuk beranda.

Memiliki kode di atas, maka dimungkinkan untuk menggunakan sesuatu seperti

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

di <head>bagian halaman.

Sebagian

WordPress memiliki fungsi seperti get_header()atau get_template_part()yang dapat digunakan untuk memuat sebagian ke templat utama.

Fungsi-fungsi ini, seperti semua fungsi WordPress lainnya, dapat digunakan dalam template ketika menggunakan Enginekelas.

Satu-satunya masalah adalah bahwa di dalam parsial dimuat menggunakan fungsi inti WordPress tidak mungkin untuk menggunakan fitur canggih untuk mendapatkan data templat kustom menggunakan $this.

Untuk alasan ini, Enginekelas memiliki metode partial()yang memungkinkan untuk memuat sebagian (dengan cara yang sepenuhnya kompatibel dengan tema anak) dan masih dapat digunakan secara parsial data templat kustom.

Penggunaannya cukup sederhana.

Dengan asumsi ada file bernama folder partials/content.phptheme (atau child theme), itu dapat dimasukkan menggunakan:

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

Di dalam parsial itu akan mungkin untuk mengakses semua data tema induk dengan cara yang sama.

Tidak seperti fungsi WordPress, Engine::partial()metode memungkinkan untuk mengirimkan data tertentu ke parsial, hanya meneruskan array data sebagai argumen kedua.

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

Secara default, sebagian memiliki akses ke data yang tersedia dalam tema induk dan ke data yang dilewatkan secara eksplisit.

Jika beberapa variabel yang secara eksplisit diteruskan ke parsial memiliki nama yang sama dengan variabel tema induk, maka variabel yang diteruskan secara eksplisit menang.

Namun, juga dimungkinkan untuk memasukkan parsial dalam mode terisolasi , yaitu parsial tidak memiliki akses ke data tema induk. Untuk melakukan itu, sampaikan trueargumen ketiga ke partial():

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

Kesimpulan

Sekalipun cukup sederhana, Enginekelasnya cukup lengkap, tetapi tentunya bisa lebih ditingkatkan. Misalnya tidak ada cara untuk memeriksa apakah suatu variabel didefinisikan atau tidak.

Berkat kompatibilitasnya 100% dengan fitur WordPress dan hierarki templat Anda dapat mengintegrasikannya dengan kode pihak ketiga yang ada dan tidak ada masalah.

Namun, perhatikan bahwa hanya sebagian yang diuji, jadi ada kemungkinan ada masalah yang belum saya temukan.

Lima poin di bawah "Apa yang kita dapatkan?" dalam jawaban @iser :

  1. Tukar templat dengan mudah tanpa mengubah struktur data
  2. Memiliki tempalt mudah dibaca
  3. Hindari ruang lingkup global
  4. Dapat Unit-Test
  5. Dapat bertukar Model / data tanpa merusak komponen lain

semua berlaku untuk kelas saya juga.

gmazzap
sumber
1
Hehe. Bagus, sobat :) +1
kaiser
@gmazzap hampir 3 tahun kemudian, apakah ada pembaruan untuk pemikiran Anda di atas? Templating inti WP belum benar-benar berkembang ke arah yang lebih maju, sehingga solusi pihak ketiga masih menjadi masalah.
lkraav
1
Saya tidak melakukan banyak pekerjaan tema hari ini. Akhir-akhir ini cara saya untuk pergi adalah menggabungkan github.com/Brain-WP/Context + github.com/Brain-WP/Hierarchy untuk membangun data dan meneruskan ke templat. Untuk mesin template itu sendiri, saya menggunakan pendekatan yang berbeda, Foil (tentu saja), Kumis, tetapi juga Twig (hanya ketika saya memiliki kontrol pada seluruh webiste untuk menghindari neraka ketergantungan) @ lkraav
gmazzap
5

Jawaban sederhana, jangan lewat variabel di mana pun karena bau menggunakan variabel global yang jahat.

Dari contoh Anda, sepertinya Anda mencoba melakukan pengoptimalan awal, namun kejahatan lain;)

Gunakan wordpress API untuk mendapatkan data yang disimpan dalam DB dan jangan mencoba mengakali dan mengoptimalkan penggunaannya karena API melakukan lebih dari sekadar mengambil nilai dan mengaktifkan filter dan tindakan. Dengan menghapus panggilan API, Anda menghapus kemampuan pengembang lain untuk mengubah perilaku kode Anda tanpa memodifikasinya.

Mark Kaplun
sumber
2

Meskipun jawaban kaiser secara teknis benar, saya ragu itu adalah jawaban terbaik untuk Anda.

Jika Anda membuat tema Anda sendiri, maka saya pikir itu memang cara terbaik untuk membuat semacam kerangka menggunakan kelas (dan mungkin ruang nama dan antarmuka juga, meskipun itu mungkin sedikit terlalu banyak untuk tema WP).

Di sisi lain, jika Anda hanya memperluas / menyesuaikan tema yang ada dan hanya perlu melewati satu atau beberapa variabel, saya pikir Anda harus tetap dengan global. Karena header.phptermasuk dalam suatu fungsi, variabel yang Anda nyatakan dalam file itu hanya dapat digunakan dalam file itu. Dengan globalAnda membuatnya dapat diakses di seluruh proyek WP:

Dalam header.php:

global $page_extra_title;

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

Dalam single.php(misalnya):

global $page_extra_title;

var_dump( $page_extra_title );
redelschaap
sumber
3
Saya tidak ingin bersikap kasar atau apa pun, tetapi itu benar-benar praktik buruk menyelam ke lingkup global. Anda harus menghindari menambahkan ke ruang lingkup global sepenuhnya. Anda harus benar-benar berhati-hati dalam penamaan konvensi di sini dan Anda perlu memastikan bahwa nama variabel Anda akan menjadi unik sedemikian rupa sehingga tidak ada orang lain yang dapat mereproduksi nama seperti itu. Pendekatan @iser mungkin terlihat berlebihan bagi Anda, tetapi sejauh ini yang terbaik dan teraman. Saya tidak dapat memberi tahu Anda cara mengatasi hal ini, tetapi saya benar-benar menyarankan Anda untuk tetap berada di luar lingkup global :-)
Pieter Goosen
3
Tentu saja Anda harus berhati-hati agar tidak menimpa variabel lain. Anda bisa mengatasinya dengan menggunakan awalan unik atau array dengan variabel khusus Anda, $wp_theme_vars_page_extra_titleatau $wp_theme_vars['page_extra_title']misalnya. Itu hanya penjelasan mengapa global akan bekerja di sini. OP bertanya cara melewati variabel melalui semua file, menggunakan globaladalah cara untuk melakukan itu.
redelschaap
2
Tidak, global bukan cara untuk melakukan itu. Ada cara yang jauh lebih baik untuk mencapai hal yang sama tanpa menggunakan global. Seperti yang saya katakan sebelumnya, dan seperti @kaiser katakan dalam jawabannya, hindari ruang lingkup global dan jauhi itu. Sama seperti contoh, ambil alternatif yang sangat mudah ini, bungkus kode Anda dalam suatu fungsi dan panggil fungsi tersebut jika diperlukan. Dengan cara ini, Anda tidak perlu mengatur atau menggunakan global.
Pieter Goosen
3
Ya itu. Ini mungkin bukan cara terbaik, tetapi jelas cara.
redelschaap
2
but it is really bad practice diving into the global scopeSaya berharap seseorang mengatakan itu kepada pengembang inti WP. Saya benar-benar tidak mengerti titik menggunakan ruang nama, abstraksi data, pola desain, pengujian unit, dan praktik / teknik pemrograman terbaik lainnya dalam kode yang ditulis untuk Wordpress ketika inti Wordpress dipenuhi dengan praktik pengkodean yang buruk seperti variabel glabal (misalnya, widget kode).
Ejaz
1

Solusi mudah adalah menulis fungsi untuk mendapatkan judul tambahan. Saya menggunakan variabel statis untuk menjaga panggilan basis data menjadi satu saja. Letakkan ini di functions.php Anda.

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

Di luar header.php, panggil fungsi untuk mendapatkan nilai:

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