Comments.php dosyasını yüklemek için comments_template () yöntemini önleyin


9

Bir şablon motoru kullanarak bir WordPress teması geliştiriyorum. Kodumu WP çekirdek işlevselliği ile mümkün olduğunca uyumlu olmasını istiyorum.

Önce bir bağlam

İlk sorunum WP sorgusundan başlayarak şablonu çözmenin bir yolunu bulmaktı . Bunu bir kütüphanem olan Brain \ Hiyerarşi kullanarak çözdüm .

İlgili get_template_part()ve yükler Partials gibi diğer işlevleri olduğunu get_header(), get_footer()ve benzeri, bu şablon motoru kısmi işlevselliği yazma sargıya oldukça kolaydı.

Sorun

Benim sorunum şimdi yorum şablonu nasıl yüklenir.

WordPress işlevi comments_template(), maksimum çekirdek uyumluluğu için de yapmak istediğim bir çok şey yapan ~ 200 satır işlevidir.

Ancak, en kısa sürede ben comments_template()bir dosya required, bu ilki:

  • COMMENTS_TEMPLATEtanımlanmışsa sabitteki dosya
  • comments.php tema klasöründe, bulunursa
  • /theme-compat/comments.php WP de son çare yedek olarak klasör içerir

Kısacası, fonksiyonun benim için arzu edilmeyen bir PHP dosyasını yüklemesini önlemenin bir yolu yok, çünkü şablonlarımı oluşturmam ve sadece kullanmam gerekiyor require.

Mevcut çözüm

Şu anda boş bir comments.phpdosya gönderiyorum ve 'comments_template'WordPress'in hangi şablonu yüklemek istediğini bilmek için filtre kancasını kullanıyorum ve şablonu yüklemek için şablon motorumdan özelliği kullanıyorum.

Bunun gibi bir şey:

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

Soru

Bu çalışır, çekirdek uyumludur, ancak ... boş bir gemi göndermeden çalışmasını sağlamanın bir yolu var comments.phpmı?

Çünkü hoşuma gitmedi.

Yanıtlar:


4

Aşağıdaki çözümün OP'deki çözümden daha iyi olduğundan emin değil , diyelim ki alternatif, muhtemelen daha hackish bir çözüm.

'comments_template'Filtre uygulandığında WordPress yürütülmesini durdurmak için bir PHP istisnası kullanabilirsiniz düşünüyorum .

Şablonu taşımak için DTO olarak özel bir özel durum sınıfı kullanabilirsiniz .

Bu istisna için bir taslak:

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

Kullanılabilir bu istisna sınıfı ile işleviniz şöyle olur:

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

finallyBlok PHP 5.5+ gerektirir.

Aynı şekilde çalışır ve boş bir şablon gerektirmez.


4

Bunu daha önce ile güreşen ve benim çözüm oldu - bu sürece olmadığı için, dosyayı gerektiren kendini nakavt olabilir yapmak bir şey.

İşte Meadow şablon projemin ilgili kodu :

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()Global ve benzeri kurmak için hareketleri geçmesine izin , ama boş PHP dosyasını beslemek requireve çıktı için gerçek Twig şablonuna geçmek.

comments_template()Bunun, Twig şablonumun gerçek PHP işlevi yerine aracı soyutlamayı çağırdığı için yapabileceğim ilk çağrıyı kesebilmesi gerektiğini unutmayın.

Yine de bunun için boş bir dosya göndermem gerekse de, bunu kütüphanede yapıyorum ve uygulama temasının hiç ilgilenmesi gerekmiyor.


Oy verildi, teşekkürler. Daha önce Meadow'ı kullandığımdan beri yaklaşımını gördüm. Burada sevmediğim şey, boş bir şablonun yine de gönderilmesi gerektiğidir. Dahası, bu , şablonu özelleştirmek için comments_templatefiltre veya COMMENTS_TEMPLATEsabit kullanma girişimlerini keser . Bu çok önemli değil, ama dediğim gibi, çekirdek ile mümkün olduğunca uyumlu kalmak istedim.
gmazzap

@gmazzap hmmm ... neden sarıcıya filtre ve sabit için destek ekleyemedim, ama bu mikro yönetime girer.
Nadir

3

Çözüm: Geçici bir dosya kullanın - benzersiz bir dosya adıyla

Bir sürü atlamadan ve PHP'nin en kirli köşelerine sürüldükten sonra, soruyu sadece şöyle anlattım:

Nasıl bir PHP geri dönüş içine kandırmakTRUE için file_exists( $file )?

özünde kod olduğu gibi

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

Sonra soru daha hızlı çözüldü:

$template = tempnam( __DIR__, '' );

ve bu kadar. Belki kullanmak daha iyi olurwp_upload_dir() yerine :

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

Başka bir seçenek get_temp_dir()hangi sargıları kullanmak olabilir WP_TEMP_DIR. İpucu: Bu garip geri düşer /tmp/dosyalar böylece değil ki, yeniden doğmuş arasında korunmuş olsun /var/tmp/isterim. Sonunda basit bir dize karşılaştırması yapabilir ve dönüş değerini kontrol edebilir ve ardından gerekirse bu sorunu düzeltebilirsiniz - bu durumda değil:

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

Şimdi içeriği olmayan geçici bir dosya için atılan hatalar olup olmadığını hızlı bir şekilde test etmek için:

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

Ve: Hata Yok → çalışıyor.

DÜZENLEME: As @toscho Açıklamalarda belirttiği, hala var bir bunu yapmanın daha iyi bir yolu var:

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

