You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WooCommerce添加购物车触发‘最大函数嵌套层级256’错误

致命错误:函数嵌套层级超过256的解决方案(WooCommerce插件开发)

问题描述

我正在开发一款WooCommerce插件,核心需求:

  • 将「Compressor Kits」品类的产品拉取到「Complete A/C Systems」品类的产品页面下拉菜单中
  • 点击添加购物车时,同时将主产品和选中的关联产品加入购物车
  • 主产品SKU根据选中的关联产品变更,并同步到购物车、结账页及订单页

但点击添加购物车时触发致命错误:

Fatal error: Uncaught Error: Maximum function nesting level of '256' reached, aborting! in /Local Sites/woocommerce-test-site/app/public/wp-includes/option.php on line 81

对应代码行:

if ( is_scalar( $option ) ) {
   $option = trim( $option );
}

原始插件代码

基础功能代码

function get_current_category() {
    global $post;

    // Initialize array for product categories
    $current_cats = array();
    $cat_terms = get_the_terms($post->ID, 'product_cat');

    // Get main product category names
    foreach ($cat_terms as $term) {
        $product_cat_name = $term->name;
        // Assign category names to array
        $current_cats[] = $product_cat_name;
    }
    return $current_cats;
}

// Display Compressor Location Field in 'Compressor Kits'  
add_action('woocommerce_product_options_general_product_data', 'add_compressor_location');
function add_compressor_location() {
    $current_cats = get_current_category();

    if (in_array('Compressor Kits', $current_cats)) {
        woocommerce_wp_select(
            array(
                'id' => 'compressor_location',
                'label' => __('Compressor Location', 'woocommerce'),
                'options' => array(
                    '' => __('choose', 'woocommerce'),
                    'L8' => __('L8', 'woocommerce'),
                    'L6' => __('L6', 'woocommerce'),
                    'R8' => __('R8', 'woocommerce'),
                    'R6' => __('R6', 'woocommerce')
                ),
            )
        );
    }
}

// Save Compressor Location 
add_action('woocommerce_process_product_meta', 'save_compressor_location');
function save_compressor_location($post_id) {
    $compressor_location_select = $_POST['compressor_location'];
    update_post_meta($post_id, 'compressor_location', esc_attr($compressor_location_select));
}

add_action('woocommerce_before_add_to_cart_button', 'compressor_kit_dropdown');
function compressor_kit_dropdown() {
    $domain = 'woocommerce';
    $current_cats = get_current_category();

    if (in_array('Complete A/C Systems', $current_cats)) {
        // create array of category products
        $args = array(
            'category'      => 'compressor-kits',
            'limit'         => -1,
        );

        $comp_product_array = wc_get_products($args); // Retrieve products

        // Check if category products array is not empty 
        if (!empty($comp_product_array)) {

            // Loop thru product array
            foreach ($comp_product_array as $products) {

                // Initialize array for product tags
                $tags_array =  array();
                $product_tags = wp_get_post_terms($products->get_id(), 'product_tag', array('fields' => 'names')); // Get array of Product tags 
                $tags_array = $product_tags; // Assign product tags to array    

                if (array_intersect($tags_array, $current_cats)) {

                    $product_id = $products->get_id(); // Get product ID
                    $product_price = " +$" . $products->get_regular_price(); // Get product price
                    $product_name = $products->get_name(); // Get product name
                    $product_sku = $products->get_sku(); // Get product SKU
                    $product_meta = $products->get_meta('compressor_location'); // Get product meta

                    $options[$product_id . $product_meta] = $product_name . ' - ' . $product_meta; // Display product as name + price
                }
            }

            $options = array(
                0 => __('Select an option...', 'woocommerce'),
                1 => __('No Compressor & Bracket ', 'woocommerce')
            ) + $options;

            // Add select field
            woocommerce_form_field('compressor-options', array(
                'type'          => 'select',
                'label'         => __('Add Compressor & Bracket', $domain),
                'required'      => false,
                'options'       => $options,
            ), '');
            echo '<p>Number of Compressor & Bracket options: ' . count($options) . '</p>';
        }
    }
}

