remove_action sau remove_filter cu clase externe?

9 dec. 2011, 19:54:35
Vizualizări: 69.4K
Voturi: 77

Într-o situație în care un plugin și-a încapsulat metodele într-o clasă și apoi a înregistrat un filtru sau o acțiune pentru una dintre aceste metode, cum poți elimina acțiunea sau filtrul dacă nu mai ai acces la instanța acelei clase?

De exemplu, să presupunem că ai un plugin care face următoarele:

class MyClass {
    function __construct() {
       add_action( "plugins_loaded", array( $this, 'my_action' ) );
    }

    function my_action() {
       // execută diverse...
    }
}

new MyClass();

Observând că acum nu mai am nicio modalitate de a accesa instanța, cum pot să deînregistrez clasa? Această abordare: remove_action( "plugins_loaded", array( MyClass, 'my_action' ) ); nu pare să fie cea corectă - cel puțin, nu a funcționat în cazul meu.

1
Comentarii

N/P. Funcționează varianta de mai jos pentru tine?

kaiser kaiser
9 dec. 2011 23:20:33
Toate răspunsurile la întrebare 9
5
97

Ori de câte ori un plugin creează un new MyClass();, ar trebui să îl atribuie unei variabile cu un nume unic. În acest fel, instanța clasei este accesibilă.

Deci, dacă ar fi făcut $myclass = new MyClass();, atunci ai putea face asta:

global $myclass;
remove_action( 'wp_footer', array( $myclass, 'my_action' ) );

Aceasta funcționează pentru că pluginurile sunt incluse în namespace-ul global, așadar declarațiile implicite de variabile în corpul principal al unui plugin sunt variabile globale.

Dacă pluginul nu salvează identificatorul noii clase undeva, atunci, tehnic vorbind, este un bug. Unul dintre principiile generale ale Programării Orientate pe Obiecte este că obiectele care nu sunt referențiate de o variabilă undeva sunt supuse curățării sau eliminării.

Acum, PHP în special nu face asta așa cum ar face Java, deoarece PHP este o implementare cam pe jumătate a OOP. Variabilele de instanță sunt doar șiruri de caractere cu nume unice de obiecte în ele, cam așa. Ele funcționează doar datorită modului în care interacțiunea numelui variabilei funcționează cu operatorul ->. Deci, doar făcând new class() poate funcționa perfect, dar stupid. :)

Așadar, concluzia este să nu faci niciodată new class();. Fă $var = new class(); și asigură-te că acea variabilă $var este accesibilă într-un fel pentru alte părți să o poată referenția.

Editare: ani mai târziu

Un lucru pe care l-am văzut la multe pluginuri este utilizarea unui model similar cu "Singleton". Ele creează o metodă getInstance() pentru a obține singura instanță a clasei. Aceasta este probabil cea mai bună soluție pe care am văzut-o. Exemplu de plugin:

class ExamplePlugin
{
    protected static $instance = NULL;

    public static function getInstance() {
        NULL === self::$instance and self::$instance = new self;
        return self::$instance;
    }
}

Prima dată când getInstance() este apelată, instantiază clasa și salvează pointerul acesteia. Poți folosi asta pentru a lega acțiuni.

O problemă cu aceasta este că nu poți folosi getInstance() în interiorul constructorului dacă folosești așa ceva. Acest lucru se întâmplă pentru că "new" apelează constructorul înainte de a seta $instance, așadar apelarea getInstance() din constructor duce la o buclă infinită și strică totul.

O soluție este să nu folosești constructorul (sau, cel puțin, să nu folosești getInstance() în el), ci să ai în mod explicit o funcție "init" în clasă pentru a configura acțiunile tale și altele. Ca aceasta:

public static function init() {
    add_action( 'wp_footer', array( ExamplePlugin::getInstance(), 'my_action' ) );
}

Cu ceva de genul acesta, la sfârșitul fișierului, după ce clasa a fost definită complet, instantierea pluginului devine la fel de simplă ca aceasta:

ExamplePlugin::init();

Funcția init începe să adauge acțiunile tale, iar în acest proces apelează getInstance(), care instantiază clasa și se asigură că există doar una singură. Dacă nu ai o funcție init, ai face asta pentru a instantia clasa inițial:

ExamplePlugin::getInstance();

Pentru a răspunde la întrebarea inițială, eliminarea acelui action hook din exterior (adică, din alt plugin) poate fi făcută astfel:

remove_action( 'wp_footer', array( ExamplePlugin::getInstance(), 'my_action' ) );

