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>";


      ?>

在 Windows 10 上,利用 Docker 建立 PHP 網站執行環境

設定目標:
  • 利用 Docker ,啟動 nginx + PHP 網站執行環境
  • 利用 Docker ,啟動 mysql 網站執行環境

啟動 nginx + PHP 網站執行環境
  1. 開啟 Windows Power Shell
  2. 在 Power Shell 視窗中,執行 docker 指令:
    docker pull webdevops/php-nginx
    
  3. 開啟桌面上的 Docker Desktop
  4. 選擇左上方的 images
  5. 選擇右方的「run」
  6. 選擇下拉「Optional Settings」
  7. 依序填入對應的資料內容,再按下「run」:
  8. 正常結束後的畫面:

2021年4月15日 星期四

PHP 處理 CSV 檔案內容

設定目標:
  • 了解 PHP 處理 CSV 檔案的方法

PHP 處理CSV檔案
  1. 範例用的檔案內容:email.csv
    "hello@test.com",5,10,2010,100
    "test@hello.com",6,20,2021,200
    
  2. 讀取資料:readcsv.php
    <?php
    //開啟 CSV 檔案
    $csv = fopen('email.csv','rb');

    //列出檔案內容
    while ((! feof($csv)) && ($line = fgetcsv($csv))){
    printf("信箱名稱:%s ",$line[0]);
    printf("建立時間:%d/%d/%d ",$line[3],$line[1],$line[2]);
    printf("年費: %d ",$line[4]);
    printf("\n");
    }

    //關閉檔案
    fclose($csv);
    ?>
  3. 寫入資料: write.php
    <?php
    //開啟檔案,設定成寫入權限
    $csv = fopen("email2.csv",'wb');

    //設定寫入的資料
    $line = array("testhello@example.com",7,21,2021,300);

    //寫入檔案中
    fputcsv($csv,$line);
    print("寫入完成!");
    ?>
  4. 輸出至瀏覽器:tobrowser.php
    <?php
    //通知瀏覽器準備讀取 CSV 檔案
    header('Content-Type: text/csv');
    header('Content-Disposition: attachment; filename="email.csv"');

    //開啟 CSV 檔案
    $lines = fopen('email.csv','rb');

    //開啟檔案,做為輸出串流
    $csv = fopen('php://output','wb');

    //輸出內容
    while ((! feof($lines)) && ($line = fgetcsv($lines))){
    fputcsv($csv,$line);
    }
    ?>
  5. 有關 CSV 檔案,可參考更進階的套件:PHPOffice PHPExcel 套件!

2021年4月3日 星期六

在 CentOS / RHEL 8 上,安裝 Laravel 開發環境!

設定目標:
  • 在 CentOS / RHEL 8 上,安裝 Laravel 開發環境!

安裝流程
  1. 安裝 PHP 套件
    # dnf -y module reset php
    # dnf module enable php:7.4
    # dnf module install php
    
  2. 切換至資料夾 /opt
    # cd /opt
    
  3. 安裝 Composer!由 Composer 官網下載檔案,並執行安裝過程:
    # php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
    # php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
    # php composer-setup.php
    # php -r "unlink('composer-setup.php');"
    
  4. 移動 composer.phar 至 /sbin/composer
    # mv composer.phar /sbin/composer
    
  5. 利用 composer 建立 Laravel 專案
    # composer create-project --prefer-dist laravel/laravel phpproject
    
  6. 啟動測試:
    # cd phpproject
    # php artisan serve
    

2021年3月18日 星期四

PHP 處理一般文字檔案

設定目標:
  • 了解 PHP 處理一般文字檔案的概念與方法

PHP 處理檔案概念
  1. 權限認知
    • PHP 引擎需要有對應的系統權限,才可以對系統本身的檔案目錄進行存取!
    • PHP 如果是在 web server 上運作,其使用的權限就只是運作 web server 使用者的權限!
    • PHP 可否讀寫系統的檔案,視系統付予 PHP 使用者多大的可用權限!
PHP 處理檔案實際操作
  1. 讀檔
    • 範例: readfile.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>{titlename}</title>
      </head>
      <body bgcolor="{color}">
      <h1>Hello, {name}</h1>
      </body>
      </html>
    • 範例:readfile.php
      <?php
      $page = file_get_contents('readfile.html');
      
      $page = str_replace('{titlename}', 'Welcome', $page);
      
      if (date('H') >= 12)){
          $page = str_replace('{color}','blue',$page);
      } else {
          $page = str_replace('{color}','red',$page);
      }
      
      $page = str_replace('{name}', 'Kitty', $page);
      
      print $page;
      ?>
      
  2. 寫檔
    • writefile.php
      <?php
      $page = file_get_content('readfile.html');
      
      $page = str_replace('{titlename}','HelloWorld',$page);
      
      if (date('H' >= 12)) {
      	$page = str_replace('{color}','blue',$page);
      } else {
          $page = str_replace('{color}','green',$page);
      }
      
      $page = str_replace('{name}',$_SESSION['name'],$page);
      
      file_put_contents('writefile.html', $page);
      
      ?>
      
  3. 讀寫部份檔案:
    • 使用 file() 函數,一行一行讀取需要的內容!
    • 範例:email.txt
      apple@example.com|apple mac os
      microsoft@example.com|bill gates
      aws@example.com|bruce fu
      
    • reademail.php
      <?php
      foreach (file('email.txt') as $line){
          $line = trim($line);
          $info = explode('|', $line);
          print '<li><a href="mailto:' . $info[0] . '">' . $info[1] ."</a></li>\n';
      }
      ?>
      
    • 使用 fopen()函數:一次只讀取一行,節省記憶體空間的浪費!
    • freademail.php
      <?php
      $fh = fopen('email.txt'),'rb');
      while ((! feof($fh)) && ($line = fgets($fh))){
          $line = trim($line);
          $info = explode('|', $line);
          print '<li><a href="mailto:' . $info[0] . '">' . $info[1] ."</a></li>\n';
      }
      fclose($fh);
      ?>
      
    • 使用 fwrite() 函數,將資料寫回檔案內!
    • fwritemail.php
      <?php
      $fh = fopen('email.txt'),'wb');
      fwrite($fh,"james@example.com|lebron james\n");
      fclose($fh);
      ?>
      

2021年3月14日 星期日

2021 海外青年專班專題目錄

宗旨目的:
  • 記錄專班學員學習成長歷程

2021 海外青年專班 Github 連結列表
  1. 李沃利
  2. 林觀生
  3. 林清豪
  4. 莊槿鴻
  5. 德來
  6. 許立安
  7. 李政興

PHP 錯誤與例外處理

設定目標:
  • 了解 PHP 錯誤與例外發生時的處理方式

