From 2a2bd6011d5a58c7f392f5fbb4f8bf11bf319102 Mon Sep 17 00:00:00 2001 From: edwardyi Date: Sun, 3 Mar 2019 18:05:17 +0800 Subject: [PATCH] initialization design pattern in php --- adapter/payment.php | 38 +++++ observer/controllers/IndexController.php | 184 +++++++++++++++++++++++ observer/index.php | 141 +++++++++++++++++ observer/models/Customer.php | 83 ++++++++++ observer/models/IObserver.php | 5 + observer/models/Store.php | 51 +++++++ observer/models/Updater.php | 43 ++++++ observer/readMe.md | 12 ++ observer/views/ReportWindow.php | 55 +++++++ observer/views/StoreWindow.php | 47 ++++++ observer/web/ReportWinow.php | 89 +++++++++++ observer/web/StoreWindow.php | 106 +++++++++++++ observer/web/base.js | 88 +++++++++++ 13 files changed, 942 insertions(+) create mode 100644 adapter/payment.php create mode 100644 observer/controllers/IndexController.php create mode 100644 observer/index.php create mode 100644 observer/models/Customer.php create mode 100644 observer/models/IObserver.php create mode 100644 observer/models/Store.php create mode 100644 observer/models/Updater.php create mode 100644 observer/readMe.md create mode 100644 observer/views/ReportWindow.php create mode 100644 observer/views/StoreWindow.php create mode 100644 observer/web/ReportWinow.php create mode 100644 observer/web/StoreWindow.php create mode 100644 observer/web/base.js diff --git a/adapter/payment.php b/adapter/payment.php new file mode 100644 index 0000000..a89ab50 --- /dev/null +++ b/adapter/payment.php @@ -0,0 +1,38 @@ +visa = $visa; + } + + public function pay($amount) + { + $this->visa->visa_pay($amount); + } +} + +// 沒有使用adapter的作法 +// $visa = new Visa(); +// $visa->pay(1500); + +$visa = new VisaAdapter(new Visa()); +$visa->pay(2000); + + diff --git a/observer/controllers/IndexController.php b/observer/controllers/IndexController.php new file mode 100644 index 0000000..cd3f991 --- /dev/null +++ b/observer/controllers/IndexController.php @@ -0,0 +1,184 @@ +modelCustomer = new Customer(); + $this->modelStore = new Store(); + + // 取得view和view所需要的參數 + $this->storeWindow = new StoreWindow(); + $this->reportWindow = new ReportWindow(); + + // attach + $this->attach(); + + $this->viewStores = $this->storeWindow->getStoreItems(); + $this->viewReports = $this->reportWindow->getReportItem(); + + // 第一次載入頁面的時候決定要render到哪一頁去 + $this->action_switcher(); + } + + // observer attach + public function attach() + { + // var_dump($this->modelCustomer); + // attach + $this->modelCustomer->attach($this->storeWindow); + $this->modelCustomer->attach($this->reportWindow); + $this->modelStore->attach($this->storeWindow); + $this->modelStore->attach($this->reportWindow); + } + + // observer notify + public function notify($action) + { + $this->modelCustomer->setAction($action); + $this->modelStore->setAction($action); + + $this->modelCustomer->notify(); + $this->modelStore->notify(); + } + + // controller base function + public function render($uri, $params = array()) + { + // echo $uri; + $stores = array(); + extract($params); + ob_start(); + include($uri); + $file = ob_get_clean(); + echo $file; + } + + // controller base function + public function is_ajax() + { + if(!empty($_SERVER['CONTENT_TYPE']) && strtolower($_SERVER['CONTENT_TYPE']) == 'application/x-www-form-urlencoded') { + /* special ajax here */ + // echo 'this is ajax'; + return true; + // die('test ajax'); + } + return false; + } + + // controller base function + public function action_switcher() + { + $action = 'store'; + if(!empty($_GET)) { + $action = $_GET['action']; + } + // 用URL區別不同的action + switch($action){ + case 'store': + $this->store(); + break; + case 'report': + $this->report(); + break; + case'getStoreList': + $this->getStoreList(); + break; + case 'addCustomerList': + $this->addCustomerList(); + break; + case 'getAjaxList': + $this->getAjaxList(); + break; + case 'reportReoveList': + $this->reportReoveList(); + break; + case 'reportBuyList': + $this->reportBuyList(); + break; + } + } + + // ajax + public function getAjaxList() + { + header("Content-type: application/json"); + echo json_encode($this->reportWindow->report()); + } + + // ajax + public function getStoreList() + { + if(isset($_POST) && !empty($_POST)) { + $storeList = $this->modelCustomer->getBuyStock($this->viewStores); + echo json_encode($storeList); + } + } + + // ajax + public function addCustomerList() + { + // var_dump($_POST); + if(isset($_POST) && !empty($_POST)) { + header("Content-type: application/json"); + $this->storeWindow->addItem($_POST); + // nofity變化 + $this->notify(__FUNCTION__); + echo 'success'; + } + } + + // ajax + public function reportReoveList() + { + if(isset($_POST) && !empty($_POST)) { + $this->reportWindow->clearList(); + // nofity變化 + $this->notify(__FUNCTION__); + echo 'success'; + } + } + + // ajax + public function reportBuyList() + { + if(isset($_POST) && !empty($_POST)) { + echo 'ajax request update'; + $this->reportWindow->reportBuyList(); + // nofity變化 + $this->notify(__FUNCTION__); + echo 'success'; + } + } + + + // storeWindow + public function store() + { + $this->render('../web/StoreWindow.php', array('stores'=>$this->viewStores,'title'=>'Observer商城')); + } + + + // reportWindow + public function report() + { + $this->render('../web/ReportWinow.php', array('customers'=>$this->reportWindow->report(),'title'=>'Observer商城-購物清單')); + } +} + +$index = new IndexController(); \ No newline at end of file diff --git a/observer/index.php b/observer/index.php new file mode 100644 index 0000000..4e5075d --- /dev/null +++ b/observer/index.php @@ -0,0 +1,141 @@ +observers = new \SplObjectStorage; + } + + /** + * The subscription management methods. + */ + public function attach(\SplObserver $observer): void + { + echo "Subject: Attached an observer.\n"; + $this->observers->attach($observer); + } + + public function detach(\SplObserver $observer): void + { + $this->observers->detach($observer); + echo "Subject: Detached an observer.\n"; + } + + /** + * Trigger an update in each subscriber. + */ + public function notify(): void + { + echo "Subject: Notifying observers...\n"; + foreach ($this->observers as $observer) { + $observer->update($this); + } + } + + /** + * Usually, the subscription logic is only a fraction of what a Subject can + * really do. Subjects commonly hold some important business logic, that + * triggers a notification method whenever something important is about to + * happen (or after it). + */ + public function someBusinessLogic(): void + { + echo "\nSubject: I'm doing something important.\n"; + $this->state = rand(0, 10); + + echo "Subject: My state has just changed to: {$this->state}\n"; + $this->notify(); + } +} + +/** + * Concrete Observers react to the updates issued by the Subject they had been + * attached to. + */ +class ConcreteObserverA implements \SplObserver +{ + public function update(\SplSubject $subject): void + { + if ($subject->state < 3) { + echo "ConcreteObserverA: Reacted to the event.\n"; + } + } +} + +class ConcreteObserverB implements \SplObserver +{ + public function update(\SplSubject $subject): void + { + if ($subject->state == 0 || $subject->state >= 2) { + echo "ConcreteObserverB: Reacted to the event.\n"; + } + } +} + +/** + * The client code. + */ + +$subject = new Subject; + +$o1 = new ConcreteObserverA; +$subject->attach($o1); + +$o2 = new ConcreteObserverB; +$subject->attach($o2); + +$subject->someBusinessLogic(); +$subject->someBusinessLogic(); + +$subject->detach($o2); + +$subject->someBusinessLogic(); \ No newline at end of file diff --git a/observer/models/Customer.php b/observer/models/Customer.php new file mode 100644 index 0000000..466dd7a --- /dev/null +++ b/observer/models/Customer.php @@ -0,0 +1,83 @@ +getList(); + $result = array(); + if ($viewStores && $customerList) { + foreach ($viewStores as $k=>$v) { + if(array_key_exists($v['id'],$customerList)) { + $v['total_price'] = $customerList[$v['id']]['amount'] * $v['price']; + $result[] = array_merge($customerList[$v['id']],$v); + } + } + } + return $result; + } + + public function getBuyStock($viewStores) + { + $customerList = $this->getList(); + $result = array(); + if ($viewStores && $customerList) { + foreach ($viewStores as $k=>$v) { + if(array_key_exists($v['id'],$customerList)) { + $amount = (int)$customerList[$v['id']]['amount']; + (int)$v['stock'] -= $amount; + $v['total_price'] = $amount * $v['price']; + $result[] = array_merge($customerList[$v['id']],$v); + } + } + } + return $result; + } + + public function addItem($item) + { + if (!$_SESSION['customer_id']) { + $_SESSION['customer_id'] = array(); + } + $_SESSION['customer_id'][$item['id']] = $item; + $this->notify(); + } + + public function getList() + { + if (isset($_SESSION['customer_id'])) { + return $_SESSION['customer_id']; + } + } + + public function clearList() + { + if ($_SESSION) { + unset($_SESSION['customer_id']); + } + } + + public function setBuyIt() + { + $this->isBuy = true; + // $this->notify(); + } + + public function getBuyIt() + { + return $this->isBuy; + } +} \ No newline at end of file diff --git a/observer/models/IObserver.php b/observer/models/IObserver.php new file mode 100644 index 0000000..109006a --- /dev/null +++ b/observer/models/IObserver.php @@ -0,0 +1,5 @@ +initData(); + // 避免重複寫code,如果不加上去的話,沒有辦法建立SplObjectStorage + parent::__construct(); + // $this->observers = new \SplObjectStorage(); + } + + + public function initData() + { + // 商品清單,考量到有可能新增或刪除商品,多增加id的屬性,對應到商品資料 + $this->addItem(array("id"=>1,"name"=>"蘋果","price"=>100,"stock"=>300)); + $this->addItem(array("id"=>2,"name"=>"香蕉","price"=>150,"stock"=>340)); + $this->addItem(array("id"=>3,"name"=>"巴辣","price"=>200,"stock"=>600)); + } + + public function addItem($item) + { + array_push($this->items, $item); + $this->notify(); + } + + public function removeItem($id) + { + foreach($this->items as $key => $item) { + if($item['id'] == $id) { + unset($this->items[$key]); + } + } + } + + public function getList() + { + return $this->items; + } + +} + +// $store = new Store(); +// $tt = $store->getList(); +// var_dump($tt); \ No newline at end of file diff --git a/observer/models/Updater.php b/observer/models/Updater.php new file mode 100644 index 0000000..f426cad --- /dev/null +++ b/observer/models/Updater.php @@ -0,0 +1,43 @@ +observers = new \SplObjectStorage(); + } + + public function attach(\SplObserver $ob) + { + $this->observers->attach($ob); + } + + public function detach(\SplObserver $ob) + { + $this->observers->detach($ob); + } + + public function notify() + { + if ($this->observers) { + foreach($this->observers as $observer) { + $observer->update($this); + echo 'notify updating...'; + } + } + } + + public function setAction($action) + { + $this->action = $action; + } + + public function getAction() + { + return $this->action; + } +} \ No newline at end of file diff --git a/observer/readMe.md b/observer/readMe.md new file mode 100644 index 0000000..5d00a1f --- /dev/null +++ b/observer/readMe.md @@ -0,0 +1,12 @@ +## Observer Pattern +- storeWindow和reportWindow為兩個observer負責Customer和Store兩個Model的變化 +- 使用adapter pattern隔離變化 + - 換成其他儲存方式可以用到 + +## TODO +- 將購物清單的存放機制從Session改成cookie +- 用member_id來區別不同的消費者 +- Long Polling的寫法,而不是用setInterval的方式取得資料 + - socket.io +- javascript操作 + - table input的在重新取得新資料input資料會被清掉 \ No newline at end of file diff --git a/observer/views/ReportWindow.php b/observer/views/ReportWindow.php new file mode 100644 index 0000000..bb9a379 --- /dev/null +++ b/observer/views/ReportWindow.php @@ -0,0 +1,55 @@ +store = new store(); + $this->customer = new Customer(); + } + + public function update(\SplSubject $subject): void + { + switch($subject->getAction()){ + case "addCustomerList": + $this->JSScript = sprintf('onGetCustomerList();'); + break; + } + } + + public function report() + { + return $this->customer->report($this->store->getList()); + } + + public function clearList() + { + $this->customer->clearList(); + } + + public function reportBuyList() + { + $this->customer->setBuyIt(); + $this->customer->notify(); + return $this->customer->getBuyIt(); + } + + // 實作updateView + public function updateView() + { + $this->customer->update(); + } + + public function getReportItem() + { + return $this->customer->report($this->store->getList()); + } +} \ No newline at end of file diff --git a/observer/views/StoreWindow.php b/observer/views/StoreWindow.php new file mode 100644 index 0000000..613439b --- /dev/null +++ b/observer/views/StoreWindow.php @@ -0,0 +1,47 @@ +store = new store(); + $this->customer = new Customer(); + } + + public function update(\SplSubject $subject): void + { + switch($subject->getAction()){ + case "addCustomerList": + $this->JSScript = sprintf('onGetCustomerList();'); + break; + } + } + + public function getStoreItems() + { + return $this->store->getList(); + } + + public function addItem($item) + { + // 增加購物清單 + $this->customer->addItem($item); + } + + public function getCustomerList() + { + return $this->customer->getList(); + } +} + + +?> \ No newline at end of file diff --git a/observer/web/ReportWinow.php b/observer/web/ReportWinow.php new file mode 100644 index 0000000..9dadb42 --- /dev/null +++ b/observer/web/ReportWinow.php @@ -0,0 +1,89 @@ + + + + + + <?php echo $title;?> + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
商品名稱價格購買數量金額
+ + + +
+
+ + \ No newline at end of file diff --git a/observer/web/StoreWindow.php b/observer/web/StoreWindow.php new file mode 100644 index 0000000..53eabd2 --- /dev/null +++ b/observer/web/StoreWindow.php @@ -0,0 +1,106 @@ + + + + + + + <?php echo $title;?> + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
商品名稱價格庫存購買數量加入購物清單
+ + +
+
+
+ + \ No newline at end of file diff --git a/observer/web/base.js b/observer/web/base.js new file mode 100644 index 0000000..4d4ce7c --- /dev/null +++ b/observer/web/base.js @@ -0,0 +1,88 @@ +function ajaxPostRequest(uri, item) { + var xhttp = new XMLHttpRequest(); + var responseText; + xhttp.open("POST", uri, true); + // xhttp.setRequestHeader("Content-type","application/json"); + xhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhttp.onload = function() { + if (this.status === 200 && this.readyState == 4) { + // console.log(this.responseText); + if (this.responseText === "success") { + // console.log('成功加入購物清單') + } + } + } + + var params = typeof item == 'string' ? item : Object.keys(item).map( + function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(item[k]) } + ).join('&'); + + xhttp.send(encodeURI(params)); + return xhttp; +} + + +function removeRowByCellValue(table) +{ + var cells = table.getElementsByTagName("TD"); + if (cells.length < 2) { + return false; + } + for(var x = 0; x < cells.length; x++) { + + // check if cell has a childNode, prevent errors + if(!cells[x].firstChild) { + continue; + } + var row = cells[x].parentNode; + row.parentNode.removeChild(row); + } +} + +function addDataToCell(table, data, fields) { + if (data) { + data = JSON.parse(data); + var tbody = table.querySelector('tbody'); + for(var i = 0; i < data.length; i++) { + var row = document.createElement('tr'); + + for (var j = 0; j < fields.length; j++) { + var fieldObj = fields[j]; + // 根據傳入的類型建立元素 + var cell = document.createElement(fieldObj.type); + + // 判斷input tag begin + let isInput = fieldObj.type == "input"; + if (isInput && typeof fieldObj.onclick != "undefined") { + let pid = fieldObj.onclick.replace('placeholder', data[i]['id']); + cell.setAttribute("onclick", pid); + } + if (isInput && typeof fieldObj.class != "undefined") { + cell.setAttribute("class", fieldObj.class); + } + + if (isInput && typeof fieldObj.typeAttribute != "undefined") { + cell.setAttribute("type", fieldObj.typeAttribute); + } + var inputVal = table.querySelector(`tr:nth-child(2)`); + + if (isInput && typeof fieldObj.value != "undefined") { + cell.setAttribute("value", fieldObj.value); + } + + if (!isInput) { + var nodeData = document.createTextNode(data[i][fieldObj.name]); + cell.appendChild(nodeData); + row.appendChild(cell); + } else { + // 如果是其他元素的話,需要再創建TD的格子給他存放 + var td = document.createElement('td'); + td.appendChild(cell); + row.append(td) + } + // 判斷input tag end + } + tbody.appendChild(row); + } + } +}