2021年6月24日 星期四

PHPUnit 單元測試工具安裝

設定目標:
  • 將 PHPUnit 單元測試工具加入自己實作的 MVC 框架!

安裝 PHPUnit 套件
  1. 在專案目錄 helloMVC 下,利用 composer 執行 phpunit 安裝工程 :
    cd C:\workspace\helloMVA
    composer require phpunit/phpunit ^9.5
    
  2. 將 vendor/bin 目錄,寫入系統環境設定值 PATH 中
  3. 測試一下是否可以正常運行:
    phpunit --version
    
  4. 可在 VScode 內,安裝 PHPUnit 套件,協助開發與測試:
  5. 在專案目錄下,新增一個 tests 目錄,放置測試用的程式!
  6. 在專案目錄下,新增一個 phpunit.xml 檔案,放置大規模測試用的設定值
    phpunit --version
    

參考文獻

2021年6月23日 星期三

使用 PHP 實作 MVC 框架(四)

設定目標:
  • 修改自己實作的 MVC 框架,加入靜態檔案的連結與分類!
  • 接續上一篇文章進行 kernel 模組的修改!

修改 kernel 模組
  1. 修改 kernel 目錄下的 kernel.php :
    <?php
    (前面略過...)
        public function run(){
            spl_autoload_register(array($this, 'loadClass'));
            $this->unregisterGlobals();
            $this->_config->show();
    
            //由路由設定,取出需要使用的控制器
            include ('Router.php');
            //將 css / javascripts / images 檔案歸類
            if (preg_match('/.js/i',$this->_router->request)){
                include ('../statics/js/'.$this->_router->request);
            }elseif (preg_match('/.css/i',$this->_router->request)){
                include ('../statics/css/'.$this->_router->request);
            } elseif (preg_match('/./i',$this->_router->request)){
                include ('../statics/images/'.$this->_router->request);
            } else {
                $uri = $this->_router->run();
                $controller = 'App\\Controllers\\'.$uri[1];
                //找出控制器後,程式交給控制器執行
                if (!class_exists($controller)){
                    exit($controller.'控制器不存在');
                } else {
                    (new $controller($uri[0]))->run();
                // \call_user_func_array($dispatch);
                }
            }
        }
    (後面也略過....)
    
  2. 修改在 app\Views 目錄下,indexView.php :
    <?php
    (前面略過...)
        public function show($user){
            include(APP_PATH.'app/Views/header.html');
            $twig = $this->getTwig();
            echo $twig->render('index.twig.html', ['name' => 'John Doe', 
        'occupation' => 'gardener']);
    
            include(APP_PATH.'app/Views/footer.html');
        }
    (後面也略過....)
    
  3. 在 app\Views 目錄下,新增 header.html 檔案
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
    <script src="myScripts.js"></script>
    <title>Document</title>
    </head>
    <body>
    <div class="container">
    <nav class="navbar navbar-expand-sm bg-primary">
    <!-- Links -->
    <ul class="navbar-nav">
    <li class="nav-item">
    <a class="nav-link" href="#">Link 1</a>
    </li>
    <li class="nav-item">
    <a class="nav-link" href="#">Link 2</a>
    </li>
    <li class="nav-item">
    <a class="nav-link" href="#">Link 3</a>
    </li>
    </ul>
    </nav>
  4. 在 app\Views 目錄下,新增 footer.html 檔案
    <nav class="navbar navbar-expand-sm bg-light">
    <ul class="navbar-nav">
    <li class="nav-item">
    <a class="nav-link" href="#">Link 1</a>
    </li>
    <li class="nav-item">
    <a class="nav-link" href="#">Link 2</a>
    </li>
    <li class="nav-item">
    <a class="nav-link" href="#">Link 3</a>
    </li>
    </ul>
    </nav>
    </div>
    </body>
    </html>
  5. 在 app\Views 目錄下,修改 index.twig.html 檔案
    <h2>最新消息列表</h2>
    <p>最近本站熱門討論項目</p>
    <div class="media border p-3">
    <img src="img_avatar3.png" alt="John Doe" class="align-self-start mr-3" style="width:60px;">
    <div class="media-body">
    <h4>Peter Wang <small><i>發表於 五月 19, 2021</i></small></h4>
    <p>最新 MVC 框架發表了!</p>
    <div class="media p-3">
    <img src="img_avatar4.png" alt="Jane Doe" class="align-self-start mr-3" style="width:45px;">
    <div class="media-body">
    <h4>Jane Doe <small><i>Posted on February 20 2016</i></small></h4>
    <p>Lorem ipsum...</p>
    </div>
    </div>
    </div>
    </div>
    <p>
    {{ name }}test is a {{ occupation }}
    </p>
    <p id="demo">Hello, This is a site</p>
    <button type="button" onclick='demo()'>Click Me !!</button>
    <br>
    <button onclick="turnOn()">Turn on the light</button>
    <img id="myImage" src="pic_bulboff.gif" style="width:100px">
    <button onclick="turnOff()">Turn off the light</button>
  6. 在專案 helloMVC 目錄下,新增 statics 目錄,並在其下建立 images、css、js 三個子目錄
  7. 將網頁中,會使用到的圖檔,放入 statics\images 目錄下!將 CSS 檔案放入 statics\css 目錄下!將 JavaScripts 檔案放入 statics\js 目錄下!
  8. 使用瀏覽器查看網頁內容: http://hellomvc !!