Pune asta în ceva legat de action hook-ul plugins_loaded și va anula acțiunea legată de pluginul original.

11 dec. 2011 01:39:30
Comentarii

+1 Adevărat. Aceasta este clar o practică recomandată. Ar trebui să ne străduim cu toții să scriem codul pluginului în acest fel.

Tom Auger Tom Auger
24 apr. 2012 20:41:18

+1 aceste instrucțiuni m-au ajutat cu adevărat să elimin un filtru într-o clasă cu model singleton.

Devin Walker Devin Walker
28 mar. 2014 04:01:01

+1, dar cred că în general ar trebui să folosești hook-ul wp_loaded, nu plugins_loaded, care poate fi apelat prea devreme.

EML EML
28 apr. 2015 16:00:00

Nu, plugins_loaded ar fi locul corect. Acțiunea wp_loaded are loc după acțiunea init, deci dacă plugin-ul tău efectuează orice acțiuni pe init (și majoritatea o fac), atunci vrei să inițializezi și să configurezi plugin-ul înainte de asta. Hook-ul plugins_loaded este locul potrivit pentru această fază de construcție.

Otto Otto
28 apr. 2015 16:26:11

Acest răspuns poate fi puțin întârziat. Dar îți mulțumesc pentru această observație. După ce am căutat ore întregi, am realizat că nu am respectat principiile generale ale Programării Orientate pe Obiecte, care afirmă că obiectele care nu sunt referențiate de nicio variabilă undeva sunt supuse eliminării sau curățării.

NME New Media Entertainment NME New Media Entertainment
21 ian. 2020 03:45:20
3
24

Cel mai bun lucru de făcut aici este să folosești o clasă statică. Următorul cod ar trebui să fie instructiv:

class MyClass {
    function __construct() {
        add_action( 'wp_footer', array( $this, 'my_action' ) );
    }
    function my_action() {
        print '<h1>' . __class__ . ' - ' . __function__ . '</h1>';
    }
}
new MyClass();


class MyStaticClass {
    public static function init() {
        add_action( 'wp_footer', array( __class__, 'my_action' ) );
    }
    public static function my_action() {
        print '<h1>' . __class__ . ' - ' . __function__ . '</h1>';
    }
}
MyStaticClass::init();

function my_wp_footer() {
    print '<h1>my_wp_footer()</h1>';
}
add_action( 'wp_footer', 'my_wp_footer' );

function mfields_test_remove_actions() {
    remove_action( 'wp_footer', 'my_wp_footer' );
    remove_action( 'wp_footer', array( 'MyClass', 'my_action' ), 10 );
    remove_action( 'wp_footer', array( 'MyStaticClass', 'my_action' ), 10 );
}
add_action( 'wp_head', 'mfields_test_remove_actions' );

Dacă rulezi acest cod dintr-un plugin, ar trebui să observi că metoda StaticClass, precum și funcția, vor fi eliminate din wp_footer.

10 dec. 2011 23:25:25
Comentarii

Punct de vedere acceptat, dar nu toate clasele pot fi pur și simplu transformate în statice.

Geert Geert
29 feb. 2012 13:40:37

Am acceptat acest răspuns pentru că răspunde cel mai direct la întrebare, deși răspunsul lui Otto este cea mai bună practică. Menționez aici că nu cred că trebuie să declari în mod explicit static. Din experiența mea (deși aș putea greși), poți trata funcția ca și cum ar fi statică array( 'MyClass', 'member_function' ) și de multe ori funcționează fără cuvântul cheie 'static'.

Tom Auger Tom Auger
24 apr. 2012 20:40:52

@TomAuger nu, nu poți, DOAR dacă este adăugată ca o clasă statică poți folosi funcția remove_action, altfel nu va funcționa... de aceea a trebuit să scriu propria mea funcție pentru a gestiona cazul în care nu este o clasă statică. Acest răspuns ar fi cel mai bun doar dacă întrebarea ta era legată de propriul cod, altfel vei încerca să elimini un alt filtru/acțiune din codul altcuiva și nu poți să-l schimbi în static

sMyles sMyles
20 sept. 2016 03:23:38
3
24

2 funcții PHP mici pentru a permite eliminarea filtrelor/acțiunilor cu clase "anonime": https://github.com/herewithme/wp-filters-extras/

6 nov. 2012 09:55:57
Comentarii

Funcții foarte interesante. Mulțumesc pentru postare!

Tom Auger Tom Auger
6 nov. 2012 16:22:01

