Я разрабатываю приложение в Laravel 5.4 и мне нужен global scope, который позволит мне фильтровать различные элементы приложения в зависимости от пользователя, который их создал.
У меня есть класс BaseEloquentModel.php, который расширяет Eloquent, и все мои модели происходят от этого класса. У меня есть global scope следующим образом:
protected static function boot()
{
parent::boot();
static::addGlobalScope('', function(\Illuminate\Database\Eloquent\Builder $builder) use($userId) {
/**
* I get the name of the table with <code>(with(new static))->getTable()</code>
* and then filter the query for the <b>user_id</b> field
*/
$builder->where(
(with(new static))->getTable() . '.user_id',
'=',
$userId
);
});
}
Когда у меня есть такой запрос с оператором or, глобальная область действия «нейтрализуется»:
$qBuilderCars = Car::whereRaw("name like ? or id = ?", [
'%' . $searchCriteria. '%',
$searchCriteria
]);
Если я вызову метод toSql() для $qBuilderCars, я увижу, что он "правильно" добавляет оператор AND в конец запроса.
select * from `cars` where name like ? or id = ? and user_id = ?
Возможно, вы уже заметили мою проблему... Если построитель элемента, в данном случае cars, использовал оператор OR, то глобальная область видимости не поможет, так как между where name like ? or id = ? нет скобки. Таким образом, результирующий запрос будет примерно таким:
select * from `cars` where name like ? (or id = ? and user_id = ?)
Таким образом, этот запрос вернет все автомобили, чье имя совпадает или чей идентификатор совпадает с полученным и созданным пользователем...
Когда мне нужно:
select * from `cars` where (name like ? or id = ?) and user_id = ?
Я попытался изменить свой global scope, чтобы попытаться сделать оператор AND, который я добавляю, наиболее ограничительным в запросе, но безуспешно.
Я не могу вручную добавлять скобки ко всем запросам приложения, поэтому... Есть ли способ добавить глобальные скобки из глобальной области видимости в builder?
Решение состоит в том, чтобы добавить круглые скобки ко всем необработанным запросам.
✅✅ Вы можете посмотреть @Стикс решение который я считаю самым удачным
Я также буду использовать оставить мой ответ, который действует непосредственно внутри global scope и который я считаю интересным, чтобы увидеть, как работает объект \Illuminate\Database\Eloquent\Builder.
Есть Laravel 5.4 и скобки не добавляются автоматически :-S
@ yrv16 Не могли бы вы показать мне документацию об этом?
Я установил 5.4, и действительно в запросе нет скобок, как в версии 5.2. Я нашел информацию о том, что реализация глобальной области видимости была переписана. laravel.com/docs/5.3/обновление
@ yrv15 Прежде всего, спасибо за ваше время. Вау, как жаль, что Laravel 5.4 глобальная область видимости изменилась. Я думаю, что единственное, что я могу сделать, это попытаться увидеть, как они ввели эти круглые скобки, чтобы воспроизвести их в моем приложении...
Пожалуйста, если я найду решение, я отвечу.
@yrv16 Спасибо. Давайте посмотрим, если между ними мы найдем решение. В любом случае, вы можете переместить свой комментарий в ответ, чтобы я мог его оценить :-)
Какую именно версию Laravel вы используете?
@Стикс Ларавель 5.4
Вы имеете в виду 5.4.0?
@Styx Прежде всего, спасибо за ваше время. Я думаю, что из Laravel 5.4 есть только две версии 5.4 и 5.4.22, последняя исправляет уязвимость. Я использую 5.4.22.
Покажите, пожалуйста, как именно вы определяете свою глобальную область видимости, и какой именно код неправильный SQL. Я просто не могу воспроизвести эту ошибку.
@Styx, я обновил свой ответ. Если вам нужны другие детали, пожалуйста, спросите меня.
Вы показали, как вы "нейтрализуете", но какой код выдает неправильно SQL?
Мы не смогли воспроизвести это из-за whereRaw(). С where('name', 'like', '%'.$searchCriteria.'%')->orWhere('id', $searchCriteria) Laravel автоматически добавляет скобки. Я предполагаю, что вы должны использовать whereRaw()?
@Styx @Jonas Staudenmeir Я использую whereRaw, пожалуйста, смотрите мой $qBuilderCars в моем вопросе
@Styx, Йонас Стауденмейр Не могли бы вы высказать свое мнение об ответе, который я опубликовал на свой вопрос? Очевидно, это работает; но я не знаю, может ли это иметь последствия, которые я не принимаю во внимание.
@tomloprod Почему бы вам просто не использовать обычные where и orWhere вместо whereRaw?
@Styx Я применяю это global scope в очень большом приложении со слишком большим количеством whereRaw, поэтому я не могу их изменить и проверить стабильность приложения. Кроме того, нет автоматических тестов ☹...