add_action( 'woocommerce_add_to_cart', 'add_compressor_to_cart', 10, 6 );
function add_compressor_to_cart() {
    if ( isset( $_POST['compressor-options'] ) ) {
        // Get product ID
        $the_product_id = sanitize_text_field( $_POST['compressor-options'] );

        // WC Cart
        if ( WC()->cart ) {
            // Get cart
            $cart = WC()->cart;

            // If cart is NOT empty
            if ( ! $cart->is_empty() ) {
                // Cart id
                $product_cart_id = $cart->generate_cart_id( $the_product_id );

                // Find product in cart
                $in_cart = $cart->find_product_in_cart( $product_cart_id );

                // If product NOT in cart
                if ( ! $in_cart ) {
                    $cart->add_to_cart( $the_product_id );
                }
            } else {
                $cart->add_to_cart( $the_product_id );
            }
            return $the_product_id;
        }
    }
}

自定义字段与SKU同步代码

add_filter('woocommerce_add_cart_item_data', 'add_compressor_location_data', 10, 3);
function add_compressor_location_data($cart_item_data, $product_id, $variation_id) {
    $compressor_option = filter_input(INPUT_POST, 'compressor-options');

    if (! empty($compressor_option) ) {
        $cart_item_data['compressor-options'] = substr($compressor_option, -2);
        return $cart_item_data;
    }

    return $cart_item_data;
}


add_filter('woocommerce_cart_item_name', 'compressor_location_cart_field', 10, 2);
function compressor_location_cart_field($title, $cart_item) {
    global $product;

    $product = $cart_item['data'];

    if (is_cart() || is_checkout() && $product->get_sku()) {

        if (!empty($cart_item['compressor-options'])) {
            $compressor_location = $cart_item['compressor-options'];
        }

        $sku = '<br/>SKU: ' . $product->get_sku();

        echo $title;
        echo $sku . $compressor_location;
    }

}

问题根源分析

  1. 无限递归循环:add_compressor_to_cart函数挂在woocommerce_add_to_cart钩子上,函数内部又调用$cart->add_to_cart(),该方法会再次触发woocommerce_add_to_cart钩子,导致无限递归,最终触发嵌套层级上限。
  2. 过滤器错误使用:compressor_location_cart_field是过滤器钩子,但没有返回值,而是直接echo输出,违反了WordPress过滤器规则,会导致输出顺序混乱。
  3. 未处理异常场景:get_current_category中未处理get_the_terms返回false或WP_Error的情况,当产品无分类时会报错。

修复后的完整代码

基础功能修复版

function get_current_category() {
    global $post;
    $current_cats = array();

    // 校验post对象合法性
    if (!isset($post) || !is_a($post, 'WP_Post') || $post->post_type !== 'product') {
        return $current_cats;
    }

    $cat_terms = get_the_terms($post->ID, 'product_cat');
    // 处理函数返回异常
    if (!is_wp_error($cat_terms) && $cat_terms) {
        foreach ($cat_terms as $term) {
            $current_cats[] = $term->name;
        }
    }
    return $current_cats;
}

// Display Compressor Location Field in 'Compressor Kits'  
add_action('woocommerce_product_options_general_product_data', 'add_compressor_location');
function add_compressor_location() {
    $current_cats = get_current_category();

    if (in_array('Compressor Kits', $current_cats)) {
        woocommerce_wp_select(
            array(
                'id' => 'compressor_location',
                'label' => __('Compressor Location', 'woocommerce'),
                'options' => array(
                    '' => __('choose', 'woocommerce'),
                    'L8' => __('L8', 'woocommerce'),
                    'L6' => __('L6', 'woocommerce'),
                    'R8' => __('R8', 'woocommerce'),
                    'R6' => __('R6', 'woocommerce')
                ),
            )
        );
    }
}

// Save Compressor Location 
add_action('woocommerce_process_product_meta', 'save_compressor_location');
function save_compressor_location($post_id) {
    if (isset($_POST['compressor_location'])) {
        $compressor_location_select = sanitize_text_field($_POST['compressor_location']);
        update_post_meta($post_id, 'compressor_location', $compressor_location_select);
    }
}

