Development

/doc/branches/1.1/cookbook/en/shopping_cart.txt

You must first sign up to be able to contribute.

root/doc/branches/1.1/cookbook/en/shopping_cart.txt

Revision 9521, 11.7 kB (checked in by fabien, 5 years ago)

doc: made the cookbook multi-lingual (added translations for es, pt, and fr)

Line 
1 How to manage a Shopping Cart
2 =============================
3
4 Overview
5 --------
6
7 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.
8
9 Installation
10 ------------
11
12 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](http://www.symfony-project.com/book/1_1/17-Extending-Symfony#Plug-Ins)).
13
14 The installation of the `sfShoppingCart` plugin is very straightforward, as described in the [plugin page](`sfShoppingCart`). You just need to type in the command line:
15
16     $ symfony plugin:install sfShoppingCartPlugin
17
18 Then clear the cache to enable autoloading on the plugin's classes:
19
20     $ symfony cc
21
22 Constructor
23 -----------
24
25 The `sfShoppingCart` class is aimed to manage shopping carts. It can contain any kind of object.
26
27 The constructor allows to declare the tax rate to apply to the shopping cart:
28
29     [php]
30     $my_shopping_cart = new sfShoppingCart(sfConfig::get('app_cart_tax'));
31
32 In this example, the shopping cart tax rate is written in the `app.yml` configuration file of the application for easy change:
33
34     all:
35       cart:
36         tax:  19.6
37
38 Create a User Shopping Cart
39 ---------------------------
40
41 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][1] of an `sfShoppingCart` object into the `sfUser` class. To do that, add a custom method to the `myproject/apps/myapp/lib/myUser.php` class:
42
43     [php]
44     class myUser extends sfUser
45     {
46       public function getShoppingCart()
47       {
48         if (!$this->hasAttribute('shopping_cart'))
49         {
50           $this->setAttribute('shopping_cart', new sfShoppingCart(sfConfig::get('app_cart_tax')));
51         }
52
53         return $this->getAttribute('shopping_cart');
54       }
55
56       // ...
57     }
58
59 The `$user->getShoppingCart()` method will create a new shopping cart if the user doesn't already have one.
60
61 >**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](http://www.symfony-project.com/book/1_1/17-Extending-Symfony#Factories).
62
63 Add, modify and remove Items
64 -----------------------------
65
66 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.
67
68 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.
69
70 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.
71
72 ### The shoppingcart module
73
74 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':
75
76     [php]
77     class shoppingcartActions extends sfActions
78     {
79       // ...
80
81       public function executeIndex()
82       {
83         $this->shopping_cart = $shopping_cart;
84         $this->items = $shopping_cart->getItems();
85
86         // ...
87       }
88
89       public function executeAdd($request)
90       {
91         // ...
92
93         if ($request->hasParameter('id'))
94         {
95           $product = ProductPeer::retrieveByPk($request->getParameter('id'));
96           $item = new sfShoppingCartItem('Product', $request->getParameter('id'));
97           $item->setQuantity(1);
98           $item->setPrice($product->getPrice());
99           $shopping_cart = $this->getUser()->getShoppingCart();
100           $shopping_cart->addItem($item);
101         }
102
103         // ...
104       }
105
106       public function executeUpdate($request)
107       {
108         $shopping_cart = $this->getUser()->getShoppingCart();
109         foreach ($shopping_cart->getItems() as $item)
110         {
111           if ($request->hasParameter('quantity_'.$item->getId()))
112           {
113             $item->setQuantity($request->getParameter('quantity_'.$item->getId()));
114           }
115         }
116
117         // ...
118       }
119
120       public function executeDelete($request)
121       {
122         if ($request->hasParameter('id'))
123         {
124           $shopping_cart = $this->getUser()->getShoppingCart();
125           $shopping_cart->deleteItem('Product', $requets->getParameter('id'));
126         }
127
128         // ...
129       }
130
131       ...
132     }
133
134 ### Add an item
135
136 Let's take a closer look at this code.
137
138 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.
139
140 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:
141
142     [php]
143     if ($quantity > 10)
144     {
145       $item->setPrice($product->getPrice() * 0.8);
146     }
147     else
148     {
149       $item->setPrice($product->getPrice());
150     }
151
152 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:
153
154     [php]
155     if ($quantity > 10)
156     {
157       $item->setPrice($product->getPrice());
158       $item->setDiscount(20);
159     }
160     else
161     {
162       $item->setPrice($product->getPrice());
163     }
164
165 ### Modify an item
166
167 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)`.
168
169 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:
170
171     [php]
172     $item = new sfShoppingCartItem('Product', $request->getParameter('id'));
173     $item->setQuantity(1);
174     $item->setPrice($product->getPrice());
175     $shopping_cart = $this->getUser()->getShoppingCart();
176     $shopping_cart->addItem($item);
177     $shopping_cart->addItem($item);
178
179     // same as
180
181     $item = new sfShoppingCartItem('Product', $request->getParameter('id'));
182     $item->setQuantity(2);
183     $item->setPrice($product->getPrice());
184     $shopping_cart = $this->getUser()->getShoppingCart();
185     $shopping_cart->addItem($item);
186    
187 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.
188
189 ### Delete an entire shopping cart
190
191 To reset the shopping cart, simply call the `clear()` method of the `sfShoppingCart` instance.
192
193     [php]
194     $this->getUser()->getShoppingCart()->clear();
195
196 Display the Shopping Cart in a Template
197 ---------------------------------------
198
199 The action `shoppingcart/index` should display the content of the shopping cart. Let's examine a possible implementation.
200
201 ### Get the content of the shopping cart
202
203 Three methods of the `sfShoppingCart` object will help you get the content of a shopping cart:
204
205 * `->getItems()`: array of all the `sfShoppingCartItem` objects in the shopping cart
206 * `->getItem($class_name, $object_id)`: one specific `sfShoppingCartItem` object
207 * `->getTotal()`: Total amount of the shopping cart (sum of the quantity*price for each item)
208
209 Shopping cart items also have a [parameter holder](http://www.symfony-project.com/book/1_1/02-Exploring-Symfony-s-Code#Parameter%20Holders). This means that you can add custom information to any item.
210
211 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:
212
213     [php]
214     $item->setParameter('vehicle', $vehicle_id);
215
216 >**Note**: you may need a `getObjects()` method instead of `getItems()`. This method exists but it relies on the [Propel][2] 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](http://www.symfony-project.com/book/1_1/08-Inside-the-Model-Layer).
217
218 ### Pass the values to the template
219
220 In order to display the content of the shopping cart, the `index` action has to define a few variables accessible to the template:
221
222     [php]
223     // ...
224
225     $this->shopping_cart = $shopping_cart;
226     $this->items = $shopping_cart->getItems();
227
228 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:
229
230     [php]
231     <?php if ($shopping_cart->isEmpty()): ?>
232
233       Your shopping cart is empty.
234
235     <?php else: ?>
236
237       <?php foreach ($items as $item): ?>
238         <?php $object = call_user_func(array($item->getClass().'Peer', 'retrieveByPK'), $item->getId()) ?>
239         <?php echo $object->getLabel() ?><br />
240         <?php echo $item->getQuantity() ?><br />
241         <?php echo currency_format($item->getPrice(), 'EUR' ) ?>
242         <?php if ($item->getDiscount()): >
243            (- <?php echo $item->getDiscount() ?> %)
244         <?php endif; ?><br />
245       <?php endforeach; ?>
246
247       Total : <?php echo currency_format($shopping_cart->getTotal(), 'EUR' ) ?><br />
248
249     <?php endif; ?>
250
251 Note that this example uses the Propel data access layer. If your project uses another data access layer, this example might need adaptations.
252
253 With or without Taxes
254 ---------------------
255
256 By default, all the operations (addition with `$shopping_cart->addItem()`, access with `$item->getPrice()` and `$shopping_cart->getTotal()` use prices **without taxes**.
257
258 To get the total amount with taxes, you have to call:
259
260     [php]
261     $total_with_taxes = $shopping_cart->getTotalWithTaxes()
262
263 If you need it, the `sfShoppingCart` object can be initialized so that the `add` and `get` methods use the price including taxes:
264
265     [php]
266     class myUser extends sfUser
267     {
268       public function getShoppingCart()
269       {
270         if (!$this->hasAttribute('shopping_cart'))
271         {
272           $this->setAttribute('shopping_cart', new sfShoppingCart(sfConfig::get('app_cart_tax')));
273         }
274         $this->getAttribute('shopping_cart')->setUnitPriceWithTaxes(sfConfig::get('app_cart_withtaxes'));
275
276         return $this->getAttribute('shopping_cart');
277       }
278
279       // ...
280     }
281
282 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.
283
284 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:
285
286     [yml]
287     all:
288       cart:
289         tax:       19.6
290         withtaxes: true
291
292 If you are unsure of the way taxes are handled, just ask the shopping cart:
293
294     [php]
295     $uses_tax = $shopping_cart->getUnitPriceWithTaxes();
296
297 [1]: http://en.wikipedia.org/wiki/Object_composition  "Object composition definition at Wikipedia"
298 [2]: http://propel.phpdb.org/trac/                    "Propel"
Note: See TracBrowser for help on using the browser.