2021年6月21日 星期一

使用 PHP 實作 MVC 框架(三)

設定目標:
  • 實作自己的 DB 模組,加入自己實作的 MVC 框架!
  • 接續上一篇文章進行 DB 模組的實作!

實作 DB 模組 !
  1. 撰寫 DB 連線核心模組,放置於 config 目錄下,例:dbconnect.php
    <?php
    //DB 連線用模組
    namespace config;
    
    use PDO;
    use PDOexception;
    
    class DBconnect {
        private static $pdo = null;
        
        public static function pdo(){
            if (self::$pdo !== null){
                return self::$pdo;
            }
            try {
                $dsn = sprintf('mysql:host=%s;dbname=%s;charset=utf8', DB_HOST, DB_NAME);
                $option = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
    
                return self::$pdo = new PDO($dsn, DB_USER, DB_PASSWORD, $option);
            } catch (PDOException $e) {
                exit($e->getMessage());
            }
        }
    }
    
  2. 撰寫 DB 基本模組,放置於 app/DBs 目錄下,例:DB.php
    <?php
    //建立一個通用的 DB 模組
    namespace App\DBs;
    
    use config\DBconnect;
    use PDOStatement;
    
    class DB extends DBconnect {
    
        protected $table;
        protected $primary = 'id';
        private $filter = '';
        private $param = array();
    
        public function __construct($table){
            $this->table = $table;
            //$this->filter = $filter;
        }
    
        // 條件查詢 where ! 
        // 輸入格式 where(['id = :id'], [':id' => $id]])
        public function where($where = array(), $param = array()){
            if (isset($where)) {
                $this->fileter .= ' WHERE ';
                $this->filter .= \implode(' ', $where);
                $this->param = $param;
            }
    
            return $this;
        }
    
        // 排序方式,由使用者自行輸入
        // 例: order(['id DESC', 'name ASC',...])
        public function order($order = array()){
            if (isset($order)){
                $this->filter .= ' ORDER BY ';
                $this->filter .= \implode(',', $order);
            }
    
            return $this;
        }
        //新增資料
        public function add($data){
            $sql = \sprintf("insert into `%s` %s",$this->table, $this->formatInsert($data));
            $sth = DBconnect::pdo()->prepare($sql);
            $sth = $this->formatParam($sth, $data);
            $sth = $this->formatParam($sth, $this->param);
            $th->execute();
    
            return $sth->rowCount();
        }
        //修改資料
        public function update($data){
            $sql = \sprintf("update `%s` set %s %s", $this->table, $this->formatUpdate($data), $this->filter);
            $sth = DBconnect::pdo()->prepare($sql);
            $sth = $this->formatParam($sth, $data);
            $sth = $this->formatParam($sth, $this->param);
            $th->execute();
    
            return $sth->rowCount();
        }
    
        //一次取回所有資料
        public function fetchAll(){
            $sql = \sprintf("select * from `%s` %s", $this->table, $this->filter);
            $sth = DBconnect::pdo()->prepare($sql);
            //var_dump(($sth));
            $sth = $this->formatParam($sth, $this->param);
            $sth->execute();
    
            return $sth->fetchAll();
        }
    
        //一次只取回一筆資料
        public function fetch(){
            $sql = \sprintf("select * from `%s` %s", $this->table, $this->filter);
            $sth = DBconnect::pdo()->prepare($sql);
            $sth = $this->formatParam($sth, $this->param);
            $sth->execute();
    
            return $sth->fetch();
        }
    
        //刪除資料,以 id 欄位為主要刪除方式,較為方便
        public function delete($id){
            $sql = \sprintf("delete from `%s` where `%s` = :%s", $this->table, $this->primary, $this->primary);
            $sth = DBconnect::pdo()->prepare($sql);
            $sth = $this->formatParam($sth, [$this->primary => $id]);
            $sth->execute();
    
            return $sth->rowCount();
        }
        //格式化資料
        private function formatParam(PDOStatement $sth, $params = array()){
            foreach ($params as $param => &$value) {
                $param = is_int($param) ? $param + 1 : ':' . ltrim($param, ':');
                $sth->bindParam($param, $value);
            }
    
            return $sth;
        }
    
        //轉換成INSERT SQL 語法
        private function formatInsert($data){
            $fields = array();
            $names = array();
            foreach ($data as $key => $value) {
                $fields[] = \sprintf("`%s`", $key);
                $names[] = \sprintf(":%s", $key);
            }
    
            $field = implode(',' ,$fields);
            $name = implode(',', $names);
    
            return \sprintf("(%s) values (%s)", $field, $name);
        }
    
        //轉換成UPDATE SQL 語法
        private function formatUpdate($data){
            $fields = array();
            foreach ($data as $key => $value) {
                $fields[] = \sprintf("`%s` = :%s", $key, $key);
            }
            return implode(',', $fields);
        }
    }
    
  3. 撰寫 LoginController 控制器模組,放置於 app\Controllers 目錄下,使用 DB 模組,進行資料庫存取,例:LoginController.php
    <?php
    namespace App\Controllers;
    
    use kernel\Controller;
    use App\Models\indexModel;
    use App\Views\indexView;
    use App\DBs\DB;
    
    class LoginController extends Controller {
       
        protected $paras;
    
        public function __construct($parameter){
           parent::__construct($parameter);
        }
    
        public function getUri(){
            $this->paras = parent::getUri();
            return $this->paras;
        }
        
        public function run(){
            $db = new DB("students");
            var_dump($db->fetchAll());
        }
    }
    
  4. 在 MySQL 資料庫內,請增一個 contact 資料庫,並在資料庫內,新增一個 student 表格:
    DROP TABLE IF EXISTS `students`;
    CREATE TABLE IF NOT EXISTS `students` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `studentid` varchar(100) NOT NULL,
      `name` varchar(100) DEFAULT NULL,
      `email` varchar(100) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `studentID` (`studentid`)
    ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    
    INSERT INTO `students` (`id`, `studentid`, `name`, `email`) VALUES
    (1, 'ABC123', 'hellokitty', 'kitty@hello.com');
    COMMIT;
    
  5. 使用 http://hellomvc/login 來查看一下網頁!

2021年6月18日 星期五

使用 PHP 實作 MVC 框架(二)

設定目標:
  • 使用 php 程式語言實作 MVC 框架!
  • 接續上一篇文章進行View 的實作!

實作 View 模組與檔案內容!
  1. 撰寫框架核心檢視類別,放置於 kernel 目錄下,例:View.php
    <?php
    
    namespace kernel;
    
    abstract class View {
      
      abstract public function __construct();
      abstract public function __destruct();
      
    }
    
  2. 撰寫測試用 indexView 檢視器,放置於 app\Views 目錄下,例:indexView.php
    <?php
    
    namespace App\Views;
    
    use kernel\View;
    
    class indexView extends View {
    
        public function __construct(){}
        public function show($user){
            print "Hello ,".$user;
        }
        public function __destruct(){}
    }
    
    
  3. 修改原來的 IndexController.php 檔案:
    <?php
    (追加下一行程式)
    use App\Views\indexView;
    (中間略過...)
    
    	//print $result;
        (new indexView())->show($result);
    
    
    PS:修改完後,重新整理網頁,應該會有資料產生!
  4. 修改 kernel.php ,將 $_SERVER 參數,傳向控制器!
    <?php
    (修改下列程式碼)
    (new $controller($uri[0]))->run();
    
  5. 修改 kernel 目錄下的 Controller.php :
    <?php
    namespace kernel;
    
    class Controller {
        protected $tmp_uri = array();
        protected $uri = array();
    
        public function __construct($parameter){
            $this->initUri($parameter); 
        }
            
        private function initUri($parameter){
            if (strlen(trim($parameter))){
                $this->tmp_uri = \explode('&',$parameter);
                foreach ($this->tmp_uri as $value) {
                    $info = (explode('=',$value));
                    $this->uri[$info[0]] = $info[1];
                }
            }
        }
        
        public function getUri(){
            return $this->uri;
        }
    }
    
  6. 修改 app/Controllers 目錄下的 IndexController.php :
    <?php
    (修改下列程式碼)
    class IndexController extends Controller {
       
        protected $paras;
    
        public function __construct($parameter){
           parent::__construct($parameter);
        }
    
        public function getUri(){
            $this->paras = parent::getUri();
            return $this->paras;
        }
        
        public function run(){
            var_dump($this->getUri());
            $username = new indexModel();
            $result = $username->printName();
            $view = new indexView();
            $view->show($result);
        }
    }
    
  7. 使用 http://hellomvc/?test=123 來進行測試,看看是否運作正常!
