creating a chainable method in laravel

You can do that in Laravel with the "query scopes". You can find the doc here.

You just have to write a function with the prefix scope and you will be able to chain this method like the other query builder ones :

class Post extends Eloquent {

    public function scopeWhereCategories($query, $categories)
    {
        return $query->whereIn('categories_id', $categories, 'AND');
    }

}

$posts = Post::whereCategories([1, 2, 3])->get();
$posts = Post::orderBy('date')->whereCategories([1, 2, 3])->take(5)->get();

OK... This may warp your brain a bit but stick with me. The actual method defined is _where(). So how does both Post::where and $post->where end up calling the _where() method? The answer is 'magic'. :D

PHP has these things called 'magic methods' which enable some very dynamic behaviour. In this case Laravel is using __callStatic() and __call() to handle calls to undefined methods. This enabled the same method to be called statically and non-statically.

public static function __callStatic($method, $parameters)
{
    // Create a new instance of the called class, in this case it is Post
    $model = get_called_class();

    // Call the requested method on the newly created object
    return call_user_func_array(array(new $model, $method), $parameters);
}

So basically Post::where() is just shorthand for $post = new Post; $post->where()

From here the request goes to __call() in which there is an array of underscored method names. If the requested method is in the list of underscored names, then $this->_method() is called and returned.

But this is still not the whole story. Drew is correct in that 'where_in' returns a Query object. Most of the ORM chaining methods you are familiar with are actually part of the ORM Query object. Here is the whole process.

  • Post::where( ... )
  • Post::__callStatic( 'where', ... )
  • $post->__call( 'where', ... )
  • $query->_call( 'where', ... )
  • $query->_where( ... )

The class you want to extend is the Query that is used by Model. There is no way add another method name to the list of underscored methods defined in __call(). You will have to copy __call() in it's entirety into your Query definition in order to enable your new method to have that behaviour.

I have accomplished this in my projects be pointing Eloquent ( the alias for the Laravel model ) to my extended version, enabling all my models to use the extended Query methods.

// application/libraries/mylib/query.php
namespace MyLib;

class Model extends \Laravel\Model {

    // Tell \MyLib\Model to use \MyLib\Query instead of Laravels Query
    protected function query()
    { 
        return new \MyLib\Query($this);
    } 

}

// application/libraries/mylib/query.php
namespace MyLib;

class Query extends \Laravel\Database\Eloquent\Query {

    function _my_method() { ... }

    function __call() { ... }
}

// application/config/application.php
'aliases' => array(
    ...
    'Eloquent' => 'MyLib\\Model',
    ...
)

http://php.net/manual/en/language.oop5.magic.php https://github.com/laravel/laravel/blob/master/laravel/database/eloquent/model.php#L734 https://github.com/laravel/laravel/blob/master/laravel/database/eloquent/query.php#L274