Caution: You are browsing the legacy 1.x part of this website.
This version of symfony is not maintained anymore. If some of your projects still use this version, consider upgrading.
This work is licensed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License license.

Master Symfony2 fundamentals

Be trained by SensioLabs experts (2 to 6 day sessions -- French or English).
trainings.sensiolabs.com

Discover the SensioLabs Support

Access to the SensioLabs Competency Center for an exclusive and tailor-made support on Symfony
sensiolabs.com
Blackfire Profiler Fire up your PHP Apps Performance

PHP Project Quality Done Right

How to manage a Shopping Cart

Overview

Symfony offers a plugin to manage shopping carts in ebusiness websites. Adding an item, changing quantities and displaying the content of a shopping cart is made easy and painless.

Installation

The shopping cart classes are are not shipped with the default symfony installation, but packaged into a plugin called sfShoppingCart. Symfony plugins are installed via PEAR (find more about plugins in Chapter 17).

The installation of the sfShoppingCart plugin is very straightforward, as described in the plugin page. You just need to type in the command line:

$ symfony plugin-install http://plugins.symfony-project.com/sfShoppingCartPlugin

Then clear the cache to enable autoloading on the plugin's classes:

$ symfony cc

Constructor

The sfShoppingCart class is aimed to manage shopping carts. It can contain any kind of object.

The constructor allows to declare the tax rate to apply to the shopping cart:

$my_shopping_cart = new sfShoppingCart(sfConfig::get('app_cart_tax'));

In this example, the shopping cart tax rate is written in the app.yml configuration file of the application for easy change:

all:
  cart:
    tax:  19.6

Create a User Shopping Cart

You can easily create a new sfShoppingCart object in an action with the new constructor. However, it will not be of any use if it is not linked to a user session. The easiest way to keep a user's choice in a shopping cart is to make a composition of an sfShoppingCart object into the sfUser class. To do that, add a custom method to the myproject/apps/myapp/lib/myUser.php class:

class myUser extends sfUser
{
  public function getShoppingCart()
  {
    if (!$this->hasAttribute('shopping_cart'))
    {
      $this->setAttribute('shopping_cart', new sfShoppingCart(sfConfig::get('app_cart_tax')));
    }
 
    return $this->getAttribute('shopping_cart');
  }
 
  // ...
}

The $user->getShoppingCart() method will create a new shopping cart if the user doesn't already have one.

note

if you need more information about the way to override the default sfUser class by a custom myUser class, you should read the section about factories in Chapter 17.

Add, modify and remove Items

The shopping cart can contain any quantity of objects from different classes. Each item stored in the shopping cart is an instance of the sfShoppingCartItem class.

The sfShoppingCart class has addItem() and deleteItem() methods. As you can add or delete any type of object, the first argument of these method calls is the class name of the object.

To modify the quantity of one item, first get the sfShoppingCartItem object itself (via the getItems() method of the sfShoppingCart object) and call its setQuantity() method.

The shoppingcart module

Here is a possible module implementation of the shopping cart management where objects of class 'Product' (representing products) can be added, modified or suppressed with the actions 'add', 'update' and 'delete':

class shoppingcartActions extends sfActions
{
  // ...
 
  public function executeIndex()
  {
    $this->shopping_cart = $shopping_cart;
    $this->items = $shopping_cart->getItems();
 
    // ...
  }
 
  public function executeAdd()
  {
    // ...
 
    if ($this->hasRequestParameter('id'))
    {
      $product = ProductPeer::retrieveByPk($this->getRequestParameter('id'));
      $item = new sfShoppingCartItem('Product', $this->getRequestParameter('id'));
      $item->setQuantity(1);
      $item->setPrice($product->getPrice());
      $shopping_cart = $this->getUser()->getShoppingCart();
      $shopping_cart->addItem($item);
    }
 
    // ...
  }
 
  public function executeUpdate()
  {
    $shopping_cart = $this->getUser()->getShoppingCart();
    foreach ($shopping_cart->getItems() as $item)
    {
      if ($this->hasRequestParameter('quantity_'.$item->getId()))
      {
        $item->setQuantity($this->getRequestParameter('quantity_'.$item->getId()));
      }
    }
 
    // ...
  }
 
  public function executeDelete()
  {
    if ($this->hasRequestParameter('id'))
    {
      $shopping_cart = $this->getUser()->getShoppingCart();
      $shopping_cart->deleteItem('Product', $requets->getParameter('id'));
    }
 
    // ...
  }
 
  ...
}

Add an item

Let's take a closer look at this code.

To add an item to the shopping cart, you call the addItem() method, passing it a sfShoppingCartItem object. This object contains the object class and the unique id of the item to be added, the quantity to be added and the price of the item. This allows the shopping cart to contain objects of any class. For example, you could have a shopping cart containing books and CDs.

The price is stored at this moment to avoid difference of price between the product addition and the checkout if a product price is modified in between in a back-office (or if the cart can be kept between sessions). This also allows to apply price discount according to the amount ordered by the client:

if ($quantity > 10)
{
  $item->setPrice($product->getPrice() * 0.8);
}
else
{
  $item->setPrice($product->getPrice());
}