PHP 處理錯誤與例外發生的方式
  1. 掌握錯誤與例外
    • 避免非預期的結果干擾使用者!
    • 強化安全性,避免惡意的使用者查覺系統問題!
    • 在預期錯誤發生時,仍可導入正常處理流程!
  2. 錯誤的情況處理分類
    • die 況狀 : 利用 die() 函數處理錯誤情形,將程式導出並且中止執行!
    • 客製化錯誤 : 發生錯誤情形時,導入自製的處理程序!
    • 錯誤報告 : 將錯誤情形報告出來,寫入檔案或是資料庫!此種報告可協助找出錯誤問題,加以處理!
    • 例 : 自行客製化錯誤的方式 myError.php
      <?php
      function my_error_handler($error_no, $error_msg) {
        echo "Opps, something went wrong:";
        echo "Error number: [$error_no]";
        echo "Error Description: [$error_msg]";
      }
      set_error_handler("my_error_handler");
      echo (5 / 0);
      ?>
      
  3. 錯誤與例外的不同
    • 例外是可以抛出與接收處理,但錯誤則是無法恢復!
    • 例外可以使用物件導向的方式來掌握與處理!
  4. 例外的情況處理分類
    • 注意(Notice): 不嚴重的問題,程式也不會停止
    • 警告(Warning): 程式碼有錯誤,但程式亦不會停止執行
    • 致命錯誤 (Fata Error): 遇上嚴重錯誤,程式停止執行
  5. try…catch 敘述
    • 語法
      try {
        可能會發生錯誤的程式碼;
      } catch(Exception $e){
        處理例外的程式碼;
      }
      
    • Exception 是 PHP 處理例外的類別
      • 屬性值有兩個,一個是訊息,另一個則是代號值
      • 訊息會描述程式產生錯誤的地方
      • 代號值為錯誤代碼
      • 使用的語法 : 可以自行抛出 Exception
        throw new Exception('Error Message', 100);
        
  6. 抛出例外與接收
    • 當程式抛出例外時,程式會自動暫停,接下來的程式碼也不會運作!
    • 例 : throwExp.php
      <?php
      function check($num){
        if ( $num == 0 ){
          throw new Exception('num can not be 0', 100);
        }
        return $num;
      }
      
      try{
        echo check(0);
      } catch (Exception $e){
        echo "Error Message: ".$e->getMessage()."
      "; echo "Error Code: ".$e->getCode()."
      "; } ?>
  7. Exception 類別的繼承
    • 利用繼承 Exception 類別,自訂例外處理的屬性與方法!
    • 例 : selfExp.php
      <?php
      class EmailFormatException extends Exception {
        function printMessage(){
          echo "例外訊息:" . $this->getMessage() . "
      "; } } function checkEmail($email){ if (!strpos($email,"@")) throw new EmailFormatException("E-mail需要包含'@'"); } try { checkEmail('gmail.com'); } catch (EmailFormatException $e){ $e->printMessage(); } ?>
  8. 多層次的例外處理
    • 執行程式時,可能會發生多種情況的例外,可以寫多個 catch 來補抓例外!
    • 例 : multiCatchExp.php
      <?php
      class DivideByZeroException extends Exception {};
      class DivideByNegativeException extends Exception {};
      function process($denominator) {
        try    {
          if ($denominator == 0) {
            throw new DivideByZeroException();
          }    else if ($denominator < 0) {
            throw new DivideByNegativeException();
          }    else {
            echo 25 / $denominator;
          }
        }    catch (DivideByZeroException $ex)    {
          echo "DIVIDE BY ZERO EXCEPTION!";
        }    catch (DivideByNegativeException $ex)    {
          echo "DIVIDE BY NEGATIVE NUMBER EXCEPTION!";
        }    catch (Exception $x) {
          echo "UNKNOWN EXCEPTION!";
        }
      }
      process(0);
      ?>
      

本章練習:
  • 修改你的 dog、cat、Human 類別,別讓它被餵食錯誤的飼料,例如:Human 不可以吃 cat 罐頭!

PHP 的 Trait 用法

設定目標:
  • 了解 PHP 如何使用 Trait 來解決跨類別問題

Trait 概念與實作
  1. 特徵(Trait)
    • Trait 與抽象類別相似,無法使用 new 來產生物件(被實例化)!
    • 因為 PHP 只能單一繼承,所以無法跨多個類別使用這些類別內的方法!
    • 為了減少程式碼的重複撰寫,所以 PHP 使用 Trait 來克服這個問題!
    • 使用 Trait 可以讓 PHP 實現單一類別(Singleton)的使用!
    • 單一 Trait 使用範例 :
      • oneTrait.php
        <?php
        class Base {
          public function sayHello() {
            echo 'Hello ';
          }
        }
        trait SayWorld {
          public function sayHello() {
            parent::sayHello();
            echo 'World!';
          }
        }
        class MyHelloWorld extends Base {
          use SayWorld;
        }
        
        $o = new MyHelloWorld();
        $o->sayHello();
        ?>
        
    • 多個 Trait 使用範例 :
      • multiTrait.php
        <?php
        trait Hello {
          public function sayHello() {
            echo 'Hello ';
          }
        }
        trait World {
          public function sayWorld() {
            echo 'World';
          }
        }
        class MyHelloWorld {
          use Hello, World;
          public function sayExclamationMark() {
            echo '!';
          }
        }
        $o = new MyHelloWorld();
        $o->sayHello();
        $o->sayWorld();
        $o->sayExclamationMark();
        ?>
        
      • integrateTrait.php
        <?php
        trait Hello {
          public function sayHello() {
            echo 'Hello ';
          }
        }
        trait World {
          public function sayWorld() {
            echo 'World';
          }
        }
        trait HelloWorld{
          use Hello, World;
        }
        class MyWorld{
          use HelloWorld;
        }
        $world = new MyWorld();
        echo $world->sayHello() . " " . $world->sayWorld(); //Hello World
        ?>
        
    • 優先序問題
      • Trait 內可以使用 overwrite 功能來覆蓋父類別有的功能!
      • 優先序的範例 : orderTrait.php
        <?php
        trait Hello{
          function sayHello() {
            return "Hello";
          }
          function sayWorld() {
            return "Trait World";
          }
          function sayHelloWorld() {
            echo $this->sayHello() . " " . $this->sayWorld();
          }
          function sayBaseWorld() {
            echo $this->sayHello() . " " . parent::sayWorld();
          }
        }
        
        class Base{
          function sayWorld(){
            return "Base World";
          }
        }
        class HelloWorld extends Base{
          use Hello;
          function sayWorld() {
            return "World";
          }
        }
        $h =  new HelloWorld();
        $h->sayHelloWorld(); // Hello World
        ?>
        
    • Trait 的衝突與別名 : 使用 insteadof 與 as
      • 利用 insteadof 來解決不同 Trait 但相同方法名稱的問題
      • 例 : 有一衝突的範例 confuse.php
        <?php
        trait Game{
          function play() {
            echo "Playing a game";
          }
        }
        trait Music{
          function play() {
            echo "Playing music";
          }
        }
        
        class Player{
          use Game, Music;
        }
        $player = new Player();
        $player->play();
        ?>
        
        PS: 以上範例有兩個 Trait ,有相同的方法名稱 play()!在使用當下,就會發生衝突!
      • 修改後的 confuse.php 如下:
        <?php
        trait Game{
          function play() {
            echo "Playing a game";
          }
        }
        trait Music{
          function play() {
            echo "Playing music";
          }
        }
        
        class Player{
          use Game, Music{
            //將 Game 的 play 別名成 gamePlay
            Game::play as gamePlay;
            //使用 Music 的 play 功能,取消 Game 的 play 功能!
            Music::play insteadof Game;
          }
        }
        $player = new Player();
        $player->play();
        $player->gamePlay();
        ?>
        
  2. Trait 其它功能
    • 利用 Trait 獨特的使用功能,可以取得 private 權限的類別屬性值
      • 例 : getPritrait.php
        <?php
        trait Message {
          function alert() {
            echo $this->message;
          }
        }
        class Messenger {
          use Message;
          private $message = "This is a message";
        }
        
        $messenger = new Messenger;
        $messenger->alert(); //This is a message
        ?>
        
    • 利用 Trait 內可抽象化方法,強迫使用該 Trait 的類別,實作該抽象方法!
      • 例 : abtractTrait.php
        <?php
        trait Message {
          private $message;
              
          function alert() {
            $this->define();
            echo $this->message;
          }
          abstract function define();
        }
        
        class Messenger {
          use Message;
          function define() {
            $this->message = "Custom Message";
          }
        }
        
        $messenger = new Messenger;
        $messenger->alert(); //Custom Message
        ?>
        
    • Trait 的 use 與類別導入的 use 是不同的功能
      • Trait 的 use : 置放於 class 內部!
      • 類別導入的 use : 置放於 class 外部!

本章練習:
  • 修改你的 Human 類別,讓它可以使用 Bird 類別中的 fly 方法!

PHP 的命名空間

設定目標:
  • 了解 PHP 如何定義命名空間,改善自動載入的缺點!

命名空間概念與實作
  1. 命名空間(Namespace)
    • PSR 規範 : PHP-FIG 提出的規範,目的是讓 PHP 開發者在撰寫時有一個建議規範可遵循,使得開發者間可進行有效溝通與開發!
    • PSR-4 規範了一套 namespace 統一作法,包含了原有的 PSR-0 所規範的 autoload 作法!
    • 利用Composer 來進行 autoload 可提昇編輯效率!
    • PHP 檔案內的關鍵字 :
      • namespace : 定義類別的完整類別名稱
      • use : 導入需要使用的類別名稱
    • PSR-4 正確的命名空間,其完整的類別名稱語法如下 :
      \<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
      
      • 完整的類別名稱(fully qualified class name)必須有最高層級(top-level)的命名空間(namespace),通常稱這個命名空間為「供應商命名空間(vendor namespace)」。
      • 完整的類別名稱可以有一個或多個子命名空間。
      • 一個完整的類別名稱必須以類別名稱結束。
      • 在完整的類別名稱中,底線沒有其他意義。
      • 完整的類別名稱可以由英文字母大小寫組合而成。
      • 所有的類別名稱必須是有區分大小寫的。
    • 重點 :
      • 命名空間 + 類別名稱 = 路徑 + 檔名.php
      • 命名空間、類別,都需要開頭大寫
      • 底線不要拿來當作分類,例如 : Like_Book.php、Like_Movie.php,應該改成路徑 Like/Book.php、Like/Movie.php
  2. 實作範例:
    • 安裝 Composer 至專案目錄內!(Windows 使用者可以參考這一篇)
      # yum install wget
      # cd /usr/share/nginx/html
      # wget https://getcomposer.org/installer -O composer-installer.php
      # php composer-installer.php --filename=composer --install-dir=/usr/local/bin
      # composer --version
      
    • 建立測試用目錄 :
      +Hello
      ++public
      ++-index.php
      ++src
      +++Cars
      +++-Car.php
      ++vendor (該目錄由 composer 自建,一開始不用手動建立)
      +++composer
      ++-autoload.php
      +-composer.json
      
    • 編寫 composer.json
      {
        "autoload": {
          "psr-4": {
            "Cars\\": "src/Cars"
          }
        }
      }
      
    • 初次建立名稱空間
      # composer dump-autoload
      
    • 開始建立 PHP 類別程式
      • 建立 Cars/Car.php
        <?php
        namespace Cars;
        
        class Car{
        
          protected $name;
        
          public function setName($name){
            $this->name = $name;
            return "completed...";
          }
        
          public function getName(){
            return $this->name;
          }
        }
        ?>
        
      • 建立 public/index.php
        <?php
        require_once '../vendor/autoload.php';
        
        use \Cars\Car;
        $mycar = new Car();
        $mycar->setName("Ford...");
        echo $mycar->getName();
        ?>
        
      • 使用瀏覽器 http://你的IP/Hello/public/index.php
    • 增加新的類別 : Customers
      • 在 src 目錄下建立新的目錄 Customers
        # mkdir /usr/share/nginx/html/Hello/src/Customers
        
      • 修改 composer.json
        • 加入 Customers 路徑的對應
          {
            "autoload": {
              "psr-4": {
                "Cars\\": "src/Cars",
                "Customers\\": "src/Customers"
              }
            }
          }
          
        • 再執行一次 composer
          # composer dump-autoload
          
        • 開始新增新的類別 Customer
          • 在 src/Customers 目錄下,新增 Customer.php
            <?php
            namespace Customers;
            
            class Customer{
              protected $cname;
              public function setName($cname){
                $this->cname = $cname;
                return "completed...";
              }
              
              public function getName(){
                return $this->cname;
              }
            }
            ?>
            
          • 修改 index.php 檔案內容
            <?php
            require_once '../vendor/autoload.php';
                
            use Cars\Car;
            use Customers\Customer;
                    
            $mycar = new Car();
            $mycar->setName("Ford...");
            echo "My Car: ".$mycar->getName();
            echo "
            "; $myprofile = new Customer(); $myprofile->setName("Peter"); echo "My Name: ".$myprofile->getName(); ?>
          • 在 Customers 下增加子目錄 Block
            # cd /usr/share/nginx/html/Hello/src
            # mkdir Customers/Block
            
          • 在 Block 目錄下,增加一個 Blocklist.php
            <?php
            namespace Customers\Block;
            
            use Customers\Customer;
            
            class Blocklist extends Customer {
            
              protected $state;
                    
              public function setState($state){
                $this->state = $state;
                return "Complete...";
              }
                    
              public function getState(){
                return $this->state;
              }
            }
            ?>
            
          • 修改 index.php 檔案內容
            <?php
            require_once '../vendor/autoload.php';
                
            use Cars\Car;
            use Customers\Customer;
            use Customers\Block\Blocklist;        
                    
            $mycar = new Car();
            $mycar->setName("Ford...");
            echo "My Car: ".$mycar->getName();
            echo "
            "; $myprofile = new Customer(); $myprofile->setName("Peter"); echo "My Name: ".$myprofile->getName(); echo "
            "; $mystate = new Blocklist(); $mystate->setState("Forzien..."); echo $mystate->getState(); ?>
          • 使用瀏覽器 http://你的IP/Hello/public/index.php

本章練習:
  • 修改你的 dog 、cat 以及 human 的所有類別,讓他們有自己所屬目錄,並且可以自動導入到執行程式中!

2021年3月13日 星期六

PHP 的自動載入功能

設定目標:
  • 了解 PHP 如何自動載入需要的模組程式

模組自動載入
  1. PHP 原有的模組載入方式
    • include : 載入其他的 php 程式檔!當執行錯誤時,不會停止程式運作!
    • include_once : 相同的檔案,只會載入一次!
    • require : 載入其他的 php 程式檔!當執行錯誤時,會停止程式運作!
    • reuqire_once : 相同的檔案,只會載入一次!
  2. 缺點 : 若需要載入大量檔案程式,程式碼將會十分難看與難以除錯與管理!
  3. PHP 自動導入模組方式
    • 當須要用到該使用的物件時,才去真正的引入該類別
    • __autoload 用法 :
      • 例 : 有一個目錄 Classes ,裡面有個 Member.php 的檔案,Classes 目錄外面有兩個檔案 autoload.php 以及 index.php
        +Classes
        +--Member.php
        -autoload.php
        -index.php
        
      • Member.php 檔案內容:
        <?php
        //這是 Classes/Member.php
        class Member{
          public function getMemberList(){
            echo "print member list...
        "; } } ?>
      • autoload.php 檔案內容:
        <?php
        function __autoload($className){
          $filename = __DIR__ . "/classes/" . $className . ".php";
          if (is_readable($filename)){
            require $filename;
          }
        }
        ?>
        
      • index.php 檔案內容:
        <?php
        //這是 index.php
        include_once __DIR__ . "/autoload.php";
        
        $member = new Member();
        $member->getMemberList();
        ?>
        ?>
        
      • 缺點 : __autoload() 不能重覆的被定義!
  4. 使用 SPL(Standard PHP Library)
    • spl_autoload:
      • 預設實作 __autoload() magic function
      • 當 spl_autoload_register() 沒有定義或是沒有帶任何參數的時候,預設會採用 __autoload() 這個 function
    • spl_autoload_register
      • 能夠註冊自行定義的 autoload function
      • 當 spl_autoload_register() 有註冊之後,__autoload() 將會失效,必需自己手動將 __autoload() 註冊,才能使用。
    • 例 : 在原來的範例目錄內,增加 first 以及 second 這兩個目錄以及 First.php 與 Second.php 兩個檔案!另外再新增兩個 firstAutoload.php 以及 secondAutoload.php 檔案!
      +Classes
      +--Member.php
      +first
      +--First.php
      +second
      +--Second.php
      -firstAutoload.php
      -secondAutoload.php
      -autoload.php
      -index.php
      
    • First.php 檔案內容:
      <?php
      //這是 first/First.php
      class First{
        public function doSomething(){
          echo "I am first class...
      "; } } ?>
    •     
    • Second.php 檔案內容:
      <?php
      //這是 second/Second.php
      class Second{
        public function doSomething(){
          echo "I am second class...
      "; } } ?>
    •     
    • firstAutoload.php 檔案內容:
      <?php
      //這是 firstAutoload.php
      function firstAutoload($className){
        $filename = __DIR__ . "/first/" . $className . ".php";
        if (is_readable($filename)){
          require $filename;
        }
      }
      //向 spl_autoload 註冊這個方法的名稱
      spl_autoload_register('firstAutoload');
      ?>
      
    •     
    • secondAutoload.php 檔案內容:
      <?php
      //這是 secondAutoload.php
      function secondAutoload($className){
        $filename = __DIR__ . "/second/" . $className . ".php";
        if (is_readable($filename)){
          require $filename;
        }
      }
      //向 spl_autoload 註冊這個方法的名稱
      spl_autoload_register('secondAutoload');
      ?>
      
    • 修改 index.php 檔案內容:
      <?php
      //修改後的 index.php
      include_once __DIR__ . "/firstAutoload.php";
      include_once __DIR__ . "/secondAutoload.php";
      
      $first = new First();
      $first->doSomething();
      
      $second = new Second();
      $second->doSomething();
      ?>
      
    • 缺點 : 每新增一個目錄,就需要寫一個 autoload 檔案!不好管理!
    • 改進方式 : 寫一個 allAutoload.php,再使用 array、foreach 導入!
      <?php
      //這是 allAutoload.php
      function allAutoload($className){
      $folders = array(__DIR__ . "/first/",
                       __DIR__ ."/second/",
                       );
      
      foreach ($folders as $folder){
          $fileName = $folder . $className . ".php";
          if (is_readable($fileName)){
             require $fileName;
          }
        }
      }
      
      //註冊
      spl_autoload_register('allAutoload');
      ?>
      
    • 再次修改 index.php 檔案內容:
      <?php
      //又修改了 index.php
      include_once __DIR__ . "/allAutoload.php";
      $first = new First();
      $first->doSomething();
      
      $second = new Second();
      $second->doSomething();
      ?>
      
    • 缺點 : 每次新增一個 class 目錄,就要異動一次 autoload 檔案!

本章練習:
  • 修改你的 dog 、cat 以及 human 的所有類別,讓他們可以自動導入到執程式中!

PHP 多型方法與多載功能

設定目標:
  • 了解 PHP 如何使用多型方法
  • 了解 PHP 如何使用多載功能

多型概念與使用方法
  1. 多型(Polymorphism)
    • 使用概念與方法:
      • 有繼承的關係才能使用多型
      • 利用有相同父類別的關係,進行不同子類別的方法實作,產生不同的結果!
    • 例:demo5.php
      <?php
      abstract class Animal {
        public function sleep(){
            echo '睡';
        }
      }
      class Dog extends Animal {
        public function move(){
            echo '跑';
        }
      }
      class Bird extends Animal{
        public function move()
        {
            echo '飛';
        }
      }
      class Action{
        public function actionMove(Animal $obj)
        {
            $obj->move();
        }
      }
      
      $dog = new Dog();
      $bird = new Bird();
      $action = new Action();
      $action->actionMove($dog);
      $action->actionMove($bird);
      ?>
      
多載功能的應用
  1. 多載(overloading)
    • PHP 無法像其他物件導向程式語言,可以利用相同名稱,不同參數屬性值來實作多載功能!
    • PHP 利用自定義的 Magic methods 來實作多載功能!
    • 屬性多載的 magic function :
      • __set(): 若有屬性名稱是設定未定義或無法讀取的,就「設定」對應的屬性值!
      • __get(): 若有屬性名稱是設定未定義或無法讀取的,就「取得」對應的屬性值!
      • __isset(): 查詢某個屬性是否有被定義!
      • __unset(): 解除某個屬性定義值!
      • 範例 : Person.php
        <?php
          class Person {
              private $sex;
              public function __set($name, $value){
                
                //限制不可動態產生屬性
                if (isset($this->$name)) {
                    return $this->$name = $value;
                } else {
                    return null;
                }
              }
              // 取得屬性名稱的值
              public function __get($name){
                return $name;
              }
              // 判斷是否有該屬性參數   
              public function __isset($name){
                return $name;
              }
              // 取消該屬性
              public function __unset($name){
                return $name;
              }
            }
            
            $person = new Person();
            //Person 類別沒有 name 這個屬性名稱
            //PHP_EOL 空隔或是換行
            $person->name = 'PHP';
            echo $person->name.PHP_EOL;
            // sex 這個屬性是無法取得的
            echo $person->sex.PHP_EOL;
            echo isset($person->address);
            unset($person->name);
        ?>
        
    • 方法多載的 magic functions :
      • __call() : 當呼叫一個未定義的物件方法時,會呼叫本法函數!
      • __callStatic() : 當呼叫一個未定義的靜態物件的方法時,會呼叫本法函數!
      • 例 : Shape.php
        <?php
        class Shape {
          const PI = 3.142 ;
          // name 是方法名稱,arg 則是陣列名稱
          function __call($name,$arg){
            if($name == 'area')
              switch(count($arg)){
                case 0 : return 0 ;
                //self 指物件自己
                case 1 : return self::PI * $arg[0] ;
                case 2 : return $arg[0] * $arg[1];
              }
          }
          function __callStatic($name,$arg){
            return array(3,5);
          }
        }
        $circle = new Shape();
        echo $circle->area(3);
        $rect = new Shape();
        echo $rect->area(8,6);
        echo Sharp::hello()[1];
        ?>
        

本章練習:
  • 寫個利用衣服的類別,讓你的 dog 、cat 以及 human 穿上適合的衣服!
  • 寫個檢查有沒有穿衣服的方法,讓 dog 、cat 不用穿衣服,以及 human 穿上泳衣!

PHP 靜態屬性與匿名類別

設定目標:
  • 了解 PHP 靜態屬性的用法
  • 了解 PHP 匿名類別概念與應用方法

靜態屬性概念與使用方法
  1. 靜態屬性(static)
    • 作用:
      • 用於不會改變的類別屬性與方法
      • 不需要實例化產生物件,直接呼叫類別即可使用
    • 例:MathRate.php (計算複利用的方法)
      <?php
      class MathRate{
        public static function ComplexRate($principal,$yearRate,$period,$years){
           $result = 0;
           $result =  $principal * pow((1 + ((float)$yearRate/$period)),($period*$years));
           return $result;
        } 
      }
      ?>
      
    • 利用一個 demo4.php 來實現上述程式:
      <?php
        include "MathRate.php";
        echo MathRate::ComplexRate(1000,0.18,12,3);
      ?>
      
匿名類別觀念與應用
  1. 匿名類別(Anonymous class)
    • 作用
      • 有些類別用完即丟,沒有需要一直存在,即可使用匿名類別,減輕程式的複雜程度!
    • 範例 : Application.php
      <?php
      interface Logger {
        public function log(string $msg);
      }
      class Application {
        private $logger;
        public function getLogger(): Logger {
          return $this->logger;
        }
        public function setLogger(Logger $logger) {
          $this->logger = $logger;
        }
      }
      $app = new Application;
      // 使用 new class 建立匿名類別
      $app->setLogger(new class implements Logger {
        public function log(string $msg) {
          print($msg);
        }
      });
      
      $app->getLogger()->log("這是一個匿名類別的例子");
      ?>
      

本章練習:
  • 寫個疫苗的匿名類別,讓你的 dog 以及 cat 接種疫苗!
  • 寫個計算寵物一年花費的費用計算靜態程式,算一下你的寵物一年的花費!

PHP 介面與抽象類別的應用

設定目標:
  • 了解介面與抽象類別的概念
  • 實作介面與抽象類別的方法

介面概念與使用方法
  1. 介面(interface)
    • 作用:
      • 定義相同的功能名稱,但可以由執行的類別,各自實作功能!
      • 有實作介面的類別,一定要實作內容!
      • 介面可以繼承其他的介面!
      • 類別可以同時執行多個介面!
    • 例:Bark.php (吠叫...)
      <?php
        interface Bark{
          public function bark();
        }
      ?>
      
    • 例:Swim.php (游泳...)
      <?php
        interface Swim{
          public function swim();
        }
      ?>
      
    • 例:Human.php (建一個人類的類別...)
      <?php
        include "Bark.php";
        include "Swim.php";
        class Human implements Bark, Swim{
          public function bark(){
            echo "人類叫聲...是在叫什麼啦!!";
          }
          public function swim(){
            echo "人類在游泳...不是在洗澡嗎?";
          }
        }
      ?>
      
    • 利用一個 demo3.php 來實現上述幾個程式:
      <?php
        include "Human.php";
        $man = new Human();
        echo $man->bark();
        echo $man->swim();
      ?>
      
抽象類別觀念與應用
  1. 抽象類別 (Abstract)
    • 作用
      • 父類別在使用介面之後,必須要實作該介面功能內容!
      • 子類別在繼承父類別之後,如想要改變介面功能內容,必須使用覆寫的方式!
      • 父類別若不想實作介面方法,子類別又需要定義介面功能,可以將父類別抽象化,即可不用實作介面方法!
      • 子類別繼承抽象父類別之後,必須要實作父類別的介面方法!
    • 範例 : 修改 dog.php
      <?php
      include "Bark.php";
      abstract class dog implements Bark {
         // 設定狗的屬性
         public $name;
         public $color;
         public $style;
         
         //設定建構子
         public function __construct($name,$color,$style){
           $this->name = $name;
           $this->color = $color;
           $this->style = $style;
         }
      
         public function dogRun(){
           echo "狗狗跑步中...";
         }
         // 修改狗叫的功能
         public function Bark(){
           //這裡保持空白
         }
      
         public function __destruct(){
           echo "狗狗回家了...";
         }
      }
      ?>
      
    • 範例 : 修改 Poodle.php
      <?php
      include "dog.php";
      
      class Poodle extends dog{
        protected $size;
      
        public function __construct($name,$color,$style,$size){
          parent::__construct($name,$color,$style);
          $this->size = $size;
        }
        // 實作父類別的狗叫 function
        public function Bark(){
          echo "狗叫...但小聲....";
          //呼叫父類別的 dogBark()
        }
      }
      ?>
      
    • 利用 demo2.php 來使用上述程式:
      <?php
        include "Poodle.php";
      
        $mydog = new Poodle("Windy","白色","貴賓狗",30);
        //修改此行
        echo $mydog->Bark();
      ?>
      

本章練習:
  • 修改 dog.php 檔案,使用 Bark.php 介面程式
  • 修改 cat.php 檔案,將 cat 類別抽象化,並且使用 Bark.php 介面程式

2021年3月12日 星期五

PHP 物件封裝與繼承方法

設定目標:
  • 了解物件導向封裝與繼承的概念
  • 了解 PHP 使用封裝與繼承的方法

物件封裝概念與方法
  1. 封裝 (Encapsulation)的目的 :
    • 用於限制物件資源的使用
    • 存取權限分類:
      • public : 可以在任何地方存取該物件資源!若沒指定,PHP 會指定其為預設值!
      • protected : 可以在自身類別和子類別中存取!
      • private : 只能在自身類別中存取!
    • 例:MyDog.php
      <?php
        class MyDog {
          // 定義 dog 的種類「屬性」,並設定「公開」權限
          public $raise = "柴犬";
          // 定義 dog 的年紀「屬性」,並設定「保護」權限
          protected $age = 5;
          // 定義 dog 的名字「屬性」,並設定「私人」權限
          private $name = "Peter";
          
          // 定義取得 dog 屬性值取得的「方法」, 並設定「公開」權限
          public function getPara(){
            echo "MyDog->raise : ".$this->raise."<br />";
            echo "MyDog->age : ".$this->age."<br />";
            echo "MyDog->name : ".$this->name."<br />";
          }
         } 
          // 產生實際物件
          $mydog = new MyDog();
          // 利用 getPara() 方法,取出實際物件的屬性值
          $mydog->getPara();
          // 直接使用物件,取出實際物件的屬性值,並且與 getPara() 方法對照
          echo "mydog->raise : ".$mydog->raise."<br />";
          echo "mydog->age : ".$mydog->age."<br />";
          echo "mydog->name : ".$mydog->name."<br />";
      ?>
      
  2. 建構子(constructor)與解建構子(destructor)
    • 建構子 : 用於建立物件時,一併設定好物件相關屬性與設定!
    • 解建構子 : 用於消除物件時,一併解除物件相關屬性與設定!通常放於類別最後面,程式會自動執行,不需要特別呼叫!
    • 例 : 修改先前的 dog.php 程式
      <?php
      class dog {
         // 設定狗的屬性
         public $name;
         public $color;
         public $style;
         
         //設定建構子
         public function __construct($name,$color,$style){
           $this->name = $name;
           $this->color = $color;
           $this->style = $style;
         }
      
         public function dogRun(){
           echo "狗狗跑步中...";
         }
      
         public function dogBark(){
           echo "狗叫...";
         }
      
         public function __destruct(){
           echo "狗狗回家了...";
         }
      }
      ?>
      
    • 利用 demo.php 來使用上述 dog.php 程式:
      <?php
          include "dog.php";
          $myDog = new dog("來福","白色","台狗土狗");
          $myDog->dogRun();
      ?>
      
物件繼承觀念與應用
  1. 繼承(extends)
    • 作用
      • 避免寫出重複的程式碼!
      • 依據需求,重複利用相同程式碼,做出功能不同的程式!
      • 子類別繼承父類別的屬性與方法,免去定義相同功能!
      • 必要時,子類別除了可以定義自己的屬性與方法,也可改寫父類別的屬性與方法!
      • 語法格式 :
        class 類別名稱 {
        class 子類別名稱 extends 父類別名稱 {
          可執行的程式;
        }
        
      • 範例 : 牧羊犬是狗的一個子類別 Shepherd.php
        <?php
          // 引入 dog 類別
          include "dog.php";
          // 定義牧羊犬 Shepherd 是 狗 dog 的子類別
          class Shepherd extends dog{
             // 定義子類別需要的屬性
             protected $age;
        
             public function __construct($name,$color,$style,$age){
               // 繼承父類別的屬性
               parent::__construct($name,$color,$style);
               $this->name = $name;
               $this->color = $color;
               $this->style = $style;
               $this->age = $age;
              }
             // 定義子類別的方法 
             public function doWork(){
               echo "趕羊..."."
        "; } // 覆寫(override)解構子 public function __destruct(){ echo "趕羊到家了"; } } ?>
      • 利用 demo1.php 來使用上述 Shepherd.php 程式:
        <?php
          include  "Shepherd.php";
          $mydog = new Shepherd("peter","棕色","牧羊犬",10);
          echo $mydog->name."
        "; echo $mydog->color."
        "; echo $mydog->style."
        "; echo $mydog->doWork()."
        "; //子類別未改寫父類別的方法 echo $mydog->dogRun()."
        "; ?>
  2. 覆寫(override)
    • 作用  
      • 子類別需要改寫父類別中,相同的方法名稱時!
      • 覆寫後,仍可以呼叫父類別中相同的方法!
    • 範例 : Poodle.php
      <?php
      include "dog.php";
      
      class Poodle extends dog{
        protected $size;
      
        public function __construct($name,$color,$style,$size){
          parent::__construct($name,$color,$style);
          $this->size = $size;
        }
        // 覆寫狗叫的 function
        public function dogBark(){
          echo "狗叫...但小聲....";
          //呼叫父類別的 dogBark()
          parent::dogBark();
        }
      }
      ?>
      
    • 利用 demo2.php 來使用上述 Poodle.php 程式:
      <?php
        include "Poodle.php";
      
        $mydog = new Poodle("Windy","白色","貴賓狗",30);
        echo $mydog->dogBark();
      ?>
      

本章練習:
  • 寫兩支程式褲子與上衣,表述其繼承衣服的類別,並計算分別購買10件的費用!

PHP 類別宣告與物件使用方式

設定目標:
  • 了解基本物件導向觀念與應用
  • 了解 PHP 類別與物件的使用方式

基本物件導向觀念
  1. 物件導向的目的 :
    • 增加程式的重複利用性
    • 減少重複撰寫程式
    • 增加程式可擴充及延展性
    • 增加開發程式的靈活性
基本物件導向觀念
  1. 類別與物件 (Class &  Object)
    • 類別 : 定義物件的內容!其包含類別的函數與變數!
      • 函數 : 類別的方法
      • 變數 : 類別的屬性
      • 語法格式 :
        class 類別名稱 {
          // 設定變數
          public 變數名稱;
          .....
          // 設定函數
          public function 函數名稱(參數1,參數2,....){
            // 可以執行程式的區塊
          }
        }
        
      • 範例 : dog.php
        <?php
          class dog {
            // 定義 dog 的年紀「屬性」
            public $age;
            // 定義取得 dog 年紀屬性值的「方法」
            public function getAge(){
              return $this->age;
            }
          }
        ?>
        
      • 「->」: 代表呼叫物件的屬性或方法名稱
      • this : 表示「這個物件」自己
    • 物件 : 實際使用的程式碼,由類別實例化而來
      • 語法格式 :
        變數名稱 = new 類別名稱;
        
      • 範例 : demo.php
        <?php
            include "dog.php";
            $mydog = new dog();
            $mydog->age = 10;
            echo "我的狗年紀 : ".$mydog->getAge();
        ?>
        

本章練習:
  • 寫一支有關於衣服的類別,並計算購買10件衣服的費用!

PHP 函數基本應用

設定目標:
  • 了解 PHP 函數宣告與使用方式

PHP 函數宣告與呼叫方式
  1. 函數的宣告
    • 函數的作用:將功能相同的程式碼提出,以減少撰寫相同功能的程式碼
    • 使用方式:
      // 有數值需要傳遞時,需要設定參數
      // 若無傳遞數值的必要時,可不用設定參數
      function 函數名稱 (參數1,參數2,.....){
          需要執行的程式;
      
          //無需將執行結果傳出時,可以不用寫回傳值
          return 回傳值;
      }
      
    • 範例 : ex5_1.php
      <?php
        // 顯示名字
        function name(){
            echo "Peter";
        }
      
        // 計算成績等級
        function score($i) {
            $j = "F";
            $level = intval($i / 10);
            switch ($level){
                case 10:
                case 9:
                  $j = "A";
                  break;
                case 8:
                  $j = "B";
                  break;
                case 7:
                  $j = "C";
                  break;
                case 6:
                  $j = "D";
                  break;
                default:
                  $j = "E";
            }
            return $j;
        }
      
        echo name();
        $backscore = score(85);
        echo " 成績等級:$backscore";
      ?>
      
  2. 可變長度引數的使用
    • 若傳遞進入函數的參數個數不確定,可以使用可變長度引數的參數設定
    • 語法格式 :
      //重點在於那個「...」
      function 函數名稱 (...$參數名稱){
          需要執行的程式碼;
          return 回傳值;
      }
      
    • 範例 : ex5_2.php
      <?php
          function sum(...$numbers){
              $total = 0;
              foreach ($numbers as $i){
                  $total += $i;
              }
              return $total;
          }
      
          echo "總共是:".sum(1,3,5,7,9);
      ?>
      
  3. 遞迴函數
    • 基本精神 : 函數自己呼叫自己!
    • 範例 : ex5_3.php
      <?php
        function table99($i=2,$j=1){
          if ($j > 9){
            $i++;
            $j = 1;
            echo "
      "; } if ( $i <= 9 ){ printf("%d*%d=%d \t",$i,$j,($i*$j)); $j++; table99($i,$j); } } echo table99(); ?>
  4. 參數與回傳值宣告
    • PHP 7 之後,函數的參數與回傳值,可宣告資料型態
    • 範例 : ex5_4.php
      <?php
        function add(int $i,int $j):int{
          return ($i+$j);
        }
        echo add(3,5);
      ?>
      

本章練習:
  • 寫一支費式數列程式,計算前10個費式數列數值!

PHP 迴圈語法的運用

設定目標:
  • 了解 PHP 迴圈結構與使用方式

PHP 迴圈結構基本用法
  1. for 迴圈 :
    • 用於可數的重複工作
    • 語法格式 :
      for (起始值;條件式;步進值){
          需要重複做的事;
        }
      
    • 範例 : ex4_6.php
      <?php
        // 從 1 加到 50 的整數和
        // 以下方式只是為了說明 for 迴圈的使用方式,不是好的程式碼寫法!!
        $sum = 0;
        for ($i = 1; $i <= 50; i++){
            $sum += $i;
        }  
        echo "總合是: $sum";
      ?>
      
  2. while 迴圈 :
    • 用於有條件限制的重複工作
    • 先判斷條件,成立後再執行工作!
    • 語法格式 :
      while ( 條件式 ){
          需要重複做的事;
        }
      
    • 範例 : ex4_7.php
      <?php
        $sum = 0;
        while ( $i <= 50 ){
          $sum += $i;
          $i++;
        }
      ?>
      
  3. do … while 迴圈 :
    • 用於有條件限制的重複工作
    • 先工作,再進條件判斷,決定需不需要繼續執行!
    • 語法格式 :
      do {
          需要重複做的事;
        } while (條件式);
      
    • 範例 : ex4_8.php
      <?php
        $sum = 0;
        $i = 1;
        do {
          $sum += $i;
          $i++;
        } while ($i <= 50);
      ?>
      
  4. 無窮迴圈 :
    • 永不停止的重複執行
    • 建議放入一個可中斷程式的判斷程式
    • 語法格式 :
      for (;;){ }
        while(true){ }
        do { } while (true);
      
    • 範例 : ex4_9.php
      <?php
      $sum = 0;
        $i = 0;
        while (true){
          $i++;
          $sum += $i;
          if ( $sum >= 100) break;
        }
        echo "\$sum : $sum";
      ?>
      
  5. 巢狀迴圈
    • 迴圈內有迴圈,即為巢狀迴圈!
    • 沒事請少用巢狀迴圈
    • 語法格式 :
      //以下僅表示雙層巢狀迴圈,可以再多幾層迴圈
        for(起始值;條件式;步進值){
          for(起始值;條件式;步進值){
            需要重複做的事;
          }
        }
      
        while(條件式){
          while(條件式){
            需要重複做的事;
          }
        }
      
        do {
          do {
            需要重複做的事;
          } while (條件式);
        }while (條件式);
      
    • 範例 : ex4_10.php
      <?php
      //九九乘法表
      for ($i = 2; $i < 10; $i++){
        for ($j = 1; $j < 10; $j++){
          printf("%d*%d=%d \t",$i,$j,($i * $j));
        }
        printf("\n");
      } 
      ?>
      
  6. break
    • break 用法 : 跳出迴圈、switch 時使用!
    • 語法格式 :
      break;   //跳出目前的迴圈
        break 1; //跳出目前的一層迴圈
        break 2; //跳出目前的二層迴圈
      
    • 範例 : ex4_11.php
      <?php
      while (true){
          echo "這是第一層迴圈!
      "; while (true){ echo "這是第二層迴圈!
      "; break 2; } } echo "己跳出二層迴圈!"; ?>
  7. continue
    • continue 用法 : 于迴圈中,略過這一次,執行下一次的工作!
    • 範例 : ex4_12.php
      <?php
          while($i <= 10){
            if ( $i == 5) {
              echo "略過 \$i = $i 
      "; continue; } echo $i."
      "; } ?>

本章練習:
  • 寫一支程式判斷成績等級:

PHP 控制結構的運用

設定目標:
  • 了解 PHP 控制結構與使用方式

PHP 決策控制基本用法
  1. 語法:if...else
    • 格式 :
      if (條件式){
        條件式為真時,執行本程式區塊
      } else {
        條件事為假時,執行本程式區塊
      }
      
    • 範例 : ex4_1.php
      <?php
      $i = 10;
      if ( $i > 10 ){
           echo "Hello World";
         } else {
           echo "Good Bye";
      }
      if ( $i == 10 ) echo "Too Many";
         else $i += 100;
         echo $i; 
      ?>
      
  2. 三元運算子(一) : 「 ? : 」
    • 格式 :
      (條件式) ? 條件為真執行這行 : 條件為假執行這行 ;
      
    • 範例 : ex4_2.php
      <?php
        $i = 10;
        echo ($i > 101)?"大":"小";
      ?>
      
  3. 三元運算子(二) : 「 ?? 」
    • 格式 :
      (內容值) ?? 內容值為Null時,顯示該結果 ;
      
    • 範例 : ex4_3.php
      <?php
        $input = isset($value)? $value : "空的";
        echo $input."\n";
        $input = $value ?? "空的";
        echo $input;
      ?>
      
  4. 語法:if … elseif … else
    • 格式 :
      if (條件式1) {
        條件式 1 成真時,執行此程式區塊;
      } elseif (條件式2) {
        條件式 2 成真時,執行此程式區塊;
      } else {
        以上條件式均為假時,執行此程式區塊;
      }
      
    • 範例 : ex4_4.php
      <?php
      $i = 10;
      if ( $i >= 200 ){
        echo "\$i 大於 200";
      } elseif ( $i >= 100 ){
        echo "\$i 大於100,小於200";
      } else {
        echo "\$i 小於 100";
      }
      ?>
      
  5. switch 語法 : 一種判斷可得多種不同結果
    • 格式 :
      switch(條件變數或表達式){
        case "A":
          當結果為 A 時,執行本區塊程式;
          break;
        case "B":
          當結果為 B 時,執行本區塊程式;
          break;
        default:
          沒有符合的結果,執行本區程式;
      }
      
    • 範例 : ex4_5.php
      <?php
        $fruit = "Apple";
        switch($fruit){
          case "apple":
            echo "這是 apple";
            break;
          case "banana":
            echo "這是 banana";
            break;
          default:
            echo "打錯字了嗎";
        }
      ?>
      

本章練習:
  • 寫一支程式判斷成績等級:

2021年3月11日 星期四

PHP 陣列基本應用

設定目標:
  • 了解 PHP 陣列與基本用法

PHP 陣列與基本用法
  1. 宣告方式
    • 格式 :
      • 一維 : $變數名稱[“key” or index]=value;
      • 二維 : $變數名稱[“key” or index][“key” or index]=value;
        • 可以視為多個一維陣列組合起來!
      • 多維 : []一直追加即可!
    • 例 : ex3_11.php
      <?php  //一維陣列
      $eggbox["土雞"]=30;
      $eggbox[1]=100;
      $eggbox[]=20;
      $eggbox["duck"]=50;
      $drinkbox=("啤酒"=>70,"cola"=>30);
      $foodbox=["飯團"=>50];
      echo $eggbox;
      echo $foodbox;
      echo $drinkbox;

      //二維陣列
      $lunch[0]["素食"]=100;
      $lunch[0]["葷食"]=150;
      $lunch["麵類"]["拉麵"]=200;
      $lunch["飯類"]["雞肉飯"]="今天不賣";
      $lunch[1]=array("廣東粥"=>80,"炸雞排"=>70);
      echo $lunch; ?>
  2. 新增資料方式
    • 直接新增方式!
    • 使用 array_push() 函數
    • 例 : ex3_12.php
      <?php
      $fruits = array("Apple","Pineapple");
      print_r($fruits);
      $fruits[] = "Banana";
      print_r($fruits);
      array_push($fruits,"Starfruit");
      print_r($fruits); ?>
  3. 刪除資料方式
    • 重新寫入陣列!
    • 使用 array_pop() 函數
    • 例 : ex3_12.php
      <?php
      $fruits = array("Apple","Pineapple","Banana","Starfruit");
      print_r($fruits);
      $fruits = array("Apple","Pineapple","Starfruit");
      print_r($fruits);
      array_pop($fruits);
      print_r($fruits); ?>

PHP 常見運算子

設定目標:
  • 了解 PHP 運算子種類與用法

PHP 運算子種類與用法
  1. 運算式與運算子
    • 例 : $a = $b + $c
      • 運算元 : $a $b $c
      • 運算子 : +
  2. 運算子的優先順序
    1. 物件建立 : 複製(clone)、新增(new)
    2. 陣列 : []
    3. 轉型與遞增、遞減 : ++、--、intval()!例:
      • ++$a : 先加再回傳
      • $a++ : 先回傳再加
    4. 邏輯運算子 : (非)「 !」
    5. 算術運算子 : 「 * 」、「 / 」、「 % 」(取餘數)
    6. 算術運算子 : 「+」、「-」、「.」(字串運算子)
    7. 比較運算子 : 「>」、「<」、「>=」、「<=」、「==」、「!=」、「===」(型態也要相同)
    8. 邏輯運算子 : (且)「&&」、(或)「||」
    9. 條件運算子 : 「?:」(三元運算子)
    10. 指定運算子 : 「=」、「+=」、「-=」、「*=」、「/=」、「%=」、「.=」
      • 例 : $a += $b --> $a = $a + $b
      • 例 : $a = 10
    11. 邏輯運算子 : 「and」、「or」、「xor」
      • 回傳值通常是 true 或 false !!
    12. 例 : ex3_9.php
      <?php
        $a = 10;
      $b = 20;
      echo '$a = 10'."\n";
      echo '$b = 20'."\n";
      echo '$a + $b = '.($a + $b)."\n";
      echo '$a - $b = '.($a - $b)."\n";
      echo '$a * $b = '.($a * $b)."\n";
      echo '$a / $b = '.($a / $b)."\n";
      echo '$a++ = '.($a++)."\n";
      echo '$a = '.($a)."\n";
      echo '++$a = '.(++$a)."\n";
      echo '$a = '.($a)."\n";
      echo '$b % $a = '.($b%$a)."\n";
      echo '$a > $b '.($a > $b ? 'true' : 'false')."\n";
      echo '$a += 1 ==> '.($a += 1)."\n";
      echo '$a = '.($a)."\n";
      echo '$a *= $b ==> '.($a *= $b)."\n";
      echo '$a = '.($a)."\n";
      echo '$a and $b ==> '.($a and $b)."\n";
      echo '$a or $b ==> '.($a or $b)."\n"; ?>
  3. 太空船運算子
    • 例 : ex3_10.php
      <?php
         echo 1<=>1;
      echo 1<=>2;
      echo 2<=>1; ?>