The right way to run multiple WordPress loops

Hello everyone, I am new to sage 9 and looking for some guidance.
I’m doing a basic project to get acclimated to the changes in sage9. I’ve create a links custom post type, given it some fields with cmb2. I also gave it categories using WordPress’s taxonomies, set up a template to query each and whipped up some quick Bootstrap 4 cards in blade to display them.

sage9 made the whole process a dream.

controlers/page-link.php

<?php // @ app/controllers/page-link.php

namespace App;

use Sober\Controller\Controller;

class Link extends Controller
{
    public static $prefix = '_link_';

    public function linkCategories()
    {
        $categories_array = get_terms([
            'taxonomy' => 'link_category',
            'hide_empty' => false,
         ]);

        return $categories_array;
    }

    public function linkId()
    {
        return get_the_ID();
    }

    public function linkURL()
    {
        return get_post_meta(get_the_ID(), self::$prefix . 'url', true);
    }

    public function linkUrlSecondary()
    {
        return get_post_meta(get_the_ID(), self::$prefix . 'url_secondary', true);
    }
    public function linkTextSecondary()
    {
        return get_post_meta(get_the_ID(), self::$prefix . 'text_secondary', true);
    }
}

vies/page-link.blade.php

We get the categories from a variable passed by the controller

@extends('layouts.app')

@section('content')

  @foreach ($link_categories as $category)
    @include('partials.link-category', ['category' => $category])
  @endforeach

@endsection

Link Category Partial

Runs once per category. Displays links in that category

@php
$links = new WP_Query([
  'post_type' => 'link',
  'orderby' => 'title',
  'order' => 'ASC',
  'nopaging' => true,
  'tax_query' => [
    [
      'taxonomy' => 'link_category',
      'field' => 'slug',
      'terms' => $category->slug,
    ],
  ],
]);

@endphp

<div class="link-category">
  <h2>{{$category->name}}</h2>
  <p>{{$category->description}}</p>
  @while($links->have_posts()) @php $links->the_post() @endphp
    @include('partials.link-card')
  @endwhile
</div>

Link Card

Simply displays a single link

<div class="card" style="width: 18rem;">
  <img class="card-img-top" src="..." alt="Card image cap">
  <div class="card-body">
    <h5 class="card-title">{{ get_the_title() }}</h5>
    <p class="card-text">{{ get_the_content() }}</p>
    <a href="{{$link_url}}" class="btn btn-primary">Visit Site</a>
    @if ($link_url_secondary)
      <a href="{{ $link_url_secondary }}" class="btn btn-secondary">{{ $secondary_link_text }}</a>
    @endif
  </div>
</div>

So here is the issue:
The $link_url and all other link variables are empty, because when the controller runs get_the_id() returns the id of the links page, Not the id I need, which is the id of the link post type.

Now there are 2 ways I can go about this.

Make functions static and call them within the loop

Requires minimal changes and keeps things intuitive.
In the controler:

    public static function linkURL()
    {
        return get_post_meta(get_the_ID(), self::$prefix . 'url', true);
    }

In the template:

{{ Link::linkUrl() }}

Run all queries in controller

This would be the textbook answer. We remove all logic from our views and handle everything in the controller, then we pass that information to the views as variables.
Somehow this feels like the messier answer because I would be working against WordPress rather than with it.

What are your thoughts? How would you go about doing this?

I have increasingly been leaning heavily toward the “run all logic in the controller, pass only data to Blades” approach. It “feels better” but it can also make it much easier to track down logic issues, since you need only look through your controllers (your views can’t be the source of logic errors if they run no logic). It takes a little more effort initially, but I generally feel much more comfortable when I compile data in my controllers and pass my Blades only what they need. This also has the effect of making Blades much more readable.

In general, I don’t pass the raw results of WordPress queries directly to my Blades, expect if those queries are very simple (i.e. a title). For instance, the controller for my blog index processes the posts appearing on the archive like this (I’m using Sage’s filters for my controller instead of soberwp/controller, but the principle is more or less the same):

    /** @var $wp_query \WP_Query */
    global $wp_query;

    $data['posts'] = false;
    if ($wp_query->post_count > 0) {
        $data['posts'] = array_map(function ($post) {
            /** @var $post \WP_Post */
            $video = \Samrap\Acf\Acf::field(KEY__FEATURED_VIDEO, $post)->default(false)->get();
            return array(
                'image'   => wp_get_attachment_image(
                    get_post_thumbnail_id($post->ID),
                    'post_featured_image_400',
                    false,
                    array(
                        'class' => 's-news__archive__postImage'
                    )
                ),
                'title'   => $post->post_title,
                'date'    => date_format(DateTime::createFromFormat('Y-m-d H:i:s', $post->post_date), 'M. j, Y'),
                'excerpt' => Murmur\WP\Excerpt::getByCharacters($post, 150, '...'),
                'link'    => array(
                    'url'   => get_permalink($post->ID),
                    'text'  => 'Read More',
                    'class' => 's-news__archive__postLink',
                ),
                'video'   => $video,
            );
        }, $wp_query->posts);
    }

My archive.blade.php looks like this:

@extends('layouts.app')

@section('content')
    @if($posts)
        <ul class="s-news__archive__postList t-clear-list">
            @foreach($posts as $post)
                @include('partials.news.index-post-block', $post)
            @endforeach
        </ul>
    @endif
@endsection

And the partial it calls looks like this:

<li class="s-news__archive__post">
    <a href="{{$link['url']}}" class="s-news__archive__postImageWrapper o-imageBlock">
        {!! $image !!}
        @if($video)
            <div class="o-imageBlock__playIcon">
                <svg class="a-icon" width="70" height="70">
                    <use xlink:href="#play-icon"></use>
                </svg>
            </div>
        @endif
    </a>

    <div class="s-news__archive__postContent">
        <h3 class="s-news__archive__postHeading">{{$title}}</h3>
        <div class="s-news__archive__postMeta style-emphasis">Posted {{$date}}</div>
        <div class="s-news__archive__postExcerpt">{!! $excerpt !!}</div>
        @include('partials.m-arrowLink', $link)
    </div>
</li>

I feel like this approach is the most in-line with how Sage/Blade “wants” you to go at it, because it has a clear separation between views and logic, and your data is clear and descriptive.

15 Likes

I agree that this is the way to do it. Ruby on Rails has done it like this since the beginning of time. I found WP really messy after working in RoR. Sage cleans up the mess. Logic does not belong in the views.

2 Likes

I’ve been trying his as well as custom loop variables but keep running into issues with pagination (404s on page 2+). I don’t suppose you have this example or similar with pagination setup in the controller and a pagination nav in the template ? @alwaysblank.

thanks.