开发者

PHP: Keeping Objects Separate?

开发者 https://www.devze.com 2023-03-17 16:25 出处:网络
I\'m working on a complex project in PHP and keep running into the same problem: How to keep separate objects separate?

I'm working on a complex project in PHP and keep running into the same problem: How to keep separate objects separate?

The idea behind OO programming is that none of the objects need to know how any other object works internally. But with a 3rd normal database, everything is in a separate table and all of the tables are interconnected.

Let's say I have several objects. An Order object (with an Order class, and several Order related database tables), Address objects with an Address Class and Address tables), Items (tables and class) and Customers (table and class).

Many tasks, such as shipping an order, require data from all of the above tables, and all of the data is interconnected via foreign keys. So what is the most elegant/efficient way to load all of the objects/data needed, without making the code a disaster or violating OO principals?

Approach A: One class does a single query and loads other classes into itself.

class Order {

   public function LoadFromID($OrderID)
   {
      SELECT * FROM Orders
      LEFT JOIN Customers
      LEFT JOIN Addresses
      LEFT JOIN Items

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

      $this->oAddress = new Address();
      $this->oAddress->SetCity($W->City);

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

The problem is that the Order class needs to be aware of how the Customers, Items, and Addresses tables are laid out. What if we want to go the other way and have the address object find Orders and Vendors that are attached to it? Since everything is interlinked, every object would need to know how every other object's tables are structured. Which means if you wanted to add a new field to a table, you would have to find and edit those queries inside of every single object. Which creates a huge maintenance problem.

Approach B: One class instantiates itself and directs other classes to instantiate themselves within itself.

class Order {

   public function LoadFromID($OrderID)
   {
      SELECT * FROM Orders
      $W = mysql_row();

      // A customer object is instantated and passed
      // a customer ID to load from.
      $this->oCustomer = new Customer($W->CustomerID); 
      $this->oShipAddress = new Address($W->ShipAddressID);
      $this->oBillAddress = new Address($W->BillAddressID);

      $this->oaItems = array();
      foreach($w->Items AS $Item)
      {
         $this->oaItems[] = new Item($Item->ItemID);
      }
   }
}

class Customer{
   public function __construct($CustID)
   {
      SELECT * FROM Customers WHERE $CustID;
      $W = mysql_row();

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

This method keeps objects separate, where only a class can access its own tables, but introduces new problems. First, it's inefficient from a MySQL standpoint, doing many queries where one could get all the needed data. Not sure if the performance is negligible or not. Also, an object never knows whether it was created by a controller or another object since objects can be spawned anywhere. This is particularly problematic when you need to use transactions that wrap around multiple objects doing multiple things. Where does the call to "begin" and "commit" go? As an object, am I already inside of a transaction, or do I need to start one myself? This inconsistency can clash as method are used for more than one specific task. Sometimes a method needs to create the transaction, other times it's already inside of it.

Approach C: The controller instantiates all objects.

class Controller {

   public function DoSomething()
   {
      $DB->Begin();
      $oOrder=new Order();
      $aAddressIDs = $oOrder->GetAddressIDs();
      $CustomerID = $oOrder->GetCustomerID();

      foreach($aAddressIDs AS $ID)
      {
         // Take the order's address IDs 
         // and turn them into adress objects then 
         // pass them back to the order.
         $oAddress = new Address($ID);
         $oOrder->AddAddress($oAddress);
      }

      // Create a customer ID for the order
      // and pass the built object back into the order.
      $oCustomer = new Customer($CustomerID);
      $oOrder->AddCustomer($oCustomer);

      if($oOrder->DoSomethingComplex())
         $DB->Commit();
      else
         $DB->Rollback();
   }
}

This solves the problem of consistency regarding starting and completing database transactions by placing them outside the 开发者_开发百科objects. Also, there's no confusion over where an object was instantiated, since all objects must be created by a controller. But this is really sloppy looking, and requires each controller method to know what the Order object needs in order to function.

Is there any clean way to do this?


Sorry, you have felled into a well known pitfall.
Objects are not supposed to mirror the DB. The order object needs to get all relevant data to process the order into himself.
He absolutely can use other classes/objects (A tree object to get the order items correctly ordered, a db connection class, a Sql abstraction class etc)
If you can, you should load the data in one query. Not one query per table.
If you want to go with the paradigm of one query per one data source I would say that a relational DB is not what you want, but rather a NoSQL DB (like MongoDB, for example), but there too, you will need to use but one object to handle the order, as the MongoDB pretty much enforces this.

0

精彩评论

暂无评论...
验证码 换一张
取 消