Laravel 5 - Finding the pagination page for a mode

2019-03-30 22:51发布

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;
    }

2条回答
老娘就宠你
2楼-- · 2019-03-30 23:47

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.

查看更多
ゆ 、 Hurt°
3楼-- · 2019-03-30 23:51

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;
    }
查看更多
登录 后发表回答