16
Php

Extend Laravel Eloquent Collection Object

According to Laravel, all multi-result sets returned by Eloquent, either via the get method or a relationship, will return a collection object. This object implements the IteratorAggregate PHP interface so it can be iterated over like an array. However, this object also has a variety of other helpful methods for working with result sets. For example, we may determine if a result set contains a given primary key using the contains method:

$users = User::all();
if ($users->contains(5))
{
    // A record available with primary key (basically id) 5
}

You can also use something like this:

User::all()->each(function($user){
    echo $user->username;
});

Or you can get a single item from the collection using it’s index like:

$users = User::all();
// Print username from first record
echo $users->get(0)->username;
// Print username from third record
echo $users->get(2)->username;

Well, the base class for this collection object is \Illuminate\Support\Collection.php and this class contains a bunch of methods that could be applied on a collection object and there is another class \Illuminate\Database\Eloquent\Collection.php, this class extends the base collection class and this child class also contains some useful methods to use with a collection object. When we call something like $users = User::all() we get a collection object and this collection object is an instance of \Illuminate\Database\Eloquent\Collection, which extends the base collection class \Illuminate\Support\Collection.php.

Well, Laravel is really a powerful and flexible framework and it has provided us so many ways to extend the core functionality. In this case, if we want to add more methods in collection object we can do it by extending the \Illuminate\Database\Eloquent\Collection.php class and overriding the newCollection method of \Illuminate\Database\Eloquent\Model.php class in our Eloquent Model. Actually, this Model.php is responsible for the Eloquent ORM and this class contains a public method, which is:

public function newCollection(array $models = array())
{
    return new Collection($models);
}

This method returns a collection object and we can override this method in our Eloquent Model and we can return a custom collection object with our custom methods in it by extending the \Illuminate\Database\Eloquent\Collection.php class. To do this, we have to create a class at first, so we can create a class by extending \Illuminate\Database\Eloquent\Collection.php class:


Now if we create an Eloquent Model like:

class Order extends Eloquent {
    // Override the parent method
    public function newCollection(array $models = Array())
    {
        return new Extensions\CustomCollection($models);
    }
}

Now, if we use Order::all()->foo() then it'll call the method in our CustomCollection because we have returned our CustomCollection class which contains this method. This way we can add our custom methods and can apply those methods on any Eloquent collection object.

I've done this, added some custom methods to collection object by extending the collection class and works very fine. This is the step by step process that I've done, you may follow and try it, at first create the customCollection class, I've put it in a new directory inside app folder:

// app/extensions/customCollection.php
toArray();
		$header_keys = array_keys($items[0]);
		
		if(!is_null($options)) {
			if(array_key_exists('only', $options)) {
				$header_keys = $options['only'];
			}
			if(array_key_exists('attributes', $options)) {
				$attr = $options['attributes'];
			}
		}

		// Thead
		if(is_null($options) || (!isset($options['header']) || isset($options['header']) && $options['header'] != false)) {
			$header = "";
			foreach ($header_keys as $value) {
				$header .= "" . ucwords(str_replace('_', ' ', $value)) . "";
			}
			$header .= "";
		}

		// Tbody
		$tbody = "";
		foreach ($items as $values) {
			$tbody .= "";
				foreach($header_keys as $key){
					$tbody .= "" . $values[$key] . "";
				}
			$tbody .= "";
		}
		$tbody .= "";

		// Build attributes (id, class, style etc)
		if(isset($attr)) {
			foreach ($attr as $key => $value) {
				$atts .= " " . $key . "='" . $value . "'";
			}
		}

		// Return only Tbody (if table == false)
		if(!is_null($options) && isset($options['table']) && $options['table'] == false) return $tbody;
		
		// Return table with attributes (class, id, style etc)
		else return "" . $header . $tbody . "
"; } }

In my composer.json file I've added, "app/extensions" in autoload > classmap section like:

"autoload": {
    "classmap": [
        "app/commands",
        // more...
        "app/extensions"
        ]
    }

Then I've created a BaseModel class in my app/models folder and all of my Eloquent models extends this base class:


So, all of my Eloquent models extends this base model and I can use my customCollection class' methods in any model. For example, I've a User model like:


So, I can now use User::all()->toTable(), here toTable() method is added an my customCollection class and what it does is, just generates a HTML table. How do I use it:

// In a controller
$users = User::all();
$options = array(
    'only' => array('id', 'first_name', 'last_name', 'username', 'email', 'bio'),
    'attributes' => array( 'class' => 'table', 'id' => 'tbl1' )
);
return View::make('user.index')
->with('users', $users)
->with('options', $options)

In my user/index.blade.php view file:

@extends('layouts.master')
@section('content')
    {{ $users->toTable($options) }}
@stop

Which outputs something like this:
blog_pic1

So, this toTable() method is just a custom method for easily generating a table with an eloquent collection, well, this is just an example of adding more methods in collection object and nothing else.

BTW, if anyone interested in my custom toTable() method then it could be used in alternative ways too:

// Only data without header
$options = array(
    'only' => array('id', 'first_name', 'last_name', 'username', 'email', 'bio'),
    'attributes' => array( 'class' => 'table table-striped', 'id' => 'tbl1'	),
    'header' => false
);
// Only returns a tbody,
// without table and header tags
$options = array(
    'only' => array('id', 'first_name', 'last_name', 'username', 'email', 'bio'),
    'attributes' => array( 'class' => 'table table-striped', 'id' => 'tbl1' ),
    'table' => false
);

// Using "table => false" a view could be something like this

@extends('layouts.master')

@section('content')
    
        {{ $users->toTable($options) }}
    
IDUsernameEmail
@stop

I've added other methods, just for some fun with power of Laravel

// Returns odd records
public function odds()
{
    $odd = array();
    foreach ($this->items as $k => $v) {
        if ($k % 2 == 0) $odd[] = $v;
    }
    return new static($odd);    
}
// Returns even records
public function evens()
{
    $even = array();
    foreach ($this->items as $k => $v) {
        if ($k % 2 !== 0) $even[] = $v;
    }
    return new static($even);
}

This is another dynamic method getWhere($value, $key), using this method I can get all the records from a collection where a given attribute matches a value, for example, $users->getWhereFirstName('Mr') and this method requires the __call() magic method, here it is:

public function getWhere($value, $key)
{
    $index = $this->fetch($key)->toArray();
    $collection = array();
    foreach ($index as $k => $val) {
        if($value == $val) $collection[] = $this->items[$k];
    }
    return count($collection) ? new static($collection) : null;
}
// The __call() magic method, required for this dynamic getWhere() method
public function __call($method, $args)
{
    $key = snake_case(substr($method, 8));
    $args[] = $key;
    return call_user_func_array(array($this, 'getWhere'), $args);
}

SO, I can use $users->getWhereUsername('user1') or $users->getWhereId(5) and so on.

Well, Laravel has already provided a great collection of methods for the collection object but all of these built in methods are not documented in the online documentation, so by reading the source code in both Collection.php files, (mentioned earlier) we can know more about these methods. Some useful methods are given below:

// make() method for making a collection from an array
$array = array('name' => 'Heera', 'email' => 'heerasheikh@ymail.com');
$collection = \Illuminate\Database\Eloquent\Collection::make($array);
// Now any method could be applied on this new collection, ie.
// converts to an array
$collection->toArray();
// Or get an item
$collection->get('name') // Heera
// Index based array
$array = array('Heera', 'heerasheikh@ymail.com');
$collection = \Illuminate\Database\Eloquent\Collection::make($array);
// Get the second item from collection
$collection->get(1); // heerasheikh@ymail.com

// First item in the collection is 0 when using get/put methods
// Some other useful methods available in collection object

// Get the item with id 5 (not 4th index)
$users = User::all();
$users->find(5);

// Put an item to an index
$collection->put(2, 'Male');
// Get and remove the first item from the collection
$collection->shift();
// Prepend an item onto the beginning of the collection
$collection->prepend('Married');
// Push an item at the end of the collection
$collection->push('Married');
// Get and remove the last item from the collection
$collection->pop();
// Remove an item from the collection by key
$collection->forget(0); // 1st
$collection->forget('name');
// Load a relationship using load (Lazy load relationship)
$users = User::all();
$users->load('role');
// And more...

There are so many built in useful methods available in the collection object, it's not documented on the online manual but reading the source code will give a clear idea because each method provides commented instruction above them. So, read the code and learn more.

Latest Blog

0
Php

PHP – 8.0 Match Expression

In PHP 8.0 there is a new feature or I should say a new language construct (keyword) going to be introduced,  which has been implemented depending […]