So entfernen Sie einen Filter, der ein anonymes Objekt ist?

62

In meiner functions.phpDatei möchte ich den folgenden Filter entfernen, aber ich bin mir nicht sicher, wie ich das machen soll, da es in einer Klasse ist. Wie soll das remove_filter()aussehen?

add_filter('comments_array',array( &$this, 'FbComments' ));

Es ist auf der Linie 88 hier .

Jonas
quelle
Sie sollten das entfernen &von Ihrem &$this, es ist ein PHP 4 Sache
Tom J Nowell

Antworten:

79

Das ist eine sehr gute Frage. Es geht um das dunkle Herz der Plugin-API und die besten Programmiermethoden.

Für die folgende Antwort habe ich ein einfaches Plugin erstellt, um das Problem mit einfach zu lesendem Code zu veranschaulichen.

<?php # -*- coding: utf-8 -*-
/* Plugin Name: Anonymous OOP Action */

if ( ! class_exists( 'Anonymous_Object' ) )
{
    /**
     * Add some actions with randomized global identifiers.
     */
    class Anonymous_Object
    {
        public function __construct()
        {
            add_action( 'wp_footer', array ( $this, 'print_message_1' ), 5 );
            add_action( 'wp_footer', array ( $this, 'print_message_2' ), 5 );
            add_action( 'wp_footer', array ( $this, 'print_message_3' ), 12 );
        }

        public function print_message_1()
        {
            print '<p>Kill me!</p>';
        }

        public function print_message_2()
        {
            print '<p>Me too!</p>';
        }

        public function print_message_3()
        {
            print '<p>Aaaand me!</p>';
        }
    }

    // Good luck finding me!
    new Anonymous_Object;
}

Jetzt sehen wir Folgendes:

Bildbeschreibung hier eingeben

WordPress benötigt einen Namen für den Filter. Wir haben keine bereitgestellt, also ruft WordPress an _wp_filter_build_unique_id()und erstellt eine. Dieser Name ist nicht vorhersehbar, da er verwendet wird spl_object_hash().

Wenn wir ein Lauf var_export()auf $GLOBALS['wp_filter'][ 'wp_footer' ]bekommen wir so etwas wie dies jetzt:

array (
  5 => 
  array (
    '000000002296220e0000000013735e2bprint_message_1' => 
    array (
      'function' => 
      array (
        0 => 
        Anonymous_Object::__set_state(array(
        )),
        1 => 'print_message_1',
      ),
      'accepted_args' => 1,
    ),
    '000000002296220e0000000013735e2bprint_message_2' => 
    array (
      'function' => 
      array (
        0 => 
        Anonymous_Object::__set_state(array(
        )),
        1 => 'print_message_2',
      ),
      'accepted_args' => 1,
    ),
  ),
  12 => 
  array (
    '000000002296220e0000000013735e2bprint_message_3' => 
    array (
      'function' => 
      array (
        0 => 
        Anonymous_Object::__set_state(array(
        )),
        1 => 'print_message_3',
      ),
      'accepted_args' => 1,
    ),
  ),
  20 => 
  array (
    'wp_print_footer_scripts' => 
    array (
      'function' => 'wp_print_footer_scripts',
      'accepted_args' => 1,
    ),
  ),
  1000 => 
  array (
    'wp_admin_bar_render' => 
    array (
      'function' => 'wp_admin_bar_render',
      'accepted_args' => 1,
    ),
  ),
)

Um unsere böse Aktion zu finden und zu entfernen, müssen wir die zugehörigen Filter für den Hook durchgehen (eine Aktion ist nur ein sehr einfacher Filter), prüfen, ob es sich um ein Array handelt und ob das Objekt eine Instanz der Klasse ist. Dann nehmen wir die Priorität und entfernen den Filter, ohne jemals die reale Kennung zu sehen .

Okay, lassen Sie uns das in eine Funktion umwandeln:

if ( ! function_exists( 'remove_anonymous_object_filter' ) )
{
    /**
     * Remove an anonymous object filter.
     *
     * @param  string $tag    Hook name.
     * @param  string $class  Class name
     * @param  string $method Method name
     * @return void
     */
    function remove_anonymous_object_filter( $tag, $class, $method )
    {
        $filters = $GLOBALS['wp_filter'][ $tag ];

        if ( empty ( $filters ) )
        {
            return;
        }

        foreach ( $filters as $priority => $filter )
        {
            foreach ( $filter as $identifier => $function )
            {
                if ( is_array( $function)
                    and is_a( $function['function'][0], $class )
                    and $method === $function['function'][1]
                )
                {
                    remove_filter(
                        $tag,
                        array ( $function['function'][0], $method ),
                        $priority
                    );
                }
            }
        }
    }
}

