부분간에 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 등)를 붙여 넣었지만 광기이며 모든 것을 유지하기가 거의 불가능합니다.

내 테마의 모든 파일을 통해 변수를 전달하는 가장 좋은 방법이 무엇인지 궁금합니다. "get_post_meta"와 함께 functions.php를 사용하는 것이 가장 좋은 아이디어는 아닐까요? :)



변수가 같은 범위에 있다고 생각하고 명백한 이유로 GLOBAL을 사용하지 않기를 원합니다.
Wordpressor

나는 ialocin의 의견이 주목적이라고 생각합니다. 하나의 PHP 스크립트는 다른 스크립트가 존재하지 않으며 로컬 변수 또는 해당 값에 액세스 할 수 없습니다.
jdm2112

1
머리글과 바닥 글은 함수를 통해 포함되므로 해당 파일의 모든 범위는 해당 함수의 범위입니다.
Milo

4
메신저를 쏘지 마십시오 :) 내가 말한 유일한 것은 실제로 범위 문제입니다. 방법이 있습니다 global. 맞습니까? 그러나 그것은 좋은 이유 때문에 의문의 여지가 없습니다. 이외에도 global키워드를 사용하여 변수 를 "호출" 해야합니다. 사용 사례에 따라 세션이 해결책 일 수 있습니다. 그렇지 않으면-언급했듯이-당신을 위해 일을하는 기능이나 수업이 갈 길이라고 생각합니다.
Nicolai

답변:


10

기본 분리 된 데이터 구조

데이터를 전달하려면 일반적으로 모델 ( "MVC"의 "M")을 사용하십시오. 데이터를위한 매우 간단한 인터페이스를 보자. 인터페이스 는 빌딩 블록의 "수령자"로 사용됩니다.

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

위는 우리가 전달하는 것입니다 : 공통 ID와 "라벨".

원자 조각을 결합하여 데이터 표시

다음으로 Model과 ... 템플릿 사이를 협상 하는 View 가 필요합니다 .

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>

콘텐츠를 렌더링합니다. 조각을 모으면 컨트롤러, 중재자 등의 다음 줄에 무언가가 생깁니다.

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
좋아 보인다! 이것에 대해 더 자세히 살펴볼 것입니다.
marko

@kaiser 거의 3 년 후에 위의 생각에 대한 업데이트가 있습니까? WP 핵심 템플릿은 더 이상 고급 방향으로 진행되지 않았으므로 타사 솔루션은 여전히 ​​중요한 일입니다.
lkraav

1
@Ikraav 나는 요즘처럼 작성하지 않을 것이지만 HTML 태그 내부의 변수 내용을 출력하기 위해 별도의 구문을 사용하지 않는 것이 좋은 방법이라고 확신합니다 (그리고 불필요한 오버 헤드를 피하십시오). 반면에, 요즘 PHP에서는 프론트 엔드를 거의 쓰지 않지만 JavaScript로 작성합니다. 그리고 저는 VueJS와 친구들이 테이블에 가져 오는 것을 정말 좋아합니다.
카이저

11

이것은 @kaiser에 대한 대안입니다. 답변에 , 꽤 괜찮은 것을 발견했지만 (+1) 핵심 WP 함수와 함께 사용하려면 추가 작업이 필요하며 템플릿 계층 구조와 통합되어 있습니다.

내가 공유하려는 접근 방식은 템플릿의 렌더 데이터를 처리하는 단일 클래스 (내가 작업중 인 것에서 제거 된 버전)를 기반으로합니다.

