warpig's avatar
Level 12

Appending Query String Values

Having some issues understanding:

https://laravel.com/docs/8.x/pagination#appending-query-string-values

Steps to reproduce

  1. You see a post you like on the main page and click on the title
  2. An error is shown:

Undefined variable: posts in Posts.Index

The way im trying to set up for the append method in my routes is like this:

Route::get('posts/{tag}', function () {
    $tag = Tag::simplePaginate()->withQueryString();

    $tag->appends(['tag' => 'tag']);

    return view('posts.show');
});

Associate the tag model with the simplePaginate() method and withQueryString(), maybe im not using the appends properties the right way? In the Post model that I have I also have a tags(), the relationship it has with it is a, many to many, but I don't know how this is relatable when one is trying to append something in the URL because I don't know which model to use.

I want to paginate my index of posts and this would work fine if I didn't had any tags associated, but since I do, whenever I try to use the links() method I get an error saying that links() does not exist in this collection.

Using the appends inside the view like this:

  <span> {{ $posts->appends[('tag' == $tag)]->links() }} </span>
0 likes
16 replies
MarianoMoreyra's avatar

Hi @warpig

I'm sorry but I'm not understanding your question.

On one side, the error says:

Undefined variable: posts in Posts.Index

but on the other side, you show a route that returns the posts.show view and not the Posts.Index:

return view('posts.show');

So I'm not sure if the error is related to the appends or is occurring somewhere else.

Also, it's not clear what the final result should be...

Hope you can give some more insights so I can try to help!

1 like
warpig's avatar
Level 12

Hello @marianomoreyra I know and im sorry, I was going to write a longer post but I found out that I was confused about a few other things, like which models to use in the routes and I didn't know if the appends had any syntax error both in the routes or in the view.

The main view where the paginator exists is in the posts.index.

There is already a tags()in Post model, and well of course I have Tag model. but on the routes, which one do you reference? Could be convenient to use the Post since we're talking about sorting posts (but that may as well be the topic for another day, if maybe that is relatable to "best practices")

The problem at hand is this: how to use the paginator that exists in this view and still be able to have a string in the URL, in this case "tag"?

Well why did this became a problem in the first place? I think because Im displaying the paginator inside the index view and whenever I want to sort the posts by clicking on a given tag, the exceptions begin to emerge.

For example by adding this inside posts.index:

<span> {{ $posts->links() }} </span>

And after I've clicked on a post to see it's content, and select the given tag to view other posts that might have this tag also, the exception being thrown is this: http://flareapp.io/share/x7KKnKB7

Next thing I did was going over to my routes and then making a GET request for this given URL: http://my-blog.test/posts?tag=webdev So I did my best to extract it, the error could also be found here though, im not sure:

Route::get('posts/{tag}', function () {
    $tag = Tag::simplePaginate()->withQueryString();

    $tag->appends(['name' => 'tag']);

    return view('posts.index', ['tag' => $tag]);
});

And well this is the beginning of another journey through Laravel :-) Thanks! :D

EDIT - I realize now I might've overlooked the fact that I included the wrong view under the routes. (!!)

warpig's avatar
Level 12

Still doesn’t resolve it :/

MarianoMoreyra's avatar

@warpig I'm sorry but I'm still a little bit confused...

Are you displaying Posts or Tags? In case you are displaying (and hence, paginating) Posts, I believe you should have the Post in the Route parameter, but most importantly, you should be doing the simplePaginate on your Post model instead of Tag.

whenever I want to sort the posts by clicking on a given tag, the exceptions begin to emerge.

I don't see how you might be ordering based on a selected Tag...or do you mean you are filtering the results to show only the Posts that have the selected Tag?

Now from the error shared on flare...it seems to me that you might be having different routes that catch by mistake the URL you are trying to access...

Also, you are looping over a $posts variable and doing $posts->links() but this code is not sending $posts to that view, but $tags instead:

Route::get('posts/{tag}', function () {
    $tag = Tag::simplePaginate()->withQueryString();

    $tag->appends(['name' => 'tag']);

    return view('posts.index', ['tag' => $tag]);
});

If you want/can please share the rest of the routes involved with Posts and Tags to see if there is any definitions colliding.

warpig's avatar
Level 12

I am displaying Posts but i also have the tags in the same view like this: https://imgur.com/F6fi5eD so yes I am sort of filtering posts with a given tag. Once the reader is done with the post, they can click on the tags so any posts associated with one can be showed. Thats basically what this page has: posts.show

Regarding the routes, yes here goes:

Route::get('posts', 
    [PostsController::class, 'index']
);

Route::get('/posts/category', function () {
    return view('posts.category', [
        'posts' => App\Models\Post::latest()->get()
    ]);
});

Route::resource('posts', PostsController::class)
    ->except('index', 'show')
    ->middleware('auth');

Route::get(
    'posts/{slug}',
    [PostsController::class, 'show']
);

This is my controller:

    public function index(Category $category)
    {   
        if (request('tag')) {
        $post = Tag::where('name', request('tag'))
                ->firstOrFail()
                ->posts;
    } else {
        $post = Post::latest()->simplePaginate(5);
        $category = Category::select('image_url')->get();
    }   
        return view('posts.index', [
            'posts' => $post,
            'category' => $category
        ]);
    }

And I believe this is where they merge the 2 things. But back on the view when you click on a tag it throws the exception. The way this is set up I can view the index but once you click on a tag it throws an exception about appends not exisiting in this collection: http://flareapp.io/share/dmkzgnb5