Wann rufen wir diese Funktion auf? Es ist nicht sicher, wann das ursprüngliche Objekt erstellt wird. Vielleicht schon mal 'plugins_loaded'? Vielleicht später?

Wir verwenden genau den Haken, dem das Objekt zugeordnet ist, und springen sehr früh mit Priorität ein 0. Das ist der einzige Weg, um wirklich sicher zu sein. So würden wir die Methode entfernen print_message_3():

add_action( 'wp_footer', 'kill_anonymous_example', 0 );

function kill_anonymous_example()
{
    remove_anonymous_object_filter(
        'wp_footer',
        'Anonymous_Object',
        'print_message_3'
    );
}

Ergebnis:

Bildbeschreibung hier eingeben

Und das sollte die Aktion aus Ihrer Frage entfernen (nicht getestet):

add_action( 'comments_array', 'kill_FbComments', 0 );

function kill_FbComments()
{
    remove_anonymous_object_filter(
        'comments_array',
        'SEOFacebookComments',
        'FbComments'
    );
}

Fazit

  • Schreiben Sie immer vorhersehbaren Code. Legen Sie lesbare Namen für Ihre Filter und Aktionen fest. Haken lassen sich leicht entfernen.
  • Erstellen Sie Ihr Objekt auf eine vorhersehbare Aktion, zum Beispiel auf 'plugins_loaded'. Nicht nur, wenn Ihr Plugin von WordPress aufgerufen wird.
fuxia
quelle
Zu
Ihrer Information
@MikeSchinkel Verwandte Idee , habe es in der Praxis noch nicht ausprobiert.
Fuxia
Interessant. Ich finde Ihre Antwort sehr gut, aber Ihre letzte Schlussfolgerung ist ziemlich schlecht. Meiner Meinung nach sollten Klasseninstanzen im Allgemeinen instanziiert werden, sobald WordPress das Plugin lädt. Dann sollte der Konstruktor der Klasseninstanz keine echten Aktionen ausführen, sondern nur Aktionen und Filter hinzufügen. Auf diese Weise können Plug-ins, die Aktionen und Filter aus Ihrer Klasseninstanz entfernen möchten, sicherstellen, dass sie tatsächlich hinzugefügt werden, wenn sie plugins_loadedaufgerufen werden plugins_loaded. Natürlich muss die Klasseninstanz weiterhin zugänglich sein, möglicherweise über ein Singleton-Muster.
Engelen
@engelen Dies ist eine alte Antwort. Heutzutage würde ich eine Aktion anbieten, um die Rückrufe zu entfernen. Aber kein Singleton, das ist aus vielen Gründen ein Anti-Pattern .
fuxia
Diese Antwort kann auch zum Entfernen von Aktionen verwendet werden, z. B.remove_action()
Nick Pyett
0

Ich bin nicht sicher, aber Sie können versuchen, einen Singleton zu verwenden.
Sie müssen den Objektverweis in einer statischen Eigenschaft Ihrer Klasse speichern und dann diese statische Variable von einer statischen Methode zurückgeben. Etwas wie das:

class MyClass{
    private static $ref;
    function MyClass(){
        $ref = &$this;
    }
    public static function getReference(){
        return self::$ref;
    }
}
Hamed Momeni
quelle
0

Solange Sie das Objekt kennen (und PHP 5.2 oder höher verwenden - die aktuelle stabile PHP-Version ist 5.5, 5.4 wird weiterhin unterstützt, 5.3 ist das Lebensende), können Sie es einfach mit der remove_filter()Methode entfernen . Alles, was Sie sich merken müssen, ist das Objekt, der Methodenname und die Priorität (falls verwendet):

remove_filter('comment_array', [$this, 'FbComments']);

Sie machen jedoch einen kleinen Fehler in Ihrem Code. $thisSetzen Sie kein kaufmännisches Und voran &, das in PHP 4 (!) Benötigt wurde, und es ist seit langem überfällig. Dies kann den Umgang mit Ihren Hooks problematisch machen. Lassen Sie es einfach aus dem Weg:

add_filter('comments_array', [$this, 'FbComments]));

Und das ist es.

hakre
quelle
1
Sie haben keinen Zugriff $thisvon außen (ein anderes Plugin / Theme).
Fuxia