add_action('woocommerce_before_add_to_cart_button', 'compressor_kit_dropdown');
function compressor_kit_dropdown() {
    $domain = 'woocommerce';
    $current_cats = get_current_category();

    if (in_array('Complete A/C Systems', $current_cats)) {
        $args = array(
            'category'      => 'compressor-kits',
            'limit'         => -1,
        );

        $comp_product_array = wc_get_products($args);
        $options = array();

        if (!empty($comp_product_array)) {
            foreach ($comp_product_array as $product) {
                $product_tags = wp_get_post_terms($product->get_id(), 'product_tag', array('fields' => 'names'));
                if (!is_wp_error($product_tags) && array_intersect($product_tags, $current_cats)) {
                    $product_id = $product->get_id();
                    $product_meta = $product->get_meta('compressor_location');
                    // 仅存储产品ID作为选项值,避免后续识别错误
                    $options[$product_id] = sprintf('%s - %s', $product->get_name(), $product_meta);
                }
            }
        }

        // 合并默认选项
        $options = array(
            0 => __('Select an option...', 'woocommerce'),
            1 => __('No Compressor & Bracket ', 'woocommerce')
        ) + $options;

        woocommerce_form_field('compressor-options', array(
            'type'          => 'select',
            'label'         => __('Add Compressor & Bracket', $domain),
            'required'      => false,
            'options'       => $options,
        ), '');
        echo '<p>Number of Compressor & Bracket options: ' . count($options) . '</p>';
    }
}

// 替换钩子避免递归
add_action('woocommerce_before_add_to_cart_action', 'add_compressor_to_cart', 10);
function add_compressor_to_cart() {
    if (!isset($_POST['compressor-options']) || $_POST['compressor-options'] <= 1) {
        return;
    }

    $the_product_id = (int)sanitize_text_field($_POST['compressor-options']);
    if (!$the_product_id || !wc_get_product($the_product_id)) {
        return;
    }

    $cart = WC()->cart;
    if (!$cart) {
        return;
    }

    $product_cart_id = $cart->generate_cart_id($the_product_id);
    $in_cart = $cart->find_product_in_cart($product_cart_id);

    if (!$in_cart) {
        $cart->add_to_cart($the_product_id);
    }
}

自定义字段与SKU显示修复版

add_filter('woocommerce_add_cart_item_data', 'add_compressor_location_data', 10, 3);
function add_compressor_location_data($cart_item_data, $product_id, $variation_id) {
    $compressor_option = filter_input(INPUT_POST, 'compressor-options', FILTER_SANITIZE_NUMBER_INT);
    if ($compressor_option && $compressor_option > 1) {
        $comp_product = wc_get_product($compressor_option);
        if ($comp_product) {
            $cart_item_data['compressor-options'] = $comp_product->get_meta('compressor_location');
        }
    }
    return $cart_item_data;
}

add_filter('woocommerce_cart_item_name', 'compressor_location_cart_field', 10, 2);
function compressor_location_cart_field($title, $cart_item) {
    if ((is_cart() || is_checkout()) && !empty($cart_item['compressor-options'])) {
        $sku = $cart_item['data']->get_sku();
        if ($sku) {
            $title .= sprintf('<br/>SKU: %s%s', $sku, $cart_item['compressor-options']);
        }
    }
    // 必须返回修改后的标题,符合过滤器规则
    return $title;
}

关键修复点说明

  1. 解决递归问题:将添加关联产品的逻辑从woocommerce_add_to_cart钩子换成woocommerce_before_add_to_cart_action,该钩子在主产品添加到购物车之前触发,不会因调用add_to_cart()产生递归。
  2. 修正过滤器用法:compressor_location_cart_field改为返回修改后的标题,遵循WordPress过滤器的工作机制。
  3. 增强错误处理:在获取分类、标签、产品数据的场景中添加合法性校验,避免无效数据导致的报错。
  4. 优化选项值存储:下拉菜单选项仅存储产品ID,简化后续处理逻辑,避免之前拼接ID与meta导致的产品识别错误。

内容的提问来源于stack exchange,提问作者Deadpanprogram

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 18:54:24