查看: 2019|回复: 8
|
用OOP的方法实现PHP购物车类型
[复制链接]
|
|
我没有正式用自己做的Shopping Cart,不过,参考了一些网站的Shopping Cart之后,再加上自己刚刚接触不久的PHP object oriented Programming 的知识,花了两个小时实现了以OO机制运作的Shopping Cart,在这里将源码写出来。基本上我所加入的功能是属于基本的,所以请不要见怪。
在PHP运用OOP的好处很多,虽然看起来复杂,不过debug时却是出乎意料的方便,我花了不到半小时就可以弄好了,这连我自己也没预料到。。而且在延伸功能方面也更方便,可以随心所欲地加入自己所要得功能。
基本上这个购物车的结构很简单,由两个部分组成,一个是购物车本身,另一个是货品。再货品部分先定义一个validator的界面,然后再定义一个抽象特性物件,这个特性物件利用了validator的界面,而且将被货品类本身继承,例子如下:
- //saveas class.propertyObject.php
- interface validator {
- abstract function validate();
- }
- abstract class propertyObject implements validator {
- //利用protected让变数能够让子类型利用
- protected $propertyTable = array();
- protected $changedProperties = array();
- protected $data;
- protected $errors = array();
-
- //$arData是数据来源,货品将从这里取得特性数据
- public function __construct($arData){
- $this->data = $arData;
- }
-
- //利用__get函式取得所要的特性
- function __get($propertyName){
- if(!array_key_exists($propertyName, $this->propertyTable)){
- throw new Exception("Invalid property "$propertyName"!");
- }
- if(method_exists($this,'get'.$propertyName)){
- return call_user_func(array($this, 'get'.$propertyName));
- }else{
- return $this->data[$this->propertyTable[$propertyName]];
- }
- }
- //__set的函式这里将不需要用到,不过有必要的话,你可以修改。
- /*function __set($propertyName, $value){
- if(!array_key_exists($propertyName, $this->propertyTable)){
- throw new Exception("Invalid property "$propertyName"!");
- }
- if(method_exists($this, 'set'.$propertyName)){
- return call_user_func(array($this,'set'.$propertyName),$value);
- }else{
- if($this->data[$this->propertyTable[$propertyName]] != $value && !in_array($propertyName, $this->changedProperties)){
- $this->changedProperties[] = $propertyName;
- }
- $this->data[$this->propertyTable[$propertyName]] = $value;
- }
- }*/
-
- function validate(){
- //由于propertyObject没有直接被利用,所以这里放空
- }
- }
复制代码
以上只是一个抽象类,他的功能是管理货品的特性,而且功能可以在延伸至其他的类型。定义为抽象类的原因是我们不要它被实体化,因为它本身只是一个辅助的角色;接下来我们还得制作一个和数据库沟通的界面来管理购物车和数据库的沟通,我们成为DataManager类:
- //saveas class.DataManager.php
- class DataManager {
- //将DataManager的函式设为静态,以便在不必实体化的情况下运用
- public static function getConnection(){
- static $hDB;
-
- if(isset($hDB)){
- return $hDB;
- }
-
- $hDB = mysql_connect("localhost","username","password");
- return $hDB;
- }
- //从数据库取出货品资料,然后再把mysql_fetch_assoc输出。
- public static function getGoodData($goodid){
- $sql = "SELECT * FROM goods WHERE goodid = $goodid";
- $res = mysql_query($sql, DataManager::getConnection());
- if(!($res && mysql_num_rows($res))){
- die("Failed to retrieve $goodid data");
- }
-
- return mysql_fetch_assoc($res);
- }
- }
复制代码
以上两个类是一个很强的工具,可以用在不同的地方。接下来就是真正建造货品类了,有了以上两个工具,你的货品类结构将很简单而且适用:
- //saveas class.Good.php
- require_once('class.propertyObject.php');
- require_once('class.DataManager.php');
- //别忘了Good继承了propertyObject的特性和方法
- class Good extends propertyObject {
- function __construct($goodid){
- //利用DataManager从数据库提取资料
- $arData = DataManager::getGoodData($goodid);
-
- parent::__construct($arData);
- //将datafield资料输入propertyObject的propertyTable。
- //之后如果需要提取任何一个特性,__get函式将自动帮你从propertyTable里搜寻然后输出指定的数据。
- $this->propertyTable['goodid'] = 'goodid';
- $this->propertyTable['id'] = 'goodid';
- $this->propertyTable['name'] = 'gname';
- $this->propertyTable['category'] = 'gcategory';
- $this->propertyTable['discount'] = 'gdiscount';
- $this->propertyTable['price'] = 'gprice';
- $this->propertyTable['quantity'] = 'gquantity';
- $this->propertyTable['maxqty'] = 'gmaxqty';
- }
- //由于运用了validator的界面,所以在这里定义validate()函式
- function validate(){
- if(!$this->name) {
- $this->errors['name'] = "The name of $this->goodid is missing";
- }
- if($this->discount == 100){
- $this->errors['discount'] = "Invalid Discount";
- }
- if($this->price <= 0){
- $this->errors['price'] = "Invalid Price";
- }
- if($this->quantity <= 0){
- $this->errors['quantity'] = "The good is out of stock!";
- }
- if(sizeof($this->errors)){
- return false;
- }else{
- return true;
- }
- }
- //另加一个toString()以备用(暂时没实际用途)
- function __toString(){
- return 'Good: '.$this->name. '; Price: '. $this->price . '; Qty: '.$this->quantity;
- }
- }
复制代码
货品类完成了!现在如果你已经有数据库的话,可以先进行货品类的测试,不过得先再mysql弄一个表,再自行输入一些数据。
- create table goods (
- goodid int not null auto_increment primary key,
- gname varchar(200),
- gcategory varchar(200),
- gdiscount int(3),
- gprice int(5),
- gquantity int(5),
- gmaxqty int(5)
- )
复制代码
我这里跳过Good的测试,因为在最后购物车类完成时我将会一起进行测试。
待续..... |
|
|
|
|
|
|
|

