در وردپرس، Walker Class یک الگوی طراحی برای پیمایش ساختارهای درختی و تولید خروجی HTML سفارشی است که در منو، لیست دسته‌بندی، لیست برگه و لیست کامنت کاربرد دارد. بدون درک درست متدهای start_el، end_el، start_lvl و end_lvl، هر پیاده‌سازی سفارشی در مقیاس بزرگ به کد شکننده و ناتست‌پذیر تبدیل می‌شود. Walker_Nav_Menu، Walker_Page، Walker_Category و Walker_Comment نمونه‌های هسته‌ای این کلاس هستند و درک ساختار آن‌ها، پیش‌نیاز ساخت کلاس‌های سفارشی است. ساختار extend صحیح، رعایت اصول امنیت و Escape در خروجی، و تست رفتار در سطوح مختلف، تفاوت بین کد حرفه‌ای و کد ناپایدار است. در این راهنما از ساختار پایه Walker تا سناریوهای پیشرفته سفارشی‌سازی منو و لیست را با نگاه مهندسی و کد عملی پوشش می‌دهیم.

در پروژه‌های واقعی، هر بار که تیم توسعه به ساختار پیش‌فرض منو اکتفا کرده، در ادامه راه به دیوار برخورد کرده است. Walker Class همان لایه‌ای است که کنترل کامل روی HTML خروجی منو و لیست می‌دهد و بدون آن، سفارشی‌سازی عمیق ناممکن است.

Walker Class چیست و چرا اهمیت دارد؟

Walker یک کلاس انتزاعی (Abstract Class) در هسته وردپرس است که ساختار درختی را پیمایش می‌کند و برای هر آیتم، خروجی HTML تولید می‌کند. کلاس‌های مشتق‌شده از Walker، برای انواع مختلف داده استفاده می‌شوند.

پیش از ادامه، توصیه می‌کنم راهنمای Custom Nav Walker برای منوی مگا را مطالعه کنید. اگر با ساختار منو آشنایی ندارید، راهنمای تابع wp_nav_menu در وردپرس نقطه شروع مناسبی است.

الگوی طراحی Walker در وردپرس

Walker از الگوی Template Method استفاده می‌کند. متدهای start_el، end_el، start_lvl و end_lvl نقاط توسعه‌پذیر هستند و کلاس‌های مشتق‌شده می‌توانند آن‌ها را بازنویسی کنند.

ساختار داخلی کلاس Walker در هسته

کلاس Walker دارای متدهای کلیدی است: walk() که نقطه ورود است، display_element() که آیتم جاری را پردازش می‌کند، و متدهای قبلی که در بالا ذکر شدند.

در walk، درخت داده پیمایش می‌شود و برای هر آیتم، متد display_element فراخوانی می‌شود. در display_element، بررسی می‌شود که آیا آیتم فرزند دارد یا خیر و بر اساس آن، start_lvl و end_lvl فراخوانی می‌شوند.

پارامترهای مهم در متدها

پارامترهای $output، $depth، $args و $current_object_id در همه متدها ظاهر می‌شوند. آشنایی دقیق با این پارامترها، پیش‌نیاز بازنویسی صحیح است.

ساخت کلاس سفارشی با extend

ساخت کلاس سفارشی با ارث‌بری از Walker_Nav_Menu یا Walker_Page انجام می‌شود. الگوی پایه به این شکل است:

class WordPressKar_Walker extends Walker_Nav_Menu {
    public function start_lvl( &$output, $depth = 0, $args = null ) {
        $indent = str_repeat( "	", $depth );
        $output .= "
$indent<ul class="sub-menu">
";
    }

    public function end_lvl( &$output, $depth = 0, $args = null ) {
        $indent = str_repeat( "	", $depth );
        $output .= "$indent</ul>
";
    }

    public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
        $output .= "<li><a href="" . esc_url( $item->url ) . "">" . esc_html( $item->title ) . "</a>";
    }

    public function end_el( &$output, $item, $depth = 0, $args = null ) {
        $output .= "</li>
";
    }
}

این ساختار پایه، امکان سفارشی‌سازی کامل را می‌دهد. برای مطالعه بیشتر در مورد ساختار ماژولار قالب، راهنمای ساخت ماژول سفارشی در قالب را ببینید.

