PHP: держать объекты отдельно?

Я работаю над сложным проектом на PHP и продолжаю сталкиваться с одной и той же проблемой: как разделить отдельные объекты?

Идея ОО-программирования заключается в том, что ни одному из объектов не нужно знать, как любой другой объект работает внутри. Но с третьей нормальной базой данных все находится в отдельной таблице, и все таблицы взаимосвязаны.

Допустим, у меня есть несколько объектов. Объект Order (с классом Order и несколькими таблицами базы данных, относящимися к Order), объекты Address с классом Address и таблицами Address), Items (таблицы и класс) и Customers (таблица и класс).

Многие задачи, такие как доставка заказа, требуют данных из всех вышеперечисленных таблиц, и все данные связаны между собой посредством внешних ключей. Так какой же самый элегантный / эффективный способ загрузить все необходимые объекты / данные, не делая код катастрофой или нарушая принципы ОО?

Подход A: Один класс выполняет один запрос и загружает другие классы в себя.

Класс Order {

   публичная функция LoadFromID($OrderID)
   {
      ВЫБРАТЬ * ИЗ ЗАКАЗОВ
      LEFT JOIN
      ЛЕВЫЙ РЕЙТИНГ
      Левые элементы

      $this->oCustomer = new Customer();
      $ This->oCustomer->SetName($W->CustomerName);

      $this->oAddress = new Address();
      $ This->oAddress->SetCity($W-> Город);

      $this->oaItems = array();
      foreach($w->Items AS $Item)
      {
         $oItem = new Item();
         $ OItem->SetName($ item->ItemName);
         $this->oaItems[] = $oItem;
      }
   }
}

Проблема в том, что класс Order должен знать, как устроены таблицы Customers, Items и Addresses. Что если мы хотим пойти другим путем и заставить адресный объект найти Приказы и Продавцов, которые к нему прикреплены? Поскольку все взаимосвязано, каждый объект должен знать, как структурированы таблицы каждого другого объекта. Это означает, что если вы хотите добавить новое поле в таблицу, вам придется находить и редактировать эти запросы внутри каждого отдельного объекта. Что создает огромную проблему обслуживания.

Подход B: Один класс создает себя и предписывает другим классам создавать себя внутри себя.

Класс Order {

   публичная функция LoadFromID($OrderID)
   {
      ВЫБРАТЬ * ИЗ ЗАКАЗОВ
      $W = mysql_row();

      // Объект клиента создается и передается
      // идентификатор клиента для загрузки.
      $this->oCustomer = new Customer($W->CustomerID); 
      $this->oShipAddress = новый адрес ($W->ShipAddressID);
      $this->oBillAddress = новый адрес ($W->BillAddressID);

      $this->oaItems = array();
      foreach($w->Items AS $Item)
      {
         $this->oaItems[] = новый элемент ($Item->ItemID);
      }
   }
}

класс Customer{
   публичная функция __construct($CustID)
   {
      ВЫБЕРИТЕ * ОТ ПОКУПАТЕЛЕЙ ГДЕ $CustID;
      $W = mysql_row();

      $this->CustomerName = $W->Name;
      ...
   }
}

Этот метод разделяет объекты, где только класс может обращаться к своим собственным таблицам, но создает новые проблемы. Во-первых, это неэффективно с точки зрения MySQL, делая много запросов, где можно получить все необходимые данные. Не уверен, что производительность незначительна или нет. Кроме того, объект никогда не знает, был ли он создан контроллером или другим объектом, поскольку объекты могут быть созданы где угодно. Это особенно проблематично, когда вам нужно использовать транзакции, которые оборачиваются вокруг нескольких объектов, делая несколько вещей. Куда идет вызов "begin" и "commit"? Как объект, я уже нахожусь внутри транзакции, или мне нужно начать ее самостоятельно? Это несоответствие может конфликтовать, так как метод используется для более чем одной конкретной задачи. Иногда метод должен создать транзакцию, а иногда он уже внутри нее.

Подход C: контроллер создает все объекты.

Контроллер класса {

   публичная функция DoSomething()
   {
      $Db->Begin();
      $oOrder=new Order();
      $aAddressIDs = $oOrder->GetAddressIDs();
      $CustomerID = $oOrder->GetCustomerID();

      foreach($aAddressIDs AS $ID)
      {
         // Взять идентификаторы адресов заказа 
         // и затем превращаем их в адресные объекты 
         // передать их обратно в порядок.
         $oAddress = новый адрес ($ID);
         $ OOrder->AddAddress($oAddress);
      }

      // Создать идентификатор клиента для заказа
      // и передаем построенный объект обратно в порядок.
      $oCustomer = новый клиент ($CustomerID);
      $ OOrder->AddCustomer($oCustomer);

      если ($oOrder->DoSomethingComplex())
         $Db->Commit();
      еще
         $ Db-> Откат ();
   }
}

Это решает проблему согласованности в отношении запуска и завершения транзакций базы данных, размещая их вне объектов. Кроме того, нет никакой путаницы в том, где был создан экземпляр объекта, поскольку все объекты должны быть созданы контроллером. Но это выглядит очень неаккуратно и требует, чтобы каждый метод контроллера знал, что нужно объекту Order для функционирования.

Есть ли чистый способ сделать это?

1 ответ

Решение

Извините, вы попали в известную ловушку.
Объекты не должны отражать БД. Объект заказа должен получить все соответствующие данные, чтобы обработать заказ в себя.
Он абсолютно может использовать другие классы / объекты (объект дерева, чтобы правильно упорядочить элементы порядка, класс соединения БД, класс абстракции Sql и т. Д.)
Если вы можете, вы должны загрузить данные в одном запросе. Не один запрос на таблицу.
Если вы хотите использовать парадигму одного запроса на один источник данных, я бы сказал, что реляционная БД - это не то, что вам нужно, а скорее БД NoSQL (например, MongoDB), но и там вам придется использовать но один объект для обработки заказа, поскольку MongoDB в значительной степени обеспечивает это.

Другие вопросы по тегам