楼主 |
发表于 25-10-2005 05:11 PM
|
显示全部楼层
实现购物车类
由于购物车本身具有集合体的特性,所以接下来我将利用集合体来作为购物车的蓝图。首先,现制作一个抽象的集合体类 -- GoodsCollection,它将包含了大部分购物车所具有的特性和方法:
- //saveas class.GoodsCollection.php
- abstract class GoodsCollection {
- //protected以便让子类分享
- protected $_goodies = array();//这个是集合货品的阵列
- protected $_curPrice; //目前的价钱总和
-
- //加入货品的函式
- public function addItem(Good $objgoods, $ID=null){
- if($ID){
- if(isset($this->_goodies[$ID])){
- throw new KeyInUseException("The $ID key is already in user!");
- }else {
- $this->_goodies[$ID] = $objgoods;
-
- $this->updateprice();//当有影响到货品的数量时,价钱将自动更新
- return;
- }
- }else{
- $key = $objgoods->id;
- $this->_goodies[$key] = $objgoods;
- $this->updateprice();
- return;
- }
- }
-
- //移除货品
- public function removeItem($ID){
- if(!isset($this->_goodies[$ID])){
- throw new KeyInvalidException("The $ID key is not exists");
- }else{
- unset($this->_goodies[$ID]);
- $this->updateprice();
- }
- return ;
- }
-
- //以下加入几个典型集合体的功能
- public function getQty(){
- return sizeof($this->_goodies);
- }
- public function getGoodByID($ID){
- return $this->_goodies[$ID];
- }
- public function getKey(){
- return array_keys($this->_goodies);
- }
-
- public function getGoodName(){
- $goodname = array();
- foreach($this->_goodies as $good){
- $goodname[$good->id] = $good->name;
- }
- return $goodname;
- }
-
- //更新价钱的方法。只供物件本身调用。
- private function updateprice(){
- if($this->getQty()!=0){
- $this->_curPrice = 0;
- foreach($this->_goodies as $good) {
- $price = $good->price;
- $discount = $good->discount;
- $price = $price - ($price * ($discount/100));
- $this->_curPrice += $price;
- }
- }
- }
- //清空购物车
- protected function clearAllGoodies(){
- $this->_goodies = null;
- $this->_goodies = array();
- }
- }
- //建立两个Exception的子类以便分开不同的错误
- class KeyInUseException extends Exception {}
- class KeyInvalidException extends Exception {}
复制代码
待续。。。。(下班中...)
[ 本帖最后由 苦瓜汤 于 25-10-2005 07:35 PM 编辑 ] |
|
|
|
|
|
|
|

