How do query scopes work in Laravel? - php

I'm watching the Laracasts: Laravel 5.4 from Scratch series, and have come across the concept of query scopes.
In the video, we set up a class like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
public function scopeIncomplete($query)
{
return $query->where('completed', 0);
}
}
My first question:
Why is a method like: public function scopeIncomplete($query) called like this: App\Task::incomplete() with the :: operator?
Isn't that a non-static method being called statically?
My second question:
Where does $query come from?
From what I can understand this is the "existing" query, but it is called like this: App\Task::incomplete()->where('id', '>', 1)->get();
So I'm not really sure where this variable is coming from.
Maybe it's explained more in depth later but I can't wrap my head around how this works.

This is pretty broad since it is covering two large topics on Eloquent.
The first refers to Eloquent's use of the facade pattern to pass static calls to non-static methods.
The facade pattern utilizes the IoC container to pass a method call to a bound, or new, instance of a class.
The facade pattern makes use of overloading and the magic method __callStatic.
The second question refers to Eloquent's overloading that passes the unregistered method calls to a query builder object (specifically \Illuminate\Database\Eloquent\Builder). This is what allows for where() and various other Query Builder methods to be called on the Eloquent model itself. Inside the Model class, you can see:
/**
* Handle dynamic method calls into the model.
*
* #param string $method
* #param array $parameters
* #return mixed
*/
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
return $this->newQuery()->$method(...$parameters);
}
Since where() or incomplete() are not defined in Model, they'll be passed to the Builder class, which is returned by Model::newQuery().
Inside the Builder __call method, you have:
if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) {
return $this->callScope([$this->model, $scope], $parameters);
}
So this is checking for the scopes defined inside the model and passing the $parameters which includes this Builder instance.
So in summary, the call for Model::incomplete() will go:
Model __call() -> Builder __call() -> Model scopeIncomplete(Builder $builder)

Related

Does Laravel Framework instantiate every model class at boot?

With, where, hasMany etc. methods of Eloquent class are not static.
However we call these functions like that:
// Post is a child of Model class.
Post::where(...); // we don't use New keyword.
So, does Laravel Framework initiate all Model instances before we call their methods?
In the Eloquent's Model class, you have the below function which handles the static method calls dynamically.
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}
As you can see, it creates an instance of the Model class on which a non static method is invoked statically, then invokes that method on the instance.

How does the abstract Model class in laravel handle static dynamic method calls such as "::find()", "::where()" etc

I am learning how some features are being implemented in laravel, because i want to understand some software design techniques and principles.
i understand that when a static method call such as "App\User::find(1) or App\User::whereId(1)" are made on an eloquent model that the abstract model class implements a magic method "__callStatic" like so :
/**
* Handle dynamic static method calls into the method.
*
* #param string $method
* #param array $parameters
* #return mixed
*/
public static function __callStatic($method, $parameters)
{
$instance = new static;
return call_user_func_array([$instance, $method], $parameters);
}
I also understand that this line '$instance = new static;' makes an instance of whatever eloquent model in which the static call was made e.g App\User.
However i don't completely understand whats going on in the next line "call_user_func_array([$instance, $method], $parameters);".
From what i have learnt so far the function call_user_func_array() is suppose to call the $method(e.g find($parameters) ) on the eloquent model instance (e.g App\user).
But i dont understand why that is not the case,and i discovered that method do not exist on the eloquent model. i tried calling a none existing method like "blah()" :
App\User::blah();
But i get the exception "BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::fisd()'".
Please how is the class "Builder" entering the scene ?
why is the exception not ""BadMethodCallException with message 'Call to undefined method Illuminate\Database\Eloquent\Model::fisd()'"" ?
i cannot see the model class extending Builder Class.
The reason this happens is because it then triggers the __call method on the class which will try and call the method using newQuery() which returns an instance of Builder.
__call, like __callStatic, is called when you try and call an inaccessible (or nonexistent) method on a class.
So, when you try and call find() statically it will get caught by __callStatic because it doesn't exist which in turn then tries calling find() on the new instance which again doesn't exist so it gets caught by __call and it finally tries to call that method on Builder.
Hope this helps!

Laravel Request::all() Should Not Be Called Statically