După cum au menționat și alții în postarea mea de mai jos, acestea nu vor funcționa în WordPress 4.7 (decât dacă repo-ul este actualizat, dar nu s-a întâmplat în ultimii 2 ani)

sMyles sMyles
20 sept. 2016 03:25:39

Doar menționez că repo-ul wp-filters-extras a fost actualizat pentru versiunea 4.7 și clasa WP_Hook.

Dave Romsey Dave Romsey
4 mai 2017 09:28:25
6
17

Iată o funcție bine documentată pe care am creat-o pentru eliminarea filtrelor atunci când nu ai acces la obiectul clasei (funcționează cu WordPress 1.2+, inclusiv 4.7+):

https://gist.github.com/tripflex/c6518efc1753cf2392559866b4bd1a53

/**
 * Elimină Filtru de Clasă Fără Acces la Obiectul Clasei
 *
 * Pentru a utiliza remove_filter() din nucleul WordPress pe un filtru adăugat cu callback-ul
 * către o clasă, fie trebuie să ai acces la acel obiect de clasă, fie trebuie să fie un apel
 * către o metodă statică. Această metodă îți permite să elimini filtrele cu un callback către o clasă
 * la care nu ai acces.
 *
 * Funcționează cu WordPress 1.2+ (suport pentru 4.7+ adăugat pe 19-09-2016)
 * Actualizat pe 27-02-2017 pentru a utiliza eliminarea internă WordPress pentru 4.7+ (pentru a preveni avertismentele PHP)
 *
 * @param string $tag         Filtru de eliminat
 * @param string $class_name  Numele clasei pentru callback-ul filtrului
 * @param string $method_name Numele metodei pentru callback-ul filtrului
 * @param int    $priority    Prioritatea filtrului (implicit 10)
 *
 * @return bool Dacă funcția a fost eliminată.
 */
function remove_class_filter( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    global $wp_filter;

    // Verifică dacă filtrul există de fapt
    if ( ! isset( $wp_filter[ $tag ] ) ) return FALSE;

    /**
     * Dacă configurația filtrului este un obiect, înseamnă că folosim WordPress 4.7+ și configurația nu mai este
     * un simplu array, ci un obiect care implementează interfața ArrayAccess.
     *
     * Pentru compatibilitate înapoi, setăm $callbacks egal cu array-ul corect ca referință (astfel $wp_filter este actualizat)
     *
     * @see https://make.wordpress.org/core/2016/09/08/wp_hook-next-generation-actions-and-filters/
     */
    if ( is_object( $wp_filter[ $tag ] ) && isset( $wp_filter[ $tag ]->callbacks ) ) {
        // Crează obiectul $fob din eticheta filtrului, pentru a-l utiliza mai jos
        $fob = $wp_filter[ $tag ];
        $callbacks = &$wp_filter[ $tag ]->callbacks;
    } else {
        $callbacks = &$wp_filter[ $tag ];
    }

    // Ieși dacă nu există callback-uri pentru prioritatea specificată
    if ( ! isset( $callbacks[ $priority ] ) || empty( $callbacks[ $priority ] ) ) return FALSE;

    // Parcurge fiecare filtru pentru prioritatea specificată, căutând clasa și metoda noastră
    foreach( (array) $callbacks[ $priority ] as $filter_id => $filter ) {

        // Filtrul ar trebui să fie întotdeauna un array - array( $this, 'method' ), dacă nu, treci mai departe
        if ( ! isset( $filter[ 'function' ] ) || ! is_array( $filter[ 'function' ] ) ) continue;

        // Dacă prima valoare din array nu este un obiect, nu poate fi o clasă
        if ( ! is_object( $filter[ 'function' ][ 0 ] ) ) continue;

        // Metoda nu se potrivește cu cea pe care o căutăm, treci mai departe
        if ( $filter[ 'function' ][ 1 ] !== $method_name ) continue;

        // Metoda se potrivește, acum verificăm clasa
        if ( get_class( $filter[ 'function' ][ 0 ] ) === $class_name ) {

            // WordPress 4.7+ utilizează remove_filter() din nucleu deoarece am găsit obiectul clasei
            if( isset( $fob ) ){
                // Gestionează eliminarea filtrului, resetarea cheilor de prioritate callback în timpul iterației, etc.
                $fob->remove_filter( $tag, $filter['function'], $priority );

            } else {
                // Folosește procesul de eliminare vechi (pre 4.7)
                unset( $callbacks[ $priority ][ $filter_id ] );
                // și dacă era singurul filtru din acea prioritate, elimină prioritatea
                if ( empty( $callbacks[ $priority ] ) ) {
                    unset( $callbacks[ $priority ] );
                }
                // și dacă singurul filtru pentru acea etichetă, setează eticheta la un array gol
                if ( empty( $callbacks ) ) {
                    $callbacks = array();
                }
                // Elimină acest filtru din merged_filters, care specifică dacă filtrele au fost sortate
                unset( $GLOBALS['merged_filters'][ $tag ] );
            }

            return TRUE;
        }
    }

    return FALSE;
}

