comments_template ()가 comments.php를로드하지 못하도록 방지


9

템플릿 엔진을 사용하여 WordPress 테마를 개발 중입니다. 내 코드가 WP 핵심 기능과 최대한 호환되기를 바랍니다.

어떤 맥락 먼저

첫 번째 문제는 WP 쿼리에서 시작하여 템플릿 을 해결 하는 방법을 찾는 것이 었습니다 . 내 라이브러리 인 Brain \ Hierarchy를 사용하여 그 문제를 해결했습니다 .

에 관한 get_template_part()로드의 파셜이 좋아하는 다른 기능 것으로 get_header(), get_footer()유사한, 그것은 템플릿 엔진 부분의 기능에 대한 쓰기 래퍼에 꽤 쉬웠다.

문제

내 문제는 이제 주석 템플릿을로드하는 방법입니다.

워드 프레스 기능 comments_template()은 많은 기능을 수행하는 ~ 200 줄 기능으로, 최대 코어 호환성을 위해서도하고 싶습니다.

그러나을 호출하자마자 comments_template()파일은 required이며 첫 번째 파일 입니다.

  • 상수의 파일 ( COMMENTS_TEMPLATE정의 된 경우)
  • comments.php 테마 폴더에있는 경우
  • /theme-compat/comments.php WP의 마지막 대안으로 폴더가 포함됩니다.

간단히 말해서, 함수가 PHP 파일을로드하는 것을 막을 수있는 방법이 없습니다 . 템플릿 을 렌더링 하고 단순히 사용할 필요가 없기 때문에 바람직 하지 않습니다 require.

현재 솔루션

현재 빈 comments.php파일을 배송 'comments_template'중이며 필터 후크를 사용하여 WordPress에서로드하려는 템플릿을 확인하고 템플릿 엔진의 기능을 사용하여 템플릿을로드합니다.

이 같은:

function engineCommentsTemplate($myEngine) {

    $toLoad = null; // this will hold the template path

    $tmplGetter = function($tmpl) use(&$toLoad) {
       $toLoad = $tmpl;

       return $tmpl;
    };

    // late priority to allow filters attached here to do their job
    add_filter('comments_template', $tmplGetter, PHP_INT_MAX);

    // this will load an empty comments.php file I ship in my theme
    comments_template();

    remove_filter('comments_template', $tmplGetter, PHP_INT_MAX);

    if (is_file($toLoad) && is_readable($toLoad)) {
       return $myEngine->render($toLoad);
    }

    return '';    
}

질문

이것은 작동하지만 핵심 호환 가능하지만 ... 빈을 선적하지 않고도 작동시킬 수 comments.php있습니까?

나는 그것을 좋아하지 않기 때문에.

답변:


4

다음 솔루션이 OP 솔루션보다 낫다 는 것을 확신하지 못하고 대안, 아마도 더 해킹적인 솔루션이라고 가정 해 봅시다.

'comments_template'필터가 적용될 때 PHP 예외를 사용하여 WordPress 실행을 중지 할 수 있다고 생각합니다 .

템플릿 을 전달 하기 위해 사용자 지정 예외 클래스를 DTO로 사용할 수 있습니다 .

이것은 예외에 대한 초안입니다.

class CommentsTemplateException extends \Exception {

   protected $template;

   public static function forTemplate($template) {
     $instance = new static();
     $instance->template = $template;

     return $instance;
   }

   public function template() {
      return $this->template;
   }
}

이 예외 클래스를 사용할 수 있으면 함수는 다음과 같습니다.

function engineCommentsTemplate($myEngine) {

    $filter = function($template) {
       throw CommentsTemplateException::forTemplate($template);
    };  

    try {
       add_filter('comments_template', $filter, PHP_INT_MAX); 
       // this will throw the excption that makes `catch` block run
       comments_template();
    } catch(CommentsTemplateException $e) {
       return $myEngine->render($e->template());
    } finally {
       remove_filter('comments_template', $filter, PHP_INT_MAX);
    }
}

finally블록에는 PHP 5.5 이상이 필요합니다.

같은 방식으로 작동하며 빈 템플릿이 필요하지 않습니다.


4