In Laravel, I'm trying to call $input = Request::all(); on a store() method in my controller, but I'm getting the following error:
Non-static method Illuminate\Http\Request::all() should not be called statically, assuming $this from incompatible context
Any help figuring out the best way to correct this? (I'm following a Laracast)
The error message is due to the call not going through the Request facade.
Change
use Illuminate\Http\Request;
To
use Request;
and it should start working.
In the config/app.php file, you can find a list of the class aliases. There, you will see that the base class Request has been aliased to the Illuminate\Support\Facades\Request class. Because of this, to use the Request facade in a namespaced file, you need to specify to use the base class: use Request;.
Edit
Since this question seems to get some traffic, I wanted to update the answer a little bit since Laravel 5 was officially released.
While the above is still technically correct and will work, the use Illuminate\Http\Request; statement is included in the new Controller template to help push developers in the direction of using dependency injection versus relying on the Facade.
When injecting the Request object into the constructor (or methods, as available in Laravel 5), it is the Illuminate\Http\Request object that should be injected, and not the Request facade.
So, instead of changing the Controller template to work with the Request facade, it is better recommended to work with the given Controller template and move towards using dependency injection (via constructor or methods).
Example via method
<?php namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class UserController extends Controller {
/**
* Store a newly created resource in storage.
*
* #param Illuminate\Http\Request $request
* #return Response
*/
public function store(Request $request) {
$name = $request->input('name');
}
}
Example via constructor
<?php namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class UserController extends Controller {
protected $request;
public function __construct(Request $request) {
$this->request = $request;
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store() {
$name = $this->request->input('name');
}
}
use the request() helper instead. You don't have to worry about use statements and thus this sort of problem wont happen again.
$input = request()->all();
simple
Inject the request object into the controller using Laravel's magic injection and then access the function non-statically. Laravel will automatically inject concrete dependencies into autoloaded classes
class MyController()
{
protected $request;
public function __construct(\Illuminate\Http\Request $request)
{
$this->request = $request;
}
public function myFunc()
{
$input = $this->request->all();
}
}
The facade is another Request class, access it with the full path:
$input = \Request::all();
From laravel 5 you can also access it through the request() function:
$input = request()->all();
I thought it would be useful for future visitors to provide a bit of an explanation on what is happening here.
The Illuminate\Http\Request class
Laravel's Illuminate\Http\Request class has a method named all (in fact the all method is defined in a trait that the Request class uses, called Illuminate\Http\Concerns\InteractsWithInput). The signature of the all method at the time of writing looks like this:
public function all($keys = null)
This method is not defined as static and so when you try to call the method in a static context, i.e. Illuminate\Http\Request::all() you will get the error displayed in OP's question. The all method is an instance method and deals with information that is present in an instance of the Request class, so calling it in this way makes no sense.
Facades
A facade in Laravel provides developers with a convenient way of accessing objects in the IoC container, and calling methods on those objects. A developer can call a method "statically" on a facade like Request::all(), but the actual method call on the real Illuminate\Http\Request object is not static.
A facade works like a proxy - it refers to an object in the IoC container and passes the static method call onto that object (non-statically). For instance, take the Illuminate\Support\Facades\Request facade, this is what it looks like:
class Request extends Facade
{
protected static function getFacadeAccessor()
{
return 'request';
}
}
Under the hood, the base Illuminate\Support\Facades\Facade class uses some PHP magic, namely the __callStatic method to:
Listen for a static method call, in this case all with no parameters
Grab the underlying object from the IoC container using the key returned by getFacadeAccessor, in this case a Illuminate\Http\Request object
Dynamically call the method that it received statically on the object it has retrieved, in this case all is called non-statically on an instance of Illuminate\Http\Request.
This is why, as #patricus pointed out in his answer above, by changing the use/import statement to refer to the facade, the error is no longer there, because as far as PHP is concerned, all has been correctly called on an instance of Illuminate\Http\Request.
Aliasing
Aliasing is another feature that Laravel provides for convenience. It works by effectively creating alias classes that point to facades in the root namespace. If you take a look at your config/app.php file, under the aliases key, you will find a long list of mappings of strings to facade classes. For example:
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
// ...
'Request' => Illuminate\Support\Facades\Request::class,
Laravel creates these alias classes for you, based on your configuration and this allows you to utilise classes available in the root namespace (as referred to by the string keys of the aliases config) as if you're using the facade itself:
use Request:
class YourController extends Controller
{
public function yourMethod()
{
$input = Request::all();
// ...
}
}
A note on dependency injection
While facades and aliasing are still provided in Laravel, it is possible and usually encouraged to go down the dependency injection route. For example, using constructor injection to achieve the same result:
use Illuminate\Http\Request;
class YourController extends Controller
{
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function yourMethod()
{
$input = $this->request->all();
// ...
}
}
There are a number of benefits to this approach but in my personal opinion the greatest pro for dependency injection is that it makes your code way easier to test. By declaring the dependencies of your classes as constructor or method arguments, it becomes very easy to mock out those dependencies and unit test your class in isolation.
also it happens when you import following library to api.php file.
this happens by some IDE's suggestion to import it for not finding the Route Class.
just remove it and everything going to work fine.
use Illuminate\Routing\Route;
update:
seems if you add this library it wont lead to error
use Illuminate\Support\Facades\Route;
use Illuminate\Http\Request;
public function store(Request $request){
dd($request->all());
}
is same in context saying
use Request;
public function store(){
dd(Request::all());
}
I was facing this problem even with use Illuminate\Http\Request; line at the top of my controller. Kept pulling my hair till I realized that I was doing $request::ip() instead of $request->ip(). Can happen to you if you didn't sleep all night and are looking at the code at 6am with half-opened eyes.
Hope this helps someone down the road.
i make it work with a scope definition
public function pagar(\Illuminate\Http\Request $request)
{
//

How to load models in Laravel?

I started studying Laravel and ran into a problem using models. How to load them? For example in CodeIgniter i used it like $model = $this->load->model('some_model'). In Laravel when i call it from controller like Sites::OfUser() it work fine, but when i call Sites::getId() it says that method should be static...
Is it possible to call method without static or i need to create facades for each model?
My model looks like this:
namespace Models;
use Eloquent;
class Sites extends Eloquent {
public function scopeOfUser($query)
{}
public function getId($name)
{}
}
For static method--
$type = Sites ::scopeOfUser($query);
and if you want normal like codeingiter then use--
$model = new Sites ();
$type = $model->scopeOfUser($query);
You can of course make a static method in the model, and do some static work in it (get ID for name or whatever).
That's no problem.
However, you must declare it static if you want to use the ::, which you are doing not.
public static /* <-- this */ function getId($name)
{
// Do work
// return $result;
}
If you want to access a method with ::, you will need to make it a static method or create a Facade.
The reason why Sites::OfUser() is "working" is because you have prefixed that method with scope.
Scopes allow you to easily re-use query logic in your models. To
define a scope, simply prefix a model method with scope.
If you want to use Facades you can follow my answer here on how to create a Facade.

PHP: Call parent magic method from function of same name

I am using an ORM class - each table in the DB is represented using a subclass of the ORM class.
I am using PHP interfaces, and I wish to specify which methods (db fields) are required in some of my ORM subclasses. Adding a function to an interface requires the method to be explicitly declared in the class. However, these methods rely on magic methods for the actual functionality as the DB structure is unknown to the ORM before run time.
What I imagined doing was creating functions for each, which would return a result from the parent class.
Consider:
class ORM
{
// Library code here. Can't change this.
public function __call($name, $arguments)
{
return call_user_func_array(array($this, $method), $arguments);
}
}
interface MyTableInterface
{
public function myDbField();
}
class MyTable extends ORM implements MyTableInterface
{
public function myDbField()
{
return parent::myDbField();
}
}
With this code, when I call parent::myDbField() from the MyTable class, it correctly moves to the ORM class and uses the __call magic method. Once here, $this equals MyTable and it calls the original function from the MyTable class instead of initiating it's own logic to pull info from the DB.
How can I avoid this recursion?
One way to do it could be passing $orm instance as a dep (constructor, setter or whatever logic). Ex:
class MyTable {
protected $orm;
public function __construct(Orm $orm)
{
$this->orm = $orm;
}
public function myDbField()
{
return $this->orm->myDbField();
}
}
This way, $this inside Orm __call refers to Orm instance. Perhaps, this could be a scenario to use adapter pattern?
call method is only invoked when there is no method found, in your example it is not possible because the method is found in the subclass

Categories