在Laravel中的whereHas模型调用中删除特定的全局范围

时间:2016-02-17 20:00:40

标签: php laravel scope

我想在 whereHas 调用中删除 EmpresaScope 全局范围:

// $tipoTreinamento is a Eloquent model

$tipoTreinamento->with(['funcaoEmpresa' => function($relation){
    // Here it works
    (new EmpresaScope())->remove($relation->getQuery(), $relation->getRelated());
}])
->whereHas('funcaoEmpresa', function($query) use ($colaborador){
    // Here it doesn't
    (new EmpresaScope())->remove($query, $query->getModel());

    $query->where('id', $colaborador['funcao_empresa_id']);
});

不幸的是,似乎 with whereHas 方法并没有以类似的方式使用它的闭包。

有人知道实现它的方法吗?

这是删除实现

class EmpresaScope implements ScopeInterface
{

 ...

    public function remove(Builder $builder, Model $model) 
    {
        // Reference https://laracasts.com/discuss/channels/eloquent/need-help-writing-remove-for-global-query-scope
        $query = $builder->getQuery();

        $bindingKey = 0;
        foreach ($query->wheres as $i => $where)
        {
            if ($where['column'] == $model['table'].'.empresa_id')
            {
                // Remove o where do scope
                unset($query->wheres[$i]);

                // Remove o valor que entraria no where
                $bindings = $query->getBindings();
                unset($bindings[$bindingKey]);
                $query->setBindings($bindings);

                break;
            }

            if ( ! in_array($where['type'], ['Null', 'NotNull'])) $bindingKey++;
        }
    }

}

2 个答案:

答案 0 :(得分:1)

我能够通过覆盖相关方法来解决这个问题。我已经创造了一个易于使用的特性。

<?php

namespace App;

use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

trait UnscopedRelations
{
    /**
     * Define an inverse one-to-one or many relationship.
     *
     * @param  string  $related
     * @param  string  $foreignKey
     * @param  string  $otherKey
     * @param  string  $relation
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null, array $removeScopes = [])
    {
        // If no relation name was given, we will use this debug backtrace to extract
        // the calling method's name and use that as the relationship name as most
        // of the time this will be what we desire to use for the relationships.
        if (is_null($relation)) {
            list($current, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);

            $relation = $caller['function'];
        }

        // If no foreign key was supplied, we can use a backtrace to guess the proper
        // foreign key name by using the name of the relationship function, which
        // when combined with an "_id" should conventionally match the columns.
        if (is_null($foreignKey)) {
            $foreignKey = Str::snake($relation).'_id';
        }

        $instance = new $related;

        // Once we have the foreign key names, we'll just create a new Eloquent query
        // for the related models and returns the relationship instance which will
        // actually be responsible for retrieving and hydrating every relations.
        $query = $instance->newQueryWithoutScopes();

        foreach ($instance->getGlobalScopes() as $identifier => $scope) {
            if(in_array($scope, $removeScopes)) {
                continue;
            }

            $query->withGlobalScope($identifier, $scope);
        }

        $otherKey = $otherKey ?: $instance->getKeyName();

        return new BelongsTo($query, $this, $foreignKey, $otherKey, $relation);
    }

    /**
     * Define a one-to-many relationship.
     *
     * @param  string  $related
     * @param  string  $foreignKey
     * @param  string  $localKey
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function hasMany($related, $foreignKey = null, $localKey = null, array $removeScopes = [])
    {
        $foreignKey = $foreignKey ?: $this->getForeignKey();

        $instance = new $related;

        $query = $instance->newQueryWithoutScopes();

        foreach ($instance->getGlobalScopes() as $identifier => $scope) {
            if(in_array($scope, $removeScopes)) {
                continue;
            }

            $query->withGlobalScope($identifier, $scope);
        }

        $localKey = $localKey ?: $this->getKeyName();

        return new HasMany($query, $this, $instance->getTable().'.'.$foreignKey, $localKey);
    }

    /**
     * Define a many-to-many relationship.
     *
     * @param  string  $related
     * @param  string  $table
     * @param  string  $foreignKey
     * @param  string  $otherKey
     * @param  string  $relation
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function belongsToMany($related, $table = null, $foreignKey = null, $otherKey = null, $relation = null, array $removeScopes = [])
    {
        // If no relationship name was passed, we will pull backtraces to get the
        // name of the calling function. We will use that function name as the
        // title of this relation since that is a great convention to apply.
        if (is_null($relation)) {
            $relation = $this->getBelongsToManyCaller();
        }

        // First, we'll need to determine the foreign key and "other key" for the
        // relationship. Once we have determined the keys we'll make the query
        // instances as well as the relationship instances we need for this.
        $foreignKey = $foreignKey ?: $this->getForeignKey();

        $instance = new $related;

        $otherKey = $otherKey ?: $instance->getForeignKey();

        // If no table name was provided, we can guess it by concatenating the two
        // models using underscores in alphabetical order. The two model names
        // are transformed to snake case from their default CamelCase also.
        if (is_null($table)) {
            $table = $this->joiningTable($related);
        }

        // Now we're ready to create a new query builder for the related model and
        // the relationship instances for the relation. The relations will set
        // appropriate query constraint and entirely manages the hydrations.
        $query = $instance->newQueryWithoutScopes();

        foreach ($instance->getGlobalScopes() as $identifier => $scope) {
            if(in_array($scope, $removeScopes)) {
                continue;
            }

            $query->withGlobalScope($identifier, $scope);
        }

        return new BelongsToMany($query, $this, $table, $foreignKey, $otherKey, $relation);
    }
}

在模型中,只需在构建关系时包含修改范围所需的特征。

class TipoTreinamento extends Model
{
    use UnscopedRelations;

    public function FuncaoEmpresa()
    {
        return $this->hasMany(FuncaoEmpresa::class, null, null, null, [new EmpresaScope]);
    }
}

这是如何工作的是您在belongsTohasManyhasOnebelongsToMany函数的最后一个参数中传递的范围,这些范围将从查询。

我建议您创建其他相关方法,例如FuncaoEmpresaWithoutEmpressaScope(),然后在需要的with方法中传递该函数的名称。

没有任何全局范围

我在您的评论中看到您试图删除所有全局范围。如果您希望这样做而不需要传递一系列要移除的范围,那么很容易做出改变。

在特征中的每个函数中,您将看到一个看起来像这样的部分......

        $query = $instance->newQueryWithoutScopes();

        foreach ($instance->getGlobalScopes() as $identifier => $scope) {
            if(in_array($scope, $removeScopes)) {
                continue;
            }

            $query->withGlobalScope($identifier, $scope);
        }

您应该能够删除foreach循环部分,并且每次只创建一个没有全局范围的新查询,您将不再需要传递数组。

答案 1 :(得分:1)

你可以这样做:

$tipoTreinamento->with(['funcaoEmpresa' => function($relation){
    // Here it works
    (new EmpresaScope())->remove($relation->getQuery(), $relation->getRelated());
}])
->whereHas('funcaoEmpresa', function($query) use ($colaborador){
    $query->withoutGlobalScope('EmpresaScope')
        ->where('id', $colaborador['funcao_empresa_id']);
});

$query 中的 whereHas 变量只是一个 queryBuilder 实例,您可以对其执行 withoutGlobalScope。它有效,因为我必须在我的项目中使用它,我认为它不适用于 OP 案例,因为它是旧版本