Laravel 5 - Поиск страницы разбиения на страницы для модели

Я работаю над созданием базового форума (на основе laracasts.com/discuss). Когда пользователь публикует ответ в теме:

  • Я хотел бы направить их в конец списка ответов с разбивкой на страницы с якорем их ответа (такое же поведение, как у Laracasts).
  • Я также хотел бы вернуть пользователя на правильную страницу, когда он редактирует один из своих ответов.

Как узнать, на какой странице будет опубликован новый ответ (?page=x) и как вернуться на нужную страницу после редактирования ответа? Или из основного списка постов, на какой странице находится последний ответ?

Вот моя текущая модель ForumPost (за вычетом нескольких несвязанных вещей) -

<?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();
    }

}

ОБНОВЛЕНИЕ

Взгляните на это и дайте мне знать, что вы думаете. Это немного странно в том, как это работает, но возвращает правильную страницу для данного идентификатора ответа, и это всего лишь один метод:

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

person NightMICU    schedule 19.03.2015    source источник
comment
комментируя ваше обновление: $count % $paginate вернет остаток ($ offset) от $count/$paginate, поэтому 11%2 = 1 даст вам смещение. Pageno можно проверить с помощью floor($count / $paginate). Возможно, это небольшое улучшение.   -  person Luceos    schedule 21.03.2015
comment
Ага, спасибо... это было кодирование поздно ночью с небольшим количеством вина;) Я думаю, что я на правильном пути благодаря @MirroredFate, но мне все еще интересно, как великий Джеффри Уэй добился этого. С тех пор я нашел несколько других людей, пытающихся понять это без особой удачи. В основном интересно, какой самый эффективный способ сделать это на самом деле, не уверен, какой удар по производительности там выполняет эти запросы для каждого отдельного объекта Post в моем списке.   -  person NightMICU    schedule 21.03.2015


Ответы (2)


По сути, вы работаете с двумя значениями: во-первых, каков индекс ответа по отношению ко всем ответам на сообщение, а во-вторых, количество ответов на странице.

Например, у вас может быть ответ с идентификатором 301. Однако это 21-й ответ на конкретную публикацию. Вам нужно каким-то образом выяснить, что это 21-й ответ. На самом деле это относительно просто: вы просто подсчитываете, сколько ответов связано с этим сообщением, но имеет меньшие идентификаторы.

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

Этот метод должен возвращать индекс ответа, который вы ищете, при условии, конечно, что ваши ответы используют идентификаторы с автоинкрементом.

Вторая часть головоломки — выяснить, какая страница вам нужна. Это делается с помощью целочисленного деления. В основном вы просто делите число как обычно, но не используете остаток. Если вы смотрите на 21-й ответ, и у вас есть 10 ответов на страницу, вы знаете, что он должен быть на третьей странице (страница 1: 1-10, страница 2: 11-20, страница 3: 21-30). Это означает, что вам нужно разделить ваш индекс ответов на число ваших ответов на странице, а затем добавить 1. Это даст нам 21/10+1, что при целочисленном делении даст нам 3. Ура!

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

Хорошо, теперь вам просто нужно вытащить эту страницу. Для этого просто требуется метод, в котором вы указываете, какой номер страницы вам нужен и сколько ответов на странице есть. Затем этот метод может вычислить смещение и предел, а также получить необходимые записи.

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

Однако на всякий случай мы можем создать метод для получения индекса окончательного ответа.

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

Большой! Теперь у нас есть все необходимые строительные блоки. Мы можем создать несколько простых методов, которые используют наши методы более общего назначения, чтобы легко извлекать нужные нам данные.

Давайте начнем с метода, который получает всю страницу ответов, на которой находится один ответ (не стесняйтесь менять имена (также я предполагаю, что на странице 10 ответов)):

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

На всякий случай мы можем создать метод, который получает страницу окончательных ответов.

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

Вы можете создать множество других методов, чтобы использовать наши методы стандартных блоков и переходить по страницам, получать страницы после или до ответа и т. д.

Несколько замечаний

Все они входят в вашу модель ForumPost, которая должна иметь отношение «один ко многим» с вашими ответами.

Это различные методы, предназначенные для обеспечения широкого спектра функциональных возможностей. Не бойтесь читать их и тестировать по отдельности, чтобы точно увидеть, что они делают. Ни один из них не очень длинный, так что это не должно быть сложно сделать.