나는 전에 이것과 씨름했고 내 해결책은- 아무것도 하지 않는 한 파일을 요구하지 않을 있습니다.

메도우 템플릿 프로젝트의 관련 코드는 다음과 같습니다 .

public function comments_template( \Twig_Environment $env, $context, $file = 'comments.twig', $separate_comments = false ) {

    try {
        $env->loadTemplate( $file );
    } catch ( \Twig_Error_Loader $e ) {
        ob_start();
        comments_template( '/comments.php', $separate_comments );
        return ob_get_clean();
    }

    add_filter( 'comments_template', array( $this, 'return_blank_template' ) );
    comments_template( '/comments.php', $separate_comments );
    remove_filter( 'comments_template', array( $this, 'return_blank_template' ) );

    return twig_include( $env, $context, $file );
}

public function return_blank_template() {

    return __DIR__ . '/blank.php';
}

comments_template()전역을 설정하는 동작을 살펴 보았지만 빈 PHP 파일을 피드하고 require실제 Twig 템플릿으로 이동하여 출력하십시오.

이것은 comments_template()Twig 템플릿이 실제 PHP 함수가 아닌 중간 추상화를 호출하기 때문에 초기 호출 을 가로 챌 수 있어야 합니다.

여전히 빈 파일을 제공해야하지만 라이브러리에서 그렇게하고 테마를 구현해도 전혀 신경 쓰지 않아도됩니다.


감사합니다. 나는 전에 초원을 사용한 이후로 당신의 접근 방식을 이미 보았습니다. 내가 여기서 싫어했던 것은 어쨌든 빈 템플릿을 배송해야한다는 사실입니다. 또한 comments_template필터 또는 COMMENTS_TEMPLATE상수를 사용 하여 템플릿을 사용자 정의 하려는 시도가 중단 됩니다. 중추적 인 것은 아니지만 내가 말했듯이 가능한 한 코어와 호환되도록하고 싶었습니다.
gmazzap

@gmazzap hmmm ... 래퍼에서 필터 및 상수에 대한 지원을 추가 할 수는 없었지만 마이크로 관리에 들어갔습니다.
Rarst

3

해결 방법 : 고유 한 파일 이름을 가진 임시 파일을 사용하십시오.

많은 점프와 PHP의 가장 더러운 구석으로 기어 들어간 후, 나는 그 질문을 다음과 같이 표현했다.

어떻게 PHP를 반환 하도록 속일 수 있습니까?TRUEfile_exists( $file )

핵심 코드는

file_exists( apply_filters( 'comments_template', $template ) )

그런 다음 질문이 더 빨리 해결되었습니다.

$template = tempnam( __DIR__, '' );

그리고 그게 다야. 어쩌면 wp_upload_dir()대신 사용하는 것이 좋습니다.

$uploads = wp_upload_dir();
$template = tempname( $uploads['basedir'], '' );

또 다른 옵션은 get_temp_dir()어떤 랩 을 사용 하는 것 WP_TEMP_DIR입니다. 힌트 : 이상하게 돌아가서 재부팅 할 /tmp/때마다 파일이 보존 되지 않습니다/var/tmp/ . 마지막에 간단한 문자열 비교를 수행하고 반환 값을 확인한 다음 필요한 경우 수정하십시오.이 경우에는 그렇지 않습니다.

$template = tempname( get_temp_dir(), '' )

내용이없는 임시 파일에 대해 오류가 발생했는지 빠르게 테스트하려면 다음을 수행하십시오.

<?php
error_reporting( E_ALL );
$template = tempnam( __DIR__, '' );
var_dump( $template );
require $template;

그리고 : 오류 없음 → 작동합니다.

편집 : 바와 같이 @toscho 정지 거기에, 코멘트에 지적 더 나은 그것을 할 방법 :

$template = tempnam( trailingslashit( untrailingslashit( sys_get_temp_dir() ) ), 'comments.php' );

참고 : php.net docs의 사용자 노트에 따르면 , sys_get_temp_dir()동작은 시스템마다 다릅니다. 따라서 결과는 후행 슬래시를 제거한 다음 다시 추가합니다. 핵심 버그 # 22267 이 수정 되었으므로 이제 Win / IIS 서버에서도 작동합니다.

