Что касается моего предыдущего вопроса, я узнал, что из-за допущенной мной ошибки Laravel генерирует неправильный SQL-запрос:
select * from "companies" where "companies"."id" = '9c54986f-8284-4da9-b826-c7a723de279b' and "companies"."deleted_at" is null and "company_id" = '9c54986f-8284-4da9-b826-c7a723de279b'
Проблема здесь в том, что company_id
не существует в companies
; однако запрос не генерирует ошибку при запуске, он просто не возвращает результата.
Полагаю, проблема здесь в том, что "company_id"
рассматривается как литерал, а не как ссылка на столбец; если я удалю кавычки, я получу правильную ошибку:
Error: in prepare, no such column: company_id (1)
Я также получаю правильную ошибку, если добавляю префикс таблицы к имени столбца:
sqlite> select * from "companies" where "companies"."id" = '9c54986f-8284-4da9-b826-c7a723de279b' and "companies"."deleted_at" is null and "companies"."compa
ny_id" = '9c54986f-8284-4da9-b826-c7a723de279b';
Error: in prepare, no such column: companies.company_id (1)
Есть ли способ решить эту проблему, воздействуя на конфигурацию Laravel или SQLite? Я не могу изменить способ генерации запросов, поскольку они генерируются самой платформой.
Кроме того, я НЕ спрашиваю, почему этот конкретный запрос ведет себя именно так, это мне уже было ясно.
Фрагмент and "company_id" = '9c54986f-8284-4da9-b826-c7a723de279b'
генерируется глобальной областью видимости, реализованной следующим образом:
abstract readonly class UnlessAuthorizedScope implements Scope {
public function __construct(
private string $modelField,
protected ?string $authorizingPermission,
private string $userField,
) {}
public function apply(Builder $builder, Model $model): void {
if (Auth::hasUser()) {
$user = Auth::user();
if (
!$this->authorizingPermission
|| !$user?->can($this->authorizingPermission)
) {
$builder->where(
$this->modelField,
$user?->{$this->userField}
);
}
}
}
}
который затем реализуется в:
readonly class CurrentCompanyScope extends UnlessAuthorizedScope {
public function __construct(?string $authorizingPermission = null, ?string $modelField = null) {
parent::__construct(
$modelField ?? "company_id",
$authorizingPermission,
"company_id"
);
}
}
и, наконец, используется как:
class Company extends Model {
protected static function booted(): void {
parent::booted();
static::addGlobalScope(new CurrentCompanyScope(
CompanyPermission::ViewAll->value,
// the error was here, instead of specifying "id", I kept the default "company_id" value
));
}
}
🤔 А знаете ли вы, что...
PHP имеет встроенную поддержку сессий для отслеживания состояния пользователей на веб-сайтах.
Я думаю, что здесь происходит то, что "company_id"
интерпретируется как строковый литерал, а не как столбец. Имейте в виду, что SQLite принимает строковые литералы как в одинарных, так и в двойных кавычках. Судя по всему, эвристика SQLite для интерпретации строк в двойных кавычках такова:
Вот ваш запрос еще раз в формате:
SELECT *
FROM "companies"
WHERE
"companies"."id" = '9c54986f-8284-4da9-b826-c7a723de279b' AND
"companies"."deleted_at" IS NULL AND
"company_id" = '9c54986f-8284-4da9-b826-c7a723de279b';
Поскольку столбец company_id
не существует, этот запрос интерпретируется как:
SELECT *
FROM "companies"
WHERE
"companies"."id" = '9c54986f-8284-4da9-b826-c7a723de279b' AND
"companies"."deleted_at" IS NULL AND
'company_id' = '9c54986f-8284-4da9-b826-c7a723de279b';
Конечно, сравнение строк в последнем члене предложения WHERE
никогда не будет истинным, поэтому никакие записи не возвращаются.
Нет необходимости заключать столбцы в двойные кавычки, поэтому не используйте их, чтобы избежать проблем такого рода.
Мне удалось решить эту проблему, изменив реализацию моей базовой глобальной области:
abstract readonly class UnlessAuthorizedScope implements Scope {
public function __construct(
private string $modelField,
protected ?string $authorizingPermission,
private string $userField,
) {}
public function apply(Builder $builder, Model $model): void {
if (Auth::hasUser()) {
$user = Auth::user();
if (
!$this->authorizingPermission
|| !$user?->can($this->authorizingPermission)
) {
$builder->where(
$model->getTable().".".$this->modelField, // changed here
$user?->{$this->userField}
);
}
}
}
}
добавив явный префикс таблицы к имени поля, он сохранялся в запросе, сгенерированном платформой, который затем стал:
select * from "companies" where "companies"."id" = '9c54986f-8284-4da9-b826-c7a723de279b' and "companies"."deleted_at" is null and "companies"."compa
ny_id" = '9c54986f-8284-4da9-b826-c7a723de279b'
и этот новый запрос сгенерировал правильное исключение, которое было перехвачено платформой и отображено на странице.