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; } }
问题根源分析
- 无限递归循环:
add_compressor_to_cart函数挂在woocommerce_add_to_cart钩子上,函数内部又调用$cart->add_to_cart(),该方法会再次触发woocommerce_add_to_cart钩子,导致无限递归,最终触发嵌套层级上限。 - 过滤器错误使用:
compressor_location_cart_field是过滤器钩子,但没有返回值,而是直接echo输出,违反了WordPress过滤器规则,会导致输出顺序混乱。 - 未处理异常场景:
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; }
关键修复点说明
- 解决递归问题:将添加关联产品的逻辑从
woocommerce_add_to_cart钩子换成woocommerce_before_add_to_cart_action,该钩子在主产品添加到购物车之前触发,不会因调用add_to_cart()产生递归。 - 修正过滤器用法:
compressor_location_cart_field改为返回修改后的标题,遵循WordPress过滤器的工作机制。 - 增强错误处理:在获取分类、标签、产品数据的场景中添加合法性校验,避免无效数据导致的报错。
- 优化选项值存储:下拉菜单选项仅存储产品ID,简化后续处理逻辑,避免之前拼接ID与meta导致的产品识别错误。
内容的提问来源于stack exchange,提问作者Deadpanprogram
相关产品推荐
相关产品推荐