그것은 (IMO) 흥미로운 기능을 가지고 있습니다 :

  • 템플릿은 표준 워드 프레스 템플릿 파일 (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 템플릿 .

이러한 후크에 연결된 콜백 은 배열을 반환해야합니다 . 여기서 키는 변수 이름입니다.

예를 들어 템플릿 데이터처럼 메타 데이터를 다음과 같이 전달할 수 있습니다.

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_DISPLAYtrue가 모두 같으면 클래스는 디버그 모드에서 작동합니다. 변수가 정의되어 있지 않으면 예외가 발생 함을 의미합니다.

클래스가 디버그 모드에 있지 않을 때 (아마도 프로덕션 환경에서) 정의되지 않은 변수에 액세스하면 빈 문자열이 출력됩니다.

데이터 모델

데이터를 정리하는 좋은 방법은 모델 클래스를 사용하는 것입니다.

그것들은 위에서 설명한 것과 동일한 필터를 사용하여 데이터를 반환하는 매우 간단한 클래스 일 수 있습니다. 따라야 할 특정 인터페이스가 없으며 선호도에 따라 구성 할 수 있습니다.

아래에는 예가 있지만 자신의 방식으로 자유롭게 할 수 있습니다.

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>페이지의 섹션을 참조하십시오.

부분

워드 프레스는 같은 기능을 가지고 get_header()또는 get_template_part()그 메인 템플릿에 파셜을로드 할 수 있습니다.

이 기능은 다른 모든 WordPress 기능과 마찬가지로 Engine클래스를 사용할 때 템플릿에서 사용할 수 있습니다 .

유일한 문제는 핵심 WordPress 기능을 사용하여로드 된 부분 내부에서을 사용하여 사용자 정의 템플릿 데이터를 얻는 고급 기능 을 사용할 수 없다는 것 $this입니다.

이러한 이유로이 Engine클래스에는 partial()부분 (전체 하위 테마 호환 방식)을로드하고 사용자 정의 템플릿 데이터를 부분적으로 사용할 수 있는 메소드 가 있습니다.

사용법은 매우 간단합니다.

partials/content.phptheme (또는 child theme) 폴더 안에 파일이 있다고 가정하면 다음을 사용하여 포함시킬 수 있습니다.

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

그 부분 안에서는 모든 부모 테마 데이터에 액세스하는 것이 가능합니다.

WordPress 함수와 달리이 Engine::partial()방법을 사용하면 특정 데이터를 부분적으로 전달할 수 있으며 단순히 데이터 배열을 두 번째 인수로 전달할 수 있습니다.

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

기본적으로 partials는 상위 테마에서 사용 가능한 데이터 및 전달 된 데이터 설명에 액세스 할 수 있습니다.

부분적으로 명시 적으로 전달 된 일부 변수의 상위 테마 변수 이름이 동일하면 명시 적으로 전달 된 변수가 우선합니다.

그러나 격리 된 모드 에서 부분을 포함 할 수도 있습니다 . 즉 부분이 상위 테마 데이터에 액세스 할 수 없습니다. 그렇게하려면 true세 번째 인수로 전달하십시오 partial().

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

결론

꽤 단순하더라도 Engine수업은 완벽하지만 확실히 향상 될 수 있습니다. 예를 들어 변수가 정의되어 있는지 여부를 확인할 수있는 방법이 없습니다.

WordPress 기능 및 템플릿 계층 구조와 100 % 호환되므로 기존 및 타사 코드와 문제없이 통합 할 수 있습니다.

그러나 부분적으로 만 테스트되었으므로 아직 발견하지 못한 문제가있을 수 있습니다.

"우리는 무엇을 얻었습니까?" 의 다섯 가지 포인트 에서 @kaiser의 대답 :

  1. 데이터 구조를 변경하지 않고 템플릿을 쉽게 교환
  2. 템포를 쉽게 읽을 수 있습니다
  3. 글로벌 범위를 피하십시오
  4. 단위 테스트 가능
  5. 다른 부품을 손상시키지 않고 모델 / 데이터 교환 가능

수업에도 유효합니다.


1
헤헤 잘 했어, 친구 :) +1
kaiser

@gmazzap 거의 3 년 후에 위의 생각에 대한 업데이트가 있습니까? WP 핵심 템플릿은 더 이상 고급 방향으로 진행되지 않았으므로 타사 솔루션은 여전히 ​​중요한 일입니다.
lkraav

1
요즘에는 많은 테마가 작동하지 않습니다. 최근에 github.com/Brain-WP/Context + github.com/Brain-WP/Hierarchy 를 결합 하여 데이터를 작성하고 템플릿으로 전달했습니다. 템플릿 엔진 자체의 경우 Foil (물론), Mustache 및 Twig (종종 지옥을 피하기 위해 전체 웹리스트에서 제어 할 때만) @lkraav
gmazzap

5

간단한 대답, 사악한 전역 변수 사용에 악영향을 미치므로 어디에서나 변수를 전달하지 마십시오.

귀하의 예에서 조기 최적화를 시도하는 것처럼 보이지만 또 다른 악이 있습니다.)

wordpress API를 사용하여 DB에 저장된 데이터를 가져 오십시오. API가 더 많은 값을 검색하고 필터와 작업을 활성화하기 때문에 사용량을 능가하고 사용을 최적화하려고하지 마십시오. API 호출을 제거하면 다른 개발자가 코드를 수정하지 않고 코드 동작을 변경할 수있는 기능이 제거됩니다.


2

카이저의 대답은 기술적으로 옳지 만, 그것이 당신에게 가장 적합한 대답이라고 생각합니다.

당신이 당신의 자신의 테마를 만들고 있다면, 실제로 클래스를 사용하여 일종의 프레임 워크를 설정하는 가장 좋은 방법이라고 생각합니다.

반면에 기존 테마를 확장 / 조정하고 하나 또는 몇 개의 변수 만 전달 해야하는 경우을 사용해야한다고 생각합니다 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 접근 방식은 선상으로 보이지만 가장 안전하고 안전합니다. 나는 어떻게이 문제를 해결하는 방법을 말할 수 없다,하지만 난 정말 당신이 :-) 전역 끼어 들지 조언 할
피터 구센

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 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));
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.