WordPress Hooks Pattern in Wikit
Overview
Hooks are the foundation of WordPress extensibility. Wikit uses WordPress hooks extensively and provides patterns for organizing and managing them effectively within the framework's architecture.
Hook Types
Actions
Execute code at specific points without modifying data.
Filters
Modify data before it's used or returned.
Registering Hooks
In Singleton Classes
php
namespace WDG\Theme;
use WDG\Core\SingletonTrait;
class Navigation {
use SingletonTrait;
/**
* Constructor - hooks are registered here
*/
protected function __construct() {
$this->init_hooks();
}
/**
* Initialize all hooks for this feature
*/
private function init_hooks(): void {
// Actions
add_action( 'init', [ $this, 'register_menus' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] );
add_action( 'after_setup_theme', [ $this, 'theme_support' ] );
// Filters
add_filter( 'nav_menu_css_class', [ $this, 'menu_classes' ], 10, 2 );
add_filter( 'wp_nav_menu_args', [ $this, 'menu_args' ] );
// Conditional hooks
if ( is_admin() ) {
add_action( 'admin_menu', [ $this, 'admin_menu' ] );
}
}
}In PostType Classes
php
namespace WDG\App\PostType;
use WDG\Core\PostType;
class Products extends PostType {
protected $post_type = 'product';
public function init() {
parent::init();
// PostType-specific hooks
add_action( 'save_post_' . $this->post_type, [ $this, 'save_post' ], 10, 3 );
add_filter( 'manage_' . $this->post_type . '_posts_columns', [ $this, 'admin_columns' ] );
add_action( 'manage_' . $this->post_type . '_posts_custom_column', [ $this, 'admin_column_content' ], 10, 2 );
}
/**
* Handle post save
*/
public function save_post( int $post_id, \WP_Post $post, bool $update ): void {
// Verify nonce
if ( ! isset( $_POST['product_nonce'] ) || ! wp_verify_nonce( $_POST['product_nonce'], 'save_product' ) ) {
return;
}
// Check autosave
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// Check permissions
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// Save meta data
if ( isset( $_POST['price'] ) ) {
update_post_meta( $post_id, 'price', sanitize_text_field( $_POST['price'] ) );
}
}
}Hook Priority and Order
Priority Conventions
php
// EARLY: Priority 5 - Setup and initialization
add_action( 'init', [ $this, 'early_init' ], 5 );
// DEFAULT: Priority 10 - Standard functionality
add_action( 'init', [ $this, 'standard_init' ] ); // 10 is implicit
// LATE: Priority 20 - Cleanup and finalization
add_action( 'init', [ $this, 'late_init' ], 20 );
// VERY LATE: Priority 999 - Last resort modifications
add_action( 'init', [ $this, 'final_init' ], 999 );Execution Order Pattern
php
class FeatureLoader {
protected function __construct() {
// 1. Register post types and taxonomies
add_action( 'init', [ $this, 'register_types' ], 0 );
// 2. Register blocks
add_action( 'init', [ $this, 'register_blocks' ], 5 );
// 3. Standard initialization
add_action( 'init', [ $this, 'init_features' ], 10 );
// 4. Late initialization depending on other plugins
add_action( 'init', [ $this, 'late_init' ], 20 );
}
}Custom Hooks
Creating Custom Actions
php
namespace WDG\App;
class CustomFeature {
/**
* Process data with custom hook points
*/
public function process_data( array $data ): array {
// Before processing hook
do_action( 'wdg/before_process_data', $data );
// Process data
$processed = $this->transform_data( $data );
// After processing hook
do_action( 'wdg/after_process_data', $processed, $data );
return $processed;
}
}
// Using the custom action
add_action( 'wdg/before_process_data', function( $data ) {
error_log( 'Processing: ' . wp_json_encode( $data ) );
} );Creating Custom Filters
php
namespace WDG\Theme;
class Content {
/**
* Get filtered content
*/
public function get_content( string $content ): string {
// Apply custom filter
$content = apply_filters( 'wdg/content/before_display', $content );
// Process content
$content = $this->process_content( $content );
// Apply another filter with context
$content = apply_filters( 'wdg/content/after_process', $content, get_the_ID() );
return $content;
}
}
// Using the custom filter
add_filter( 'wdg/content/before_display', function( $content ) {
return wpautop( $content );
} );
add_filter( 'wdg/content/after_process', function( $content, $post_id ) {
if ( get_post_type( $post_id ) === 'product' ) {
$content .= '<div class="product-cta">Buy Now!</div>';
}
return $content;
}, 10, 2 );Common WordPress Hooks
Action Hooks by Lifecycle
php
// Theme/Plugin Initialization
add_action( 'after_setup_theme', [ $this, 'theme_setup' ] );
add_action( 'init', [ $this, 'initialize' ] );
add_action( 'widgets_init', [ $this, 'register_widgets' ] );
// Request Lifecycle
add_action( 'parse_request', [ $this, 'handle_request' ] );
add_action( 'template_redirect', [ $this, 'redirect_logic' ] );
add_action( 'wp', [ $this, 'wp_loaded' ] );
// Head and Footer
add_action( 'wp_head', [ $this, 'add_head_tags' ] );
add_action( 'wp_footer', [ $this, 'add_footer_scripts' ] );
// Admin
add_action( 'admin_init', [ $this, 'admin_initialize' ] );
add_action( 'admin_menu', [ $this, 'add_menu_pages' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'admin_assets' ] );
// User
add_action( 'wp_login', [ $this, 'on_user_login' ], 10, 2 );
add_action( 'wp_logout', [ $this, 'on_user_logout' ] );
add_action( 'user_register', [ $this, 'on_user_register' ] );Filter Hooks by Purpose
php
// Content Filters
add_filter( 'the_content', [ $this, 'filter_content' ] );
add_filter( 'the_title', [ $this, 'filter_title' ], 10, 2 );
add_filter( 'the_excerpt', [ $this, 'filter_excerpt' ] );
// Query Filters
add_filter( 'pre_get_posts', [ $this, 'modify_query' ] );
add_filter( 'posts_where', [ $this, 'filter_where' ], 10, 2 );
add_filter( 'posts_orderby', [ $this, 'filter_orderby' ], 10, 2 );
// URL Filters
add_filter( 'post_link', [ $this, 'filter_permalink' ], 10, 2 );
add_filter( 'term_link', [ $this, 'filter_term_link' ], 10, 3 );
add_filter( 'home_url', [ $this, 'filter_home_url' ], 10, 2 );
// User Filters
add_filter( 'user_has_cap', [ $this, 'filter_capabilities' ], 10, 3 );
add_filter( 'authenticate', [ $this, 'custom_authenticate' ], 10, 3 );AJAX Hooks
Registering AJAX Handlers
php
namespace WDG\App;
class AjaxHandler {
use \WDG\Core\SingletonTrait;
protected function __construct() {
// For logged-in users
add_action( 'wp_ajax_wdg_get_data', [ $this, 'get_data' ] );
// For non-logged-in users
add_action( 'wp_ajax_nopriv_wdg_get_data', [ $this, 'get_data' ] );
// Admin only
add_action( 'wp_ajax_wdg_admin_action', [ $this, 'admin_action' ] );
}
/**
* Handle AJAX request
*/
public function get_data(): void {
// Verify nonce
check_ajax_referer( 'wdg_ajax_nonce', 'nonce' );
// Get and sanitize data
$type = isset( $_POST['type'] ) ? sanitize_key( $_POST['type'] ) : '';
// Process request
$data = $this->fetch_data( $type );
// Return JSON response
if ( $data ) {
wp_send_json_success( $data );
} else {
wp_send_json_error( 'No data found' );
}
}
}REST API Hooks
Registering REST Routes
php
namespace WDG\App;
class RestEndpoints {
use \WDG\Core\SingletonTrait;
protected function __construct() {
add_action( 'rest_api_init', [ $this, 'register_routes' ] );
}
/**
* Register REST API routes
*/
public function register_routes(): void {
register_rest_route( 'wdg/v1', '/products', [
'methods' => 'GET',
'callback' => [ $this, 'get_products' ],
'permission_callback' => '__return_true',
'args' => [
'per_page' => [
'default' => 10,
'validate_callback' => function( $param ) {
return is_numeric( $param );
},
],
],
] );
register_rest_route( 'wdg/v1', '/product/(?P<id>\d+)', [
'methods' => 'POST',
'callback' => [ $this, 'update_product' ],
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
'args' => [
'id' => [
'validate_callback' => function( $param ) {
return is_numeric( $param );
},
],
],
] );
}
}Block Editor Hooks
Block Registration
php
namespace WDG\Theme;
class BlockEditor {
use \WDG\Core\SingletonTrait;
protected function __construct() {
add_action( 'init', [ $this, 'register_blocks' ] );
add_filter( 'block_categories_all', [ $this, 'block_categories' ] );
add_action( 'enqueue_block_editor_assets', [ $this, 'editor_assets' ] );
}
/**
* Register custom blocks
*/
public function register_blocks(): void {
register_block_type(
get_template_directory() . '/blocks/custom-block',
[
'render_callback' => [ $this, 'render_custom_block' ],
]
);
}
/**
* Add custom block category
*/
public function block_categories( array $categories ): array {
return array_merge(
[
[
'slug' => 'wikit',
'title' => __( 'Wikit Blocks', 'wikit' ),
'icon' => 'admin-site',
],
],
$categories
);
}
}Dynamic Hooks
Using Variable Hook Names
php
class DynamicHooks {
private string $prefix = 'wdg';
private string $feature = 'products';
protected function __construct() {
// Dynamic action name
$action_name = "{$this->prefix}/{$this->feature}/init";
add_action( $action_name, [ $this, 'feature_init' ] );
// Dynamic filter name
$filter_name = "{$this->prefix}/{$this->feature}/data";
add_filter( $filter_name, [ $this, 'filter_data' ] );
// Post type specific
$post_type = 'product';
add_action( "save_post_{$post_type}", [ $this, 'save_post' ] );
add_filter( "manage_{$post_type}_posts_columns", [ $this, 'columns' ] );
}
}Removing Hooks
Unhooking Actions and Filters
php
class HookRemoval {
protected function __construct() {
// Remove with specific priority
remove_action( 'wp_head', 'wp_generator' );
// Remove class method with priority
remove_filter( 'the_content', [ SomeClass::instance(), 'filter_method' ], 10 );
// Remove all hooks for an action
remove_all_actions( 'wp_footer' );
// Conditional removal
if ( ! is_admin() ) {
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
}
}
/**
* Remove hooks added by other plugins/themes
*/
public function late_removal(): void {
// Must run after the hook was added
add_action( 'init', function() {
remove_action( 'init', 'unwanted_function', 15 );
}, 14 ); // Run at priority 14 to remove priority 15
}
}Hook Documentation
Documenting Custom Hooks
php
/**
* Fires before product data is saved
*
* @since 1.0.0
*
* @param array $data Product data array
* @param int $post_id Post ID
*/
do_action( 'wdg/product/before_save', $data, $post_id );
/**
* Filters the product price display
*
* @since 1.0.0
*
* @param string $price_html Formatted price HTML
* @param float $price Raw price value
* @param int $product_id Product post ID
* @return string Modified price HTML
*/
$price_html = apply_filters( 'wdg/product/price_html', $price_html, $price, $product_id );Best Practices
Do's
- ✅ Use descriptive hook names
- ✅ Document custom hooks thoroughly
- ✅ Use namespaced hook names (wdg/feature/hook)
- ✅ Check nonces in form/AJAX handlers
- ✅ Verify capabilities in admin hooks
- ✅ Use appropriate priorities
- ✅ Return filtered values
- ✅ Remove hooks when necessary
Don'ts
- ❌ Don't use generic hook names
- ❌ Don't forget the number of parameters
- ❌ Don't modify global variables in hooks
- ❌ Don't echo in filter callbacks
- ❌ Don't use anonymous functions for removable hooks
- ❌ Don't assume hook execution order
Hook Debugging
Viewing Attached Hooks
php
// Debug: See all hooks attached to an action
global $wp_filter;
if ( isset( $wp_filter['init'] ) ) {
error_log( print_r( $wp_filter['init'], true ) );
}
// Check if action has been fired
if ( did_action( 'init' ) ) {
// Init has already run
}
// Check current filter
$current = current_filter();
if ( $current === 'the_content' ) {
// Currently filtering content
}Performance Considerations
- Minimize Hook Usage: Don't add unnecessary hooks
- Use Appropriate Priorities: Let WordPress core run first
- Conditional Loading: Only add hooks when needed
- Remove Unused Hooks: Clean up third-party hooks
- Cache Hook Results: Don't recalculate on every call
Common Patterns
Conditional Hook Registration
php
protected function __construct() {
// Only on frontend
if ( ! is_admin() ) {
add_action( 'wp_enqueue_scripts', [ $this, 'frontend_assets' ] );
}
// Only in admin
if ( is_admin() ) {
add_action( 'admin_init', [ $this, 'admin_init' ] );
}
// Only for specific post type
add_action( 'wp', function() {
if ( is_singular( 'product' ) ) {
add_filter( 'the_content', [ $this, 'product_content' ] );
}
} );
// Only for logged-in users
if ( is_user_logged_in() ) {
add_action( 'init', [ $this, 'user_features' ] );
}
}One-time Hooks
php
// Self-removing hook
add_action( 'init', function() {
// Do something once
update_option( 'wdg_initialized', true );
// Remove this hook
remove_action( 'init', __FUNCTION__ );
} );Hooks are the backbone of WordPress extensibility. Master them to create flexible, maintainable Wikit applications.