Custom Post Types in loop

Hello! What is the best practices to loop through custom post types?
I only make it work with this code:
@php
$args = array('post_type' => 'gallery','posts_per_page'=>'10');
$loop = new WP_Query($args);
@endphp
@while($loop->have_posts()) @php($loop->the_post())
@php(the_content())
@endwhile

Maybe there is right way to do this with blade?

And do we need add @php(wp_reset_postdata()) at the end of the loop?

Thank you guys!

We really need more information on what you’re trying to accomplish. If you’re listing posts on a Page, then this method works fine (though I would set up your $loop in a controller).

I’ve created new post type through cpt ui. Now in front-page.blade.php in the middle of the page i want to show items from this post type. Is this method is fine or there is more preferable way? And do i need to call wp_reset_posrdata if there eill be more same loops on the page?

It’s a good best practice to reset post data after a custom loop.

I’d recommend using a different variable than $loop here, since Blade uses $loop (for some pretty neat stuff) and you might see odd behavior if you’re overwriting it.

My personal opinion on best practices here is that you should extract as much logic as you can from your Blades to your controller (assuming you’re using soberwp/controller). I usually prefer to isolate custom queries completely by putting them in controller methods and then returning arrays that I iterate over in my Blades. The downside to that approach is that you don’t have access to functions like the_content() inside those loops, but for relatively trivial stuff IMO the pros usually outweigh the cons.

That might look like this:

// controllers/FrontPage.php
public function galleryLoop()
{
    $gallery_items = get_posts([
        'post_type' => 'gallery',
        'posts_per_page'=>'10',
    ]);

    return array_map(function ($post) {
        return apply_filters('the_content', $post->post_content);
    }, $gallery_items);
}

// views/front-page.blade.php
@foreach($gallery_loop as $gallery_item)
    {!! $gallery_item !!}
@endforeach

(Not 100% sure this code will work as written—I haven’t tested it—but it should provide a starting point.)

7 Likes

So this code is fine to make a cutom loop? I thought maybe sage has built in functions and i don’t know about them) just want make it right way

Okay, it is working) One question, if i want to return not only content, but the_post_thumbnail and maybe custom fields, how to do this? (sorry, couple days with sage, just learning…)

If you’re using my controller code, you’d just need to access those things in a way that allows you to access them directly instead of using functions that assume the availability of $post (as the_post_thumbnail() does). For WordPress content, many functions have a get_ variant that allows you to pass a post ID to specify the post you want to get data from. For instance, the_post_thumbnail() has get_the_post_thumbnail().

To use it, you could modify the code I posted to something similar to this following:

// controllers/FrontPage.php
public function galleryLoop()
{
    $gallery_items = get_posts([
        'post_type' => 'gallery',
        'posts_per_page'=>'10',
    ]);

    return array_map(function ($post) {
        return [
            'content' => apply_filters('the_content', $post->post_content),
            'thumbnail' => get_the_post_thumbnail($post->ID, 'large'),
        ];
    }, $gallery_items);
}

// views/front-page.blade.php
@foreach($gallery_loop as $gallery_item)
    {!! $gallery_item['content'] !!}
    {!! $gallery_item['thumbnail'] !!}
@endforeach

Custom fields you can access similarly, depending on what you’re using to create them. In general, they can be accessed with get_post_meta(). Some frameworks give you other functions to access the custom fields they create—i.e. ACF offers get_field().

10 Likes

Now it is working perfectly, thank you guys very much! I think it should be in the documentation…)

Hey man!

I’m kinda new with sage, and with blade, so I’m using this and is actually working great!

But i’m having a curiosity: based in this custom loop I need to return in the gallery item the categories in which my custom post is listed. Is there any way to do this with this code?

Yes, you’d just need to add a value to the array returned by the anonymous function inside array_map that contains your category information. Something like 'categories' => get_the_terms($post, 'whatever_your_taxonomy_is_called').

Hello. I just migrated from Sage8 to Sage9 and I am trying to understand Blade as much as I can and I love it so far. Now I have created a custom post type in my “app/setup.php” file and I added 10 posts in the backend. I now want to do a custom WP Query as I usually do but using Blade so I tried to add the following to “app/controllers/app.php”:

    public function projectsLoop()
{
    $projects_items = get_posts([
        'post_type' => 'ctp_projects',
        'posts_per_page'=>'6',
    ]);

    return array_map(function ($post) {
        return apply_filters('the_content', $post->post_content);
    }, $projects_items);
}

