在局部之间传递PHP变量的最佳方法?


16

我在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等),但这很疯狂,几乎所有内容都无法维护。

我想知道通过主题中所有文件传递变量的最佳方法是什么?我猜想将functions.php与“ get_post_meta”一起使用可能不是最好的主意?:)



我确实认为该变量在同一范围内,出于明显的原因,我也想避免使用GLOBAL。
Wordpressor'Apr

我相信ialocin的评论是当场的。一个PHP脚本不知道另一个脚本,因此无法访问它的局部变量或它们的值。
jdm2112

1
页眉和页脚是通过函数包含的,因此这些文件中所有内容的作用域就是这些函数的作用域。
米洛

4
不要开枪:)我唯一说的是,这确实是一个示波器问题。有办法global,对吧?但这是有充分理由的。此外,您还必须global通过使用关键字使变量可用,从而“调用” 变量。根据用例,会话可能是解决方案。否则,如前所述,我认为要为您完成工作的函数或类是要走的路。
Nicolai 2015年

Answers:


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。如您所见,构造函数告诉我们视图的强制性内容是模板,并且可以渲染它。为了易于开发,我们甚至检查模板文件是否确实存在,以便使其他开发人员(以及我们的)生活更加轻松,并注意这一点。

在渲染功能的第二步中,我们使用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. 容易阅读的tempaltes
  3. 避免全球范围
  4. 可以单元测试
  5. 可以交换模型/数据而不会损害其他组件

将OOP PHP与WP API结合

当然,这几乎是不可能通过使用基本主题化功能,例如get_header()get_footer()等等,对不对?错误。只需在您想要的任何模板或模板部分中调用您的类即可。渲染,转换数据,执行任何您想做的事情。如果您真的很好,您甚至可以只添加自己的一堆自定义过滤器,并聘请一些谈判人员来照顾哪个控制器在哪个路由/条件模板上加载的内容。

结论?

您可以毫无问题地使用WP中的上述内容,并且仍然坚持使用基本API并重用代码和数据,而无需调用单个全局变量或弄乱和污染全局名称空间。


3
看起来很棒!我将进一步研究这个不错的答案!
marko 2015年

@kaiser大约3年后,您的上述想法有没有更新?WP核心模板并没有真正朝更高的方向发展,因此第三方解决方案仍然是一回事。
lkraav

1
@Ikraav我现在可能不会像现在这样写,但是我仍然可以确定,不使用单独的语法来输出HTML标签内变量的内容是可行的(并避免了不必要的开销)。另一方面,这些天我很少用PHP来编写前端的东西,而是用JavaScript。我真的很喜欢VueJS和朋友带来的东西。
kaiser

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

(此处为Gist。)

如何使用

唯一需要做的就是调用Engine::init()方法,可能是在'template_redirect'钩子上。可以在主题中functions.php或通过插件完成。

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

就这样。

您现有的模板将按预期工作。但是现在您可以访问自定义模板数据了。

自定义模板数据

要将自定义数据传递到模板,有两个过滤器:

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

第一个是针对所有模板触发的,第二个是针对模板的,实际上,动态部分{$type}是没有文件扩展名的模板文件的基本名称。

例如,过滤器'gm_template_data_single'可用于将数据传递到single.php模板。

连接到这些钩子的回调必须返回一个array,其中的键是变量名。

例如,您可以将元数据作为模板数据进行传递,如下所示:

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'是模板名称,因此该方法返回首页的自定义标题。

有了上面的代码,便可以使用类似

 <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()method允许将特定数据传递给局部函数,只需将数据数组作为第二个参数传递即可。

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

默认情况下,局部函数可以访问父主题中可用的数据以及所传递的数据显式性。

如果显式传递给partial的某些变量与父主题变量具有相同的名称,则显式传递的变量将获胜。

但是,也可以在隔离模式下包含部分内容,即该部分内容无法访问父主题数据。为此,只需将true第三个参数传递给partial()

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

结论

即使很简单,Engine类也很完整,但是肯定可以进一步改进。例如,无法检查是否定义了变量。

由于它与WordPress功能和模板层次结构具有100%的兼容性,因此您可以毫无问题地将其与现有和第三方代码集成。

但是,请注意,这只是部分测试,因此可能存在我尚未发现的问题。

“我们获得了什么?”下的五点 在@kaiser 答案中

  1. 无需更改数据结构即可轻松交换模板
  2. 容易阅读的tempaltes
  3. 避免全球范围
  4. 可以单元测试
  5. 可以交换模型/数据而不会损害其他组件

都对我的课程有效。


1
呵呵。干得好,伙伴:) +1
kaiser 2015年

@gmazzap大约3年后,您的上述想法有没有更新?WP核心模板并没有真正朝更高的方向发展,因此第三方解决方案仍然是一回事。
lkraav

1
这些天我没有做很多主题工作。最近,我的方法是结合github.com/Brain-WP/Context + github.com/Brain-WP/Hierarchy来构建数据并传递给模板。对于模板引擎本身,我使用了不同的方法,例如Foil(当然),Mustache,还有Twig(仅当我控制了整个webiste以避免依赖地狱时)@lkraav
gmazzap

5

简单的答案,不要在任何地方传递变量,因为它会使用有害的全局变量。

从您的示例看来,您似乎正在尝试尽早进行优化,但这又是另一回事;)

使用wordpress API来获取存储在数据库中的数据,并且不要试图精打细算并优化其用法,因为API会做更多的事情,然后仅检索值并激活过滤器和操作。通过删除API调用,您可以消除其他开发人员更改代码行为而不修改代码的能力。


2

尽管kaiser的答案在技术上是正确的,但我怀疑这对您来说是最佳答案。

如果您要创建自己的主题,那么我认为这确实是使用类建立某种框架的最佳方法(也许还有名称空间和接口,尽管对于WP主题来说可能太多了)。

另一方面,如果您只是扩展/调整现有主题,而只需要传递一个或几个变量,我认为您应该坚持使用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 );

3
我不想变得粗鲁无礼,但进入全球范围确实是一个坏习惯。您应该避免完全添加到全局范围。您必须非常小心此处的命名约定,并且需要确保您的变量名将是唯一的,以使其他任何人都无法复制该名称。@kaiser方法对您来说似乎有些过时,但这是最好和最安全的方法。我无法告诉您如何解决这个问题,但我确实建议您不要走出全球范围:-)
Pieter Goosen 2015年

3
当然,您需要注意不要覆盖其他变量。您可以通过使用一个唯一的前缀或阵列自定义变量,处理那些$wp_theme_vars_page_extra_title$wp_theme_vars['page_extra_title']为例子。这只是一个解释,为什么global会在这里工作。OP提出了一种在所有文件中传递变量global的方法,使用这样做是一种方法。
redelschaap 2015年

2
不,全局变量不是做到这一点的方法。有很多更好的方法可以在不使用全局变量的情况下实现相同目标。就像我之前说过的那样,正如@kaiser在他的回答中指出的那样,请避免使用全局范围并远离它。举个例子,采用这种非常简单的替代方法,将代码包装在一个函数中,并在需要时调用该函数。这样,您无需设置或使用全局变量。
Pieter Goosen

3
是的。这可能不是最佳方法,但绝对是一种方法。
redelschaap 2015年

2
but it is really bad practice diving into the global scope我希望有人告诉WP核心开发人员。我真的不明白在 Wordpress 编写代码中使用名称空间,数据抽象,设计模式,单元测试和其他编程最佳实践/技术的意义,当Wordpress核心充斥着诸如全局变量(例如小部件)之类的不良编码实践时,码)。
Ejaz

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));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.