Most e-commerce web applications sell products or services online. These products and/or services can be represented as PHP classes which define properties and methods. Throughout this help we will generically refer to these product or service classes as "cart items". Generally speaking, a cart item's properties reflect the attributes of the item that can be configured by the item's buyer. For example, if you are selling T-Shirts online, your tshirt class will most likely have a property named size so that when users purchase a T-Shirt there is a place in which to store the size of the T-shirt being ordered. Depending on the complexity of the products you are selling, your item object classes may have very few properties or they could have dozens of properties and methods.

Defining Cart Items

Once again, cart items are products and/or services that are sold by your store. Items are normally defined using the php_class model. You can use the command "example cart_item" in this model to build a prototype item that you can modify to suit your needs. In most cases your items will extend the system supplied class cart_item. The supplied cart_item includes the following properties so you won't need to include these in your subclass:

protected $currency_code;
protected $product_code;
protected $description;
protected $price;
protected $quantity = 1;
protected $weight;
protected $cost;
protected $tax_code;

cart_item extends standard_base which provides access to the site object as well as standard message handling.

Let's review how these base properties are used.

currency_code

When offering products for sale in multiple currencies, it is necessary to know the currency in which your product price is expressed.

product_code

The product code defines the type of product associated with an item. This is generally a code that reflects a classification of the product, such as levis_jeans or it could be a SKU which uniquely defines a products such as levis_straightleg_mens_navy_34x32.

description

The product description coincides with the name of the product to be shown in the cart. It is generally a good idea to try to make this as descriptive as possible without making it over long. A good description for the product code described above might be "Men's Navy Levi's Straight Leg Jeans size 34W x 32L".  By using this name, rather than something like "Men's Levi's Jeans" users are less likely to order the wrong product by mistake.

price

This is the price that the user will see when the item is added to their cart. When dealing with a multi-currency cart, we recommend always storing the price in a fixed currency and converting the price to the requested currency in the get_price method of the object. The object's currency_code property should be used to determine the currency in which to price the item.

quantity

This is the number of units of this item being ordered. Avoid repurposing quantity for some other attribute. For example, when selling rope you might be tempted to use the quantity field to represent the length of rope the user wants to order. It is cleaner to add a separate attribute, such as length, to reflect the length of rope being ordered.

weight

The weight field is optional. This can be used to develop custom code to estimate shipping costs, for example.

cost

The cost is also optional. This reflects the cost of goods sold, in other words what you paid for the item. This can be used to calculate profit, etc.

tax_code

The optional tax_code is used if you sell products whose tax treatment differ. For example, if you sell grocery items, these may be tax exempt however if you also sell kitchen supplies these may be taxable. In such a case, tax cannot be applied to your entire shopping cart subtotal. To learn more about setting the tax_code value, please follow this link which describes how to add taxes and fees to your cart.

You will generally extend cart_item to include properties that are specific to your products and/or services. Depending on the number and diversity of your product offering, you may define several different product classes or a hierarchy of classes to allow you to reuse common properties and methods. As an example, let's say we are building a store for a table tennis club. Suppose this store offers the following products:

  1. Table Tennis Balls
  2. Table Tennis Paddles
  3. Play Memberships

Since the properties and methods of balls and paddles are likely very different from those of a membership, it makes sense to define at least two different product classes. It is a good idea to define all of the values that users will enter when ordering your products and use this to decide how to define your product classes. We are mainly interested in properties that are product-specific, as opposed to properties such as quantity and description that are inherited from the cart_item class. Let's assume the following:

There are several different ways you could configure your product classes in this example. 

  1. You could define three separate product classes, say ball, paddle and membership, each with the attributes that are specific to the item. This is likely the best option in this example since there is very little commonality between our products.
  2. Since the balls and paddles both define a color you could create an intermediary class, such as product, which defined the color. This could be used to store the table tennis ball order information. The product class could be extended to create the paddle class which would add the rubber style attribute. This would be a good approach if there were lots of attributes that balls and paddles had in common. Since it is only the color this is not really worthwhile, especially since the color characteristics differ between balls and paddles. So we can see that just because several products may have like-named properties, it is important to look deeper into the semantics (rules and behavior) or the properties in order to decide where they belong in your class hierarchy. 
  3. The other option would be to create one class that contains the union of all properties needed. You could take this approach in this example since we only have three simple products and very few properties. This strategy would start to get unwieldy as you expand your product and services offering since most of the properties and methods would not be used for any specific product. If you follow this approach, your lone product class would end up needing lots of if or switch statements to cater to the specifics of each product. This undermines the goals of object-oriented design.

In addition to assessing your property requirements you should also determine any validation rules. If several of your products have complex validation in common with each other, that would be a reason to define your products in a class hierarchy that would allow this validation to be shared across several different products.

Let's create a class to represent a table tennis paddle, the other products will be defined in a similar way. Furthermore, let's assume that fast rubber only comes in black. Let's use the messages model to create a message we can use for this validation.

Validation message

Rather than making the message specific to fast rubbers being available in black, we use placeholders. This will allow us to reuse the same message if the rules change in the future.

Next we use the php_class model to create the following paddle class.

