Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel 5 - Finding the pagination page for a model

I am working on building a basic forum (inspired by laracasts.com/discuss). When a user posts a reply to a thread:

  • I'd like to direct them to the end of the list of paginated replies with their reply's anchor (same behavior as Laracasts).
  • I'd also like to return the user to the correct page when they edit one of their replies.

How can I figure out which page a new reply will be posted on (?page=x) and how can I return to the correct page after a reply has been edited? Or, from the main post listing, which page the latest reply is on?

Here is my current ForumPost model (minus a few unrelated things) -

<?php namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * Class ForumPost
 *
 * Forum Posts table
 *
 * @package App
 */
class ForumPost extends Model {
    /**
     * Post has many Replies
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function replies()
    {
        return $this->hasMany('App\ForumReply');
    }

    /**
     * Get the latest reply for a post
     * @return null
     */
    public function latestReply()
    {
        return $this->replies()->orderBy('created_at', 'desc')->first();
    }

}

UPDATE

Take a look at this and let me know what you think. It's a bit weird in how it works but it's returning the correct page for a given reply ID and it's just one method:

public function getReplyPage($replyId = null, $paginate = 2)
    {
        $id = $replyId ? $replyId : $this->latestReply()->id;
        $count = $this->replies()->where('id', '<', $id)->count();

        $page = 1; // Starting with one page

        // Counter - when we reach the number provided in $paginate, we start a new page
        $offset = 0;

        for ($i = 0; $i < $count; $i++) {

            $offset++;
            if ($offset == $paginate) {
                $page++;
                $offset = 0;
            }
        }


        return $page;
    }
like image 336
NightMICU Avatar asked Mar 19 '15 21:03

NightMICU


People also ask

How can I get pagination in Laravel?

There are several ways to paginate items. The simplest is by using the paginate method on the query builder or an Eloquent query. The paginate method provided by Laravel automatically takes care of setting the proper limit and offset based on the current page being viewed by the user.

How does pagination work Laravel?

Pagination works by selecting a specific chunk of results from the database to display to the user. The total number of results is calculated and then split between pages depending on how many results you want to return on a page.


2 Answers

Fundamentally you are working with two values: first, what the index of a reply is in relation to all the replies of a post, and second the number of replies in on a page.

For example, you might have a reply with an id of 301. However, it is the 21st reply on a specific post. You need to some way to figure out that it is the 21st reply. This is actually relatively simple: you just count how many replies are associated with that post but have smaller ids.

//get the number of replies before the one you're looking for
public function getReplyIndex($replyId)
{
    $count = $this->replies()->where('id', '<', $replyId)->count();
    return $count;
}

That method should return the index of the reply you are looking for based- assuming, of course, that your replies are using auto-increment ids.

The second piece of the puzzle is figuring out which page you need. This is done using integer division. Basically you just divide the number normally, but don't use the remainder. If you are looking at the 21st reply, and you have 10 replies to a page, you know it should be on the third page (page 1: 1-10, page 2: 11-20, page 3: 21-30). This means you need to integer divide your reply index by your replies-per-page, then add 1. This will give us 21/10+1, which, using integer division, gives us 3. Yay!

//divides where we are by the number of replies on a page and adds 1
public function getPageNumber($index, $repliesPerPage)
{
    $pageNumber = (int) ($index/$repliesPerPage+1);
    return $pageNumber;
}

Alright, so now you just need to pull that page. This simply requires a method where you specify what page number you need, and how many replies to a page there are. That method can then calculate the offset and the limit, and retrieve the records you need.

public function getPageOfReplies($pageNumber, $repliesPerPage)
{
    $pageOfReplies = $this->replies()->offset($pageNumber*$repliesPerPage)->limit($repliesPerPage)->get();
    return $pageOfReplies;
}

For good measure, though, we can build a method to get the index of the final reply.

public function getLastReplyIndex()
{
    $count = $this->replies()->count();
    return $count;
}

Great! Now we have all the building blocks we need. We can build some simple methods that use our more general-purpose ones to easily retrieve the data we need.

Let's start with a method that gets the entire page of replies on which a single reply resides (feel free to change the names (also I'm assuming there are 10 replies per page)):

public function getPageThatReplyIsOn($replyId)
{
    $repliesPerPage = 10;
    $index = $this->getReplyIndex($replyId);
    $pageNumber = $this->getPageNumber($index, $repliesPerPage);
    return $this->getPageOfReplies($pageNumber, $repliesPerPage);
}

For good measure, we can make a method that gets the page of final replies.

public function getFinalReplyPage()
{
    $repliesPerPage = 10;
    $index = $this->getLastReplyIndex();
    $pageNumber = $this->getPageNumber($index, $repliesPerPage);
    return $this->getPageOfReplies($pageNumber, $repliesPerPage);
}

You could build a variety of other methods to use our building block methods and jump around pages, get the pages after or before a reply, etc.

A couple notes

These all go in your ForumPost model, which should have a one-to-many relationship with your replies.

These are a variety of methods that are meant to provide a wide array of functionality. Don't be afraid to read through them and test them individually to see exactly what they are doing. None of them are very long, so it shouldn't be difficult to do that.

like image 193
MirroredFate Avatar answered Oct 09 '22 16:10

MirroredFate


Here is what I came up with. If anyone has any suggestions to improve on this, PLEASE let me know. I'm really wondering if there is a more Laravel way to do this and I would really appreciate Jeffrey Way sharing his secret, since he is doing this exact thing over at Laracasts.

/**
     * Get Reply Page
     * 
     * Returns the page number where a reply resides as it relates to pagination
     * 
     * @param null $replyId Optional ID for specific reply
     * @param bool $pageLink If True, return a GET parameter ?page=x
     * @param int $paginate Number of posts per page
     * @return int|null|string // Int for only the page number, null if no replies, String if $pageLink == true
     */
    public function getReplyPage($replyId = null, $pageLink = false, $paginate = 20)
    {
        // Find the page for a specific reply if provided, otherwise find the most 
        // recent post's ID. If there are no replies, return null and exit.
        if (!$replyId) {
            $latestReply = $this->latestReply();
            if ($latestReply) {
                $replyId = $latestReply->id;
            } else {
                return null;
            }
        }

        // Find the number of replies to this Post prior to the one in question
        $count = $this->replies()->where('id', '<', $replyId)->count();

        $page = CEIL($count / $paginate +1);

        // Return either the integer or URL parameter
        return $pageLink ? "?page=$page" : $page;
    }
like image 43
NightMICU Avatar answered Oct 09 '22 15:10

NightMICU