Я нашел «решение», прежде чем применить свою глобальную область действия, я перебираю все предложения where, тип которых raw:
protected static function boot()
{
parent::boot();
static::addGlobalScope('', function(\Illuminate\Database\Eloquent\Builder $builder) use($userId) {
/**
* My workaround to prevent a raw query from neutralizing the global scope.
* We go through all the queries and, if they are raw, encapsulate them in parentheses.
*/
$wheres = $builder->getQuery()->wheres;
foreach ($wheres as $iWhere => $where) {
// If where clause is "raw" I wrap with parenthesis.
if ($where['type'] == 'raw') {
$builder->getQuery()->wheres[$iWhere]["sql"] = "({$where['sql']})";
}
}
/**
* I get the name of the table with <code>(with(new static))->getTable()</code>
* and then filter the query for the <b>user_id</b> field
*/
$builder->where(
(with(new static))->getTable() . '.user_id',
'=',
$userId
);
});
}
Я не знаю, будет ли это решение иметь какие-либо неожиданные последствия или сильно повлияет на производительность запросов...
Вернет следующий SQL:
select * from `cars` where (name like ? or id = ?) and `cars`.`user_id` = ?"
Я не вижу явных проблем. Конечно, это не очень элегантно, но, вероятно, лучший обходной путь для вашей ситуации. Вы можете упростить это с помощью ссылки: foreach($wheres as &$where) { [...] $where["sql"] = [...]
@JonasStaudenmeir Да, это не очень элегантно, я хотел бы найти другое решение, которое решает эту проблему. ? Что касается передачи референса, то это была моя первая попытка; но почему-то не работает. (Я только что попробовал еще раз с вашим кодом комментария)
@JonasStaudenmeir Я согласен с тобой. Хотя преимущество, которое я вижу в этой системе (или любой другой, который автоматически добавляет скобки), заключается в том, что даже если какой-то программист сделает ошибку и не использует их в своих whereRaw, global scope продолжит работать. Вам не кажется? Может быть, есть какой-то способ добиться того же результата; но красноречиво...
ИМО, вы никогда не должны иметь что-то вроде whereRaw("name like ? or id = ?") без круглых скобок в вашем приложении. В принципе, вы не можете добавить больше ограничений к этому запросу, не нарушая его.
@JonasStaudenmeir Я согласен с тобой. К сожалению, не все знают об этом, и, возможно, такой код может служить «барьером» для того, чтобы плохая практика повлияла на что-то столь важное, как global scope, верно? Кстати, за ваш вклад в Laravel я вижу, что вы хорошо понимаете, как работает eloquent. Считаете ли вы это workaround чем-то подходящим для использования в производственной среде? Можете ли вы придумать какой-либо другой более элегантный/эффективный способ? P.S.: Спасибо за ваше время.
Ну, кажется, что ваше решение по добавлению круглых скобок - лучший обходной путь, но у меня есть предложение, как сделать это немного лучше.
Создайте новый класс QueryBuilder. Например, в пространстве имен \App\Models\ (папке app/Models/):
namespace App\Models;
use Illuminate\Database\Query\Builder as EloquentQueryBuilder;
class QueryBuilder extends EloquentQueryBuilder {
public function whereRaw($sql, $bindings = [], $boolean = 'and')
{
return parent::whereRaw('('.$sql.')', $bindings, $boolean);
}
}
Добавьте этот код в свой класс BaseEloquentModel:
use Illuminate\Database\Eloquent\Model;
use App\Models\QueryBuilder; // <-- addition
class BaseEloquentModel extends Model {
// ...
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new QueryBuilder(
$connection,
$connection->getQueryGrammar(),
$connection->getPostProcessor()
);
}
// ...
}
Теперь все вызовы whereRaw() будут автоматически заключаться в круглые скобки вокруг запроса.
+1 Кажется, интересный способ сделать это! Единственное, чего мне не хватает, так это того, что вы не охватили бы другие eloquent методы, такие как orWhereRaw(). Я понимаю, что с моим решением, находящимся в глобальной области действия моего BaseEloquentModel, оно будет применяться ко всем необработанным запросам, независимо от того, как они были сгенерированы, верно?
@tomloprod orWhereRaw звонит whereRaw внутренне, так что это тоже будет учтено :)
@tomloprod Извините, перепутал с объяснением. Оба наших метода будут применяться ко всем моделям, расширенным из BaseEloquentModel, но мой не требует затрат на foreach/modify.
Вы закончили убеждать меня. И, используя это решение, я не чувствую себя таким виноватым из-за того, что выполняю запросы в цикле ? Я попробую ваше решение через несколько минут. Спасибо!
@tomloprod Кстати, если ваш код использует havingRaw, вы также можете переопределить этот метод.
Работает как шарм! Вы знаете, если ->select(DB::raw("...")) вызвать whereRaw внутренне? Что меня беспокоит, так это возможность того, что приложение использует какой-то другой метод, например whereRaw или havingRaw, и что по незнанию я не переопределил его в своем «CustomQueryBuilder».
@tomloprod select() определенно нет вызывает любой whereRaw() внутренне :) И вам не следует беспокоиться о select() или DB:raw(), этот код не повлияет на них (и не должен).
Я запутался, когда писал этот комментарий. Слишком много движений между иностранным кодом сегодня ?? Я вас больше не беспокою, последний вопрос. Знаете ли вы какой-либо другой метод, такой как whereRaw или havingRaw, который может «изменять» запросы необработанным образом?
@tomloprod Да, есть orderByRaw, но это не должно вызывать у вас таких проблем со скобками. Ваша проблема только с where, верно?
Да, я так думаю. По крайней мере, пока...? Спасибо за все ваше время. Я предпочитаю ваш ответ моему, поэтому я отмечу его как принятый. :-)
Какая версия ларавеля? Потому что в laravel 5.2 скобки добавляются по мере необходимости по умолчанию, когда применяется глобальная область.