В последнее время я работаю над несколькими проектами, в которых используются полиморфные отношения, отношения многие-ко-многим и иногда полиморфные отношения многие-ко-многим. При работе с ними стали появляться одни и те же проблемы, которые действительно раздражают меня с точки зрения хорошей архитектуры и традиционных отношений ORM. Поэтому начиная с этой недели, мы будем рассматривать новые шаблоны для создания карт отношений. Сегодня мы рассмотрим стандартные таблицы с отношением многие-ко-многим и их связующую сводную таблицу.
Рассмотрим для примера беговой счетчик. Пользователь может вводить день, в котором может быть несколько кругов различных дорожек или маршрутов. Вы подумаете, что это относительно просто, и создадите такую схему:
days: date temperature # больше информации routes: gis_points public distance title # больше полей day_route: # это будет наша сводная (pivot) таблица многие-ко-многим day_id routes_id laps
Это работает, но все станет более странным, когда вы захотите вычислить общую дистанцию за день. Что ж, вы можете сделать что-нибудь подобное (реализация на псевдо-PHP, используя Eloquent):
public function getTotalDistanceAttribute()
{
// Создает ассоциативный массив, в котором каждый $lap => $distance
$routes = $this->routes()->list('laps', 'distance');
$total = 0;
foreach($routes as $laps => $distance) {
$total += ($laps * $distance);
}
return $total;
}
Это работает, но похоже что PHPDay
немного больше чем нужно. Например, что будет, если я захочу узнать общую дистанцию за день на одном из маршрутов? Вы можете добавить пользовательские методы доступа (accessors), но куда вы поместите эту логику? В модель маршрута? В пользовательскую службу (service)? Это кажется совсем странным.
У нас уже есть сводная таблица, и мы хотим применить логику к этой таблице, так не должно ли это быть частью модели? Рассмотрим такую стректуру БД вместо прежней:
days: date temperature # больше информации routes: gis_points public distance title # больше полей runs: # это была наша таблица day_route day_id routes_id lap_distance laps
Как вы видите, модель PHPRun
— это просто переименованная сводная таблица с той же самой информацией! Теперь PHPRun
будет знать свою общую дистанцию, которая действительно легкодоступна! И что еще лучше, наш метод доступа PHPtotalDistance
тоже выигрывает от этого.
public function getTotalDistanceAttribute()
{
$sum = 0;
$this->runs->each(function($run) use(&$sum) {
$sum += $run->distance;
});
return $sum;
}
На мой взгляд, это намного понятнее. PHPDay
не должен заботиться о том, как посчитать дистанцию по конкретному маршруту за день, он просто знает, как суммировать расстояния. Теперь PHPRun
готов заботиться обо всей его логике и в то же время сохранять дистанцию по маршруту, когда ему это потребуется (что, если пользователь позднее изменит маршрут, тогда все дистанции прошедших дней будут испорчены).
На этой неделе мы рассмотрим похожие решения и увидим, как они помогают нам повсюду, не только сэкономив нам одну строчку в функции PHPgetTotalDistanceAttribute
.