楼主 |
发表于 25-10-2005 07:33 PM
|
显示全部楼层
以上的抽象GoodsCollection类已经定义了大部分购物车类的特性,所以购物车本身的类型并不需要很多的功能,因为大部分已经继承了GoodsCollection,所以我这里只定义了一个函式 -- checkOut(),checkOut()本身搜集了最后所需要的资料,然后经由一个阵列输出。
- //saveas class.Cart.php
- require_once('class.GoodsCollection.php');
- class Cart extends GoodsCollection {
- private $_checkOutInfo = array();
-
- function checkOut(){
- $this->_checkOutInfo['quantity'] = $this->getQty();
- $this->_checkOutInfo['totalamount'] = $this->_curPrice;
- $this->_checkOutInfo['goodnamearray'] = $this->getGoodName();
- $this->_checkOutInfo['checkOutTime'] = date("d/m/Y H:i:s",time());
- $this->clearAllGoodies();
- return $this->_checkOutInfo;
- }
-
- }
复制代码
checkOut()的同时Cart也会自动清空所有的货品。clearAllGoodies可以不需要,不要考虑到如果要重复使用Cart的话就很重要,因为如果session继续保存Cart的所有资料,那么下一个checkOut()将会连上一次所购买的物品也加入。当然你也可以选择用session_destroy()来摧毁保留在session的Cart物件。
基本上购物车已经完成了,可以开始测试。利用以下的code你就可以看得出在利用OO的方法下,主要编程工作就容易了很多:
- <?php
- require_once('class.Cart.php');
- $shoppingCart = new Cart();
- $shoppingCart->addItem(new Good(1));
- $shoppingCart->addItem(new Good(2));
- $good1 = $shoppingCart->getGoodByID(1);
- echo $good1->name."<br>\n";
- //将gooid = 1的货品删除
- $shoppingCart->removeItem(1);
- $info = $shoppingCart->checkOut(); //checkout and clear cart
- echo "<pre>\n";
- print_r($info);
- echo "</pre>";
- //由于Cart已经清空,所以PHP将会提醒$good1将不被定义
- $good1 = $shoppingCart->getGoodByID(1); //display error
- ?>
复制代码
如果一切都正确,你的browser将会显示以下的资料.
- Maxtor HDD 60GB
- Array
- (
- [quantity] => 1
- [totalamount] => 136
- [goodnamearray] => Array
- (
- [2] => Western Digital HDD 80GB
- )
- [checkOutTime] => 25/10/2005 19:25:18
- )
- Notice: Undefined offset: 1 in C:\Inetpub\wwwroot\Site\ShoppingCart\class.goods.php on line 42
复制代码
由于大部分工作都由物件分担,所以在实际编程或重调源码就变得很容易,只需要生成新的物件来负责工作就可以了。session方面可以直接register整个Cart物件,PHP会自动将Cart物件serialize,然后需要用到时再deserialize。
这个购物车的功能还不很完全,如果需要更多功能的话,可以利用一样的方法加入更多类型,例如:绘图类型,货品资料建设类型等等。
暂时分享到这里,希望各位能给给意见,让我能够改良这个购物车类。
这个是购物车的源码,如果有更新我将会更新此文件:
下载
[ 本帖最后由 苦瓜汤 于 26-10-2005 07:51 PM 编辑 ] |
|
|
|
|
|
|
|
发表于 26-10-2005 12:24 AM
|
显示全部楼层
应该只有PHP5才能够Run的吧!!!!!! PHP 4好像不能Define Data Type 是属于Public, Private, Protected 等的吧!!
多谢分享. |
|
|
|
|
|
|
|