/**
 * Elimină Acțiune de Clasă Fără Acces la Obiectul Clasei
 *
 * Pentru a utiliza remove_action() din nucleul WordPress pe o acțiune adăugată cu callback-ul
 * către o clasă, fie trebuie să ai acces la acel obiect de clasă, fie trebuie să fie un apel
 * către o metodă statică. Această metodă îți permite să elimini acțiunile cu un callback către o clasă
 * la care nu ai acces.
 *
 * Funcționează cu WordPress 1.2+ (suport pentru 4.7+ adăugat pe 19-09-2016)
 *
 * @param string $tag         Acțiune de eliminat
 * @param string $class_name  Numele clasei pentru callback-ul acțiunii
 * @param string $method_name Numele metodei pentru callback-ul acțiunii
 * @param int    $priority    Prioritatea acțiunii (implicit 10)
 *
 * @return bool               Dacă funcția a fost eliminată.
 */
function remove_class_action( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    return remove_class_filter( $tag, $class_name, $method_name, $priority );
}
15 sept. 2016 22:58:52
Comentarii

Întrebare - ai testat asta în versiunea 4.7? Au fost unele modificări în modul în care sunt înregistrate funcțiile de callback în filtre care sunt complet noi. Nu m-am uitat în detaliu la codul tău, dar e ceva ce ar trebui verificat: https://make.wordpress.org/core/2016/09/08/wp_hook-next-generation-actions-and-filters/

Tom Auger Tom Auger
15 sept. 2016 23:59:45

da, sunt sigur că nu va funcționa în 4.7

gmazzap gmazzap
16 sept. 2016 01:01:53

Aha! Nu am făcut-o dar îți mulțumesc, cu siguranță voi investiga asta și voi actualiza codul să fie compatibil (dacă este nevoie)

sMyles sMyles
17 sept. 2016 22:54:19

@TomAuger mulțumesc pentru informație! Am actualizat funcția, testată și funcțională pe WordPress 4.7+ (cu menținerea compatibilității retroactive)

sMyles sMyles
20 sept. 2016 03:10:29

@gmazzap am actualizat și Gist-ul, și am adăugat remove_class_action

sMyles sMyles
20 sept. 2016 03:11:02

Tocmai am actualizat această funcție pentru a utiliza metoda internă de eliminare din nucleu (pentru a gestiona eliminarea în timpul iterației și pentru a preveni avertismentele PHP)

sMyles sMyles
28 feb. 2017 01:07:07
Arată celelalte 1 comentarii
2

Soluțiile de mai sus par învechite, a trebuit să scriu propria mea soluție...

function remove_class_action ($action,$class,$method) {
    global $wp_filter ;
    if (isset($wp_filter[$action])) {
        $len = strlen($method) ;
        foreach ($wp_filter[$action] as $pri => $actions) {
            foreach ($actions as $name => $def) {
                if (substr($name,-$len) == $method) {
                    if (is_array($def['function'])) {
                        if (get_class($def['function'][0]) == $class) {
                            if (is_object($wp_filter[$action]) && isset($wp_filter[$action]->callbacks)) {
                                unset($wp_filter[$action]->callbacks[$pri][$name]) ;
                            } else {
                                unset($wp_filter[$action][$pri][$name]) ;
                            }
                        }
                    }
                }
            }
        }
    }
}
25 dec. 2017 11:53:20
Comentarii

În cazul în care cauți o soluție modernă, aceasta a funcționat pentru mine în WP 5.6

Alex Alex
16 dec. 2020 22:57:18

Confirmat, funcționează în 5.6.2. Alte soluții nu funcționează în 5.6

Bret Weinraub Bret Weinraub
10 mar. 2021 02:08:57
0

În astfel de cazuri, WordPress adaugă un hash (ID unic) numelui funcției și îl stochează în variabila globală $wp_filter. Așadar, dacă folosești funcția remove_filter, nu se va întâmpla nimic. Chiar dacă adaugi numele clasei înaintea numelui funcției, ca în remove_filter('plugins_loaded', ['MyClass', 'my_action']). Tot ce poți face este să elimini manual toate hook-urile my_action din variabila globală $wp_filter.

