你可以在laravel中创建一个调用各种其他范围的范围吗?

时间:2015-09-21 12:45:45

标签: php laravel laravel-5.1

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

function scopeValid($query, $user_id) {
    $query = $this->scopeDateValid($query);
    $query = $this->scopeMaxUsesValid($query);
    $query = $this->scopeCustomerMaxUsesValid($query, $user_id);
    return $query;
}

这似乎不起作用,有没有办法实现这个目标?

4 个答案:

答案 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()

#10 User::scopeValid()来电

__callStatic()

的#8 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);
}

#7 User->valid()(不存在)

__call

的#5 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

#2 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()

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