remove_action หรือ remove_filter ที่มีคลาสภายนอก?


59

ในสถานการณ์ที่ปลั๊กอินมีการห่อหุ้มวิธีการภายในคลาสจากนั้นลงทะเบียนตัวกรองหรือการดำเนินการกับหนึ่งในวิธีการเหล่านั้นคุณจะลบการกระทำหรือตัวกรองได้อย่างไรถ้าคุณไม่มีสิทธิ์เข้าถึงอินสแตนซ์ของคลาสนั้นอีกต่อไป

ตัวอย่างเช่นสมมติว่าคุณมีปลั๊กอินที่ทำสิ่งนี้:

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

    function my_action() {
       // do stuff...
    }
}

new MyClass();

เมื่อสังเกตว่าตอนนี้ฉันไม่มีวิธีเข้าถึงอินสแตนซ์ฉันจะยกเลิกการลงทะเบียนชั้นเรียนได้อย่างไร สิ่งนี้: remove_action( "plugins_loaded", array( MyClass, 'my_action' ) );ดูเหมือนจะไม่ใช่วิธีที่ถูกต้อง - อย่างน้อยก็ดูเหมือนจะไม่ทำงานในกรณีของฉัน


N / P ด้านล่าง A ใช้งานได้สำหรับคุณหรือไม่
ไกเซอร์

คำตอบ:


16

สิ่งที่ดีที่สุดที่จะทำที่นี่คือการใช้ชั้นคงที่ รหัสต่อไปนี้ควรเป็นคำแนะนำ:

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

หากคุณเรียกใช้รหัสนี้จากปลั๊กอินคุณควรสังเกตว่าวิธีการของ StaticClass รวมทั้งฟังก์ชั่นจะถูกลบออกจาก wp_footer


7
จุดที่นำมาใช้ แต่ไม่ใช่ทุกคลาสที่สามารถแปลงเป็นสแตติกได้
Geert

ฉันยอมรับคำตอบนี้เพราะมันตอบคำถามมากที่สุดโดยตรงแม้ว่าคำตอบของอ็อตโตคือวิธีปฏิบัติที่ดีที่สุด ฉันทราบว่าที่นี่ฉันไม่คิดว่าคุณจะต้องประกาศคงที่อย่างชัดเจน เป็นประสบการณ์ของฉัน (แม้ว่าฉันจะผิด) ที่คุณสามารถใช้งานฟังก์ชั่นราวกับว่ามันเป็นอาร์เรย์แบบคงที่ ('MyClass', 'member_function') และมักจะทำงานโดยไม่ต้องใช้คำหลัก 'คงที่'
Tom Auger

@TomAuger ไม่คุณทำได้ แต่ถ้ามันเพิ่มเป็นคลาสแบบคงที่คุณสามารถใช้remove_actionฟังก์ชั่นมิฉะนั้นมันจะไม่ทำงาน ... นั่นเป็นเหตุผลที่ฉันต้องเขียนฟังก์ชั่นของตัวเองเพื่อจัดการเมื่อมันไม่ได้เป็นคลาสคงที่ คำตอบนี้จะดีที่สุดหากคำถามของคุณเกี่ยวกับรหัสของคุณเองมิฉะนั้นคุณจะพยายามลบตัวกรอง / การกระทำอื่นจาก codebase ของบุคคลอื่นและไม่สามารถเปลี่ยนเป็นคงที่ได้
sMyles

78

เมื่อใดก็ตามที่ปลั๊กอินสร้างnew MyClass();มันควรจะกำหนดให้กับตัวแปรที่มีชื่อไม่ซ้ำกัน ด้วยวิธีนี้สามารถเข้าถึงตัวอย่างของคลาสได้

ดังนั้นถ้าเขาทำเช่น$myclass = new MyClass();นั้นคุณสามารถทำได้:

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

สิ่งนี้ทำงานได้เนื่องจากปลั๊กอินรวมอยู่ในเนมสเปซส่วนกลางดังนั้นการประกาศตัวแปรโดยนัยในเนื้อหาหลักของปลั๊กอินจึงเป็นตัวแปรทั่วโลก

