Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

creating a chainable method in laravel

I have been trying to create my own chainable method in laravel' eloquent but I'm missing something and not sure what. This may sound a little bit nuts but have a look at my function below to get a better idea of what I'm trying to say.

class Post extends Eloquent{
    public static function custom_wh($data){
        return static::where_in('categories_id', $data, 'AND');
    }
}

//this works fine
$posts = Post::custom_wh(array(1, 2, 3))->get();

//but this says custom_wh is not defined in the query class
$posts = Post::where_in('tags', array(2, 3, 4), 'AND')->custom_wh(array(1, 2, 3))->get();

if I understand correctly then my method is not eligible to chain after another method? So I guess my question is how can I create a chainable method in my model?

P.S I have looked into the laravel's query builder class where I have seen that the chainable methods returns the instance of that object but I couldn't find a way to return the object other than the way I've done in the code above. Any kind of suggestion or advice is highly appreciated. Thanks in advance.

like image 458
Foysal Ahamed Avatar asked Feb 25 '13 15:02

Foysal Ahamed


2 Answers

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();
like image 161
Alexandre Butynski Avatar answered Nov 16 '22 00:11

Alexandre Butynski


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

like image 27
Collin James Avatar answered Nov 16 '22 01:11

Collin James