An OOP Shopping cart: calculate the total cost in the cart

class _cart
{
	
	 private $_contents = array();
	 private $_created;
	 
	 /**
	  * The class constructor
	  *
	  */
	 public function __construct() {
         $this->_created = time();
	 }
	 
	 /**
	  * Add a product to the cart
	  * @access public
	  * @param $productID integer
	  *
	  */
	 public function add_item($productID) {

	 	if (isset($this->_contents[$productID])) {
	 	    $this->_contents[$productID]++;	
	 	} else {
	 		$this->_contents[$productID] = 1;
	 	}
		
		//print_r($this->_contents);
	 }
	 
	 /**
	  * Remove product from cart
	  * @access public
	  * @param $productId integer
	  *
	  */
	 public function delete_item($productID) {
	 	
 		if (isset($this->_contents[$productID])) {
 			unset($this->_contents[$productID]);
 		}
	 		
	 }
	 
	 /**
	  * Change the quantity of a particular item held in the shopping cart
	  *
	  * @access public
	  * @param integer $productID
	  * @param integer $quantity
	  */
	 public function update_quantity($productID, $quantity) {
	 	$this->_contents[$productID] = $quantity;
	 }
	 
	 /**
	  * Get all items currently in cart
	  *
	  * @access public
	  * @return unknown
	  */
	 public function get_items() {
	 	return $this->_contents;
	 }
	 
	 /**
	  * How many items are in the user's cart?
	  * 
	  * @access public
	  * @return INTEGER
	  *
	  */
	 public function count_items() {
	 	return array_sum($this->_contents);
	 }

	 /**
	  * Calculate the cost of all items in the cart
	  * @access public
	  * @return float
	  *
	  */
	 public function calculate_cost() 
	 {
	 	
	 	$cost = 0.00;
	 	
	 	foreach($this->_contents AS $id => $quantity) {
	 		$product = new _product($id);
	 		$cost = $cost + ($product -> price * $quantity);
	 	}
	 	
	 	return number_format($cost, 2);
	 	
	 }
	 
}

I have tested the methods in the cart class above which I got it from the link here,

http://www.easyphpwebsites.com/blog/read/Easy-Object-Oriented-Programming-with-PHP

But the method of calculate_cost() requires another class - _product class I think!??

Any one know how to create this product class?

I tried to imagine how this _product class is like, so this is my version,

class _store
{	
	function get_store()
	{
		return new SimpleXMLElement(file_get_contents(STORE_XML));
	}
}

class _product
{
	public $_product_id = null;
	
	public function __construct($id) 
	{
		$this->_product_id = $id;	
	}

	public function get_price() 
	{
		$object_store = new _store();
		$store = $object_store -> get_store();
	   
		foreach ($store as $product)
		{
			if ($product -> id == $this->_product_id)
			{
				return $product -> price;
			}
				
		}	
	}
}

however, to use this _product class the version of mine, will require to change the calculate_cost() into this below which I think it is not looking good at all as it needs a few more lines… and I don’t think my _prodcut class is looking great!

public function calculate_cost() 
	 {
	 	
	 	$cost = 0.00;
	 	
	 	foreach($this->_contents AS $id => $quantity) {
	 		$product = new _product($id);
			$cost_string = $product -> get_price();
			$cost_float = "$cost_string" + 0;
			$cost = $cost + ($cost_float * $quantity);
	 	}
	 	
	 	return number_format($cost, 2);
	 	
	 }

Many thanks,
Lau

What you’re trying to do you’re doing correctly. I just wonder why you use XML instead of just accessing the database correctly. Do you have a reason for that?

As for the classes, I would slightly change them

<?
class _store
{
	// seeing is this is the only function of the store class we might as well make it static
	public static function get_store()
	{
		return new SimpleXMLElement(file_get_contents(STORE_XML));
	}
}

class _product
{
	// public variables are evil, make it private
	private $_product = null;

	public function __construct($id) 
	{
		$store = _store::get_store();

		foreach ($store as $product)
		{
			if ($product->id == $id)
			{
				$this->_product = $product;
				// we've now found the product, no need looking through the remaining products in the XML
				break;
			}		
		}

		if (is_null($this->_product))
		{
			// apperantly there is no product in the store with the given id
			die ('Product with '.$id.' not found'); // or throw an exception, would be better
		}
	}

	public function get_price() 
	{
		return $_product->price;
	}
}

That way you can easily extend the _product class to also include stuff like title, color, etc. without having to request the store and walk through it for each function seperately.

got it fixed,

public function get_price() 
    { 
        return $this -> _product -> price; 
    }

:smiley:

hi there! the products are stored in database, then are made into a xml file, like this below,

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";

--
-- Database: `products`
--

-- --------------------------------------------------------

--
-- Table structure for table `books`
--