使用樣板引擎實作 View 模組!
  • 使用 composer 進行 twig 樣板引擎安裝
  • 使用 twig 設計 View 樣板
  1. 在 Windows 上,執行 Composer-Setup.exe 安裝 Composer!
  2. 利用 composer 安裝 twig 樣板引擎:
    cd c:\workspace\helloMVC
    composer require "twig/twig:^3.0"
    
  3. 修改 kernel 目錄下的 kernel.php,加入 Twig 樣板的 autoreload.php 檔案 :
    <?php
    namespace kernel;
    use config\Config;
    use config\Router;
    
    require_once(APP_PATH.'config/config.php');
    require_once(APP_PATH.'config/router.php');
    require_once(APP_PATH.'vendor/autoload.php');
    (其它略過.....)
    
  4. 修改 kernel 目錄下的 View.php,強迫使用 View 類別的子類別,必須使用 Twig 樣板引擎 :
    <?php
    namespace kernel;
    
    use Twig\Environment;
    use Twig\Loader\FilesystemLoader;
    
    class View {
        protected $twig;
        
        public function __construct($viewpath){
            $loader = new FilesystemLoader(APP_PATH."/app/Views".$viewpath);
            $this->twig = new Environment($loader);
            
        }
        public function getTwig(){
            return $this->twig;
        }
        public function __destruct(){}
        
    }
    
  5. 修改 app\Views 目錄下的 indexView.php,使用 Twig 樣板引擎,進行測試 :
    <?php
    namespace App\Views;
    
    use kernel\View;
    
    class indexView extends View {
        protected $twig;
        public function __construct($path){
            parent::__construct($path);
        }
        public function show($user){
            $twig = $this->getTwig();
            echo $twig->render('index.html.twig', ['name' => 'John Doe', 'occupation' => 'gardener']);
        }
        public function __destruct(){
        }
    }
    
  6. 修改 app\Controllers 目錄下的 indexController.php,進行測試 :
    <?php
    (前面略過)
        public function run(){
            $view = new indexView("/");
            $view->show($result);
        }
    (後面略過)
    
  7. 在 app\Views 目錄下,新增 index.html.twig ,進行測試:
    {{ name }} is a {{ occupation }}
    
  8. 使用瀏覽器查看網址: http://hellomvc/ ,可看到結果!

參考文獻:
  • https://zetcode.com/php/twig/