【问题标题】:Can you make a scope in laravel that calls various other scopes?你可以在 laravel 中创建一个调用各种其他作用域的作用域吗?
【发布时间】:2016-11-11 17:19:58
【问题描述】:

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

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

这似乎不起作用,有没有办法做到这一点?

【问题讨论】:

  • 我从来没有这样做过,但我希望它能够工作。它会产生错误的查询或抛出异常吗?
  • 抛出异常 - 我调用一个成员函数 where() on null

标签: php laravel laravel-5.1


【解决方案1】:

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

【讨论】:

  • 这应该是最好的答案。
【解决方案2】:

原答案

查询范围是静态调用的。

$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() 通话

#8 __callStatic() Model 的处理程序

来自PHP docs on Method overloading

公共静态混合__callStatic(字符串$name,数组$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()(不存在)

#5 __call Model 的处理程序

再次,来自PHP docs on Method overloading

公共混合__call(字符串$name,数组$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);
}

#2 __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;
}

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

【讨论】:

  • 我认为这个答案不正确。文档shows public function scopePopular(,而不是public static function。我认为您将外观(看起来是静态的,但不是)与静态混淆了。根据链接的文档,您应该使用 $query 而不是 $this 尽管对传递给范围函数的查询生成器进行操作。看看我的回答@bernie。
  • @ceejayoz 确实如此。我通过扩展分析修改了我的答案
【解决方案3】:

只要你传递一个有效的 Query 对象,它就应该可以工作。也许在你的函数签名中输入提示会告诉你出了什么问题?编辑:伯尼抓住了它

有点跑题了,这是我喜欢做的让我的代码更具可读性的事情:)

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多