Page 1 of 2

Stuck at the design part of creating a shopping cart class

Posted: Thu Dec 14, 2006 1:56 pm
by Begby
I have a cart with the following classes so far, and am kind of stuck on the best way to do the item classes. I have tried to create something like this in the past with bad results and really want to get it right....

Code: Select all

// This class stores items
abstract BWItemCollection implements Countable, IteratorAggregate
{

  public function addItem( IBWItem $item, $qty ) { ... }

  public function removeItem( $itemID ) { ... }

  public function hasItem( IBWItem $item ) { ... }

  .... more misc functions ....
}


// This is the shopping cart
class BWCart extends BWItemCollection
{
   public function saveToSession( $sessionVarName ) { ... }

   public function loadFromSession( $sessionVarName ) { ... }

}


// Order class
class BWOrder extends BWItemCollection
{
  public function loadFromDB( $orderID ) { ... }

  public function buildFromCart( BWCart $cart ) { ... }

  public function saveToDB() { ... }

  public function cancel() { ... } 

  public function getTracking() { ... }

  .... etc. etc.  ....
}


// Item Interface
interface IBWItem
{
   public function getPrice() ;
   public function getName() ;
   public function getID() ;
}

// Simple item class, might make it abstract
class BWItem implements IBWItem
{
  ...  implements the interface methods ....
}

// This is a decorator class.   It adds a quantity value to any BWItem thats stored in it.  Whenever a
// item is added to BWItemCollection it gets put in this wrapper so that the quantity can be tracked
class BWOrderItem implements IBWItem
{
   // Set the component
   public function __construct( IBWItem $item ) { ... } 

   public function getQty() { ... }

   public function setQty( $qty ) { ... }

   public function addQty( $qty ) { ... }

   ... implements the interface methods and some other stuff .... 
}


//  A concrete item.
class SomeItem extends BWItem
{
   public function getImage() { ... }

   public function getThumbnail() { ... }

   public function getDescription) { ... }
}

// Some other item with a different table structure
class SomeOtherItem extends BWItem
{
   public function getEngineType() { ... }
}

I've got it all cool now where I can create items, add them to the cart, save it to a session, loop through the cart, convert it to an order and all that jazz.

The problem is that the different item types will be stored in different tables. There will be a single item table, and that will be linked to the different item types. The idea is I can take my little shopping cart API thing and set it up for a different customer who maybe has 3 or 4 item types and then just create the correct item classes and tables and good to go.

The itemOrder table will store an itemID, qty, orderID.

The item table will have itemID and an itemType?

Then in the example above I would have a SomeItem table with itemID, image, thumbnail, description...


Question Would a factory be a good solution to creating objects from data retrieved from the database? To get items I would select the ItemIDs and types from the items table, then left join all of the different item tables and call the factory on each one like

Code: Select all

$order = new BWOrder() ;
foreach( $items as $item )
{
   $order->addItem( BWItem::factory( $item ), $item['qty'] ) ;
}
The only drawback to this that I can see is that I'll need to modify the factory for every item type. Thats not super maintainable but workable. I suppose I could create a setFactory() method or something and pass it a map of classes / types. Is what I am doing a good approach?


Another question When it comes to saving the records to the db, how should I approach this? There are a few parts to this

- Modifiying items: The site admin can add/delete/modify items. This will involve modifying one item at a time. Should the sql for this be stuck into the different item implementations? Or should it be encapsulated in another class that handles this that takes IBWItem objects and manages records via a definition stored within the item? This one is hurting my head.

- Placing orders: People can place/modify/cancel orders. This will involve modifying many orderItem records at one time. I assume the SQL should go into the BWOrder object as its going to be writing a set of records to only the orderItems table and to the orders table. Does that sound right?

- Loading an order: When loading an order a somewhat large SQL statement will need to be generated utilizing each of the item types so that all the item tables can be joined. My idea was to have a clause for left join be stored in each item type in a static method so that this can be build. How do I know what item classes to call though at run time? Should I use a map or something or manually edit the sql?

Yet one more question I have seen item databases with dynamic properties for products. So you create items not by creating a new class but by creating a definition for that item and storing it in the DB. Then the item object would be build on the fly. Any insight into that or input on if that is a better solution? It sounds like a real pain in the ass.


I really appreciate any input or opinions on this.

edit: I can already see a snafu with my BWOrderItem decorator. It really shouldn't be a decorator but just some object that will return the information needed to create an orderItem record, store the qty, and hold a BWItem. I'll fix that.

Posted: Fri Dec 15, 2006 1:02 am
by Christopher
It looks ok to me until you get to the items ... then I get a little confused. I don't understand:
Begby wrote:The problem is that the different item types will be stored in different tables. There will be a single item table, and that will be linked to the different item types. The idea is I can take my little shopping cart API thing and set it up for a different customer who maybe has 3 or 4 item types and then just create the correct item classes and tables and good to go.
Different tables but single item table? Which is it? A table linked to types? What are types? Other tables? It seems like types would be defined at the application level (not in the library) and extend the basic Item class. It would be up to your specific Cart implementation to sort out the type and create the appropriate object type to add.

Similarly in your example of adding Items to an Order you try a factory, but each Item would already have been created by the Cart with a specific object type. How the Order knows how to deal with the different objects becomes the question.

Posted: Fri Dec 15, 2006 12:08 pm
by Begby
arborint wrote:It looks ok to me until you get to the items ... then I get a little confused. I don't understand:
Begby wrote:The problem is that the different item types will be stored in different tables. There will be a single item table, and that will be linked to the different item types. The idea is I can take my little shopping cart API thing and set it up for a different customer who maybe has 3 or 4 item types and then just create the correct item classes and tables and good to go.
Different tables but single item table? Which is it? A table linked to types? What are types? Other tables? It seems like types would be defined at the application level (not in the library) and extend the basic Item class. It would be up to your specific Cart implementation to sort out the type and create the appropriate object type to add.

Similarly in your example of adding Items to an Order you try a factory, but each Item would already have been created by the Cart with a specific object type. How the Order knows how to deal with the different objects becomes the question.

Sorry for the confusion... the items have me confused too... There would be different item types that would extend a base item class. This would be reflected in the database with a base item table, then another related table for each item type. Say we had a store that had bubble gum and t-shirts

tbl_items
itemID
itemName
itemPrice
itemType - tells us what the related table is

tbl_gum
itemID
flavor

tbl_shirts
itemID
size
color


My example included adding items to an order becuase if you are loading an order from the database (to view or cancel or whatever), you would fetch all the items belonging to that order from the database, then add them to the order one at a time. If you are creating an order from a cart then of course you don't need to do that. I think that code might be in a static BWOrder::getOrder($id) method that returned a populated order object.

The item factory would look at 'itemType' to know what item to create. This way the orderObject does not have to have any knowledge of what type if items its dealing with as long as they implement the base item interface.

When it comes to displaying the items in the order or cart, I think I would add display methods into the interface or a view wrapper. This way the order or cart could call display() on each item and the item would draw itself.

I don't know how how to define it at the application level though. I would have my library, then create item classes and db tables at the application level. But how do I modify the factory at the application level? Should I pass it an array or something?

Posted: Fri Dec 15, 2006 1:18 pm
by Kieran Huggins
This might just be my own personal preference, but I would ditch the one-table-per-type idea and store item "properties" separately.

tbl_items
itemID
itemName
itemPrice

tbl_properties
itemID
PropName
PropValue

That way you can still sort / search / whatever by property by doing a join, and you don't need to worry as much about keeping your master index table in sync.

Also, it makes your data layer more flexible, while lowering the design-time cost of having to create a billion new tables. I think once you get the hang of thinking about the data this way you'll be able to create your object classes faster as well... also it would make life easier as new properties crop up (they always do, don't they?).

Cheers,
Kieran

Posted: Fri Dec 15, 2006 1:26 pm
by Begby
Excellent Kieran. Thank you for this. This is something along the lines of what I was looking for.

So at the application level I would create an item with specific properties, then build the item from those properties. Do you have any examples of the class structure for this?

Posted: Fri Dec 15, 2006 1:36 pm
by Kieran Huggins
Glad you like it... :-)

I'm writing a rapid development <dirty word>framework</dirty word> that uses something similar as a default storage model.

You wouldn't need to create a static data model, per se.

You could use the __get() and __set() class methods to assign / retrieve properties in the object, moving all your property handling code to the controller.

You could also create custom getProperty('name') and setProperty('name','value') methods to your base class

Hell, you could do both... and more!

With this method you could steer your property definitions with nothing more than a custom input form. That would likely be the most flexible solution... and it's probably what I would do in your place.

Cheers,
Kieran

Posted: Fri Dec 15, 2006 2:40 pm
by Christopher
Since you are mapping objects onto your database it does not really matter what the underlaying structure is. Your mapper should be able to handle any configuration. I might start simple with a single table and then support alternate schemas as needed.

Code: Select all

temID
itemName
itemPrice
itemType - tells us what the related table is
prop1                                // size|flavor
prop2                                // color

Posted: Fri Dec 15, 2006 3:01 pm
by Begby
arborint wrote:I might start simple with a single table and then support alternate schemas as needed.
This is sound advice. Instead of thinking/planning this to death I am going to make it work for one client with a single item type and then deal with other item types later.

Posted: Fri Dec 15, 2006 4:03 pm
by Christopher
If you can, make the code that fetches Item data for some datasource be independent of the Cart/Order objects. I prefer to only have the Cart/Order object only know that the Items have 'qualities" but not the specifics about them. Usually the qualities just need to be passed through to the Presentation code and the Items can know how to save themselves. This makes the Cart/Order objects much more modular.