person MirroredFate    schedule 19.03.2015
comment
Не будете ли вы так любезны привести пример? Одной из проблем является возврат на страницу, где находится ответ после того, как пользователь отредактировал его — например, ответ является первой записью на странице 2 из 2. Если кто-то удалит ответ со страницы 1, редактируемый ответ теперь будет на странице 1 вместо страницы 2. Поэтому мне нужно будет получить правильную страницу сразу после сохранения либо нового ответа, либо отредактированного. - person NightMICU; 20.03.2015
comment
Спасибо за пример, он мне нравится, за исключением проблемы выяснения, на какой странице находится конкретный ответ... есть идеи? Спасибо за вашу помощь, мне нужно идти домой, но я проверю это снова через пару часов. :) - person NightMICU; 20.03.2015
comment
Кроме того, еще один важный элемент — мне нужно знать страницу, на которой находится ответ, из индекса основных сообщений (как видно на laracasts.com). Поэтому крайне важно, чтобы у меня был способ получить точную страницу ответа на лету для создания, обновления и индексирования. - person NightMICU; 20.03.2015
comment
Ого, сколько функций! Я прочитал ваш ответ несколько раз и все еще пытаюсь собрать все воедино. Один метод, getReplyOffset(), отсутствует или, возможно, имеет неправильное имя — не удается найти ничего, что действительно соответствовало бы .. - person NightMICU; 20.03.2015
comment
Если бы вы могли протестировать что-то из этого, когда у вас будет шанс, я был бы очень признателен за это.. много чего происходит, и я не уверен, что куда идет, какие имена методов неверны или отсутствуют. Лучшее, что я смог сделать, пытаясь найти страницу, на которой находится ответ, - это вернуть модель этого ответа - не знаю, как я это получил. - person NightMICU; 20.03.2015
comment
Спасибо, что разобрали ситуацию более подробно. Я предполагаю, что меня смутили методы, которые возвращали объекты ответов, а не совсем то, что мне было нужно - мне просто нужно было целое число страницы, на которой находится ответ. Я поиграю с этим через некоторое время, большое спасибо за всю вашу тяжелую работу над этим! - person NightMICU; 21.03.2015
comment
@NightMICU Вы можете просто объединить методы getReplyIndex и getPageNumber и получить номер страницы по идентификатору ответа. - person MirroredFate; 21.03.2015
comment
Да, я действительно не пытаюсь получить здесь наборы результатов, мне просто нужно целое число. Пагинатор Laravel загружает правильную страницу, просто указав ее целое число в URL-адресе, поэтому, если я перейду к posts?page=2, он автоматически покажет этот набор результатов. - person NightMICU; 21.03.2015
comment
Хорошо, я объединил getPageNumber и getReplyIndex в один метод, и он возвращает двойное значение. Я использую два на странице в качестве примера, с 4 записями я получаю 2,5 в результате - person NightMICU; 21.03.2015
comment
Я завернул возвращаемое значение в CEIL(), и, похоже, это помогло. Опять же, я думаю, что раньше я действительно запутался, когда ваши методы возвращали страницы сообщений. Довольно круто, что Laravel может понять это, просто предоставив целое число. Еще круче тот факт, что десятичные дроби действительно работают — с 4 постами, разбитыми на страницы по 2 на страницу, на странице 1,5 отображается один пост. - person NightMICU; 21.03.2015
comment
@NightMICU Это круто. Проблема в функции заключалась в том, что индекс должен был быть приведен до того, как уравнение было выполнено... Я заключил уравнение в круглые скобки, и теперь оно работает, как и ожидалось. - person MirroredFate; 21.03.2015
comment
про нумерацию страниц... как это работает в Laravel? Если я нахожусь на странице 3 из 10 элементов (21-31) и хочу также загрузить страницу 2 (в вызове ajax), если пользователь тем временем удалит соответствующий пост, вызовет ли это загрузку дубликатов? - person Botea Florin; 16.03.2019
comment
$page_number = ceil($current_index/$per_page); - person Botea Florin; 16.04.2019

Вот что я придумал. Если у кого-то есть какие-либо предложения по улучшению этого, ПОЖАЛУЙСТА, дайте мне знать. Мне действительно интересно, есть ли другой способ Laravel сделать это, и я был бы очень признателен, если бы Джеффри Уэй поделился своим секретом, поскольку он делает именно это в 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;
    }
person NightMICU    schedule 20.03.2015
comment
На концептуальном уровне я не вижу причин, по которым модель должна возвращать ссылку, если, конечно, это не модель для таблицы, которая отслеживает кучу ссылок. Кроме того, автоматическое назначение каждого аргумента является опасной территорией. Определите жесткий интерфейс, которому все должно следовать, и вы поблагодарите себя позже. Наконец, в этой ситуации нет причин использовать цикл for. У вас есть целочисленное деление для такого рода задач. - person MirroredFate; 21.03.2015
comment
Цените мысли. Это далеко не идеально (цикл for думал поздно ночью, конечно, деление было лучшим решением). Что меня больше всего волнует, так это знать, на какой странице находится ответ, чтобы я мог иметь прямую ссылку на этот ответ, поэтому я включил его в метод. Это помогло сократить объем встроенного кода для достижения этой цели. - person NightMICU; 21.03.2015