Php 您能在laravel中创建一个调用各种其他作用域的作用域吗?
我在Laravel中有一个模型,它定义了各种范围。我想在很多地方都使用它们,所以与其将它们链接在一起,不如只调用一个调用所有其他作用域的作用域,如下所示:Php 您能在laravel中创建一个调用各种其他作用域的作用域吗?,php,laravel,laravel-5.1,Php,Laravel,Laravel 5.1,我在Laravel中有一个模型,它定义了各种范围。我想在很多地方都使用它们,所以与其将它们链接在一起,不如只调用一个调用所有其他作用域的作用域,如下所示: function scopeValid($query, $user_id) { $query = $this->scopeDateValid($query); $query = $this->scopeMaxUsesValid($query); $query = $this->scopeCustome
function scopeValid($query, $user_id) {
$query = $this->scopeDateValid($query);
$query = $this->scopeMaxUsesValid($query);
$query = $this->scopeCustomerMaxUsesValid($query, $user_id);
return $query;
}
但这似乎不起作用,有没有办法做到这一点?只要您传递一个有效的查询对象,它就会起作用。也许函数签名中的类型提示会告诉您出了什么问题?编辑:伯尼抓到了
有点离题,下面是我想做的事情,以使我的代码更具可读性:)
原始答案
查询范围是静态调用的
$users = Model::dateValid()->get()
进行静态调用时没有$this
。尝试将$this->scopeDateValid
替换为self::scopeDateValid
订正答复
您的代码可能还有其他问题,因为调用作用域时,$this
实际上是一个模型
实例。您应该能够使用$query
参数直接调用类作用域方法(就像您所做的那样),或者使用另一个作用域方法链作为解析
就我个人而言,当您知道要在类上调用scope方法时,我不认为在整个查询范围解析过程中有什么好处,但这两种方法都可以
分析
让我们浏览一下执行查询范围的调用堆栈:
#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调用User::scopeValid()
#8\uu callStatic()
模型的处理程序
从:
公共静态混合调用静态(字符串$name,数组$arguments)
__在静态上下文中调用不可访问的方法时会触发callStatic()
Model.php
的\uu callStatic()
方法的注释代码(第3492-3497行):
#7User->valid()
(不存在)
#5\u调用模型的处理程序
同样,从以下方面:
公共混合调用(字符串$name,数组$arguments)
__调用对象上下文中不可访问的方法时会触发call()
Model.php
的\u call()
方法的注释代码(第3474-3483行):
#2\u调用查询Builder
Builder.php
的\u call()
方法的注释代码(第933-946行):
#1callScope()
查询的方法Builder
Builder.php
的\u call()
方法的注释代码(第825-830行):
如中所示,您需要针对$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);
}
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;
}
}
我从来没有这样做过,但我希望它能起作用。它会产生错误的查询还是抛出异常?抛出异常-我得到一个成员函数的调用,其中()位于null上。我认为这个答案不正确。文档,而不是公共静态函数
。我认为你把外观(看起来是静态的,但不是)和静态混淆了。根据链接的文档,您应该使用$query
而不是$this
来操作传递给scope函数的查询生成器。看看我的答案@bernie@ceejayoz确实如此。我用扩展分析修改了我的答案,这应该是最好的答案。
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;
}
public function scopeTesting($query) {
return $query->testingTwo();
}
public function scopeTestingTwo($query) {
return $query->where('testing', true);
}
>>> 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);
}
<?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');
}
}