نام‌گذاری کلاس و پیشوند

توصیه می‌کنم همیشه از پیشوند اختصاصی پروژه یا برند استفاده کنید تا از تداخل با کلاس‌های دیگر جلوگیری شود.

بازنویسی start_el برای آیتم سفارشی

متد start_el مهم‌ترین متد برای سفارشی‌سازی است. در این متد، می‌توانید کلاس‌های آیتم، Attributes لینک و محتوای داخلی را تغییر دهید.

نکته مهم، رعایت اصول امنیت است. همه مقادیر باید Escape شوند:

public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
    $classes   = empty( $item->classes ) ? array() : (array) $item->classes;
    $classes[] = "menu-item-" . $item->ID;
    if ( 0 === $depth ) {
        $classes[] = "top-level";
    }
    $class_names = implode( " ", apply_filters( "nav_menu_css_class", $classes, $item, $args, $depth ) );
    $output .= "<li class="" . esc_attr( $class_names ) . "">";
    $atts = array(
        "href"   => ! empty( $item->url ) ? $item->url : "#",
        "target" => ! empty( $item->target ) ? $item->target : "",
        "title"  => ! empty( $item->attr_title ) ? $item->attr_title : "",
    );
    $attributes = "";
    foreach ( $atts as $attr => $value ) {
        if ( ! empty( $value ) ) {
            $attributes .= " " . $attr . "="" . esc_attr( $value ) . """;
        }
    }
    $output .= "<a" . $attributes . ">" . esc_html( $item->title ) . "</a>";
}

برای مطالعه بیشتر در مورد Escape، راهنمای Escape کردن خروجی برای جلوگیری از XSS را ببینید.

افزودن متادیتای سفارشی به آیتم منو

برای افزودن آیکون یا توضیحات به آیتم، از فیلتر wp_setup_nav_menu_item استفاده کنید. این فیلتر امکان تزریق داده سفارشی به آیتم‌ها را فراهم می‌کند.

بازنویسی end_el و start_lvl

متد end_el مسئول بستن تگ </li> است. متد start_lvl و end_lvl برای باز و بسته کردن زیرمنو استفاده می‌شوند.

در ساختار منوی مگا، می‌توانید در start_lvl، کلاس‌های اضافی بر اساس depth اضافه کنید:

public function start_lvl( &$output, $depth = 0, $args = null ) {
    $indent = str_repeat( "	", $depth );
    $classes = array( "sub-menu" );
    if ( 0 === $depth ) {
        $classes[] = "mega-menu-columns";
    }
    $class_names = implode( " ", $classes );
    $output .= "
$indent<ul class="" . esc_attr( $class_names ) . "">
";
}

برای مطالعه بیشتر در مورد منوی مگا، راهنمای ساخت منو مگا در قالب را ببینید.

کنترل سطح تودرتویی با depth

پارامتر $depth در همه متدها در دسترس است. با استفاده از این پارامتر می‌توانید رفتار را در هر سطح متفاوت کنید.

Walker_Nav_Menu و ساخت منوی سفارشی

Walker_Nav_Menu پرکاربردترین کلاس مشتق‌شده از Walker است. با استفاده از آن، می‌توانید منوی سفارشی با ساختار HTML دلخواه بسازید.

الگوی حرفه‌ای این است که منطق منو را در یک کلاس اختصاصی کپسوله کنید و از فایل قالب فقط آن را فراخوانی کنید:

wp_nav_menu( array(
    "theme_location" => "primary",
    "walker"         => new WordPressKar_Walker(),
    "container"      => false,
    "menu_class"     => "primary-menu",
) );

برای مطالعه بیشتر، راهنمای تابع register_nav_menus و تابع wp_nav_menu را ببینید.

کنترل بخش‌های منو در قالب

در قالب‌های حرفه‌ای، منو معمولاً در فایل header.php قرار می‌گیرد. اما توصیه می‌کنم بخش منو را در یک partial جدا نگه دارید تا قابل استفاده مجدد باشد. راهنمای تابع get_template_part نقطه شروع مناسبی است.

Walker_Page و سفارشی‌سازی لیست برگه‌ها

Walker_Page برای نمایش لیست برگه‌ها استفاده می‌شود. این کلاس در wp_list_pages() استفاده می‌شود و امکان سفارشی‌سازی عمیق را می‌دهد.