หากปลั๊กอินไม่ได้บันทึกตัวระบุของคลาสใหม่ที่ไหนสักแห่งในทางเทคนิคแล้วนั่นเป็นข้อบกพร่อง หนึ่งในหลักการทั่วไปของการเขียนโปรแกรมเชิงวัตถุคือวัตถุที่ไม่ได้ถูกอ้างอิงโดยตัวแปรบางแห่งที่อยู่ภายใต้การทำความสะอาดหรือกำจัด

ตอนนี้ PHP โดยเฉพาะอย่างยิ่งไม่ได้ทำสิ่งนี้เหมือน Java เพราะ PHP เป็นการใช้งาน OOP แบบครึ่งๆกลางๆ ตัวแปรอินสแตนซ์เป็นเพียงสตริงที่มีชื่อวัตถุที่ไม่ซ้ำกันในพวกเขาเรียงลำดับของสิ่ง พวกเขาทำงานเพียงเพราะวิธีการที่ชื่อฟังก์ชั่นตัวแปรทำงานร่วมกับ->ผู้ประกอบการ ดังนั้นการทำเช่นnew class()นั้นสามารถทำงานได้อย่างสมบูรณ์แบบเพียงแค่โง่เขลา :)

new class();ดังนั้นบรรทัดล่างไม่เคยทำ ทำ$var = new class();และทำให้สามารถเข้าถึง $ var ในบางวิธีเพื่อให้บิตอื่นอ้างอิง

แก้ไข: ปีต่อมา

สิ่งหนึ่งที่ฉันได้เห็นปลั๊กอินจำนวนมากที่ทำคือใช้สิ่งที่คล้ายกับรูปแบบ "ซิงเกิล" พวกเขาสร้างเมธอด getInstance () เพื่อรับอินสแตนซ์เดียวของคลาส นี่อาจเป็นทางออกที่ดีที่สุดที่ฉันเคยเห็น ตัวอย่างปลั๊กอิน:

