我在Laravel中有一个模型,它定义了各种范围。我想在很多地方使用它们而不是将它们链接在一起我宁愿只能调用一个调用所有其他范围的范围,如下所示:
function scopeValid($query, $user_id) {
$query = $this->scopeDateValid($query);
$query = $this->scopeMaxUsesValid($query);
$query = $this->scopeCustomerMaxUsesValid($query, $user_id);
return $query;
}
这似乎不起作用,有没有办法实现这个目标?
答案 0 :(得分:13)
如the docs所示,您希望对$query
进行范围界定,而不是$this
,并使用范围的魔术功能而不是调用内部实施:
public function scopeTesting($query) {
return $query->testingTwo();
}
public function scopeTestingTwo($query) {
return $query->where('testing', true);
}
作为演示,您可以在此处看到调用testing()
范围应用testingTwo()
范围内的逻辑:
>>> App\User::testing()->toSql();
=> "select * from "users" where "testing" = ?"
>>>
因此,对于您的代码,这应该可以解决问题:
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);
}
答案 1 :(得分:6)
查询范围是静态调用的。
$users = Model::dateValid()->get()
进行静态调用时没有$this
。尝试将$this->scopeDateValid
替换为self::scopeDateValid
您的代码可能存在其他问题,因为$this
实际上是调用范围时的Model
实例。您应该能够使用$query
参数直接调用类范围方法(就像您所做的那样),或者使用另一个范围方法解析链作为proposed by ceejayoz。
就个人而言,当您知道要在课堂上调用范围方法时,我在整个查询范围解析过程中看不到很多优势,但无论哪种方式都有效。
让我们遍历调用堆栈以执行查询范围:
#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()
User::scopeValid()
来电__callStatic()
Model
处理程序
来自PHP docs on Method overloading:
public static mixed __callStatic(string $ name,array $ arguments)
在静态上下文中调用不可访问的方法时会触发__ callStatic()。
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);
}
User->valid()
(不存在)__call
Model
处理程序
再次,来自PHP docs on Method overloading:
public mixed __call(string $ name,array $ arguments)
在对象上下文中调用不可访问的方法时会触发__ call()。
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);
}
查询__call
的Builder
处理程序
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;
}
查询callScope()
的Builder
方法
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;
}
答案 2 :(得分:2)
只要传递有效的Query对象,它就应该有效。也许你的功能签名中的输入会告诉你出了什么问题?编辑:bernie抓住了它
稍微偏离主题,这就是我喜欢做的事情,以使我的代码更具可读性:)
static function scopeValid($query, $user_id) {
return $query->scopeDateValid()
->scopeMaxUsesValid()
->scopeCustomerMaxUsesValid($user_id);
}
答案 3 :(得分:0)
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;
}
}
<强>用法强>:
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');
}
}