ساختار مشابه Walker_Nav_Menu است، اما آیتم‌ها از نوع WP_Post هستند، نه از نوع Menu Item. این تفاوت در دسترسی به فیلدها مهم است.

class WordPressKar_Page_Walker extends Walker_Page {
    public function start_el( &$output, $page, $depth = 0, $args = array(), $current_page_id = 0 ) {
        $output .= "<li class="page-item depth-" . (int) $depth . "">";
        $output .= "<a href="" . esc_url( get_permalink( $page->ID ) ) . "">";
        $output .= esc_html( $page->post_title );
        $output .= "</a>";
    }
}

الگوی حرفه‌ای این است که در منوی فوتر یا منوی ثانویه، از Walker_Page سفارشی استفاده کنید تا کنترل کامل روی ساختار داشته باشید.

کاربرد در فهرست برگه‌های سلسله‌مراتبی

Walker_Page به‌صورت پیش‌فرض از ساختار سلسله‌مراتبی پشتیبانی می‌کند. اگر برگه والد و فرزند دارید، این کلاس ساختار را به‌درستی نمایش می‌دهد.

Walker_Category و لیست دسته‌بندی

Walker_Category برای نمایش لیست دسته‌بندی‌ها استفاده می‌شود. این کلاس در wp_list_categories() کاربرد دارد.

در ساختار پیش‌فرض، تعداد پست‌های هر دسته در کنار نام نمایش داده می‌شود. با بازنویسی start_el می‌توانید این ساختار را تغییر دهید:

class WordPressKar_Category_Walker extends Walker_Category {
    public function start_el( &$output, $category, $depth = 0, $args = array(), $id = 0 ) {
        $output .= "<li class="cat-item">";
        $output .= "<a href="" . esc_url( get_term_link( $category ) ) . "">";
        $output .= esc_html( $category->name );
        $output .= "</a>";
        if ( $category->count > 0 ) {
            $output .= " <span class="count">(" . (int) $category->count . ")</span>";
        }
    }
}

برای مطالعه بیشتر، راهنمای قالب تاکسونومی اختصاصی را ببینید.

فیلتر wp_list_categories و سفارشی‌سازی

فیلتر wp_list_categories امکان تغییر ساختار نهایی را می‌دهد. اما برای کنترل کامل، استفاده از Walker سفارشی توصیه می‌شود.

Walker_Comment و لیست دیدگاه‌ها

Walker_Comment برای نمایش لیست دیدگاه‌ها استفاده می‌شود. ساختار این کلاس پیچیده‌تر از سایر Walkerها است و به‌صورت پیش‌فرض از قالب HTML5 استفاده می‌کند.

در پروژه‌های حرفه‌ای، ممکن است نیاز به سفارشی‌سازی ساختار دیدگاه‌ها داشته باشید. برای این کار، از wp_list_comments() با پارامتر walker استفاده کنید.

wp_list_comments( array(
    "walker" => new WordPressKar_Comment_Walker(),
    "style"  => "ol",
) );

برای مطالعه بیشتر در مورد دیدگاه‌ها، راهنمای رفع خطای دیدگاه در وردپرس را ببینید.

ساختار HTML5 در Walker_Comment

اگر از تم پشتیبانی HTML5 استفاده می‌کنید، ساختار خروجی متفاوت است. توصیه می‌کنم از افزونه html5_comment در start_el استفاده کنید.

تست و دیباگ Walker Class

تست Walker در سه سطح انجام می‌شود: سطح ساختار HTML، سطح رفتار منطقی و سطح ریسپانسیو. برای ساختار، از اعتبارسنج W3C استفاده کنید. برای رفتار، تست واحد و E2E توصیه می‌شود.

add_filter( "wp_nav_menu_args", function( $args ) {
    if ( defined( "WP_DEBUG" ) && WP_DEBUG ) {
        error_log( "Walker class: " . get_class( $args["walker"] ) );
    }
    return $args;
} );

برای تست‌های خودکار، راهنمای تست E2E وردپرس با Playwright را ببینید.

اشتباهات رایج در تست Walker

اشتباه اول، تست فقط با یک ساختار منو. اشتباه دوم، نادیده گرفتن منوی خالی. اشتباه سوم، نبود تست با نام‌های طولانی. اشتباه چهارم، نبود تست ریسپانسیو. اشتباه پنجم، نبود مستندسازی ساختار.

