你能在 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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 12:22:51  来源:igfitidea点击:

Can you make a scope in laravel that calls various other scopes?

phplaravellaravel-5.1

提问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 $thiswhen making static calls. Try replacing $this->scopeDateValidwith self::scopeDateValid

$this进行静态调用时没有。尝试替换$this->scopeDateValidself::scopeDateValid

Revised answer

修改答案

There probably was something else wrong with your code since $thisis in fact a Modelinstance when scopes are called. You should be able to either call the class scope methods directly with the $queryparameter (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:

来自方法重载PHP 文档

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 __callhandler for Model

#5__call处理程序Model

Again, from the PHP docs on Method overloading:

再次,从关于方法重载PHP 文档

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 __callhandler 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');
    }
}