楼主 |
发表于 26-10-2005 09:19 AM
|
显示全部楼层
原帖由 belon_cfy 于 26-10-2005 12:24 AM 发表
应该只有PHP5才能够Run的吧!!!!!! PHP 4好像不能Define Data Type 是属于Public, Private, Protected 等的吧!!
多谢分享.
对,要PHP5才能RUN。多谢提醒。
目前要解除两个限制:
1。支持单一货品超过一个数量(已经解决);
2。支持place order的功能; |
|
|
|
|
|
|
|
发表于 26-10-2005 05:30 PM
|
显示全部楼层
哇~ 好象好容易DEBUG哦~. 看来我也要开始学习OOP了~
谢谢楼主分享! |
|
|
|
|
|
|
|

楼主 |
发表于 26-10-2005 07:44 PM
|
显示全部楼层
原帖由 地鼠 于 26-10-2005 05:30 PM 发表
哇~ 好象好容易DEBUG哦~. 看来我也要开始学习OOP了~
谢谢楼主分享!
多谢支持。
目前已经大致上解除了以上两个限制。另外附加一个比较完整的测试网页(test2.php, viewcart.php)。我将会更新帖子内的文件连接。P/S:由于已经支持place order,所以需要另外附加一个table。
- CREATE TABLE `orders` (
- `transactionid` int(11) NOT NULL auto_increment,
- `orderid` int(12) NOT NULL,
- `goodid` int(11) NOT NULL,
- `price` int(5) default NULL,
- `discount` int(3) default NULL,
- `quantity` int(5) default NULL,
- `orderdate` datetime default NULL,
- `shippen` char(1) default '0',
- PRIMARY KEY (`transactionid`),
- constraint fk_orders_goodid foreign key (goodid) references goods(goodid)
- )
复制代码
[ 本帖最后由 苦瓜汤 于 26-10-2005 07:49 PM 编辑 ] |
|
|
|
|
|
|
|
发表于 27-10-2005 09:20 AM
|
显示全部楼层
- Array
- (
- [quantity] => 1
- [totalamount] => 136
- [goodnamearray] => Array
- (
- [2] => Western Digital HDD 80GB
- )
- [checkOutTime] => 25/10/2005 19:25:18
- )
复制代码
從這裡看來 GoodsCollection 里的每一個 item 都只有 1 個數量, 可以 / 可能有不同的數量嗎 ?
例入
- Array
- (
- [quantity] => 1
- [totalamount] => 400
- [goodnamearray] => Array
- (
- Array (
- [item] => Western Digital HDD 80GB
- [qty] => 1
- [price] => 100
- )
- Array (
- [item] => 256MB DDR
- [qty] => 2
- [price] => 150
- )
- )
- [checkOutTime] => 25/10/2005 19:25:18
- )
复制代码 |
|
|
|
|
|
|
|

楼主 |
发表于 27-10-2005 09:47 AM
|
显示全部楼层
原帖由 flashang 于 27-10-2005 09:20 AM 发表
[code]
Array
(
[quantity] => 1
[totalamount] => 136
[goodnamearray] => Array
(
[2] => Western Digital HDD 80GB
)
[checkOutTime] =& ...
目前我已经修改了这个限制,完整的源码可以在帖子里的下载点下载。当你加入同一货品物件时,Cart会检查是否有同一类货品在里面,如果有的话,Cart会将该货品的数量特性增加,并不会另外重复将新的货品实体化,以免浪费系统资源。至于checkout 的info只是一个简化的输出阵列,所以资料不多,不过货品类型本身已经拥有所有的资料,看你本身如何整理资料的输出。
[ 本帖最后由 苦瓜汤 于 27-10-2005 09:49 AM 编辑 ] |
|
|
|
|
|
|
| |
本周最热论坛帖子
|