<span> {{ $posts->appends([$post])->links() }} </span>

    <div class="single-post">
        <img src="{{asset('/storage/img/post_uploads/'.$post->image_url) }}" alt="postimage"/> 
        <h1 class="header-post"> {{ $post->title }} </h1>
            {!! $post->body !!}
        <p class="space-tags"> Tags:
            @foreach ($post->tags as $tag)
                <a href="/posts?tag={{ $tag->name }}"> 
                    {{ $tag->name }}
                </a>
            @endforeach
        </p>
        <div>
            Category:
            <span style="color:#87ceeb;">
                        {{ $post->category->name }}
                </span>
        </div>
        <x-navalt/>
    </div> <!-- Single post end -->
MarianoMoreyra's avatar
Level 25

@warpig

Well...first of all, this two routes are basically the same for Laravel:

Route::get(
    'posts/{slug}',
    [PostsController::class, 'show']
);

Route::get('posts/{tag}', function () {
    $tag = Tag::simplePaginate()->withQueryString();

    $tag->appends(['name' => 'tag']);

    return view('posts.index', ['tag' => $tag]);
});

Since Laravel doesn't have a way to determine when you are passing a slug or a tag as a parameter. So basically, it will always go to the route that comes first in your routes file. I will take a guess and say that the 'posts/{slug}' one comes first...

Anyway, I believe the error that you've shared on flare saying that $posts doesn't have a ::links method, it has to be with the if on your index method from Controller...as you are not paginating there: Maybe this will work:

    public function index(Category $category)
    {   
        if (request('tag')) {
        $post = Tag::where('name', request('tag'))
                ->firstOrFail()
                ->posts()      // it needs the () otherwise you'll keep getting that error
                ->simplePaginate(5); // <--- this was missing
    } else {
        $post = Post::latest()->simplePaginate(5);
        $category = Category::select('image_url')->get();
    }   
        return view('posts.index', [
            'posts' => $post,
            'category' => $category
        ]);
    }

You may want to add the ->withQueryString() after the ->paginate(5) in both cases (the if and the else) if you want, but I'd recommend to solve one problem at a time.

And finally, at the view just call the links for now (as you were originally doing):

<span> {{ $posts->links() }} </span>

Hope this works or at least, starts to solve some problems!!

warpig's avatar
Level 12

That was the missing piece! Modified the index method like you mentioned and only had to use links() in the view. Would I need to use ->withQueryString() in the future though? Thanks so much @marianomoreyra !!

MarianoMoreyra's avatar

That's great @warpig !!

Happy to be of help!

Regarding ->withQueryString() I believe that maybe you'll need to use it in addition to ->appends to add the Tag that was selected to the pagination links, otherwise I understand that if you click on a page as it is right now, you'll be loosing the Tag filter...

1 like
warpig's avatar
Level 12

Hm, not quite. I can view the post index and a single post, once in the single post, I can go ahead and click a tag, given the number on the simplePaginate method I can also paginate through posts with the same tag. So it is working even without withQueryString and just by using links() inside the view, is this odd? I am only using an anchor tag to view the attached tags + posts.

warpig's avatar
Level 12

Ok actually you are right the Tag filter does gets lost but it does solve the issue of showing the associated tags, haha.

EDIT

So I guess everything is fine and working, added ->withQueryString to the index method.

    public function index(Category $category)
    {   
        if (request('tag')) {
        $post = Tag::where('name', request('tag'))
                ->firstOrFail()
                ->posts()
                ->simplePaginate(2)
                ->withQueryString();
    } else {
        $post = Post::latest()->simplePaginate(5);
        $category = Category::select('image_url')->get();
    }   
        return view('posts.index', [
            'posts' => $post,
            'category' => $category
        ]);
    }

And, once in the view I added the $post variable with appends:

  <span> {{ $posts->appends(['tag' == $post])->links() }} </span>

The resulting URL of paginated posts with tags: http://my-blog.test/posts?tag=thps&0=0&page=1

1 like
warpig's avatar
Level 12

Im only interested in the tag "name" I guess I can always pass it in the route declaration and skip building an array?

Im using the Tag model and hoping that's enough to retrieve the names im using for the anchor tag inside the posts.index, and for the usage of the appends it starts with the $posts variable, can't I use the relationship I already have on the Post model with the Tag model? like when you loop using a method on a model, for example, category. Since I've already established this one to many relationship with Category model, all I would need to do would be to insert it into the view like : $post->category->name, can't I do this inside the appends array?

Another thing that got me confused was, is this declaration for the routes or controller too? Because PostsController@index seems to be requesting this variable

jlrdw's avatar

@warpig you shouldn't need to have tag in query string, it's a parameter.


somesite.com/choice/blue?page=2&something=5&name=bob

// here blue is parameter
// after question mark (?) is query string.

Don't collect just paginate a query, more efficient.

1 like
warpig's avatar
Level 12
Route::get('/posts/{tag}', function () {

    $tag = Tag::simplePaginate();

    $tag->appends(['tag' => $tag]);

    return view('posts.index', compact('tag'));
});

This fixes the issue of displaying a list of posts attached to tags. If one were to visit http://my-blog.test/posts?tag=webdev

But then I was getting this whenever I wanted to view a single post.

Routing

Controller: Closure
Route name: unknown
Route parameters
    tag: hi

Middleware: web

Then I added the slug parameter to this route:

Route::get('/posts/{slug}/{tag}', function (Post $post) {

    $tag = Tag::simplePaginate();

    $tag->appends(['tag' => $tag]);

    return view('posts.index', compact('tag'));
});

And it's fixed?

I know it's near impossible for you to be sure because you would have to interact with the code and that, but in your experience, having this GET request like this, will it cost me a bug in the near future?

warpig's avatar
Level 12

Was having no exceptions because I forgot I deleted this part:

<span> {{ $posts->appends($post)->links() }} </span>

Please or to participate in this conversation.