Я действительно борюсь с повторяющейся концепцией ООП/базы данных.
Пожалуйста, позвольте мне объяснить проблему с псевдо-PHP-кодом.
Скажем, у вас есть "пользовательский" класс, который загружает свои данные из таблицы users
в свой конструктор:
class User {
public $name;
public $height;
public function __construct($user_id) {
$result = Query the database where the `users` table has `user_id` of $user_id
$this->name= $result['name'];
$this->height = $result['height'];
}
}
Простой, удивительный.
Теперь у нас есть класс "group", который загружает свои данные из таблицы groups
, объединенной с таблицей groups_users
, и создает объекты user
из возвращаемого user_id
s:
class Group {
public $type;
public $schedule;
public $users;
public function __construct($group_id) {
$result = Query the `groups` table, joining the `groups_users` table,
where `group_id` = $group_id
$this->type = $result['type'];
$this->schedule = $result['schedule'];
foreach ($result['user_ids'] as $user_id) {
// Make the user objects
$users[] = new User($user_id);
}
}
}
Группа может иметь любое количество пользователей.
Красивая, элегантная, потрясающая... на бумаге. В действительности, однако, создание нового объекта группы...
$group = new Group(21); // Get the 21st group, which happens to have 4 users
... выполняет 5 запросов вместо 1. (1 для группы и 1 для каждого пользователя.) И что еще хуже, если я создаю класс community
, в котором есть много групп, у каждого из которых есть много пользователей, запускается нечестивое число запросов!
Решение, которое не подходит мне.
В течение многих лет, как я это делал, это не кодекс, описанный выше, но вместо этого при создании group
я бы присоединился к таблице groups
в таблице groups_users
в таблицу users
и создать массив массивов объектов, подобных объектно-ориентированным объектам в объекте group
(никогда не используя/не прикасаясь к классу user
):
class Group {
public $type;
public $schedule;
public $users;
public function __construct($group_id) {
$result = Query the `groups` table, joining the `groups_users` table,
**and also joining the `users` table,**
where `group_id` = $group_id
$this->type = $result['type'];
$this->schedule = $result['schedule'];
foreach ($result['users'] as $user) {
// Make user arrays
$users[] = array_of_user_data_crafted_from_the_query_result;
}
}
}
... но тогда, конечно, если я создаю класс "community", в его конструкторе мне нужно будет присоединиться к таблице communities
с таблицей communities_groups
с таблицей groups
с помощью groups_users
с таблицей users
.
... и если я создаю класс "city", в его конструкторе мне нужно будет присоединиться к таблице cities
с таблицей cities_communities
с таблицей communities
с таблицей communities_groups
с groups
с таблицей groups_users
с таблицей users
.
Какая безвозвратная катастрофа!
Нужно ли выбирать между красивым кодом ООП с миллионом запросов VS. 1 запрос и запись этих объединений вручную для каждого отдельного супермножества? Нет ли системы, которая автоматизирует это?
Я использую CodeIgniter и смотрю на бесчисленные другие MVC и проекты, которые были построены в них, и не может найти ни одного хорошего примера для тех, кто использует модели, не прибегая к одному из двух ошибочных методов, которые я изложил.
Кажется, это никогда не делалось раньше.
Один из моих коллег пишет структуру, которая делает именно это: вы создаете класс, который включает в себя модель ваших данных. Другие, более высокие модели могут включать эту единственную модель, и она создает и автоматизирует объединения таблиц для создания модели выше, которая включает в себя объекты-экземпляры ниже, все в одном запросе. Он утверждает, что никогда раньше не видел рамки или системы для этого.
Обратите внимание: Я действительно всегда использую отдельные классы для логики и настойчивости. (VO и DAO - это весь пункт MVC). Я просто объединил их в этом эксперименте-мысли, вне архитектуры MVC, для простоты. Будьте уверены, что эта проблема сохраняется независимо от разделения логики и настойчивости. Я полагаю, что эта статья, представленная мне Джеймсом в комментариях ниже этого вопроса, кажется, указывает на то, что мое предлагаемое решение (которое я следовал в течение многих лет), фактически, то, что разработчики в настоящее время делают для решения этой проблемы. Этот вопрос, однако, пытается найти способы автоматизации точного решения, поэтому его не всегда нужно кодировать вручную для каждого надмножества. Из того, что я вижу, это никогда не делалось на PHP раньше, и моя структура коллектива будет первой, если только кто-то не может указать мне на то, что делает.
И, конечно же, я никогда не загружаю данные в конструкторы, и я вызываю только методы load(), которые создаю, когда мне действительно нужны данные. Однако это не связано с этой проблемой, как в этом мысленном эксперименте (и в реальных ситуациях, когда мне нужно автоматизировать это), я всегда должен загружать данные все подмножества дочерних элементов как можно дальше от строки, а не лениво загружать их в какой-то будущий момент времени по мере необходимости. Мысленный эксперимент является кратким - что он не соответствует лучшим методам - спорный вопрос, и ответы, которые пытаются решить его макет, также не имеют смысла.
EDIT: для ясности приведена схема базы данных.
CREATE TABLE `groups` (
`group_id` int(11) NOT NULL, <-- Auto increment
`make` varchar(20) NOT NULL,
`model` varchar(20) NOT NULL
)
CREATE TABLE `groups_users` ( <-- Relational table (many users to one group)
`group_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL
)
CREATE TABLE `users` (
`user_id` int(11) NOT NULL, <-- Auto increment
`name` varchar(20) NOT NULL,
`height` int(11) NOT NULL,
)
(Также обратите внимание, что изначально я использовал понятия wheel
и car
s, но это было глупо, и этот пример намного яснее.)
РЕШЕНИЕ:
В итоге я нашел PHP ORM, который делает именно это. Это Laravel Eloquent. Вы можете указать отношения между вашими моделями и интеллектуально строить оптимизированные запросы для активной загрузки с использованием синтаксиса следующим образом:
Group::with('users')->get();
Это абсолютная спасательная жизнь. Мне не пришлось писать ни одного запроса. Он также не работает с использованием соединений, он интеллектуально компилирует и выбирает на основе внешних ключей.