The problem is that you loose the original price if you apply the discount this way. That's why the the sfShoppingCartItem object has a setDiscount() method that expects a discount percentage:

if ($quantity > 10)
{
  $item->setPrice($product->getPrice());
  $item->setDiscount(20);
}
else
{
  $item->setPrice($product->getPrice());
}

Modify an item

To change the quantity of an item, use the method setQuantity() of the sfShoppingCartItem object. To delete an item, you can either call the deleteItem() method or change the quantity to 0 by calling setQuantity(0).

If a user adds the same item (same class and same id) several times, the shopping cart will increase the quantity of the item and not add a new one:

$item = new sfShoppingCartItem('Product', $this->getRequestParameter('id'));
$item->setQuantity(1);
$item->setPrice($product->getPrice());
$shopping_cart = $this->getUser()->getShoppingCart();
$shopping_cart->addItem($item);
$shopping_cart->addItem($item);
 
// same as
 
$item = new sfShoppingCartItem('Product', $this->getRequestParameter('id'));
$item->setQuantity(2);
$item->setPrice($product->getPrice());
$shopping_cart = $this->getUser()->getShoppingCart();
$shopping_cart->addItem($item);

Eventually, you may wonder why the update action uses arguments like 'quantity_2313=4' instead of 'id=2313&quantity=4'. As a matter of fact, the way this action is implemented allows the update of multiple article quantities at one time.

Delete an entire shopping cart

To reset the shopping cart, simply call the clear() method of the sfShoppingCart instance.

$this->getUser()->getShoppingCart()->clear();

Display the Shopping Cart in a Template

The action shoppingcart/index should display the content of the shopping cart. Let's examine a possible implementation.

Get the content of the shopping cart

Three methods of the sfShoppingCart object will help you get the content of a shopping cart:

  • ->getItems(): array of all the sfShoppingCartItem objects in the shopping cart
  • ->getItem($class_name, $object_id): one specific sfShoppingCartItem object
  • ->getTotal(): Total amount of the shopping cart (sum of the quantity*price for each item)

Shopping cart items also have a parameter holder. This means that you can add custom information to any item.

For instance, in a website that sells auto parts, the sfShoppingCartItem objects need to store the objects added, but also the vehicle for which the part was bought. This can be simply done by adding:

$item->setParameter('vehicle', $vehicle_id);

note

you may need a getObjects() method instead of getItems(). This method exists but it relies on the Propel data access layer. As the use of Propel is optional, you might not be able to use it. Learn more about the data access layer in Chapter 8.

Pass the values to the template

In order to display the content of the shopping cart, the index action has to define a few variables accessible to the template:

// ...
 
$this->shopping_cart = $shopping_cart;
$this->items = $shopping_cart->getItems();

The following example shows a simple indexSuccess.php template based on an iteration over all the items of the shopping cart to display information about each of them:

<?php if ($shopping_cart->isEmpty()): ?>
 
  Your shopping cart is empty.
 
<?php else: ?>
 
  <?php foreach ($items as $item): ?>
    <?php $object = call_user_func(array($item->getClass().'Peer', 'retrieveByPK'), $item->getId()) ?> 
    <?php echo $object->getLabel() ?><br />
    <?php echo $item->getQuantity() ?><br />
    <?php echo currency_format($item->getPrice(), 'EUR' ) ?> 
    <?php if ($item->getDiscount()): >
       (- <?php echo $item->getDiscount() ?> %)
    <?php endif; ?><br />
  <?php endforeach; ?>
 
  Total : <?php echo currency_format($shopping_cart->getTotal(), 'EUR' ) ?><br />
 
<?php endif; ?>

Note that this example uses the Propel data access layer. If your project uses another data access layer, this example might need adaptations.

With or without Taxes

By default, all the operations (addition with $shopping_cart->addItem(), access with $item->getPrice() and $shopping_cart->getTotal() use prices without taxes.

To get the total amount with taxes, you have to call:

$total_with_taxes = $shopping_cart->getTotalWithTaxes()

If you need it, the sfShoppingCart object can be initialized so that the add and get methods use the price including taxes:

class myUser extends sfUser
{
  public function getShoppingCart()
  {
    if (!$this->hasAttribute('shopping_cart'))
    {
      $this->setAttribute('shopping_cart', new sfShoppingCart(sfConfig::get('app_cart_tax')));
    }
    $this->getAttribute('shopping_cart')->setUnitPriceWithTaxes(sfConfig::get('app_cart_withtaxes'));
 
    return $this->getAttribute('shopping_cart');
  }
 
  // ...
}

If sfConfig::get('app_cart_withtaxes') is set to true, the $shopping_cart->addItem() and $item->getPrice() methods will use prices with taxes. The getTotal() and getTotalWithTaxes() methods still give the correct results.

Once again, it is a good habit to keep the tax configuration in a configuration file: that's why the example above uses sfConfig::get('app_cart_withtaxes') instead of a simple true. The myproject/apps/myapp/config/app.yml should contain:

all:
  cart:
    tax:       19.6
    withtaxes: true

If you are unsure of the way taxes are handled, just ask the shopping cart:

$uses_tax = $shopping_cart->getUnitPriceWithTaxes();