리팩토링 된 기능 (테스트되지 않음) :

function engineCommentsTemplate( $engine )
{
    $template = null;

    $tmplGetter = function( $original ) use( &$template ) {
        $template = $original;
        return tempnam( 
            trailingslashit( untrailingslashit( sys_get_temp_dir() ) ),
            'comments.php'
        );
    };

    add_filter( 'comments_template', $tmplGetter, PHP_INT_MAX );

    comments_template();

    remove_filter( 'comments_template', $tmplGetter, PHP_INT_MAX );

    if ( is_file( $template ) && is_readable( $template ) ) {
        return $engine->render( $template );
    }

    return '';
}

보너스 Nr.1 ​​: tmpfile()이 반환 NULL됩니다. 그래 진짜.

보너스 Nr.2 : file_exists( __DIR__ )이 반환 TRUE됩니다. 네, 정말로 ... 잊어 버린 경우를 대비하여.

^ 이것은 WP 코어의 실제 버그로 이어집니다.


다른 사람들이 탐색기 모드로 들어가서 문서화되지 않은 조각을 찾는 것을 돕기 위해 시도한 것을 빠르게 요약합니다.

시도 1 : 메모리의 임시 파일

첫 번째 시도는을 사용하여 임시 파일에 대한 스트림을 만드는 것 php://temp입니다. PHP 문서에서 :

이 둘의 유일한 차이점은 php://memory데이터를 항상 메모리에 저장하는 반면 php://temp저장된 데이터 양이 사전 정의 된 제한에 도달하면 임시 파일을 사용한다는 것입니다 (기본값은 2MB). 이 임시 파일의 위치는 sys_get_temp_dir()기능 과 같은 방식으로 결정됩니다 .

코드:

$handle = fopen( 'php://temp', 'r+' );
fwrite( $handle, 'foo' );
rewind( $handle );
var_dump( file_exist( stream_get_contents( $handle, 5 ) );

찾기 : 아뇨, 작동하지 않습니다.

시도 2 : 임시 파일 사용

tmpfile()있으니 사용하지 않으시겠습니까?!

var_dump( file_exists( tmpfile() ) );
// boolean FALSE

예, 바로 가기에 관한 것입니다.

시도 3 : 사용자 정의 스트림 래퍼 사용

다음으로 내가 수 있다고 생각 사용자 정의 스트림 래퍼를 구축 하고 사용하여 등록합니다stream_wrapper_register() . 그런 다음 해당 스트림 의 가상 템플릿 을 사용 하여 파일이 있다고 믿는 코어를 속일 수 있습니다. 아래 코드 예 (이미 전체 클래스를 삭제했으며 기록에 충분한 단계가 없습니다 ...)

class TemplateStreamWrapper
{
    public $context;

    public function stream_open( $path, $mode, $options, &$opened )
    {
        // return boolean
    }
}

stream_wrapper_register( 'vt://comments', 'TemplateStreamWrapper' );
// … etc. …

다시 말하지만,이 반환 NULLfile_exists().


PHP 5.6.20으로 테스트


나는 당신의 시도 3이 이론적으로 효과가 있다고 생각합니다. 사용자 정의 스트림 래퍼에서 구현 stream_stat()했습니까? 나는 이것이 무엇이라고 생각 file_exists()자사의 체크 ... 만들기 위해 호출 php.net/manual/en/streamwrapper.stream-stat.php
알랭 Schlesser

꽤 좋고 해킹이 많지 않기 때문에 공감되었습니다. 그러나 내 코드는 다른 설정에서 사용되도록 작성되었으므로 쓰기 권한이 문제가 될 수 있습니다. 또한 임시 파일을 삭제해야합니다.이 파일은에 의해 반환되는 전체 경로를 가로 채기가 쉽지 않기 때문에 즉석 에서 쉽지 않습니다 tempnam(). 크론 작업을 사용하면 작동하지만 추가 오버 헤드가 발생합니다.
gmazzap

임시 파일을 작성하는 것이 빈 템플릿을 배송하는 것보다 나쁘다고 생각합니다. 고정 된 빈 템플릿이 opcode로 캐시됩니다. 임시 파일은 디스크에 기록되고, 구문 분석 (opcode)되지 않은 다음 삭제되어야합니다. 정당한 이유없이 디스크 적중을 최소화하는 것이 좋습니다.
Rarst

@Rarst 문제는 현명한 "더 나은 것"이 아니었다 . 질문은 템플릿 파일이없는 것으로 정리되었습니다 :)
kaiser

1
tempnam( sys_get_temp_dir(), 'comments.php' )한 번 작성 되면 파일 이름을 재사용 할 수 있으며 파일이 비어 있으므로 많은 리소스를 사용하지 않습니다. 또한 코드에서 이해하기 쉽습니다. 지금까지 최고의 솔루션, imho.
fuxia

3

으로 @AlainSchlesser는 경로 (및 비 작업 일로서 항상 버그 나)에 따라 제안, 나는 가상 파일 스트림 래퍼를 구축하는 시도. 나는 그것을 스스로 해결할 수 없었지만 (읽기 : 문서의 반환 값 읽기) @HPierce on SO 의 도움으로 해결했습니다 .

class VirtualTemplateWrapper
{
    public $context;

    public function stream_open( $path, $mode, $options, &$opened_path ) { return true; }

    public function stream_read( $count ) { return ''; }

    public function stream_eof() { return ''; }

    public function stream_stat() {
        # $user = posix_getpwuid( posix_geteuid() );
        $data = [
            'dev'     => 0,
            'ino'     => getmyinode(),
            'mode'    => 'r',
            'nlink'   => 0,
            'uid'     => getmyuid(),
            'gid'     => getmygid(),
            #'uid'     => $user['uid'],
            #'gid'     => $user['gid'],
            'rdev'    => 0,
            'size'    => 0,
            'atime'   => time(),
            'mtime'   => getlastmod(),
            'ctime'   => FALSE,
            'blksize' => 0,
            'blocks'  => 0,
        ];
        return array_merge( array_values( $data ), $data );
    }

    public function url_stat( $path, $flags ) {
        return $this->stream_stat();
    }
}

새 클래스를 새 프로토콜로 등록하면됩니다.

add_action( 'template_redirect', function() {
    stream_wrapper_register( 'virtual', 'VirtualTemplateWrapper' );
}, 0 );

그러면 가상 (기존이 아닌) 파일 을 만들 수 있습니다.

$template = fopen( "virtual://comments", 'r+' );

그러면 함수가 리팩토링 될 수 있습니다.

function engineCommentsTemplate( $engine )
{
    $replacement = null;
    $virtual = fopen( "virtual://comments", 'r+' );

    $tmplGetter = function( $original ) use( &$replacement, $virtual ) {
        $replacement = $original;
        return $virtual;
    };

    add_filter( 'comments_template', $tmplGetter, PHP_INT_MAX );

    comments_template();

    remove_filter( 'comments_template', $tmplGetter, PHP_INT_MAX );

    // As the PHP internals are quite unclear: Better safe then sorry
    unset( $virtual );

    if ( is_file( $replacement ) && is_readable( $replacement ) ) {
        return $engine->render( $replacement );
    }

    return '';
}

는 AS file_exists()코어 수익률 점검 TRUE과는 require $file오류가 발생하지 않습니다.

나는 이것이 단위 테스트에 실제로 도움이 될 수 있기 때문에 이것이 어떻게 나타 났는지 매우 기쁘게 생각합니다.


1
훌륭한 결과! 나는이 접근법을 가장 좋아한다 ;-) 이것이 적용될 수있는 핵심의 다른 부분이 있다고 확신합니다.
birgire

1
공감 및 감사합니다! 단위 테스트를 위해 이미있다 github.com/mikey179/vfsStream 바퀴를 재발견 할 필요하므로, 예외 방법 메이크업이 나를 행복하게 악을 느끼기 때문에) Btw는, 나는이 방법처럼하지 않도록 내가 이것을 사용하지 않습니다 : D
gmazzap


@kaiser nah, 내가 RTFM 때문에 발견했습니다 : P phpunit.de/manual/current/en/…
gmazzap
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.