Posted: Fri Dec 15, 2006 8:28 pm
by Begby
arborint wrote:If you can, make the code that fetches Item data for some datasource be independent of the Cart/Order objects. I prefer to only have the Cart/Order object only know that the Items have 'qualities" but not the specifics about them. Usually the qualities just need to be passed through to the Presentation code and the Items can know how to save themselves. This makes the Cart/Order objects much more modular.
This is another stumbling block for me. Saving is no problem as I am only going to save/update/delete one item at a time. The problem is populate an order with multiple items from different tables, or items with different properties using a separate properties table. An order has an ID and multiple items for that order ID. If the functionality to fetch items from the database is encapsulated into the item object I could find the item IDs for an order, create an object for each itemID, then tell each item to get its own data. The problem is that if an order had 100 items, that would mean 100 queries as each item would be populating itself. This might even be a problem with only 1 item type as I am planning on coding at first.

How I have solved this before is I made each item type have an item::getJoinSQL( $parentTable) and item::getSQLFields() method that takes as an argument a parent name and returns sql like 'LEFT JOIN itemTable ON {$parentTable}.itemID = itemTable.itemID'. If I had 3 item different item types I would end up with 3 left joins and 3 field lists that I could attach to a statement like 'SELECT {$fields}, orderItems.itemID FROM orderItems {$joinSQL} WHERE orderItems.orderID = {$orderID}'.

The problem with doing it that way in the past is I had to edit my library every time I added a new item type to make sure the getJoinSQL() and getSQLFields() methods got called for all the item classes. Also I had to edit my factory when adding new item types. I was considerign creating some sort of separate inventory object or inventory factory that would handle spitting out collections of item objects given an array of itemIDs. Is that a good idea?

Is this solvable using the separate properties tables as suggested earlier? (I have not tried to work out that logic yet). Am I making any sense whatsoever (I am on my fourth beer)?

Posted: Fri Dec 15, 2006 9:41 pm
by Kieran Huggins
If I understand properly you're worried about hammering the MySQL server on each page load when you have multiple items in the shopping cart. You could solve that problem by serializing the product objects (including their properties) between sessions. After all, it's not like a sweater will suddenly not have a certain property between selection and checkout - and even then you can "double check" at checkout.

This way, each item requires one query from the items table (1 row) and one query from the properties table (n rows), then it's cached for the remainder of the session... VoilĂ : no extra trips to the MySQL server! Added bonus: no fancy MySQL query building functions needed (although that does score you loads of geek cred).

Cheers,
Kieran

Posted: Sat Dec 16, 2006 12:52 am
by Christopher
Rather than doing joins, perhaps it would be better to aggregate Items of each type together. A Factory that created Items could manage those associations. Then you could read in and write out like Items in a single query. I would still cheat and just slap extra columns in the Item table for each type to simplify things. The extra row length will have essentially no effect, whereas joins and multiple queries definitely will.

Posted: Sat Dec 16, 2006 2:57 am
by Kieran Huggins
@arborint: While I certainly agree that one query is better than two (when appropriate), I'm not sure it's worth the man-hour coding hit in this case.

For small-to-medium systems, I'm fairly confident that the added load of two queries over the same database connection instead of one is negligible. The admins at dreamhost calculates a database connection to be worth 10 times the resource hit of a single query, so in a case where we don't have a high query/connection ratio, query optimization seems somewhat less rewarding than coding/debugging time savings. Especially since he's planning to copy this code for other products and stores.

Also, the system I suggested doesn't use any joins, they're two separate queries: One to fetch the object and it's mandatory info (title, description, price, stock, etc..) and one to fetch any extra properties (colour, weight, flavour, type, team...whatever!).

I don't mean to sound defensive, btw... you do make an excellent point!

Cheers,
Kieran

Posted: Sat Dec 16, 2006 9:56 pm
by Christopher
I agree that you should do as many queries as are necessary. I really had two points. First that the system should not care where the data comes from. The Items or Item Mapper should deal with that independent of the Cart. My second point was that given the features that were being presented, adding columns to the Item table would be the simplest solution -- any other scheme would add complexity when there is not a proven need. Queries are one of the most expensive things that PHP scripts do.

Posted: Mon Dec 18, 2006 7:48 am
by Begby
Kieran Huggins wrote:If I understand properly you're worried about hammering the MySQL server on each page load when you have multiple items in the shopping cart. You could solve that problem by serializing the product objects (including their properties) between sessions. After all, it's not like a sweater will suddenly not have a certain property between selection and checkout - and even then you can "double check" at checkout.

This way, each item requires one query from the items table (1 row) and one query from the properties table (n rows), then it's cached for the remainder of the session... VoilĂ : no extra trips to the MySQL server! Added bonus: no fancy MySQL query building functions needed (although that does score you loads of geek cred).

Cheers,
Kieran

No, I will not be loading the cart from the db each time. I will serialize the cart.

The problem is that you checkout and save your order. The order is now in the database. Next day you login to see what you order so the order needs to be loaded from the db. If each item is doing its own loading, and there are 100 items, thats 100 queries.

Another thing, the app will need to be pulling out orders from the database to display using the same item objects. I would like to be able to list all the items in a particular category via one query.

Also, at some point the admin will be logging in to see orders, same deal.

So some sort of deal will need to be coded, a factory or something I think, to efficiently get items from the db.