07/11/2018, 23:29

Bắt chước Action Hooks của wordpress trong ứng dụng PHP của bạn

Action Hook là những hook liên quan đến xử lý sự kiện, ví dụ bạn muốn website xử lý một cái gì đó ở phần đầu trang (header) thì bạn có thể sử dụng hook wp_head, bạn sẽ dùng hàm add_action() để thêm đoạn xử lý của bạn vào: add_action ( string $tag , callable $function_to_add , int ...

Action Hook là những hook liên quan đến xử lý sự kiện, ví dụ bạn muốn website xử lý một cái gì đó ở phần đầu trang (header) thì bạn có thể sử dụng hook wp_head, bạn sẽ dùng hàm add_action() để thêm đoạn xử lý của bạn vào:

add_action ( string $tag, callable $function_to_add, int $priority = 10, int $accepted_args = 1 );

Trong đó:

$tag: là tên của hook (bắt buộc có) $function_to_add: là tên hàm bạn muốn bổ sung vào hook tag (bắt buộc có) `priority: độ ưu tiên, giá trị mặc định là 10, hàm nào có độ ưu tiên cao hơn sẽ được thực hiện trướcaccepted_args`: tổng số các tham số mà hàm function_to_add cần, không được vượt quá số tham số của hook (mặc định là 1) Ví dụ (bạn hãy thử viết đoạn code này vào file functions.php nhé):

add_action('wp_head','hook_javascript');

function hook_javascript() {

   $output = "<script> alert('Page is loading...'); </script>";

   echo $output;
}

Sau khi thực hiện đoạn code này, website của bạn trước khi load sẽ gọi hook wp_head và đoạn script bên trên sẽ được chạy, bình thường nếu theme của bạn không hỗ trợ hiển thị thì phải sử dụng một hàm do_action() để website thực hiện những action bạn đã add vào hook.

do_action('wp_head'); // Chèn dòng lệnh vào file header, trước tag </head> nếu theme của bạn chưa có hook này nhé.

Tất cả hệ thống hook đều quan trọng, nó giúp bạn chỉnh sửa sâu vào hệ thống WordPress mà không phá hỏng source code. Về hàm do_action(), bạn gọi nó trước hay sau hàm bạn muốn thêm vào đều được cả. Ngoài ra bạn có thể sử dụng hàm do_action() bên trên để tạo một hook riêng cho mình, ví dụ bạn muốn một hook có tên là wpguide_after_post thì bạn chỉ cần chèn đoạn code do_action(‘wpguide_after_post’) vào nơi bạn muốn rồi tiến hành add_action() như một hook bình thường.

Đấy là action hook trong wordpress, bây giờ chúng ta sẽ xây dựng cơ chế tương tự trong ứng dụng php của bạn

Ý tưởng

Một phương thức (hàm) thay vì trả về kết quả là A thì trước đó ta sẽ gán nó với 1 action method với đầu vào chính là kết A để xử lý ra kết quả B mà mình mong muốn. Ví dụ:

Hàm thông thường
public function getData() {
    //ABCXYZ 
    return $data;
}
Hàm sử dụng action hook
public function getData() {
    //ABCXYZ
    return do_action('before_return_data', $data);
} 

Ta sẽ có action name là 'before_return_data'. Đây sẽ là tên của action sẽ xử lý để trả về dữ liệu thông qua hàm do_action. Bây giờ nhiệm vụ của chúng ta là phải chỉ định được hàm nào sẽ xử lý hook có tên là 'before_return_data' bằng cách gán

add_action('before_return_data', 'beforeReturnDataMethod');
function beforeReturnDataMethod($data) {
    
}

hàm add_action sẽ có 2 tham số lần lượt là tên của action hook và hàm được chỉ định sẽ xử lý hook đó. Như vậy hiểu theo cách thông thường là trước khi trả về dữ liệu thì hàm sẽ gán nó với 1 event và đợi xem đã có hàm nào xử lý event đó chưa, nếu có rồi thì để cho hàm đó xử lý trước, còn nếu chưa có thì trả về dữ liệu được truyền vào.

Sau đây là class mình viết cho toàn bộ việc xử lý hook

<?php

class Action_hooks
// Instance of class
{
    public static $hooks_instance;

    public static $actions;

    public static $current_action;

    public static $run_actions;

    public static function instance()
    {
        if (!self::$hooks_instance) {
            self::$hooks_instance = new Action_hooks();
        }

        return self::$hooks_instance;
    }

    /**
     * Add Action
     *
     * Add a new hook trigger action
     *
     * @param mixed $name
     * @param mixed $function
     * @param mixed $priority
     */
    public function add_action($name, $function, $priority = 10)
    {
        if (is_string($function)) {
            // If we have already registered this action return true
            if (isset(self::$actions[$name][$priority][$function])) {
                return true;
            }
        } elseif (is_array($function)) {
            // Class
            if (isset(self::$actions[$name][$priority][get_class($function[0]) . '-' . $function[1]])) {
                return true;
            }
        }
        /**
         * Allows us to iterate through multiple action hooks.
         */
        if (is_array($name)) {
            foreach ($name as $name) {
                // Store the action hook in the $hooks array
                if (is_string($function)) {
                    // Store the action hook in the $hooks array
                    self::$actions[$name][$priority][$function] = [
                        'function' => $function,
                    ];
                } elseif (is_array($function)) {
                    self::$actions[$name][$priority][get_class($function[0]) . '-' . $function[1]] = [
                        'class'  => $function[0],
                        'method' => $function[1],
                    ];
                }
            }
        } else {
            if (is_string($function)) {
                // Store the action hook in the $hooks array
                self::$actions[$name][$priority][$function] = [
                    'function' => $function,
                ];
            } elseif (is_array($function)) {
                self::$actions[$name][$priority][get_class($function[0]) . '-' . $function[1]] = [
                    'class'  => $function[0],
                    'method' => $function[1],
                ];
            }
        }

        return true;
    }

    /**
     * Do Action
     *
     * Trigger an action for a particular action hook
     *
     * @param mixed $name
     * @param mixed $arguments
     * @return mixed
     */
    public function do_action($name, $arguments = ')
    {
        // Oh, no you didn't. Are you trying to run an action hook that doesn't exist?
        if (!isset(self::$actions[$name])) {
            return $arguments;
        }

        // Set the current running hook to this
        self::$current_action = $name;
        // Key sort our action hooks
        ksort(self::$actions[$name]);
        foreach (self::$actions[$name] as $priority => $names) {
            if (is_array($names)) {
                foreach ($names as $name) {
                    if (isset($name['function'])) {
                        $return = call_user_func_array($name['function'], [
                            &$arguments,
                        ]);
                        if ($return) {
                            $arguments = $return;
                        }
                        self::$run_actions[$name][$priority];
                    } else {
                        if (method_exists($name['class'], $name['method'])) {
                            $return = call_user_func_array([$name['class'], $name['method']], [
                                &$arguments,
                            ]);

                            if ($return) {
                                $arguments = $return;
                            }

                            self::$run_actions[get_class($name['class']) . '-' . $name['method']][$priority];
                        }
                    }
                }
            }
        }
        self::$current_action = ';

        return $arguments;
    }

    /**
     * Remove Action
     *
     * Remove an action hook. No more needs to be said.
     *
     * @param mixed $name
     * @param mixed $function
     * @param mixed $priority
     */
    public function remove_action($name, $function, $priority = 10)
    {
        if (!is_array($function)) {
            // If the action hook doesn't, just return true
            if (!isset(self::$actions[$name][$priority][$function])) {
                return true;
            }
            // Remove the action hook from our hooks array
            unset(self::$actions[$name][$priority][$function]);
        } elseif (is_array($function)) {
            if (!isset(self::$actions[$name][$priority][$function[0] . '-' . $function[1]])) {
                return true;
            }
            // Remove the action hook from our hooks array
            unset(self::$actions[$name][$priority][$function[0] . '-' . $function[1]]);
        }
    }

    /**
     * Current Action
     *
     * Get the currently running action hook
     *
     */
    public function current_action()
    {
        return self::$current_action;
    }

    /**
     * Has Run
     *
     * Check if a particular hook has been run
     *
     * @param mixed $hook
     * @param mixed $priority
     */
    public function has_run($action, $priority = 10)
    {
        if (isset(self::$actions[$action][$priority])) {
            return true;
        }

        return false;
    }

    /**
     * Action Exists
     *
     * Does a particular action hook even exist?
     *
     * @param mixed $name
     */
    public function action_exists($name)
    {
        if (isset(self::$actions[$name])) {
            return true;
        }

        return false;
    }
}
/**
 * Add a new action hook
 *
 * @param mixed $name
 * @param mixed $function
 * @param mixed $priority
 */
function add_action($name, $function, $priority = 10)
{
    return Action_hooks::instance()->add_action($name, $function, $priority);
}
/**
 * Run an action
 *
 * @param mixed $name
 * @param mixed $arguments
 * @return mixed
 */
function do_action($name, $arguments = ')
{
    return Action_hooks::instance()->do_action($name, $arguments);
}
/**
 * Remove an action
 *
 * @param mixed $name
 * @param mixed $function
 * @param mixed $priority
 */
function remove_action($name, $function, $priority = 10)
{
    return Action_hooks::instance()->remove_action($name, $function, $priority);
}
/**
 * Check if an action exists
 *
 * @param mixed $name
 */
function action_exists($name)
{
    return Action_hooks::instance()->action_exists($name);
}
 

Bạn để ý mình viết class với thuộc tính instance với kiểu static. Đây là Singleton class, để lấy ra thể hiện của class thì mình gọi đến Action_hooks::instance(). Vì vậy nên mọi thể hiện của class được đảm bảo là duy nhất thông qua kiểu static. Chú ý: Để gán 1 method cho 1 action, thì method đó có thể là global method hoặc method của 1 thể hiện của class. Để gán method là method của 1 thể hiện, bạn phải truyền vào function dạng mảng như sau:

add_action('before_add_online_payment_modes', [ $this, 'initMode' ]);
// [ $this, 'initMode' ] mảng có 2 thành phần, 1 là con trỏ $this của chính class hiện tại, 2 là method của chính class đó

hoặc

$anyInstanceClass = new InstanceClass();
add_action('before_add_online_payment_modes', [ $anyInstanceClass, 'initMode' ]);

class InstanceClass() {
    public function initMode() {
        //ABCD XYZ
    }
}
0