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/

2021年5月28日 星期五

使用 PHP 實作 MVC 框架(一)

設定目標:
  • 使用 php 程式語言實作 MVC 框架!
    • Model (模組) : 處理大部分的商業邏輯和資料運算邏輯。
    • Controller (控制器) : 負責接收、回應使用者請求,以及準備資料、展示資料等工作。
    • View (檢視) : 負責整理好資料,通過HTML方式回應給使用者。
    • MVC 示意圖如下:
  • 先從定義文件與規範開始,再逐一進入MVC階段!

定義目錄架構與環境設定檔案!
  • 專案根目錄 helloMVC 下的目錄結構:
    helloMVC
       |-- app
       |    |-- Models
       |    |-- Views
       |    |-- Controlles
       |-- config
       |-- kernel
       |-- public
       |-- static
       |-- tmp
       |-- scripts
    
    • app :應用程式目錄
    • config :應用程式組態檔目錄
    • kernal :MVC 核心架構程式碼目錄
    • public :公開網頁檔案目錄,例如:index.php 等 MVC 框架程式進入點!
    • static :靜態網頁目錄,可放置 css 等檔案!
    • tmp :臨時放置檔案目錄
    • scripts : 可執行系統命令工具目錄
  • 撰寫程式風格要求:
    • 資料庫表格名稱:使用小寫的名詞
    • 模組名稱:使用字首小寫名詞 + Model 組合,亦名為「駝峰命名法」,例:carModel !
    • 控制器名稱:使用字首大寫名詞 + Controller 組合,亦名為「雙駝峰命名法」,例:CarController !
    • 核心程式名稱:使用字首大寫名詞,例:Route.php, Controller.php 等等 !
    • 檢視程式名稱:使用控制器分類名稱以及行為動詞,例: Car/move.php !
  • 設定網址目錄轉向!
    • 目的:將程式的入口單一化至 index.php
    • 靜態目錄下的檔案例外!
    • 以 Apache Web Server 為例,編寫 .htaccess , 放置在專案根目錄 helloMVC/public 下:
      #<IfModule mod_rewrite.c>
      # 開啟 Apache Web Server 目錄轉向功能
      RewriteEngine On
      
      # 網址請求路徑如果存在真實檔名或目錄,就可以直接使用
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      
      # 如果網址查看的檔案或目錄不存在,則重定向所有請求到 index.php
      RewriteRule ^(.*)$ index.php?url=$1 [PT,L]
      
      #</IfModule mod_rewrite.c>
      
      • 允許靜態檔案直接存取!
      • 不存在的檔案或是目錄,直接匯入 index.php 檔案,控制程式進出!
      • 可間接最佳化存取網址,有利於 SEO 的使用!