CREATE TABLE IF NOT EXISTS `books` (
  `id` int(6) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(100) NOT NULL DEFAULT '',
  `author` varchar(100) NOT NULL DEFAULT '',
  `price` decimal(3,2) NOT NULL DEFAULT '0.00',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;

--
-- Dumping data for table `books`
--

INSERT INTO `books` (`id`, `title`, `author`, `price`) VALUES
(1, 'Where God Went Wrong', 'Oolon Colluphid', '5.99'),
(2, 'Some More of God''s Greatest Mistakes', 'Oolon Colluphid', '6.99'),
(3, 'Who Is This God Person Anyway?', 'Oolon Colluphid', '9.99');

of cos this is just an experiment only, it should have more fields to hold informations of the final store.

then exported into xml like this (store.xml),

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;store&gt;
	&lt;item&gt;
		&lt;id&gt;1&lt;/id&gt;
		&lt;title&gt;Where God Went Wrong&lt;/title&gt;
		&lt;price&gt;5.99&lt;/price&gt;
	&lt;/item&gt;
	&lt;item&gt;
		&lt;id&gt;2&lt;/id&gt;
		&lt;title&gt;Some More of God's Greatest Mistakes&lt;/title&gt;
		&lt;price&gt;6.99&lt;/price&gt;
	&lt;/item&gt;
	&lt;item&gt;
		&lt;id&gt;3&lt;/id&gt;
		&lt;title&gt;Who Is This God Person Anyway?&lt;/title&gt;
		&lt;price&gt;9.99&lt;/price&gt;
	&lt;/item&gt;
&lt;/store&gt; 

then, SimpleXMLElement() is used to extract data from the xml, which is lke this,


# the path of store.xml  
define('STORE_XML' , 'store.xml');

class _store
{	
	function get_store()
	{
		return new SimpleXMLElement(file_get_contents(STORE_XML));
	}
}

then, loop the store class object with foreach().

I hope I am doing it correctly!:rolleyes:

oh i got these error messages with the new version of the _product class,

Notice: Undefined variable: _product in C:\wamp\www\000_TEST\php.cart.2\class_lib.php on line 263

Notice: Trying to get property of non-object in C:\wamp\www\000_TEST\php.cart.2\class_lib.php on line 263
:sick:

How are the products stored? Database? XML file? csv file? hardcoded?

thanks for tidying up the code! :slight_smile:

What you’re trying to do you’re doing correctly. I just wonder why you use XML instead of just accessing the database correctly. Do you have a reason for that?

the first reason I use XML is becos of the method of calculate_cost() in original _cart class,

public function calculate_cost() 
	 {
	 	
	 	$cost = 0.00;
	 	
	 	foreach($this->_contents AS $id => $quantity) {
	 		$product = new _product($id);
	 		$cost = $cost + ($product -> price * $quantity);
	 	}
	 	
	 	return number_format($cost, 2);
	 	
	 }

I believe that the price of the item is pulled from the XML as in $product -> price. nothing else I can think of! but I could be wrong!

the second reason is - I don’t want to put SQL query literally in the store class. I would be doing this if access the database directly,

class _store 
{ 
    
    public function get_store() 
    { 
        sql = "
        SELECT * FROM books ORDER BY id
        ";

       #instantiate an object from _db class
       $obj = new _db(DB_HOST,DB_USER,DB_PASS,DB_NAME);
       return $obj -> fetch_all(sql);
    } 
}

and I have another class which is handling database results,

#connects the database and handling the result

# the host used to access DB
define('DB_HOST', 'localhost');

# the username used to access DB
define('DB_USER', 'root');

# the password for the username
define('DB_PASS', 'tklau');

# the name of your databse 
define('DB_NAME', 'products');

class _db 
{
	protected $_connection = null;
	protected $_error = null;

	#make a connection
	public function __construct($hostname,$username,$password,$database)
	{
		$this -> _connection = new mysqli($hostname,$username,$password,$database);
		
		if (mysqli_connect_errno()) 
		{
			printf("Connect failed: &#37;s\
", mysqli_connect_error());
			exit();
		}
	}
        
	#fetches all result rows as an associative array, a numeric array, or both
	public function fetch_all($query) 
	{
		$result = $this -> _connection -> query($query);
		if($result) 
		{
			return $result -> fetch_all(MYSQLI_ASSOC);
		} 
		else
		{
			$this -> _error = $this -> _connection -> get_error;
			return false;
		}
	}
	
	#fetches a result row as an associative array, a numeric array, or both
	public function fetch_assoc_while($query)
	{
		$result = $this -> _connection -> query($query);
		if($result) 
		{
			while($row = $result -> fetch_assoc())
			{
				$return_this[] = $row;
			}

			if (isset($return_this))
			{
				return $return_this;
			}
			else
			{
				return false;
			}
		}
		else
		{
			$this -> _error = $this -> _connection -> get_error;
			return false;
		}
	}
		
	#fetch a result row as an associative array
	public function fetch_assoc($query)
	{
		$result = $this -> _connection -> query($query);
		if($result) 
		{
			return $result -> fetch_assoc();
		} 
		else
		{
			$this -> _error = $this -> _connection -> get_error;
			return false;
		}
	}
		
	#get a result row as an enumerated array
	public function fetch_row($query)
	{
		$result = $this -> _connection -> query($query);
		if($result) 
		{
			return $result -> fetch_row();
		} 
		else
		{
			$this -> _error = $this -> _connection -> get_error;
			return false;
		}
	}
	
	#get the number of rows in a result
	public function num_rows($query)
	{
		$result = $this -> _connection -> query($query);
		if($result) 
		{
			return $result -> num_rows;
		} 
		else
		{
			$this -> _error = $this -> _connection -> get_error;
			return false;
		}
	}
	
	#performs a query on the database
	public function query($query)
	{
		$result = $this -> _connection -> query($query);	
		if($result) 
		{
			return $result;
		} 
		else
		{
			$this -> _error = $this -> _connection -> get_error;
			return false;
		}

	}
	
	#escapes special characters in a string for use in a SQL statement, taking into account the current charset of the connection
	public function real_escape_string($string)
	{
		$result = $this -> _connection -> real_escape_string($string);	
		if($result) 
		{
			return $result;
		} 
		else
		{
			$this -> _error = $this -> _connection -> get_error;
			return false;
		}

	}
         
	#display error
	public function get_error() 
	{
		return $this -> _error;
	}
	
	#closes the database connection when object is destroyed.
    public function __destruct()
    {
        $this -> _connection -> close();
    }
}

so I think of using SimpleXMLElement(file_get_contents(STORE_XML)) which looks simpler… again, I could be wrong! :confused: