comments_template()がcomments.phpをロードしないようにする


9

テンプレートエンジンを使用してWordPressテーマを開発しています。私のコードがWPコア機能と可能な限り互換性があることを望みます。

最初にいくつかのコンテキスト

最初の問題は、WPクエリからテンプレートを解決する方法を見つけることでした。私は自分のライブラリBrain \ Hierarchyを使用してその問題を解決しました。

get_template_part()や他の負荷のパーシャルが好きなことを機能get_header()get_footer()および同様、それはテンプレートエンジン部分的機能への書き込みラッパーにはかなり簡単でした。

問題

私の問題は、コメントテンプレートを読み込む方法です。

WordPress関数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の解決策より優れているかどうかはわかりませんが、代わりの解決策、おそらくよりハッキリとした解決策だとしましょう。

PHP例外を使用して、'comments_template'フィルターを適用したときに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

私は以前これに取り組んできましたが、私の解決策はそれでした—それ何もしない限り、それはそれ自体を必要とするファイルをノックアウトすることができます。

これが私のMeadowテンプレートプロジェクトの関連コードです。

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関数ではなく中間の抽象化を呼び出しているため、これを行うことができます。

私はまだそれのために空のファイルを出荷する必要がありますが、ライブラリでそれを出荷し、テーマを実装することはまったくそれを気にする必要はありません。


賛成、ありがとう。以前にMeadowを使用して以来、私はすでにあなたのアプローチを見ました。ここで私が気に入らなかったのは、とにかく空のテンプレートを出荷する必要があるという事実です。さらに、これはテンプレートをカスタマイズするためにcomments_templateフィルターまたはCOMMENTS_TEMPLATE定数を使用するすべての試みを壊します。これは極めて重要なことではありませんが、前述したように、コアとの互換性を可能な限り維持したいと考えていました。
gmazzap

@gmazzap hmmm ...ラッパーでフィルターと定数のサポートを追加できなかった理由はありませんが、マイクロマネージメントに入ります。
ラースト、2016年

3

解決策:一時ファイルを使用する-一意のファイル名

多くのジャンプとPHPの最も汚いコーナーにクロールした後、私は質問を次のように言い換えました。

どのようにしてでき騙し返すにPHPをTRUEするためにfile_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;

そして:エラーなし →動作しています。

EDIT:として@toschoはコメントで指摘し、まだあります、より良い、それを行う方法は:

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

注:php.net docsに関するユーザーのメモによると、sys_get_temp_dir()動作はシステム間で異なります。したがって、結果は末尾のスラッシュが削除され、再度追加されます。コアのバグとして#22267が固定され、これは勝利で動作するはずです/今だけでなく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のドキュメントから:

2つの唯一の違いは、php://memory常にデータをメモリに格納するのに対し、php://temp格納されたデータの量が事前定義された制限(デフォルトは2 MB)に達すると一時ファイルを使用することです。この一時ファイルの場所は、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. …

繰り返しますが、これはに戻りNULLましたfile_exists()


PHP 5.6.20でテスト済み


あなたの試み3は理論的にはうまくいくと思います。カスタムストリームラッパーに実装しましたstream_stat()か?これはfile_exists()、チェックを行うために呼び出されるものだと思います... php.net/manual/en/streamwrapper.stream-stat.php
Alain Schlesser

とても素晴らしく、あまりハックではないので賛成です。ただし、私のコードはさまざまな設定で使用することを目的としているため、書き込み権限が問題になる可能性があります。さらに、一時ファイルを削除する必要があります。これは、によって返される完全なパスを傍受することが容易ではないため、その場で簡単ではありませんtempnam()。cronジョブを使用しても機能しますが、追加のオーバーヘッドになります...
gmazzap

一時ファイルを書くのは、空のテンプレートを出荷するより悪いと思います。空のテンプレートがopcodeにキャッシュされるのを修正しました。一時ファイルをディスクに書き込み、コールド解析(オペコードなし)してから削除する必要があります。理由もなくディスクヒットを最小限に抑えることをお勧めします。
ラースト

@Rarst問題は、パフォーマンスに関して「何がより良いか」ではありませんでした。テンプレートファイルがないことへの質問の
要約

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

file_exists()コアのチェックが返さTRUEれ、require $fileエラーがスローされないため。

単体テストに非常に役立つので、これがどのように判明したかは非常に嬉しいことに注意する必要があります。


1
素晴らしい発見!私はこのアプローチが一番好きです;-)これを適用できるコアの他の部分があると確信しています。
バージリー

1
賛成と感謝!ユニットテストのためにすでにありますgithub.com/mikey179/vfsStreamは、車輪の再発明する必要はありませんので;)ところで、私はこのアプローチのように、必ず私は例外メソッドので、これを使用しますない私は喜んで邪悪な感じさせる:D
gmazzap


ナ@kaiser、私はRTFMので、それを見つけた:Pのphpunit.de/manual/current/en/...
gmazzap
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.