撰寫核心運作檔案!
  1. 撰寫 public 目錄下的 index.php 檔案 !
    <?php
    use kernel\Kernel;
    
    // 設定應用程式目錄為當前目錄
    define('APP_PATH', __DIR__.'../');
    
    // 開啟除錯模式
    define('APP_DEBUG', true);
    
    // 載入框架
    require(APP_PATH.'kernel/kernel.php');
    
    // 產生實例化物件
    (new Kernal())->run();
    
    PS: 不用寫結尾的 ?>
  2. 撰寫 kernel 目錄下的 kernel.php 檔案:
    <?php
    
    namespace kernel;
    use config\Config;
    use config\Router;
    
    require_once(APP_PATH.'config/config.php');
    require_once(APP_PATH.'config/router.php');
    class Kernel {
    
        protected $_config;
        protected $_router;
        
        public function __construct(){
        	$this->_config = new Config();
            $this->_router = new Router($_SERVER);
        }
    	public function run(){
        	spl_autoload_register(array($this, 'loadClass'));
            $this->unregisterGlobals();
            $this->_config->show();
            
            //由路由設定,取出需要使用的控制器
            include ('Router.php');
            $uri = $this->_router->run();
            $controller = 'App\\Controllers\\'.$uri[1];
            //找出控制器後,程式交給控制器執行
            if (!class_exists($controller)){
                exit($controller.'控制器不存在');
            } else {
                (new $controller())->run();
            }        
        }
        //自動加載類別
        public function loadClass($className){
        	$classMap = $this->classMap();
    
            if (isset($classMap[$className])){
                $file = $classMap[$className];
            } elseif (strpos($className, '\\') !== false){
                //包含 app 目錄下的文件檔案
                $file = APP_PATH.str_replace('\\','/',$className).'.php';
                if (!is_file($file)){
                    return ;
                }
            } else {
                return;
            }
            include $file;
        }
        //類別對應命名空間
        public function classMap(){
            return [
                'kernel\Controller' => CORE_PATH.'Controller.php',
                'kernel\Model' => CORE_PATH.'Model.php',
                'kernel\View' => CORE_PATH.'View.php',
                'kernel\Router' => CORE_PATH.'Router.php'
            ];
        }
        //取消全域自定義變數
        public function unregisterGlobals(){
            if (\ini_get('register_globals')){
                $array = array('_SESSION','_POST','_GET','_COOKIE','_REQUEST','_SERVER','_ENV','_FILES');
                foreach ($array as $value) {
                    foreach ($GLOBALS[$value] as $key => $var) {
                        if ($var === $GLOBALS[$key]){
                            unset($GLOBALS[$key]);
                        }
                    }
                }
            }
        }
    }
    
  3. 編寫 config 目錄下的 config.php 檔案:
    <?php
    namespace config;
    
    class Config{
        public function show(){
            $file = fopen("../config/.env",'rb');
            while ((! feof($file)) && ($line = fgets($file))){
                $line = trim($line);
                $info = explode('=',$line);
                if (empty($info[0])){
                    continue;
                }
                define($info[0],$info[1]);
            }
        }
    }
    
    
    PS: 一般程式常用的變數亦可在此設定!
  4. 撰寫環境設定檔,放在 config 目錄下,例:.env 檔案!
    DB_NAME=contact
    DB_USER=hello
    DB_PASSWORD=hello$1213
    DB_HOST=localhost
    
    APP_URL=http://localhost
    CORE_PATH=../kernel/
    
  5. 撰寫框架核心路由程式,放置於 config 目錄下,例:router.php
    <?php
    namespace config;
    
    class Router {
    
        public $request;
        public static $routes = array();
    
        //建構子必需要剖析網址 URI 的部份
        public function __construct(array $request){
            $this->request = basename($request['REQUEST_URI']);
        }
        
        public static function addRoute(string $uri, $controller) : void {
            self::$routes[$uri] = $controller;
        }
    
        public function hasRoute(string $uri) : bool {
            $uri = '/'.$uri;
            return array_key_exists($uri, self::$routes);
        }
        
        public function run(){
            //分析參數
            $uri = array();
            $uri = explode('?',$this->request);
            if (!isset($uri[1])){
                $uri[1] = "";
            }
            if ($this->hasRoute($uri[0])){
                return array($uri[1],(self::$routes['/'.$uri[0]]));
            }
        }
    }
    
  6. 撰寫路由的路徑設定檔,放置於 kernel 目錄下,例:Router.php
    <?php
    use config\Router;
    
    Router::addRoute('/login',LoginController::class);
    Router::addRoute('/',IndexController::class);
    
  7. 撰寫框架核心控制器抽象類別,放置於 kernel 目錄下,例:Controller.php
    <?php
    namespace kernel;
    
    abstract class Controller {
        
        abstract public function run();
        
    }
    
  8. 撰寫一個測試用的 IndexController 控制器,放置於 app/Controllers 目錄下,例:IndexController.php
    <?php
    namespace App\Controllers;
    
    use kernel\Controller;
    use App\Models\indexModel;
    
    class IndexController extends Controller {
    
        public function run(){
        	$user = new indexModule();
            $result = $user->printName();
            print "Hello ,".$result;
        }
    }
    
  9. 撰寫框架核心模組抽象類別,放置於 kernel 目錄下,例:Model.php
    <?php
    namespace kernel;
    
    abstract class Model {
        
        abstract public function __construct();
        abstract public function __destruct();
    }
    
  10. 撰寫一個測試用的模組程式,放置於 app/Models 目錄下,例:indexModel.php
    <?php
    namespace App\Models;
    
    use kernel\Model;
    
    class indexModel extends Model {
        
        protected $name;
        
        public function __construct(){
        	$this->name = "Peter";
        }
        public function printName(){
        	return $this->name;
        }
        public function __destruct(){
        }
    }
    
    PS: 檢查是否成功,可使用 http://helloMVC 檢查!

參考文獻

2021年5月27日 星期四

PHP 使用 Session 與 Cookie 的方式

設定目標:
  • 使用 php 程式語言操作 Session 與 Cookie 方式!

