你能在 Laravel 中创建一个范围来调用其他各种范围吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32695608/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Can you make a scope in laravel that calls various other scopes?
提问by geoffs3310
I have a model in Laravel that has various scopes defined. I want to use all of them in a lot of places so rather than chaining them together I'd rather just be able to call one scope that calls all of the other scopes like so:
我在 Laravel 中有一个模型,它定义了各种范围。我想在很多地方使用它们,而不是将它们链接在一起,我宁愿能够调用一个作用域来调用所有其他作用域,如下所示:
function scopeValid($query, $user_id) {
$query = $this->scopeDateValid($query);
$query = $this->scopeMaxUsesValid($query);
$query = $this->scopeCustomerMaxUsesValid($query, $user_id);
return $query;
}
This doesn't seem to work though, is there a way to achieve this?
但这似乎不起作用,有没有办法实现这一目标?
回答by ceejayoz
As shown in the docs, you'll want to do your scoping against $query
, not $this
, and use the scope's magic function rather than calling the internal implementation:
如docs所示,您需要对$query
、 not 进行范围界定$this
,并使用范围的魔法函数而不是调用内部实现:
public function scopeTesting($query) {
return $query->testingTwo();
}
public function scopeTestingTwo($query) {
return $query->where('testing', true);
}
As a demonstration, you can see here calling the testing()
scope applies the logic in the testingTwo()
scope:
作为演示,您可以在此处看到调用testing()
作用域应用作用域中的逻辑testingTwo()
:
>>> App\User::testing()->toSql();
=> "select * from "users" where "testing" = ?"
>>>
So, for your code, this should do the trick:
因此,对于您的代码,这应该可以解决问题:
function scopeValid($query, $user_id) {
$query = $query->dateValid();
$query = $query->maxUsesValid();
$query = $query->customerMaxUsesValid($user_id);
return $query;
// or just return $query->dateValid()
// ->maxUsesValid()
// ->customerMaxUsesValid($user_id);
}
回答by bernie
Original answer
原答案
Query scopes are called statically.
查询范围被静态调用。
$users = Model::dateValid()->get()
There is no $this
when making static calls. Try replacing $this->scopeDateValid
with self::scopeDateValid
$this
进行静态调用时没有。尝试替换$this->scopeDateValid
为self::scopeDateValid
Revised answer
修改答案
There probably was something else wrong with your code since $this
is in fact a Model
instance when scopes are called. You should be able to either call the class scope methods directly with the $query
parameter (like you did) or use another chain of scope method resolution as proposed by ceejayoz.
您的代码可能还有其他问题,因为$this
实际上Model
是调用范围的实例。您应该能够直接使用$query
参数调用类范围方法(就像您所做的那样),或者使用ceejayoz 提出的另一个范围方法解析链。
Personally, I don't see much of an advantage in going through the whole query scope resolution process when you know you want to call the scope methods on your class, but either way works.
就我个人而言,当您知道要在类上调用作用域方法时,我认为在整个查询作用域解析过程中没有什么优势,但无论哪种方式都有效。
Analysis
分析
Let's walk through the call stack for executing query scopes:
让我们来看看用于执行查询范围的调用堆栈:
#0 [internal function]: App\User->scopeValid(Object(Illuminate\Database\Eloquent\Builder))
#1 /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(829): call_user_func_array(Array, Array)
#2 /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(940): Illuminate\Database\Eloquent\Builder->callScope('scopeOff', Array)
#3 [internal function]: Illuminate\Database\Eloquent\Builder->__call('valid', Array)
#4 [internal function]: Illuminate\Database\Eloquent\Builder->valid()
#5 /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(3482): call_user_func_array(Array, Array)
#6 [internal function]: Illuminate\Database\Eloquent\Model->__call('valid', Array)
#7 [internal function]: App\User->valid()
#8 /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(3496): call_user_func_array(Array, Array)
#9 /app/Http/Controllers/UserController.php(22): Illuminate\Database\Eloquent\Model::__callStatic('valid', Array)
#10 /app/Http/Controllers/UserController.php(22): App\User::valid()
#10 The User::scopeValid()
call
#10User::scopeValid()
电话
#8 __callStatic()
handler for Model
#8__callStatic()
处理程序Model
From the PHP docs on Method overloading:
public static mixed __callStatic ( string $name , array $arguments )
__callStatic() is triggered when invoking inaccessible methods in a static context.
公共静态混合 __callStatic (字符串 $name ,数组 $arguments )
在静态上下文中调用不可访问的方法时会触发 __callStatic()。
Annotated code of Model.php
's __callStatic()
method (lines 3492-3497):
Model.php
的__callStatic()
方法的注释代码(第 3492-3497 行):
public static function __callStatic($method, $parameters)
{
// Uses PHP's late static binding to create a new instance of the
// model class (User in this case)
$instance = new static;
// Call the $method (valid()) on $instance (empty User) with $parameters
return call_user_func_array([$instance, $method], $parameters);
}
#7 User->valid()
(which doesn't exist)
#7 User->valid()
(不存在)
#5 __call
handler for Model
#5__call
处理程序Model
Again, from the PHP docs on Method overloading:
public mixed __call ( string $name , array $arguments )
__call() is triggered when invoking inaccessible methods in an object context.
公共混合 __call (字符串 $name ,数组 $arguments )
__call() 在对象上下文中调用不可访问的方法时触发。
Annotated code of Model.php
's __call()
method (lines 3474-3483):
Model.php
的__call()
方法的注释代码(第 3474-3483 行):
public function __call($method, $parameters)
{
// increment() and decrement() methods are called on the Model
// instance apparently. I don't know what they do.
if (in_array($method, ['increment', 'decrement'])) {
return call_user_func_array([$this, $method], $parameters);
}
// Create a new \Illuminate\Database\Eloquent\Builder query builder
// initialized with this model (User)
$query = $this->newQuery();
// Call the $method (valid()) on $query with $parameters
return call_user_func_array([$query, $method], $parameters);
}
#2 __call
handler for the query Builder
#2__call
查询处理程序Builder
Annotated code of Builder.php
's __call()
method (lines 933-946):
Builder.php
的__call()
方法的注释代码(第 933-946 行):
public function __call($method, $parameters)
{
if (isset($this->macros[$method])) {
// Handle query builder macros (I don't know about them)
array_unshift($parameters, $this);
return call_user_func_array($this->macros[$method], $parameters);
} elseif (method_exists($this->model, $scope = 'scope'.ucfirst($method))) {
// Now we're getting somewhere! Builds the 'scopeValid' string from
// the original 'valid()' method call. If that method exists on the
// model, use it as a scope.
return $this->callScope($scope, $parameters);
}
// Other stuff for fallback
$result = call_user_func_array([$this->query, $method], $parameters);
return in_array($method, $this->passthru) ? $result : $this;
}
#1 callScope()
method of the query Builder
#1callScope()
查询方法Builder
Annotated code of Builder.php
's __call()
method (lines 825-830):
Builder.php
的__call()
方法的注释代码(第 825-830 行):
protected function callScope($scope, $parameters)
{
// Add $this (the query) as the first parameter
array_unshift($parameters, $this);
// Call the query $scope method (scopeValid) in the context of an
// empty User model instance with the $parameters.
return call_user_func_array([$this->model, $scope], $parameters) ?: $this;
}
回答by OskarD90
It should work as long as you pass a valid Query object. Maybe typehinting in your function signature will tell you what's wrong?Edit: bernie caught it
只要您传递有效的 Query 对象,它就应该可以工作。也许在你的函数签名中打字会告诉你出了什么问题?编辑:伯尼抓住了它
Slightly off-topic, here is what I like to do to make my code even more readable :)
稍微偏离主题,这是我喜欢做的事情,以使我的代码更具可读性:)
static function scopeValid($query, $user_id) {
return $query->scopeDateValid()
->scopeMaxUsesValid()
->scopeCustomerMaxUsesValid($user_id);
}
回答by GardenRouteGold
Another solution for if, for example: You have to call a Date Rangeon multiple scopes
if 的另一种解决方案,例如:您必须在多个范围内调用日期范围
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use DB;
class UserModel extends Model
{
public function scopeCreatedBetween(Builder $query, \DateTime $start, \DateTime $end) : Builder
{
return $query->whereBetween('created_date',[$start->format('Y-m-d H:i:s'),$end->format('Y-m-d H:i:s')]);
}
public function scopeBilledBetween(Builder $query, \DateTime $start, \DateTime $end) : Builder
{
return $query->whereBetween('billed_date',[$start->format('Y-m-d H:i:s'),$end->format('Y-m-d H:i:s')]);
}
public function scopeMonthToDate(Builder $query, string ...$scopes) : Builder
{
return $this->applyDateRangeScopes(
$query,
$scopes,
new \DateTime('first day of this month'),
\DateTime::createFromFormat('Y-m-d',date('Y-m-d'))->sub(new \DateInterval('P1D'))
);
}
/**
* Applies the scopes used for our date ranges
* @param Builder $query
* @param array $scopes
* @return Builder
*/
private function applyDateRangeScopes(Builder $query,array $scopes, \DateTime $from, \DateTime $to) : Builder
{
// If there are no scopes to apply just return the query
if(!(bool)$scopes) return $query;
// So we don't count each iteration
$scopeCount = count((array)$scopes);
for ($i=0; $i < $scopeCount; $i++) {
// Method does NOT exist
if( !method_exists($this,$scopes[$i]) ) continue;
// Apply the scope
$query = $this->{$scopes[$i]}($query,$from,$to);
}
return $query;
}
}
Usage:
用法:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\UserModel;
class User extends Controller
{
public function someAction(UserModel $user)
{
$user::scopeMonthToDate('scopeCreatedBetween','scopeCreatedBetween');
}
}