Getting the next object in a collection in Laravel

2.3k views Asked by At

I have mutliple Chapters that belong to a Module.

On a chapter page I want to check if I am on the last one in the module, but I'm getting a bit stuck.

// inside Chapter model.
// The $module var is a made by something like Module::with('chapters')->find(1);
public function getNext($module){

    // Convert to array so we can call some of
    // the array functions to navigate the array
    $chapters = $module->chapters->keyBy('id')->toArray();

    // get the last element in the array
    end($chapters);

    // If the last element's key is the same as this one,
    // there is no "next" link
    if(key($chapters) == $this->id){
        return false;
    }

    // So there must be a next link. First, reset internal array pointer
    reset($chapters);

    // Advance it to the current item
    while (key($chapters) !== $this->id) next($chapters);

    // Go one further, returning the next item in the array
    next($chapters);

    // current() is now the next chapter 
    return current($chapters);
}

Cool! So this lets me know if there is a next chapter and even returns it as an array with all of its data. But I'm getting into massive problems. The Chapter has a few other methods on it which I can't call on the 'next' element as its an array, not an object any more.

// Chapter.php
public function url(){
    return url('chapter/' . $this->id);
}


$module = Module::with('chapters')->find(1);
$chapter = Chapter::find(1);
$next = $chapter->getNext($module);
if( $next )
  echo $next->url();

This gives me (obviously)

Call to a member function url() on array

So I need to rewrite this function, but I can't work out how to get the next object in a Laravel collection.

public function getNext($module){
    $last = $module->chapters->last();

    // If the last element's key is the same as this one,
    // there is no "next" link
    if($last->id == $this->id){
        return false;
    }

    ....

How can I traverse the collection to get the next Chapter as an object?

2

There are 2 answers

0
Djave On

After a little bit I have worked out my own solution:

public function getNext($module){
    $last = $module->chapters->last();

    // If the last element's key is the same as this one,
    // there is no "next" link
    if($last->id == $this->id){
        return false;
    }

    $current_order = $this->order;
    $filtered = $module->chapters->filter(function ($item) use ($current_order) {
        return $item->order > $current_order;
    });
    return $filtered->first();
}

Open to any other neater ways of doing it though! Thanks

0
Amit Gupta On

You can create collection macros as:

Collection::macro('previous', function ($key, $value = null) {
    if (func_num_args() == 1) $value = $key; $key = 'id';

    return $this->get($this->searchAfterValues($key, $value) - 1);
});

Collection::macro('next', function ($key, $value = null) {
    if (func_num_args() == 1) $value = $key; $key = 'id';

    return $this->get($this->searchAfterValues($key, $value) + 1);
});

Collection::macro('searchAfterValues', function ($key, $value) {
    return $this->values()->search(function ($item, $k) use ($key, $value) {
                             return data_get($item, $key) == $value;
                        });
});

Then you can use it as:

$module = Module::with('chapters')->find(1);
$chapter = Chapter::find(1);

$next = $module->chapters->next($chapter->id)
// or 
$next = $module->chapters->next('id', $chapter->id)