Not: php.net dokümanlarındaki bir kullanıcının notuna göre ,sys_get_temp_dir() davranış sistemler arasında farklılık gösterir. Bu nedenle sonuç, sondaki eğik çizgiyi kaldırır ve sonra tekrar ekler. Çekirdek # 22267 hatası düzeltildiğinden, bu durum şimdi Win / IIS sunucularında da çalışmalıdır.

Yeniden düzenlenmiş işleviniz (test edilmedi):

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

Bonus No.1: tmpfile() geri dönecek NULL. Evet gerçekten.

Bonus No.2: file_exists( __DIR__ ) geri dönecek TRUE. Evet, gerçekten… unutursan.

^ Bu WP çekirdeğinde gerçek bir hataya yol açar.


Diğerlerinin explorer moduna girmesine ve bunları (belgelenmemiş parçalara kötü bir şekilde) bulmasına yardımcı olmak için, denediklerimi hızlı bir şekilde özetleyeceğim:

Deneme 1: Bellekteki geçici dosya

Yaptığım ilk girişim, kullanarak geçici bir dosyaya akış oluşturmaktı php://temp. PHP belgelerinden:

İkisi arasındaki tek fark php://memory, verilerini her zaman bellekte saklayacağı, php://tempsaklanan veri miktarı önceden tanımlanmış bir sınıra ulaştığında (varsayılan 2 MB'dir) geçici bir dosya kullanmasıdır. Bu geçici dosyanın konumu, sys_get_temp_dir()işlevle aynı şekilde belirlenir .

Kod:

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

Bulma: Hayır, çalışmıyor.

Deneme 2: Geçici bir dosya kullanın

Orada tmpfile()o kullanmaz neden bu kadar, ?!

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

Evet, bu kısayol hakkında çok şey.

Deneme 3: Özel bir akış sarıcı kullanın

Sonra özel bir akış sarıcı oluşturmak ve kullanarak kayıtstream_wrapper_register() olabilir düşündüm . Sonra çekirdeğimizi bir dosyamız olduğuna inanmak için kandırmak için bu akıştan sanal bir şablon kullanabilirim . Aşağıdaki örnek kod (Ben zaten tam sınıf sildim ve geçmiş yeterli adımları yok ...)

class TemplateStreamWrapper
{
    public $context;

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

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

Yine, bu NULLdevam etti file_exists().


PHP 5.6.20 ile test edilmiştir


Deneme 3'ünüzün teoride çalışması gerektiğini düşünüyorum. Özel akış paketinize uyguladınız stream_stat()mı? Ben file_exists()onun kontrolünü yapmak için çağıracak olduğunu düşünüyorum ... php.net/manual/en/streamwrapper.stream-stat.php
Alain Schlesser

Oy verildi çünkü oldukça güzel ve çok hackish değil. Ancak, kodumun farklı kurulumlarda kullanılması amaçlandığından, yazma izninin bir sorun olabileceğinden korkuyorum. Üstelik geçici dosyalar o kadar kolay değil, hangi silinmesi gerekir anında o tarafından döndürülen tam yolunu kesmek kolay değil, çünkü tempnam(). Bir cron işi kullanmak işe yarayacak, ancak ek yükü ...
gmazzap

Geçici dosya yazmak boş şablon göndermekten daha kötü olduğunu düşünüyorum. Sabit boş şablon opcode'a önbelleğe alınır. Geçici dosyanın diske yazılması, soğuk ayrıştırılması (opcode olmadan) ve ardından silinmesi gerekir. İyi bir sebep olmadan disk isabetlerini en aza indirmek daha iyidir.
Nadir

@Rarst Asla soru "daha iyi olan" performans açısından akıllı değildi. Soru şablon dosyası olmaması için kaynatıldı :)
kaiser

1
tempnam( sys_get_temp_dir(), 'comments.php' )yazılır kez yapabilirsiniz yeniden dosya adını ve dosya boş birçok kaynak kullanmaz bu yüzden. Ayrıca kodunuzda anlaşılması kolaydır. Şimdiye kadar en iyi çözüm imho.
fuxia

3

As @AlainSchlesser rotayı (ve çalışmayan şeyler her zaman olduğu gibi böcek beni) takip önerdi, ben sanal dosyaları için bir dere sarmalayıcı bina denenecek. Kendi başıma çözemedim (okuma: dokümanlardaki dönüş değerlerini okuyarak), ancak SO'da @HPierce yardımıyla çözdüm .

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

Yeni sınıfı yeni protokol olarak kaydetmeniz yeterlidir:

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

Bu daha sonra sanal (var olmayan) bir dosya oluşturulmasına izin verir :

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

Daha sonra işleviniz yeniden düzenlenebilir:

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

olarak file_exists()çekirdek getiri check TRUEverequire $file bir hata verir.

Ünite testlerinde gerçekten yardımcı olabileceğinden, bunun nasıl ortaya çıktığını oldukça sevdiğimi belirtmeliyim.


1
Harika bulgular! Bu yaklaşımı en iyi şekilde sevdim ;-) Çekirdeğin bunun uygulanabileceği başka bölümler olduğundan eminim.
birgire

1
Oy verildi ve teşekkürler! Birim testleri için zaten github.com/mikey179/vfsStream var bu yüzden tekerleği yeniden icat etmeye gerek yok;) Btw, bu yaklaşımı sevdim, istisna yöntem beni mutlu hissettirdiğinden emin değilim: D
gmazzap

@gmazzap Bunu öğrendiğinizde böyle göründüğünüzden çok eminim .
Kaiser

@kaiser nah, bunu buldum çünkü RTFM
gmazzap
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.