عملکرد و کش Walker

Walker در هر بار رندر منو اجرا می‌شود. اگر تعداد آیتم‌ها زیاد باشد یا فیلد سفارشی متعدد داشته باشد، می‌تواند زمان رندر را افزایش دهد. استفاده از Object Cache یا Transient توصیه می‌شود.

برای مطالعه بیشتر، راهنمای Object Cache در وردپرس و Transients API را ببینید.

کش خروجی منو با Transient

در سایت‌های پربازدید، می‌توانید خروجی نهایی منو را با Transient کش کنید و در زمان تغییر منو، آن را پاک کنید:

function wordpresskar_get_menu_cached() {
    $cached = get_transient( "wpk_main_menu" );
    if ( false !== $cached ) {
        return $cached;
    }
    ob_start();
    wp_nav_menu( array( "theme_location" => "primary", "walker" => new WordPressKar_Walker() ) );
    $html = ob_get_clean();
    set_transient( "wpk_main_menu", $html, HOUR_IN_SECONDS );
    return $html;
}
add_action( "wp_update_nav_menu", function() {
    delete_transient( "wpk_main_menu" );
} );

امنیت و Escape در Walker

در Walker سفارشی، همه داده‌های خروجی باید Escape شوند. برای متن از esc_html، برای URL از esc_url، برای Attributes از esc_attr و برای محتوای HTML از wp_kses_post استفاده کنید.

برای مطالعه بیشتر، راهنمای Escape کردن خروجی برای جلوگیری از XSS را ببینید. همچنین مفهوم Walker را در ویکی‌پدیا مرور کنید.

دسترسی‌پذیری در Walker سفارشی

Walker سفارشی باید ساختار HTML معنایی و قابل دسترس تولید کند. برای مطالعه بیشتر، راهنمای ARIA در وردپرس و ناوبری با صفحه‌کلید را ببینید.

پرسش‌های پرتکرار درباره Walker Class

آیا Walker Class برای همه پروژه‌ها ضروری است؟

خیر، فقط برای پروژه‌هایی که به کنترل عمیق ساختار منو یا لیست نیاز دارند.

تفاوت Walker_Nav_Menu و Walker_Page چیست؟

Walker_Nav_Menu برای منوهای ثبت‌شده با register_nav_menus و Walker_Page برای فهرست برگه‌ها استفاده می‌شود.

آیا Walker سفارشی روی عملکرد سایت اثر دارد؟

در حالت عادی خیر، اما با تعداد آیتم زیاد یا فیلد سفارشی، کش توصیه می‌شود.

آیا می‌توان Walker سفارشی را در قالب بلاکی استفاده کرد؟

بله، اما Navigation Editor در قالب‌های بلاکی رفتار متفاوتی دارد و ممکن است نیاز به رویکرد دیگری داشته باشد.

چرا منوی سفارشی من اعمال نمی‌شود؟

احتمالاً پارامتر walker در wp_nav_menu تنظیم نشده یا کلاس اشتباه استفاده شده است.

آیا Walker سفارشی با Polylang و WPML کار می‌کند؟

بله، اگر از توابع ترجمه وردپرس استفاده کنید، سازگاری کامل دارد.

نتیجه و مسیر ادامه

Walker Class ابزار اصلی سفارشی‌سازی منو و لیست در وردپرس است. کلید موفقیت، درک متدهای start_el، end_el، start_lvl و end_lvl، رعایت امنیت در Escape خروجی، کش مناسب و تست در سه سطح است. اگر این لایه با دقت طراحی شود، ساختار منو و لیست در طول عمر پروژه پایدار باقی می‌ماند.

پیشنهاد می‌کنم مسیر یادگیری را با Custom Nav Walker برای منوی مگا ادامه دهید و سپس تابع get_template_part را در ساختار پروژه خود پیاده کنید.

اگر روی پروژه واقعی خود Walker Class سفارشی ساخته‌اید، برایم جالب است بدانید کدام بخش — بازنویسی start_el یا مدیریت ریسپانسیو — بیشترین چالش را ایجاد کرده است. تجربه خودتان را در دیدگاه‌ها بنویسید.