Php 您能在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

我在Laravel中有一个模型,它定义了各种范围。我想在很多地方都使用它们,所以与其将它们链接在一起,不如只调用一个调用所有其他作用域的作用域,如下所示:

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行):

#7
User->valid()
(不存在) #5
\u调用
模型的处理程序
同样,从以下方面:

公共混合调用(字符串$name,数组$arguments)

__调用对象上下文中不可访问的方法时会触发call()

Model.php
\u call()
方法的注释代码(第3474-3483行):

#2
\u调用查询
Builder
Builder.php
\u call()
方法的注释代码(第933-946行):

#1
callScope()
查询的方法
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');
    }
}