Методы Пользовательской Модели Laravel
Всякий раз, когда я добавляю дополнительную логику к красноречивым моделям, мне приходится делать это методом static
(т. е. менее идеальным), чтобы вызвать его с фасада модели. Я много раз пытался найти, как это сделать правильно, и почти все результаты говорят о создании методов, которые возвращают части интерфейса построителя запросов. Я пытаюсь понять, как добавить методы, которые могут возвращать что угодно и вызываться с использованием фасада модели.
Например, предположим, что у меня есть модель называется Car
и хотите получить их все:
$cars = Car::all();
Отлично, но сейчас, скажем, я хочу отсортировать результат в многомерный массив по make, чтобы мой результат выглядел следующим образом:
$cars = array(
'Ford' => array(
'F-150' => '...',
'Escape' => '...',
),
'Honda' => array(
'Accord' => '...',
'Civic' => '...',
),
);
Взяв этот теоретический пример, я испытываю искушение создать метод, который можно было бы назвать так:
$cars = Car::getAllSortedByMake();
На мгновение забудем ужасное название метода и тот факт, что он тесно связан со структурой данных. Если я сделаю метод, подобный этому в модель:
public function getAllSortedByMake()
{
// Process and return resulting array
return array('...');
}
И, наконец, вызовите его в моем контроллере, я получу это исключение:
Нестатический метод Car:: getAllSortedByMake () не следует вызывать статически, предполагая $this из несовместимого контекста
TL; DR : Как я могу добавить пользовательскую функциональность, которая имеет смысл быть в модели, не делая ее статическим методом и не вызывая ее, используя фасад модели?
Правка:
Это теоретический пример. Возможно перефразирование вопроса имело бы больше смысла. Почему некоторые нестатические методы, такие какall()
или which()
, доступны на фасаде красноречивой модели, но не дополнительные методы, добавленные в модель? Это означает, что используется магический метод __call
, но как я могу заставить его распознавать мои собственные функции в модели?
Вероятно, лучший пример по сравнению с "сортировкой" - это если мне нужно запустить вычисление или алгоритм на фрагменте данных:
$validSPG = Chemical::isValidSpecificGravity(-1.43);
Для меня это имеет смысл для что-то вроде этого должно быть в модели, поскольку она специфична для предметной области.
3 ответа:
Мой вопрос находится на более фундаментальном уровне, например, почему все() доступ через фасад?
Если вы посмотрите на ядро Laravel-all() на самом деле статическая функция
public static function all($columns = array('*'))
У вас есть два варианта:
public static function getAllSortedByMake() { return Car::where('....')->get(); }
Или
public function scopeGetAllSortedByMake($query) { return $query->where('...')->get(); }
И то и другое позволит вам сделать
Car::getAllSortedByMake();
Для лучшего динамического кода, вместо использования имени класса модели "Car",
Просто используйте "static"или" self "
public static function getAllSortedByMake() { //to return "Illuminate\Database\Query\Builder" class object you can add another where as you want return static::where('...'); //or return already as collection object return static::where('...')->get(); }
На самом деле вы можете расширить Eloquent Builder и поместить туда пользовательские методы.
Шаги по расширению builder:
1.Создать пользовательский конструктор
<?php namespace App; class CustomBuilder extends \Illuminate\Database\Eloquent\Builder { public function test() { $this->where(['id' => 1]); return $this; } }
2.Добавьте этот метод в базовую модель:
public function newEloquentBuilder($query) { return new CustomBuilder($query); }
3.Выполните запрос с помощью методов внутри пользовательского конструктора:
User::where('first_name', 'like', 'a') ->test() ->get();
Для приведенного выше кода сгенерированный mysql запрос будет иметь вид:
select * from `users` where `first_name` like ? and (`id` = ?) and `users`.`deleted_at` is null
PS:
Первый пример Laurence - это код, более подходящий для вас репозиторий не для модели, но и вы не можете передать больше методов с этим подходом:
public static function getAllSortedByMake() { return Car::where('....')->get(); }
Второй пример Лоуренса - событие худшее.
public function scopeGetAllSortedByMake($query) { return $query->where('...')->get(); }
Многие люди предлагают использовать области для расширения laravel builder, но это на самом деле плохое решение, потому что области изолированы eloquent builder, и вы не получите тот же запрос с теми же командами внутри и вне области. Я предложил PR для изменения того, следует ли изолировать scopes, но Тейлор проигнорировал меня.
Еще объяснение : Например, если у вас есть такие области, как эта :
public function scopeWhereTest($builder, $column, $operator = null, $value = null, $boolean = 'and') { $builder->where($column, $operator, $value, $boolean); }
И два красноречивых вопроса:
User::where(function($query){ $query->where('first_name', 'like', 'a'); $query->where('first_name', 'like', 'b'); })->get();
Vs
User::where(function($query){ $query->where('first_name', 'like', 'a'); $query->whereTest('first_name', 'like', 'b'); })->get();
Генерируемые запросы будут:
select * from `users` where (`first_name` like ? and `first_name` like ?) and `users`.`deleted_at` is null
Vs
select * from `users` where (`first_name` like ? and (`id` = ?)) and `users`.`deleted_at` is null
На первый взгляд запросы выглядят одинаково, но их нет. Для этого простого запроса, возможно, это не имеет значения, но для сложных запросов это так, поэтому, пожалуйста, не используйте области для расширения builder:)