Class to represent a paddle item

Notice the following model settings:

  1. We inherit base class cart_item so we don't need to define quantity, description, etc.
  2. Our color and speed properties require get and set methods. These will be generated automatically by the php_class model.
  3. We have defined a build_item method. 

Notice that our paddle class is relatively generic, rather than being tightly coupled to shopping cart behavior or page names, etc.. This allows us to use the class outside of the context of a shopping cart if such a requirement exists. You should always strive to make your classes reusable rather than catering to specific use cases.

build_item Method

Every cart item must define a build_item method. This method is called just before an item is added to a shopping cart. This method should perform the following functions:

  1. Return true if the item is valid (all required properties are assigned, all properties are valid and consistent with each other, a price can be established, etc.).
  2. Return false if the item is not valid.
  3. When returning false, the cart object must call assign_message to indicate the reason that the product is not valid. If there are several reasons, assign_message can be called multiple times with different error information.
  4. The build_item method must set a description for the item as well as a price.

validate Method

Item objects can optionally define a validate method. Sometimes multiple pages are needed to add a single item to a cart. In such a case the default shopping cart page_handlers will call the validate method for each page and pass the name of the page to the validate method. As we have mentioned, it is generally unwise to tightly couple your item objects to your cart objects. As such we don't recommend referencing page names within your item objects. Nevertheless, sometimes it is important to be able to perform validation as the user is entering the item properties rather than waiting until all of the properties have been entered and the user is ready to add the item to their cart. If you must perform page-oriented validation we recommend extending your item class with a context-oriented class and coding the validate method within this manager class. For example:

paddle
  paddle_manager extends paddle

In this example, the paddle_manager class would define the validate($page) method. This method would call methods of the paddle object to perform the required validation. The paddle_manager class could be aware of what fields are shown on the form being validated but the paddle object should be written without any such assumptions.

Improving Robustness

Although the paddle object we have defined so far would likely serve our purposes it has several shortcomings that should really be addressed. Well-written objects should be written as gate keepers to make sure that only valid data gets assigned to their properties. This is particularly true in web applications since we can't always guarantee that our objects will be called in the way that we intended.  There are two main reasons why it is important for objects to protect the integrity of their data rather than assuming they will be passed valid data:

  1. Hackers could post requests to our server and bypass our client-side validation
  2. In the future, there may be other server-based applications that want to use our item objects

With this in mind, let's add validation to the set methods of the object to prevent invalid data from being assigned to the object regardless of how the object is being used.

Let's start by defining some constants in the paddle object that we can use with our validation.

Paddle object constants

Next we will supplement the built-in set methods for color and speed by adding custom code at the top of the method:

Custom Set Methods include Validation

Notice that these methods now return false if the assigned value is rejected. We will see later that when we return false in our cart item set methods, we will show the message returned by the object next to the field where the user entered the value that we tried to set.

Make note that we did not move the validation related to "fast paddles having to have black rubbers" to the field level. Doing so would be unwise because the object would need to make assumptions about the order in which the color and speed fields are assigned. For example, suppose the color us currently black and the speed is fast (a valid combination). Now let's assume the user of this class wants to change these properties by calling the object's set methods as followed:

$paddle->set_color('red');
$paddle->set_speed('medium');

If color/speed validation was done in the set_color method the assignment of red would fail since the object does not know that the speed is about to be changed as well.

Before finishing our paddle method let's make a couple more changes. First we will update the build_item method of the paddle object as follows:

We can see these enhancements below.

function build_item() {
  	$valid = true; // Default
	if (empty($this->color)) {
		$this->assign_message('!general',1,'Color',':1: is required.',__LINE__.' '.__CLASS__);
		$valid = false;
	}
	if (empty($this->speed)) {
		$this->assign_message('!general',1,'Speed',':1: is required.',__LINE__.' '.__CLASS__);
		$valid = false;
	}
	if ($valid and $this->speed === 'fast' and $this->color !== 'black') {
		$this->assign_message('validation',1,array('Fast','black'),
							  ':1: rubber speed is only available in the color :2:',
							  __LINE__.' '.__CLASS__);
		$valid = false;
	}
  	if (!$valid) {
		return false;
	}
	$this->description = self::PADDLE_COLORS[$this->color].' Stiga Pro-Spin Table Tennis Racket with '.
						self::PADDLE_SPEED[$this->speed].' Rubber';
	$this->price = 127.99;
	if ($this->speed === 'fast') {
		$this->price += 10; // Add $10 for fast rubbers
	}
	return true;
}

Notice that the first two errors make use of general error messages provided in system. We can tell this by the fact that the message location is preceded by !.

Ecommerce Help Index

E-Commerce Overview Features and components used to build an online store.
Cart Items Defining products and services.
Shopping Cart Interacting with a shopping cart.
Working with Text Files How to store and process transactional data using text files.
Working with Databases Saving and retrieving database table data.
Transaction Numbers Generating identifiers for invoices and other transactions.
Taxes and Fees Configuring sales taxes and other cart fees.
Saving Customer Information Reading and writing customer information.
Accounting Data Managing account records.
Collecting Payments Processing credits cards as order payments.