Roots Discourse

Undefined var w/ sage @foreach, but not vanilla foreach()

Can somebody please help me understand why I can’t access a variable with a @foreach, but can with a vanilla foreach()?

For more context:

related.blade.php

@php
  if (!defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
  }
@endphp

@if ($related_products)

  <section class="related products">
    <h2>@php esc_html_e('Related products', 'woocommerce'); @endphp</h2>
    
      @php woocommerce_product_loop_start() @endphp

      {{-- this works --}}
      @php var_dump($related_products) @endphp

      {{-- this line breaks the page --}}
      @foreach ($related_products as $related_product)
      @endforeach
      
      {{-- but this works --}}
      <?php
        foreach ($related_products as $related_product){
          // 
        }
      ?>

      @php woocommerce_product_loop_end() @endphp
    
</section>

@endif
@php wp_reset_postdata() @endphp

1 Like

Strange. The @foreach is fine syntactically–can you share your controller method or data filter where you’re setting up $related_products?

Also, unless you have a special reason, you shouldn’t need to include this in your templates:

@php
  if (!defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
  }
@endphp

I’m not actually setting it up at all, which is likely what the problem is… but it still leads to the confusing fact that I can access it with vanilla php.

related.blade.php is just a copy of this woocommerce template:

<?php
/**
 * Related Products
 *
 * This template can be overridden by copying it to yourtheme/woocommerce/single-product/related.php.
 *
 * HOWEVER, on occasion WooCommerce will need to update template files and you
 * (the theme developer) will need to copy the new files to your theme to
 * maintain compatibility. We try to do this as little as possible, but it does
 * happen. When this occurs the version of the template file will be bumped and
 * the readme will list any important changes.
 *
 * @see 	    https://docs.woocommerce.com/document/template-structure/
 * @author 		WooThemes
 * @package 	WooCommerce/Templates
 * @version     3.0.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( $related_products ) : ?>

	<section class="related products">

		<h2><?php esc_html_e( 'Related products', 'woocommerce' ); ?></h2>

		<?php woocommerce_product_loop_start(); ?>

			<?php foreach ( $related_products as $related_product ) : ?>

				<?php
				 	$post_object = get_post( $related_product->get_id() );

					setup_postdata( $GLOBALS['post'] =& $post_object );

					wc_get_template_part( 'content', 'product' ); ?>

			<?php endforeach; ?>

		<?php woocommerce_product_loop_end(); ?>

	</section>

<?php endif;

wp_reset_postdata();

I’m not sure where $related_products is being set up (woocommerce confuses the shit out of me), but it was set up somewhere.

1 Like

Pretty strange. Unfortunately, I don’t think I know enough about Woocommerce to be very helpful here. Maybe @mejta or @hambos22 have run into something similar–they’ve both done good work integrating Woocommerce with Sage 9.

One of the best tools for debugging Blade problems is to look at the files that Blade generates when compiling templates. They’re usually in wp-uploads/cache unless the config has been changed. All Blade really does with @foreach() or whatever is parse them as markup to generate actual PHP—which is what you’ll find in those rendered files in wp-uploads/cache.

Does your first post reflect your exact code, i.e. is your foreach loop empty? Possible that Blade has some kind of optimization/cleanup thing that freaks out over an empty foreach loop.

1 Like

Ah okay, thanks for pointing out the cache files… I could definitely see them helping for any future issues.

As for my example, this is now how it is:


{{--
@see 	    https://docs.woocommerce.com/document/template-structure/
@author   WooThemes
@package  WooCommerce/Templates
@version  3.0.0
--}}

{{-- NOTE: only vanilla php works for the loop for some reason... --}}

@if ($related_products)

	<section class="related products">
    <h2>@php esc_html_e('Related products', 'woocommerce'); @endphp</h2>
    
    <div class="row">
      @php
        foreach ($related_products as $related_product) {
          $post_object = get_post($related_product->get_id());
          setup_postdata($GLOBALS['post'] =& $post_object);
          wc_get_template_part('content', 'product');
        }
      @endphp

      {{-- @foreach ($related_products as $related_product)
        @php
          $post_object = get_post($related_product->get_id());
          setup_postdata($GLOBALS['post'] =& $post_object);
          wc_get_template_part('content', 'product');
        @endphp
      @endforeach --}}
    </div>
    
	</section>

@endif
@php wp_reset_postdata() @endphp

The uncommented stuff is all working, and the commented out stuff is throwing a Call to a member function addLoop() on null error.

Good suggestion, @alwaysblank. Not sure if it gets us anywhere, but here’s what the empty look from the initial example compiles to (cleaned up for readability):

<?php
$__currentLoopData = $related_products;
$__env->addLoop($__currentLoopData);

foreach($__currentLoopData as $related_product):
  $__env->incrementLoopIndices();
  $loop = $__env->getLastLoop();
  echo e($related_product);
endforeach;

$__env->popLoop();
$loop = $__env->getLastLoop();
?>

So yeah, there’s plenty there that’s added by Blade.

@shaneparsons, I think $__env is supposed to be an instance of Illuminate\View\Factory and basically have a lot of data and functionality that Blade needs to work. For some reason, it’s not being defined when your template is loaded.

Re this post Why can't I foreach my var?, how is your related products Blade partial being loaded? For example, is it being called via @include from a full Blade template? By that I mean a template that corresponds to a location in the WordPress template hierarchy, like single-product.blade.php.

1 Like

Hey, the problem is, that templates are called without context and therefor there is no $__env variable defined. Please put the following at the beginning of the template:

@php
$__env = App\sage(‘blade’);
@endphp

With that everything will work.

3 Likes

Thanks @mejta! Do you know if that only occurs when a Blade partial is overriding a Woocommerce partial without being called from a parent template that is also in Blade?

E.g., this would cause the problem (ignore the fake paths):

wp-content/plugins/woocommerce/templates/single-product.php template loading resources/views/woocommerce/partials/related.blade.php

But this would work:

resources/views/woocommerce/single-product.blade.php template loading resources/views/woocommerce/partials/related.blade.php partial

1 Like

The main issue is with filter wc_get_template. We cannot simply render the template, because the filter is used in get_theme_info() function and it expects file path, not the output. If the compiled template is included directly and not with Blade’s render function, there is not defined $__env variable, therefore we have to define this variable. I will try to find a way to recognise when the filter is called from get_theme_info function and when it’s used for rendering and try to implement different behaviour for those different situations.

Filter wc_get_template_part can render output directly because the result of the filter is used for render only. I will update the package to do that.

Filter template_include is processed by sage and there is render function used, so any template that goes through this filter works as expected.

There is some work that needs to be done. As a workaround we can use the following code in the blade templates:

@php
$__env = App\sage(‘blade’);
@endphp
2 Likes

The main difference is if the template (or parent template) goes through Blade’s render function. If it does, everything work.

2 Likes

I’ve created a pull request that solves the bug:

Please merge that and increase the package version.

2 Likes

Thanks for all your help @mmirus and @mejta!

1 Like

I’ve released Sage WooCommerce 1.0.1 with this fix. Thanks @mejta!

1 Like

I think I have the exact same problem, but mine is with a custom woocommerce endpoint that I created.

I have created an endpoint like this;
This is written in setup.php file

function krenovate_add_group_classes_endpoint()
{
    add_rewrite_endpoint('group-classes', EP_ROOT | EP_PAGES);
}
add_action('init', __NAMESPACE__.'\\krenovate_add_group_classes_endpoint');


function krenovate_group_classes_query_vars($vars)
{
    $vars[] = 'group-classes';
    return $vars;
}
add_filter('query_vars', __NAMESPACE__.'\\krenovate_group_classes_query_vars', 0);


function krenovate_add_group_classes_link_my_account($items)
{
    $items['group-classes'] = 'Group Classes';
    return $items;
}
add_filter('woocommerce_account_menu_items', __NAMESPACE__.'\\krenovate_add_group_classes_link_my_account');


function krenovate_group_classes_content()
{
  include \App\template_path(locate_template('woocommerce/myaccount/group-classes.blade.php'));
}

add_action('woocommerce_account_group-classes_endpoint', __NAMESPACE__.'\\krenovate_group_classes_content');

Now my endpoint renders all the PHP stuff fine but it is missing all the blade properties.

Every time I put a foreach loop

    @php
      $Classes = \App\GroupClass::getGroupClasses();
    @endphp
    @if($Classes)
      @foreach($Classes as $class)
      @endforeach
    @endif

Call to a member function addLoop() on null (View: /var/www/html/wp-content/themes/cfi-theme/resources/views/woocommerce/myaccount/my-account.blade.php) (View: /var/www/html/wp-content/themes/cfi-theme/resources/views/woocommerce/myaccount/my-account.blade.php) (View: /var/www/html/wp-content/themes/cfi-theme/resources/views/woocommerce/myaccount/my-account.blade.php)

Now this doesn’t happen in my-account page. I have overridden it with my blade template file.

The one fix mentioned to add this

@php
$__env = App\sage(‘blade’);
@endphp

by @mejta throws this error

Class sage.‘blade’ does not exist (View: /var/www/html/wp-content/themes/cfi-theme/resources/views/woocommerce/myaccount/my-account.blade.php) (View: /var/www/html/wp-content/themes/cfi-theme/resources/views/woocommerce/myaccount/my-account.blade.php) (View: /var/www/html/wp-content/themes/cfi-theme/resources/views/woocommerce/myaccount/my-account.blade.php)

This error is thrown on both the custom endpoint and my account page.

I think its the way I added the endpoint which is creating all the issue, but I don’t know what. Pls help.