this of course is inside class App extends Controller {

and then inside “views/partials/homepage/homepage-projects.blade.php” I have added the following code:

        @foreach($projectsLoop as $project_item)
            {!! $project_item !!}
        @endforeach

I get this error:

Undefined variable: projectsLoop in /srv/www/michelec/current/web/app/uploads/cache/d8946328bcc3eb4c6204474ad8a86a6bd66b21d4.php on line 5

It’s worth to mention that I have done the following changes in this project which shouldn’t affect anything I assume:

I have created a custom “app-custom.blade.php” file inside the layouts folder which I have cloned from the original one and slightly changed to my needs:

<!doctype html>
<html @php(language_attributes())>
  @include('partials.head')
  <body @php(body_class())>
    @php(do_action('get_header'))
    @include('partials.header')
    <div class="wrap" role="document">
      <div class="content">
        <main class="main">
          @yield('content')
        </main>
      </div>
    </div>
    @php(do_action('get_footer'))
    @include('partials.footer')
    @php(wp_footer())
  </body>
</html>

Created a custom template for the homepage called “template-homepage.blade.php”:

@extends('layouts.app-custom')

@section('content')
  @while(have_posts()) @php(the_post())
    @include('partials.content-homepage')
  @endwhile
@endsection

And then inside the “partials” folder I have the “content-homepage.blade.php” that includes each and every section that I need to build the long homepage:

@include('partials.homepage.homepage-intro')
@include('partials.homepage.homepage-about')
@include('partials.homepage.homepage-projects')

Reading this post I am starting to understand how to create controllers but I have some questions. I see that there is a controller for FrontPage.php. What if I want to create a new controller just for my custom template (template-homepage.blade.php). Because I don’t have a “front-page.php” file I have put mine in “controllers/app.php”. Is that wrong?
Also I am going to add ACF soon so I might have some questions but I see that Advanced Custom Fields is already discussed in other topics so I will try to read them first after I solve this issue.

Sorry for the long post and thanks for the time.

Hi @michelecocuccio - try $projects_loop in your Blade template instead of $projectsLoop. Your controller method names are converted to snake case when they are made available in your templates.

  • Create methods within the Controller Class;
    • Use public function to return data to the Blade views/s
      • The method name becomes the variable name in Blade
      • Camel case is converted to snake case. public function ExampleForUser in the Controller becomes $example_for_user in the Blade template

Edit: regarding your second question, if you want to add a new controller for a certain template, just create a file in the controllers folder with the same name as the template (minus .blade). If your template is views/template-homepage.blade.php, then your controller would be controllers/template-homepage.php.

1 Like

I feel so stupid sometimes… Thank you very much and so sorry for this simple mistake. I didn’t noticed despite the fact that I knew about it.

Thank you very much!

1 Like

No problem! I also edited my post to respond to your second question (missed it at first).

1 Like

Hey thanks for the great explanations. Huge help.

Can the ACF get_field() usage be explained further?

I’ve got the CPT post title displaying nicely so I know things are working, but I’m struggling to get my ACF fields to show.

//controllers/FrontPage.php
...
'title' => apply_filters('the_title', $post->post_title),
'acf_field' => get_field('acf_field'),
...

// views/front-page.blade.php
@foreach($cpt_loop as $cpt_item)
    {!! $cpt_item['title'] !!}
    {!! $cpt_item['acf_field'] !!}
@endforeach

I’ve got ACF fields working elsewhere, just not in this CPT loop. Using Controller 2.0.1 and I’ve got the protected $acf = true; line up top as well, although not sure if this is related. Just trying to wrap my head around all this. Again, thanks so much :+1:

Edit: Came back to it and figured it out:

'acf_field' => get_field('acf_field', $post),

( Missing $post )

Thanks all for all the related questions and well written answers. This entire thread has all the information I was looking for.

I plan to use this to create four custom loops on the static homepage template (each one for a different custom post type or category), but none of the documentation that I’ve been able to find has covered the topic.

I’ll let you know if it works.

It worked. Thanks for all the good replies and documentation. Here is what I did for one of my custom loops. I basically created a loop for the “news” category like so:

In app.php (inside of Controller)

public function newsLoop()
  {
    $news_items = get_posts([
      'post_type' => 'post',
      'category_name' => 'news',
      'posts_per_page' => '3'
    ]);
    return array_map(function ($post) {
      $text = strip_shortcodes( $post->post_content );
      $text = apply_filters( 'the_content', $text );
      $text = str_replace(']]>', ']]&gt;', $text);
      $excerpt_length = apply_filters( 'excerpt_length', 55 );
      $excerpt_more = apply_filters( 'excerpt_more', ' ' . '[&hellip;]' );
      $text = wp_trim_words( $text, $excerpt_length, $excerpt_more );
      return [
        'title' => apply_filters('the_title', $post->post_title),
        'link' => '<a href="'.get_the_permalink($post->ID).'">',
        'excerpt' => $text,
      ];
  }, $news_items);
}

In partials folder:
I inserted the following into a UL element:
<ul class="list-group list-group-flush">
@foreach($news_loop as $news_item)
<li class="list-group-item">{!! $news_item['link'] !!}{!! $news_item['title'] !!}</a></li>
@endforeach
</ul>

Again, thanks.

1 Like

Hi, thanks for all this informations.

I have followed all this instructions for my custom post type and everything works well with my template… on local server.
But after uploading the files on my production server, the loop don’t displaying well anymore. I get the following WP_DEBUG message :

<b>Notice</b>:  Undefined variable: chantier_loop in <b>[...]/www/wp-content/uploads/cache/94dc7946fb3c7b085fa4250d7d8114045355fb05.php</b> on line <b>2</b><br>
<br>
<b>Warning</b>:  Invalid argument supplied for foreach() in <b>[...]/www/wp-content/uploads/cache/94dc7946fb3c7b085fa4250d7d8114045355fb05.php</b> on line <b>2</b>

I think all the required files have been uploaded :

├── app/
├── config/
├── dist/
├── resources/                 
│  ├── views/                  
│  ├── functions.php
│  ├── index.php
│  ├── screenshot.jpg
│  └── style.css
├── vendor/                    
└── LICENSE.md   

Can’t figure why this works well on my local server but not on my production server.
Any idea ?

I solved this problem putting my public function chantierLoop() in App.php instead of FrontPage.php (inside of Controller).
Still can’t figure why this public function in FrontPage.php works well on local server and not on production server.