PHP 使用 Cookie 的方式
  • 使用 Cookie 可以暫時存放需要的資料內容
  • Cookie 資料內容是暫時存放於本機端上!
  • 使用完 Cookie ,應立即清除資料,以利資安!
  1. 程式中,設定 cookie !例:user.php
    <?php
    setcookie('username','peter');
    ?>
    
  2. 列印出 cookie 內容!例:getuser.php
    <?php
    print 'Hello, '. $_COOKIE['username'];
    ?>
    
  3. 設定 cookie 的有效時間!例:usertimes.php
    <?php
    // cookie 一小時後過期
    setcookie('short-user','Peter',time()+60*60);
    
    // cookie 一天後過期
    setcookie('longer-user','Peter',time()+60*60*24);
    
    // cookie 指定日期過後,才會過期
    $datetime = new DataTime("2021-10-01 12:00:00");
    setcookie('date-user','Peter',$datetime->format('U');
    ?>
    
  4. 指定 cookie 可被利用的目錄路徑!例:pathuser.php
    <?php
    // 網頁根目錄下的所有檔案都可以利用的 cookie 設定
    setcookie('longer-user','Peter',time()+60*60*24,'/');
    
    // 網頁目錄 /manager 底下的檔案才可以利用的 cookie 設定
    setcookie('short-user','Peter',time()+60*60,'/manager/');
    ?>
    
  5. 指定 cookie 可被指定的網域名稱利用!例:domainuser.php
    <?php
    // example.com 網域的網頁根目錄下的所有檔案都可以利用的 cookie 設定
    setcookie('longer-user','Peter',time()+60*60*24,'/','.example.com');
    ?>
    
  6. 強調安全的 cookie 使用方式!例:secureuser.php
    <?php
    setcookie('short-user','Peter',time()+60*60,'/manager/',true,true);
    //第六個欄位:只用於 https 連線
    //第七個欄位:不給 JavaScripts 利用
    ?>
    
  7. 刪除 cookie 的方式!例:deleteuser.php
    <?php
    setcookie('longer-user','');
    ?>
    
PHP 使用 Session 的方式
  • 使用 Session 時,PHP 會在本機端建立一個 PHPSEDDID 的 Cookie 暫時存放遠端站台的 Session ID 編號
  • 如果沒有 PHPSEDDID 的 cookie 存在,PHP 程式會自動建立!
  1. 程式中,設定與使用 session_start() !例:counter.php
    <?php
    session_start();
    
    if (isset($_SESSION['count'])){
    	$_SESSION['count'] = $_SESSION['count'] + 1;
    } else {
    	$_SESSION['count'] = 1;
    }
    
    print "目前這頁面己經被重載了 ".$_SESSION['count']. " 次";
    ?>
    
  2. 程式中,設定 Session 使用時間 !例:timescounter.php
    <?php
    ini_set('session.gc_maxlifetime',600); // 設定600 秒
    session_start();
    

2021年4月17日 星期六

PHP 操作 MySQL 資料庫

設定目標:
  • 使用 php 程式語言操作資料庫系統!
  • 本練習將利用 Docker ,啟動 nginx + PHP 網站執行環境
  • 本練習將利用 Docker ,啟動 mysql 網站執行環境

PHP 連結 MySQL 資料庫
  1. 利用 Docker,開啟 php-nginx 以及 mysql 兩個容器!
    docker run --name nginx -d -p 443:443 -v c:\workspace\phpexercise:/app php-nginx
    docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=Hello123 mysql:latest
    
  2. 登入 mysql 容器,進行帳號密碼設定:
    docker exec -it mysql bash
    mysql -u root -p
    CREATE USER 'root'@'%' IDENTIFIED by 'Hello123';
    ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'Hello123';
    GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';
    FLUSH PRIVILEGES;
    exit
    exit
    
  3. 遠端登入 MySQL ,建立使用者帳號、密碼,以及建立資料庫與授權!
    mysql -u root -h 10.1.1.32 -p
    CREATE USER 'peter'@'%' IDENTIFIED by 'World123';
    ALTER USER 'peter'@'%' IDENTIFIED WITH mysql_native_password BY 'World123';
    CREATE DATABASE hello;
    GRANT ALL PRIVILEGES ON hello.* TO 'peter'@'%';
    FLUSH PRIVILEGES;
    exit
    
  4. 利用新建立的帳號,建立一個資料表:
    mysql -u peter -h 10.1.1.32 -p
    use hello;
    CREATE TABLE connects (
        id INTEGER PRIMARY KEY,
        name VARCHAR(150),
        email VARCHAR(150),
        phone VARCHAR(20)
        ) ENGINE=InnoDB;
    show columns from connects;
    exit
    
  5. 測試連線是否正常:connections.php
    <?php
    //測試連線是否正常
    try {

    $db = new PDO('mysql:host=10.1.1.32;dbname=hello','peter','World123');
    if (isset($db)) printf("Connection Success");
    } catch(PDOException $e) {
    printf("Could not connect to the database: %s ",$e->getMessage());
    }
    ?>
PHP 操作 MySQL 資料庫資料內容
  • 新增資料至 connects 資料表:
    1. 建立表單檔案:hello.htm
      <!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">
      <title>Document</title>
      </head>
      <body>
      <form action="hello.php" method="post">
      <label for="id">ID:</label>
      <input type="text" name="id"><br>
      <label for="name">Name:</label>
      <input type="text" name="name"><br>
      <label for="email">Email:</label>
      <input type="text" name="email"><br>
      <label for="phone">Phone Number:</label>
      <input type="text" name="phone"><br>
      <input type="submit" value="submit">
      <input type="reset" value="reset">
      </form>
      </body>
      </html>
    2. 建立新增資料檔案 hello.php
      <?php

      require_once "connections.php";

      $id = intval(trim($_POST['id']));
      $name = trim($_POST['name']);
      $email = trim($_POST['email']);
      $phone = trim($_POST['phone']);

      printf($id.$name.$email.$phone);

      try {
      $runSQL = $db->prepare('INSERT INTO connects(id,name,email,phone) VALUES (?,?,?,?)');
      $runSQL->execute(array($id,$name,$email,$phone));
      $runSQL->fetchAll();
      printf ("Insert data success ...");

      } catch (PDOException $e2) {
      printf("Could not connect to the database: %s ",$e2->getMessage());
      }


      ?>
  • 查詢資料方式
    1. 新增查詢資料檔案:lists.php
      <!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">
      <title>Document</title>
      </head>
      <body>
      <table border="1">
      <tr>
      <th>ID</th>
      <th>Name</th>
      <th>Email</th>
      <th>Phone</th>
      </tr>
      <?php

      require_once "connections.php";

      $runSQL = $db->prepare("SELECT * FROM connects;");
      $runSQL->execute();
      printf("<tr>");
      while ($row = $runSQL->fetch()){
      printf("<td>%d</td>",$row[0]);
      printf("<td>%s</td>",$row[1]);
      printf("<td>%s</td>",$row[2]);
      printf("<td>%s</td>",$row[3]);
      }
      printf("</tr>");
      ?>

      </table>

      </body>
      </html>
  • 修改資料內容:
    1. 新增修改內容的表單:modify.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">
      <title>Modify Data</title>
      </head>
      <body>
      <form action="modify.php" method="POST">
      <label for="">指定姓名:</label>
      <input type="text" name="name"><br>
      <label for="">修改電話:</label>
      <input type="text" name="phone"><br>
      <label for="">修改 E-mail:</label>
      <input type="text" name="email"><br>
      <input type="submit" value="Submit"><input type="reset" value="Reset">
      </form>
      </body>
      </html>
    2. 新增修改內容的程式:modify.php
      <?php
      require_once "connections.php";

      if (is_null($_POST['name'])){
      echo "<script>alert('退出!');history.back();</script>";
      }
      if (!(is_null($_POST['phone']))){
      $phone = trim($_POST['phone']);
      $name = trim($_POST['name']);
      $runSQL = $db->prepare("UPDATE connects SET phone = ? where name = ?;");
      $runSQL->execute(array($phone,$name));
      }

      if (!(is_null($_POST['email']))){
      $email = trim($_POST['email']);
      $name = trim($_POST['name']);
      $runSQL = $db->prepare("UPDATE connects SET email = ? where name = ?;");
      $runSQL->execute(array($phone,$name));
      }
      echo "<script>alert('退出!');history.back();</script>";
      ?>
  • 刪除資料內容:
    1. 新增刪除內容的表單:del.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">
      <title>Document</title>
      </head>
      <body>
      <form action="del.php" method="POST">
      <label for="">指定姓名:</label>
      <input type="text" name="name"><br>
      <input type="submit" value="Submit"><input type="reset" value="Reset">
      </form>
      </body>
      </html>
    2. 新增刪除內容的程式:del.php
      <?php
      require_once "connections.php";

      if (is_null($_POST['name'])){
      echo "<script>alert('退出!');history.back();</script>";
      } else {

      $name = trim($_POST['name']);
      $runSQL = $db->prepare("DELETE FROM connects where name = ?;");
      $runSQL->execute(array($name));
      }

      echo "<script>alert('退出!');history.back();</script>";


      ?>