Iată funcția care face asta:

function my_remove_filter($tag, $function_name, $priority = 10){

    global $wp_filter;

    if( isset($wp_filter[$tag]->callbacks[$priority]) and !empty($wp_filter[$tag]->callbacks[$priority]) ){

        $wp_filter[$tag]->callbacks[$priority] = array_filter($wp_filter[$tag]->callbacks[$priority], function($v, $k) use ($function_name){

            return ( stripos($k, $function_name) === false );

        }, ARRAY_FILTER_USE_BOTH );
    }
}

Folosește-o astfel:

my_remove_filter('plugins_loaded', 'my_action');
13 nov. 2019 21:49:15
0

Acesta nu este un răspuns generic, ci unul specific pentru tema Avada și WooCommerce, despre care cred că și alți oameni ar putea să-l găsească util:

function remove_woo_commerce_hooks() {
    global $avada_woocommerce;
    remove_action( 'woocommerce_single_product_summary', array( $avada_woocommerce, 'add_product_border' ), 19 );
}
add_action( 'after_setup_theme', 'remove_woo_commerce_hooks' );
6 aug. 2019 19:50:15
1

Am găsit acest Gist care făcea exact ce doream:

function remove_class_hook( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    global $wp_filter;
    $is_hook_removed = false;
    if ( ! empty( $wp_filter[ $tag ]->callbacks[ $priority ] ) ) {
        $methods = array_filter(wp_list_pluck(
            $wp_filter[ $tag ]->callbacks[ $priority ],
            'function'
        ), function ($method) {
            /**
             * Permite doar notația de tip array și string pentru hook-uri, deoarece
             * căutăm să eliminăm o metodă exactă a unei clase. În plus,
             * metoda clasei este transmisă ca string.
             */
            return is_string($method) || is_array($method);
        });
        $found_hooks = ! empty( $methods ) ? wp_list_filter( $methods, array( 1 => $method_name ) ) : array();
        foreach( $found_hooks as $hook_key => $hook ) {
            if ( ! empty( $hook[0] ) && is_object( $hook[0] ) && get_class( $hook[0] ) === $class_name ) {
                $wp_filter[ $tag ]->remove_filter( $tag, $hook, $priority );
                $is_hook_removed = true;
            }
        }
    }
    return $is_hook_removed;
}
15 ian. 2024 20:37:11
Comentarii

Sunt destul de sigur că TripFlex (autorul gistului pe care l-ai găsit) este de fapt @sMyles, al cărui răspuns (https://wordpress.stackexchange.com/a/239431/3687) este acum răspunsul acceptat.

Tom Auger Tom Auger
18 ian. 2024 00:02:43
0

Această funcție este bazată pe răspunsul lui @Digerkam. Am adăugat o verificare suplimentară pentru a compara dacă $def['function'][0] este un șir de caractere și în final a funcționat pentru mine.

De asemenea, utilizarea $wp_filter[$tag]->remove_filter() ar trebui să o facă mai stabilă.

function remove_class_action($tag, $class = '', $method, $priority = null) : bool {
    global $wp_filter;
    if (isset($wp_filter[$tag])) {
        $len = strlen($method);

        foreach($wp_filter[$tag] as $_priority => $actions) {

            if ($actions) {
                foreach($actions as $function_key => $data) {

                    if ($data) {
                        if (substr($function_key, -$len) == $method) {

                            if ($class !== '') {
                                $_class = '';
                                if (is_string($data['function'][0])) {
                                    $_class = $data['function'][0];
                                }
                                elseif (is_object($data['function'][0])) {
                                    $_class = get_class($data['function'][0]);
                                }
                                else {
                                    return false;
                                }

                                if ($_class !== '' && $_class == $class) {
                                    if (is_numeric($priority)) {
                                        if ($_priority == $priority) {
                                            //if (isset( $wp_filter->callbacks[$_priority][$function_key])) {}
                                            return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                        }
                                    }
                                    else {
                                        return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                    }
                                }
                            }
                            else {
                                if (is_numeric($priority)) {
                                    if ($_priority == $priority) {
                                        return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                    }
                                }
                                else {
                                    return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                }
                            }

                        }
                    }
                }
            }
        }

    }

    return false;
}

Exemplu de utilizare:

Potrivire exactă

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', 'MyClass', 'my_action', 0);
});

Orice prioritate

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', 'MyClass', 'my_action');
});

Orice clasă și orice prioritate

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', '', 'my_action');
});
29 mai 2019 09:50:16