class ExamplePlugin
{
    protected static $instance = NULL;

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

ครั้งแรกที่เรียกว่า getInstance () มันจะยกระดับชั้นและบันทึกพอยน์เตอร์ คุณสามารถใช้สิ่งนั้นเพื่อขอการกระทำ

ปัญหาหนึ่งที่เกิดขึ้นคือคุณไม่สามารถใช้ getInstance () ภายใน Constructor ได้หากคุณใช้สิ่งนี้ นี่เป็นเพราะการเรียกตัวสร้างใหม่ก่อนที่จะตั้งค่า $ อินสแตนซ์ดังนั้นการเรียกใช้ getInstance () จากตัวสร้างจะนำไปสู่การวนซ้ำไม่สิ้นสุดและแบ่งทุกอย่าง

วิธีหนึ่งคือไม่ใช้ตัวสร้าง (หรืออย่างน้อยไม่ใช้ getInstance () ภายใน) แต่มีฟังก์ชัน "init" ในชั้นเรียนอย่างชัดเจนเพื่อตั้งค่าการกระทำของคุณ แบบนี้:

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

ด้วยบางสิ่งเช่นนี้ในตอนท้ายของไฟล์หลังจากที่คลาสได้ถูกกำหนดไว้ทั้งหมดแล้วการทำให้อินสแตนซ์ของปลั๊กอินกลายเป็นเรื่องง่ายอย่างนี้:

ExamplePlugin::init();

เริ่มต้นแล้วเพื่อเพิ่มการกระทำของคุณและเพื่อให้มันเรียกใช้ getInstance () ซึ่งจะยกระดับชั้นเรียนและทำให้แน่ใจว่ามีเพียงหนึ่งในนั้น หากคุณไม่มีฟังก์ชั่นเริ่มต้นคุณจะต้องทำสิ่งนี้เพื่อยกตัวอย่างชั้นเรียนในตอนแรกแทน:

ExamplePlugin::getInstance();

หากต้องการตอบคำถามเดิมการลบการกระทำของ hook นั้นจากภายนอก (หรือที่รู้จักในปลั๊กอินอื่น) สามารถทำได้ดังนี้:

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

ใส่ลงไปในสิ่งที่ติดกับplugins_loadedตะขอการกระทำและมันจะยกเลิกการกระทำที่ถูกยึดโดยปลั๊กอินดั้งเดิม


3
+1 Tru dat นี่เป็นการปฏิบัติที่ดีที่สุดอย่างชัดเจน เราทุกคนควรพยายามเขียนโค้ดปลั๊กอินของเราด้วยวิธีนี้
Tom Auger

3
+1 คำแนะนำเหล่านี้ช่วยฉันในการลบตัวกรองในคลาสรูปแบบซิงเกิล
Devin Walker

+1 แต่ฉันคิดว่าคุณควรจะขอwp_loadedไม่ใช่plugins_loadedซึ่งอาจเรียกเร็วเกินไป
EML

4
ไม่plugins_loadedจะเป็นสถานที่ที่ถูกต้อง การwp_loadedกระทำที่เกิดขึ้นหลังจากการinitกระทำดังนั้นหากปลั๊กอินของคุณดำเนินการใด ๆinit(และส่วนใหญ่ทำ) จากนั้นคุณต้องการเริ่มต้นปลั๊กอินและตั้งค่าก่อนที่จะ plugins_loadedเบ็ดเป็นสถานที่ที่เหมาะสมสำหรับการก่อสร้างที่
อ็อตโต

13

2 ฟังก์ชั่น PHP ขนาดเล็กสำหรับอนุญาตให้ลบตัวกรอง / การดำเนินการด้วยคลาส "ไม่ระบุชื่อ": https://github.com/herewithme/wp-filters-extras/


ฟังก์ชั่นที่ยอดเยี่ยมมาก ขอบคุณสำหรับการโพสต์ที่นี่!
Tom Auger

ตามที่กล่าวไว้เป็นคนอื่น ๆ ในโพสต์ของฉันด้านล่างสิ่งเหล่านี้จะแตกใน WordPress 4.7 (เว้นแต่ว่า repo จะได้รับการปรับปรุง แต่ไม่ได้อยู่ใน 2 ปี)
sMyles

1
เพิ่งสังเกตว่า repo wp-filters-extras ได้รับการปรับปรุงแน่นอนสำหรับ v4.7 และคลาส WP_Hook
Dave Romsey

13

นี่เป็นฟังก์ชั่นที่บันทึกไว้อย่างกว้างขวางที่ฉันสร้างขึ้นเพื่อลบตัวกรองเมื่อคุณไม่สามารถเข้าถึงวัตถุคลาส (ทำงานกับ WordPress 1.2+ รวมถึง 4.7+):

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

/**
 * Remove Class Filter Without Access to Class Object
 *
 * In order to use the core WordPress remove_filter() on a filter added with the callback
 * to a class, you either have to have access to that class object, or it has to be a call
 * to a static method.  This method allows you to remove filters with a callback to a class
 * you don't have access to.
 *
 * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
 * Updated 2-27-2017 to use internal WordPress removal for 4.7+ (to prevent PHP warnings output)
 *
 * @param string $tag         Filter to remove
 * @param string $class_name  Class name for the filter's callback
 * @param string $method_name Method name for the filter's callback
 * @param int    $priority    Priority of the filter (default 10)
 *
 * @return bool Whether the function is removed.
 */
function remove_class_filter( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    global $wp_filter;

    // Check that filter actually exists first
    if ( ! isset( $wp_filter[ $tag ] ) ) return FALSE;

    /**
     * If filter config is an object, means we're using WordPress 4.7+ and the config is no longer
     * a simple array, rather it is an object that implements the ArrayAccess interface.
     *
     * To be backwards compatible, we set $callbacks equal to the correct array as a reference (so $wp_filter is updated)
     *
     * @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 ) ) {
        // Create $fob object from filter tag, to use below
        $fob = $wp_filter[ $tag ];
        $callbacks = &$wp_filter[ $tag ]->callbacks;
    } else {
        $callbacks = &$wp_filter[ $tag ];
    }

    // Exit if there aren't any callbacks for specified priority
    if ( ! isset( $callbacks[ $priority ] ) || empty( $callbacks[ $priority ] ) ) return FALSE;

    // Loop through each filter for the specified priority, looking for our class & method
    foreach( (array) $callbacks[ $priority ] as $filter_id => $filter ) {

        // Filter should always be an array - array( $this, 'method' ), if not goto next
        if ( ! isset( $filter[ 'function' ] ) || ! is_array( $filter[ 'function' ] ) ) continue;

        // If first value in array is not an object, it can't be a class
        if ( ! is_object( $filter[ 'function' ][ 0 ] ) ) continue;

        // Method doesn't match the one we're looking for, goto next
        if ( $filter[ 'function' ][ 1 ] !== $method_name ) continue;

        // Method matched, now let's check the Class
        if ( get_class( $filter[ 'function' ][ 0 ] ) === $class_name ) {

            // WordPress 4.7+ use core remove_filter() since we found the class object
            if( isset( $fob ) ){
                // Handles removing filter, reseting callback priority keys mid-iteration, etc.
                $fob->remove_filter( $tag, $filter['function'], $priority );

            } else {
                // Use legacy removal process (pre 4.7)
                unset( $callbacks[ $priority ][ $filter_id ] );
                // and if it was the only filter in that priority, unset that priority
                if ( empty( $callbacks[ $priority ] ) ) {
                    unset( $callbacks[ $priority ] );
                }
                // and if the only filter for that tag, set the tag to an empty array
                if ( empty( $callbacks ) ) {
                    $callbacks = array();
                }
                // Remove this filter from merged_filters, which specifies if filters have been sorted
                unset( $GLOBALS['merged_filters'][ $tag ] );
            }

            return TRUE;
        }
    }

    return FALSE;
}

/**
 * Remove Class Action Without Access to Class Object
 *
 * In order to use the core WordPress remove_action() on an action added with the callback
 * to a class, you either have to have access to that class object, or it has to be a call
 * to a static method.  This method allows you to remove actions with a callback to a class
 * you don't have access to.
 *
 * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
 *
 * @param string $tag         Action to remove
 * @param string $class_name  Class name for the action's callback
 * @param string $method_name Method name for the action's callback
 * @param int    $priority    Priority of the action (default 10)
 *
 * @return bool               Whether the function is removed.
 */
function remove_class_action( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    remove_class_filter( $tag, $class_name, $method_name, $priority );
}

2
คำถาม - คุณได้ทำการทดสอบใน 4.7 หรือไม่? มีการเปลี่ยนแปลงบางอย่างเกี่ยวกับวิธีการลงทะเบียนการโทรกลับในตัวกรองที่ใหม่เอี่ยม ฉันยังไม่ได้ดูรหัสของคุณในเชิงลึก แต่มันเป็นสิ่งที่คุณอาจต้องการตรวจสอบ: make.wordpress.org/core/2016/09/08/…
Tom Auger

อืมค่อนข้างแน่ใจว่าสิ่งนี้จะพังใน 4.7
gmazzap

อ่า! ไม่มีฉันไม่ได้ แต่ขอบคุณฉัน def จะมีลักษณะเป็นแบบนี้และอัปเดตนี้เพื่อให้มันเข้ากัน (ถ้าจำเป็นต้อง)
sMyles

1
@TomAuger ขอบคุณสำหรับหัวขึ้น! ผมได้ปรับปรุงฟังก์ชั่นการทดสอบการทำงานบน WordPress 4.7+ (กับหลังเข้ากันได้ยังคง)
sMyles

1
เพียงแค่การปรับปรุงนี้จะใช้หลักวิธีการกำจัดภายใน (ในการจัดการกลางย้ำและป้องกันคำเตือน PHP)
sMyles

2

วิธีแก้ปัญหาข้างต้นดูเหมือนล้าสมัยต้องเขียนของตัวเอง ...

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

0

ฟังก์ชั่นนี้ขึ้นอยู่กับคำตอบ @Digerkam เพิ่มการเปรียบเทียบถ้า$def['function'][0]เป็นสตริงและในที่สุดก็ใช้งานได้สำหรับฉัน

การใช้$wp_filter[$tag]->remove_filter()ควรทำให้มีเสถียรภาพมากขึ้น

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

ตัวอย่างการใช้งาน:

คู่ที่เหมาะสม

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

ลำดับความสำคัญใด ๆ

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

คลาสและลำดับความสำคัญใด ๆ

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

0

นี่ไม่ใช่คำตอบทั่วไป แต่มีคำตอบเฉพาะสำหรับชุดรูปแบบ Avada และ WooCommerceซึ่งฉันคิดว่าคนอื่นอาจมีประโยชน์:

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' );
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.