Como gerenciar um carrinho de compras
Visão Geral
O symfony oferece um plugin para gerenciar carrinhos de compras em sites de comércio eletrônico. Adicionar um item, modificar quantidades e exibir o conteúdo de um carrinho de compra é feito de forma fácil e sem dores.
Instalação
As classes do carrinho de compras não estão inclusas na instalação padrão do symfony, mas estão empacotadas em um plugin chamado sfShoppingCart. Os plugins do symfony são instalados através do PEAR (saiba mais sobre plugins no Capítulo 17.
A instalação do plugin sfShoppingCart é bem direta, como descrita na página do plugin. Você precisa apenas de digitar na linha de comando:
$ symfony plugin-install http://plugins.symfony_project.com/sfShoppingCartPlugin
Em seguida limpe o cache para habilitar o carregamento automático das classes do plugin:
$ symfony cc
Construtor
A classe sfShoppingCart tem o objetivo de gerenciar carrinhos de compras. Ela pode conter qualquer tipo de objeto.
O construtor permite declarar o imposto a ser aplicado no carrinho de compras:
[php]
$my_shopping_cart = new sfShoppingCart(APP_CART_TAX);
Neste exemplo, o valor do imposto do carrinho de compras está escrito no arquivo de configuração app.yml da aplicação para fácil modificação:
all:
cart:
tax: 19.6
Crie um usuário do carrinho de compras
Você pode facilmente criar um novo objeto sfShoppingCart em uma action com o construtor new. Entretanto, ele não terá nenhum uso se não estiver vinculado ao usuário na sessão. A forma mais fácil de criar esse vínculo é fazer uma composição de um objeto sfShoppingCart com a classe sfUser. Para fazer isto, adicione um método personalizado na classe meu_projeto/apps/minha_aplicação/lib/myUser.php:
[php]
class myUser extends sfUser
{
public function getShoppingCart()
{
if (!$this->hasAttribute('shopping_cart'))
$this->setAttribute('shopping_cart', new sfShoppingCart(APP_CART_TAX));
return $this->getAttribute('shopping_cart');
}
...
}
O método $user->getShoppingCart() irá criar um novo carrinho de compras caso o usuário ainda não tenha um.
Nota: se você precisar de mais informações sobre a forma de sobreescrever a classe padrão
sfUserpor uma classe personalizadamyUser, você deve ler a seçãosobre factories no Capítulo 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':
[php]
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', $this->getRequestParameter('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:
[php]
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:
[php]
if ($quantity > 10)
{
$item->setPrice($product->getPrice());
$item->setDiscount(20);
}
else
{
$item->setPrice($product->getPrice());
}
Modificar um item
Para modificar a quantidade de um item, utilize o método setQuantity() do objeto sfShoppingCartItem. Para excluir um item, você tanto pode chamar o método deleteItem(), como pode modificar a quantidade para 0 chamando o setQuantity(0).
Caso o usuário adicione o mesmo item (mesma classe e mesmo id) várias vezes, o shopping cart irá aumentar a quantidade do mesmo ao invés de um novo item:
[php]
$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);
// faz o mesmo que
$item = new sfShoppingCartItem('Product', $this->getRequestParameter('id'));
$item->setQuantity(2);
$item->setPrice($product->getPrice());
$shopping_cart = $this->getUser()->getShoppingCart();
$shopping_cart->addItem($item);
Eventualmente, você pode se perguntar por que a action update usa argumentos no formato 'quantity_2313=4' ao invés de 'id=2313&quantity=4'. Na verdade, a forma que a action é implementada permite a atualização de múltiplas quantidades de produtos de uma só vez.
Delete an entire shopping cart
To reset the shopping cart, simply call the clear() method of the sfShoppingCart instance.
[php]
$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 thesfShoppingCartItemobjects in the shopping cart -
->getItem($class_name, $object_id): one specificsfShoppingCartItemobject -
->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:
[php]
$item->setParameter('vehicle', $vehicle_id);
Note: you may need a
getObjects()method instead ofgetItems(). 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:
[php]
...
$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]
<?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:
[php]
$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:
[php]
class myUser extends sfUser
{
public function getShoppingCart()
{
if (!$this->hasAttribute('shopping_cart'))
{
$this->setAttribute('shopping_cart', new sfShoppingCart(APP_CART_TAX));
}
$this->getAttribute('shopping_cart')->setUnitPriceWithTaxes(APP_CART_WITHTAXES);
return $this->getAttribute('shopping_cart');
}
...
}
If 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 a constant 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:
[php]
$uses_tax = $shopping_cart->getUnitPriceWithTaxes();