Mars's Blog

CRUD表單設計 Day-01 環境建置

一、說明

CRUD行為(新增/讀取/修改/刪除)是一切資料處理的基本行為,例如使用者上網時看到的網頁(read),是由瀏覽器向伺服器,再向資料庫「讀取」資料而得到的,可用下圖表示:

在此以練習網頁+資料庫的CRUD行為做為程式學習入門

二、環境建置

工欲善其事,必先利其器,先裝好工作環境吧….

網頁伺服器提供Windws版與Linux版,Windows版安裝簡單,但業界大多是在Linux版中開發

三、工作目錄設置

3.1 建立工作目錄

1
2
$ sudo mkdir -p /var/www/html/crud-training
$ sudo chmod 777 /var/www/html/crud-training

3.2 建立Nginx Virtual Host

  • 建立域名為 crud-training.dev.idv 的Virtual Host
1
$ sudo vi /etc/nginx/sites-enabled/dev-web.conf
  • 增加以下設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# crud-training.dev.idv
server {
# SSL configuration
listen 443 ssl;
listen [::]:443 ssl;

root /var/www/html/crud-training;

# Add index.php to the list if you are using PHP
index index.php;

server_name crud-training.dev.idv;

# SSL相關設定
include common.conf.d/ssl.conf;

# 共用設定
include common.conf.d/common.conf;
}
  • 重新啟動Nginx
1
$ sudo service nginx reload
  • 增加DNS解析 (如果DNS未支援)
    編輯 C:\Windows\System32\drivers\etc\hosts

  • 驗証是否設定完成

    • 建立Hello檔

      1
      $ echo "hello world !" | tee -a /var/www/html/crud-training/index.php
    • 前往網址查看是否有正確顯示
      https://crud-training.dev.idv/

四、使用IDE:VSCode

  1. 打開VSCode,連線至伺服器dev.idv

  2. 切換開發目錄

  3. 遠端目錄清單

    連線過一次後,遠端目錄就會出現在清單中

五、培訓題目

PHP培訓-CRUD

六、Linux常用指令

  • sudo 其他指令: 變換(root)權限執行指令
  • ssh: ssh連線
  • showdown -h now: 關機
  • reboot: 重開機
  • logout: 登出 (或ctrl+D)
  • cd 目錄名稱: 前往目錄
  • ls: 顯示目前目錄內容
  • pwd: 顯示目前目錄路徑
  • ifconfig: 查看網路介面
  • netstat -tln: 查看服務監聽狀況
  • 指令補完: tab鍵
  • vi/vim 檔案路徑: 使用linux文字編輯器編輯檔案
  • vi/vim 快速入門
    • 在命令模式中按「a」鍵可進入編輯模式,再按「Esc」鍵可回到命令模式
    • 在命令模式中按「:wq」可儲存離開
    • 在命令模式中按「:q!」可強制離開
    • 在命令模式中按「u」鍵可還原編輯內容;按「y」可重做編輯內容
    • 在命令模式中按「yy」為複制一行;「dd」為刪除一行;「p」為貼上複制內容;「2yy」為複制二行;「3dd」為刪除3行
    • 在命令模式中按「gg」為跳到第一行;「G」為跳到最後一行;「:12」為跳到第12行
    • 在命令模式中按「/」+字串為尋找字串;尋找模式中再按n為尋找下一個;再按N為尋找上一個

未完待續: CRUD表單設計 Day-02 安裝PHP Framework

Web伺服器安裝(LNMP)-Linux

一、說明

安裝網頁執行平台,以供開發作業

很多人都知道,windows有不少PHP開發環境的安裝包,如xampp, appserv, wamp,但一個成熟的開發者,是不會用和線上不一樣的環境開發的,這會導至開發出來的程式有無法預期的錯誤,所以學習安裝一個LNMP環境是有必要的。

二、環境

  • windows
  • virtualbox
  • Ubuntu 16.04
  • Nignx 1.10
  • PHP 7
  • MySQL 5.7

三、安裝 虛擬機器

3.1 下載 VirtualBox

前往 VirtualBox 官方網站下載安裝軟體

Download VirtualBox

3.2 安裝 VirtualBox

執行安裝,一直「下一步」到底即可

3.3 建立新虛擬機器

啟動VirtualBox

  • 點選「新增」鍵

  • 設定記憶體

  • 建立硬碟

  • 選擇硬碟類型

  • 選擇硬碟使用方式

  • 選擇硬碟位置和大小

  • 建立完成,並設定光碟機

  • 設定網路

    • 增加NAT網路
      點擊左上角 檔案 => 喜好設定 => 網路

    • 虛擬機器可連線internet,且虛擬機器之間可互連

      選擇「NAT」時,無法和其他機器互連,但選擇「NAT網路」的機器之間是可以互連的

    • 讓主機端可連線虛擬機器

      主機端無法透過「NAT」、「NAT網路」連線到虛擬機器,因此要增加介面卡「僅限主機介面卡」

  • 啟動虛擬機器

四、安裝 Linux

4.1 下載 Ubuntu ISO檔

前往 Ubuntu 正體中文站 下載 Ubuntu 16.04

4.2 安裝 Ubuntu

Tips: 除了選擇軟體步驗外,基本上不是「continue」就是「yes」

  • 選擇語系 English

    不知為何,筆者用中文語系裝不起來

  • 安裝畫面 Install Ubuntu Server

  • 系統語系 建議 English

  • 地區 other

    為了選Taiwan

  • 地區 Asia

    為了選Taiwan

  • 地區 Taiwan

  • 系統語系相關 建議選United States

  • 鍵盤類型 不偵測

  • 鍵盤 English(US)

    台灣標準鍵盤基本上就選這個

  • 鍵盤 English(US)

  • 安裝部份元件

  • 設定主機名稱

    可自行設定,不影響使用

  • 建立使用者

    輸入自己的名子

  • 輸入帳號名稱

    • 輸入自己的帳號
    • 安裝時建立的帳號可當成除root外最高權限帳號,建議平時使用別的帳號
  • 輸入密碼

  • 加密帳號的家目錄[建議No]

  • 系統時區設定 Asia/Taipei

  • 格式化硬碟(LVM系統)

    不會設定的話,就預設吧

  • 設定LVM目標

    不會設定的話,就預設吧

  • 將LVM設定寫入碟碟

    Yes就對了

  • 設定LVM磁區大小

    全給了

  • LVM的建議磁碟分割

    Yes就對了,反正也玩不出花來

  • proxy,空白即可

  • 不自動更新

    更新還是手動來比較好,起碼知道更新了什麼

  • 安裝套件-建議如圖選項

    需要的套件,建議之後再裝,在此只要裝基本套件和SSH服務即可

  • 套件安裝中

  • 安裝開機引導程式[建議Yes]

  • 安裝完成

  • 重開機中

  • 開機完成

    關機步驟:

    • 登入 (用安裝時建立的帳號)
    • 關機指令: sudo shutdown -h -t now

      使用sudo執行命令時,需再輸入一次當前帳號的密碼

  • 關機,製作快照

    建立快照(Snapshot)還原點

4.3 登入 Ubuntu Server

  • 下載 Putty SSH連線程式 (windows用的)

    或使用其他SSH連線程式

  • 開機並登入系統查IP

    輸入安裝系統時建立的帳號密碼

    • 先使用VirtualBox介面登入系統中找IP (預設使用DHCP抓取浮動IP)
    • 請輸入安裝時設定的帳號密碼
    • 打密碼時,密碼欄不會有東西出現是正常的
    • 查IP的指令:ifconfig -a
    • 發現有張網卡沒有啟動

    • 編輯網路設定檔

      指令: sudo vi /etc/network/interfaces

      小貼士:打指令打個開頭後按tab鍵,會自動指令補完,如果連按二下tab會顯示所有可能的選擇

      • 增加第二張網卡「enp0s8」設定
      • vi/vim 快速入門
        • 在命令模式中按「a」鍵可進入編輯模式,再按「Esc」鍵可回到命令模式
        • 在命令模式中按「:wq」可儲存離開
        • 在命令模式中按「:q!」可強制離開
        • 在命令模式中按「u」鍵可還原編輯內容;按「y」可重做編輯內容
        • 在命令模式中按「yy」為複制一行;「dd」為刪除一行;「p」為貼上複制內容;「2yy」為複制二行;「3dd」為刪除3行
        • 在命令模式中按「gg」為跳到第一行;「G」為跳到最後一行;「:12」為跳到第12行
        • 在命令模式中按「/」+字串為尋找字串;尋找模式中再按n為尋找下一個;再按N為尋找上一個
    • 重新啟動網路,查IP

      指令: ifconfig -a
      指令: sudo service networking restart

  • 使用SSH連線程式登入

  • 從VirtualBox介面登入就像是在伺服器前用KVM登入使用一樣,從SSH登入使用才是遠端使用
  • 建立帳號 dev
    Ubuntu安裝時設定的帳號是系統的第一個帳號,在部份設定中有特殊意思,建議平時使用時重新重新建立一個帳號
1
2
3
4
5
6
7
8
9
10
11
# 建立帳號dev
$ sudo useradd -m -s /bin/bash dev
# 設定密碼
$ sudo passwd dev
Enter new UNIX password: # 輸入密碼
Retype new UNIX password: # 確認密碼
passwd: password updated successfully

# 設定帳號dev的sudo權限
$ sudo vi /etc/group
# 在 sudo:x:27:web 後面加入 ,dev

五、安裝 LNMP Server

5.1 更新套件

  • 更新apt資料庫
1
$ sudo apt-get update
  • 更新套件
1
$ sudo apt-get upgrade
  • 新系統使用前,建議全面更新
  • 舊系統更新前,請先備份config檔

5.2 安裝管理套件

  • 時間同步&vim編輯器
1
2
3
4
5
$ sudo apt-get install ntp ntpdate vim
# 開機時自動同步時間
$ sudo vi /etc/rc.local
# exit 0 之前加入下行指令
(/usr/sbin/ntpdate tock.stdtime.gov.tw && /sbin/hwclock -w) &> /dev/null

5.3 安裝Nginx,PHP套件

1
$ sudo apt-get install nginx php php-fpm php-mysql php-cli php-mcrypt php-curl php-mbstring php-imagick php-gd php-xml php-bcmath php-zip php-opcache php-common

5.4 安裝MySQL,phpMyAdmin套件

1
$ sudo apt-get install mysql-server mysql-client phpmyadmin
  • 設定MySQL root密碼

  • 略過phpMyAdmin web server安裝

    已經裝了Nginx,不需要再裝二上面個web server

  • 設定phpMyAdmin參數,預設即可

  • 設定帳號phpmyadmin的密碼

    此帳號密碼是phpMyAdmin建立的MySQL帳號

1
2
# 連結phpMyAdmin到網頁根目錄
$ sudo ln -s /usr/share/phpmyadmin /var/www/html/phpmyadmin

5.5 設定Nginx

5.5.1 nginx.conf設定檔

1
$ sudo vi /etc/nginx/nginx.conf

基本上不用改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# Nginx Server 啟動所使用的使用者
user www-data;
# 開啟的程序數量,請對應 CPU 核心數進行調整(多設無益)
worker_processes auto;
# ProcessID 存放位置 (CentOS 預設在 /var/run/nginx.pid)
pid /run/nginx.pid;

events {
# 每個程序最高可以開啟的連線數,理論上每台 nginx 服務器的最大連接數為 worker_processes*worker_connections。
worker_connections 768;
# multi_accept on;
}

http {

##
# Basic Settings
##

# 可以讓sendfile()發揮作用。因為這種拷貝是在內核完成的,
# sendfile()要比組合read()和write()以及打開關閉丟棄緩衝更加有效(更多有關於sendfile)
sendfile on;
# 在一個數據包裡發送所有頭文件,而不一個接一個的發送
tcp_nopush on;
# 不要緩存數據,而是一段一段的發送--當需要及時發送數據時
# ,就應該給應用設置這個屬性,這樣發送一小塊數據信息時就不能立即得到返回值。
tcp_nodelay on;
# keepalive 超時時間。
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;

# server_names_hash_bucket_size 64;
# server_name_in_redirect off;

include /etc/nginx/mime.types;
default_type application/octet-stream;

##
# SSL Settings
##

# 支援的加密協定
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
# 優先採取服務器算法
ssl_prefer_server_ciphers on;

##
# Logging Settings
##

# HTTP Log 存放的位置
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

##
# Gzip Settings
##

# 啟用壓縮
gzip on;
# 為指定的客戶端禁用gzip功能。我們設置成IE6或者更低版本以使我們的方案能夠廣泛兼容
gzip_disable "msie6";

# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

##
# Virtual Host Configs
##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

5.5.2 HTTP轉HTTPS設定

HTTP連線未加密,不安全
如果要做自己的開發機,可跳過HTTPS設定

1
$ sudo vi /etc/nginx/sites-available/default

只留本設定即可

1
2
3
4
5
server {
listen 80;
listen [::]:80;
return 301 https://$server_name$request_uri;
}

5.5.3 HTTPS SSL証書

申請免費通配符SSL證書

測試用

如果是自己做測試,那麼證書的申請機構和頒發機構都是自己。就可以用下面這個命令來生成證書:

  • 創建根憑証
1
2
3
4
5
6
7
8
$ sudo mkdir -p /etc/nginx/ssl/
$ cd /etc/nginx/ssl/

# 創建密鑰
$ sudo openssl genrsa -out rootCA.key 2048

# 生成憑證並自簽名
$ sudo openssl req -sha256 -new -x509 -days 3650 -key rootCA.key -out rootCA.crt -subj "/CN=rootCA"
  • 創建域名証書
1
2
3
4
5
6
7
8
9
10
11
12
# 創建密鑰
$ sudo openssl genrsa -out dev.idv.key 2048

# 創建請求文件
$ sudo openssl req -new -sha256 -key dev.idv.key -out dev.idv.csr -subj "/CN=*.dev.idv"

# 生成憑證並用根憑證簽名
$ sudo openssl x509 -req -in dev.idv.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -days 3560 -out dev.idv.crt

# 變更權限
$ sudo chown www-data *
$ sudo chmod 400 *.key

迪菲-赫爾曼密鑰

1
2
3
4
$ cd /etc/nginx/ssl
$ sudo openssl dhparam -out dhparam.pem 2048
$ sudo chown www-data dhparam.pem
$ sudo chmod 400 dhparam.pem

5.5.4 共用設定檔

  • 加密設定 ssl.conf
1
2
$ sudo mkdir -p /etc/nginx/common.conf.d
$ sudo vi /etc/nginx/common.conf.d/ssl.conf

設定檔內容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 証書文件 - 如有憑証鏈,需整合,服務器證書必須出現在組合文件中的鏈接證書之前:
ssl_certificate /etc/nginx/ssl/dev.idv.crt;
# 私鑰文件
ssl_certificate_key /etc/nginx/ssl/dev.idv.key;
# session cache大小
ssl_session_cache shared:SSL:10m;
# session timeout時間
ssl_session_timeout 10m;
#定義算法
ssl_ciphers HIGH:!aNULL:!MD5;
#優先採取服務器算法
ssl_prefer_server_ciphers on;
#使用DH文件
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

# 加入 HSTS 告訴你的瀏覽器本網站全站加密,並且強制用 HTTPS 訪問
#add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;preload" always;

#減少點擊劫持
#add_header X-Frame-Options DENY;
#禁止服務器自動解析資源類型
#add_header X-Content-Type-Options nosniff;
#防XSS攻擊
#add_header X-Xss-Protection 1;

# 移除 Nginx 版本資訊
#server_tokens off;
  • 共用設定 common.conf
1
2
$ sudo mkdir -p /etc/nginx/common.conf.d
$ sudo vi /etc/nginx/common.conf.d/common.conf

設定檔內容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
location / {
index index.php index.htm index.html;
try_files $uri $uri/ /index.php;

}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ \.php$ {
include snippets/fastcgi-php.conf;

# 連線過期時間
#fastcgi_connect_timeout 600;
#fastcgi_send_timeout 600;
#fastcgi_read_timeout 600;

# With php7.0-cgi alone:
#fastcgi_pass 127.0.0.1:9000;
# With php7.0-fpm:
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}

# 如果發生 404 可以指定到特定的頁面來顯示
error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}

5.5.5 網站設定檔

1
$ sudo vi /etc/nginx/sites-available/dev-web.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
##
# server configuration
##

# 域名:sql.dev.idv
server {
# SSL configuration
listen 443 ssl;
listen [::]:443 ssl;

root /var/www/html/phpmyadmin;

# Add index.php to the list if you are using PHP
index index.php;

server_name sql.dev.idv;

# SSL相關設定
include common.conf.d/ssl.conf;

# 共用設定
include common.conf.d/common.conf;
}

# 域名:site2.dev.idv
server {
# SSL configuration
listen 443 ssl;
listen [::]:443 ssl;

root /var/www/html/site2;

# Add index.php to the list if you are using PHP
index index.php;

server_name site2.dev.idv;

# SSL相關設定
include common.conf.d/ssl.conf;

# 共用設定
include common.conf.d/common.conf;
}
1
2
3
4
5
6
7
8
9
10
11
# 連結到啟動設定區
$ sudo ln -s /etc/nginx/sites-available/dev-web.conf /etc/nginx/sites-enabled/

# 建立site2內容
$ sudo mkdir -p /var/www/html/site2
$ echo "hello world !" | sudo tee -a /var/www/html/site2/index.html

# 變更/var/www/html目錄權限
$ sudo chmod 1777 /var/www/html
$ sudo setfacl -m d:g:www-data:rwx /var/www/html
$ sudo setfacl -m d:o:rwx /var/www/html

5.5.6 啟動 Nginx

1
2
$ sudo systemctl enable nginx.service
$ sudo systemctl restart nginx.service

5.5.7 查看成果

  • PHP還未設定完成,無法查看 https://sql.dev.idv/
  • 如果你的域名不是DNS可查到的,請在作業系統的hosts中增加解譯規則
  • 使用記事本打開 C:\Windows\System32\drivers\etc\hosts (需用管理者權限打開)
  • 在檔案最下方增加二行設定
    • 192.168.56.102 sql.dev.idv
    • 192.168.56.102 site2.dev.idv

      上述設定的IP: 192.168.56.102 是 單元登入 Ubuntu Server中查到的IP中用來連線SSH的IP

5.5.8 自簽憑証問題

  • 憑証不安全提示

選擇進階 => 繼續前往 XXXX 網站(不安全) 可略過警告

5.6 設定 PHP-FPM

5.6.1 PHP調校

1
$ sudo vi /etc/php/7.0/fpm/php.ini

調整設定內容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
; ========= 除錯 =========
display_errors = On

; ========= 時區 =========
date.timezone = "Asia/Taipei"

; ========= PHP上傳檔案 (需同步調整Web Server上傳限制) =========

; 是否允許通過HTTP上傳文件的開關。默認為ON即是開
file_uploads = On

; 望文生意,即允許上傳文件大小的最大值。默認為2M
upload_max_filesize 20M

; 指通過表單POST給PHP的所能接收的最大值,包括表單裡的所有值。默認為8M
post_max_size 40M

; 每個PHP頁面運行的最大時間值(秒),默認30秒 - 通常不用調,需要時在該頁用ini_set變更
max_execution_time 60

; 每個PHP頁面接收數據所需的最大時間,默認60秒
max_input_time 120

; 每個PHP頁面可接收的input變數數量
max_input_vars = 30000

; 每個PHP頁面所吃掉的最大內存,默認8M
memory_limit 256M

; 設定SESSION有效時間為2小時
session.gc_maxlifetime 7200

; 防止 XSS 擷取cookie:HttpOnly參數的設置
; 設置其值為1或者TRUE,來開啟全局的Cookie的HttpOnly屬性,當然也支持在代碼中來開啟:
; ini_set("session.cookie_httponly", 1);
; session_set_cookie_params(0, NULL, NULL, NULL, TRUE);
session.cookie_httponly=1
  • 必須是 memory_limit >= post_max_size >= upload_max_filesize
  • 在清單上Changeable項目被標示為 PHP_INI_ALL或是 PHP_INI_USER的選項才能被ini_set修改
  • php.ini清單請參照: http://tw.php.net/manual/en/ini.list.php

5.6.2 啟動PHP-FPM

1
2
$ sudo systemctl restart php7.0-fpm.service
$ sudo systemctl enable php7.0-fpm.service

5.7 設定MySQL

預設即可

5.7.1 啟動MySQL

1
2
$ sudo service mysql restart
$ sudo systemctl enable mysql.service

5.8 設定phpMyAdmin

預設即可

5.8.1 查看成果

  • 如果你的域名不是DNS可查到的,請在作業系統的hosts中增加解譯規則
  • 使用記事本打開 C:\Windows\System32\drivers\etc\hosts (需用管理者權限打開)
  • 在檔案最下方增加二行設定
    • 192.168.56.102 sql.dev.idv
    • 192.168.56.102 site2.dev.idv

      上述設定的IP: 192.168.56.102 是 單元登入 Ubuntu Server中查到的IP中用來連線SSH的IP

六、Linux常用指令

  • sudo 其他指令: 變換(root)權限執行指令
  • ssh: ssh連線
  • showdown -h now: 關機
  • reboot: 重開機
  • logout: 登出 (或ctrl+D)
  • cd 目錄名稱: 前往目錄
  • ls: 顯示目前目錄內容
  • pwd: 顯示目前目錄路徑
  • ifconfig: 查看網路介面
  • netstat -tln: 查看服務監聽狀況
  • 指令補完: tab鍵
  • vi/vim 檔案路徑: 使用linux文字編輯器編輯檔案
  • vi/vim 快速入門
    • 在命令模式中按「a」鍵可進入編輯模式,再按「Esc」鍵可回到命令模式
    • 在命令模式中按「:wq」可儲存離開
    • 在命令模式中按「:q!」可強制離開
    • 在命令模式中按「u」鍵可還原編輯內容;按「y」可重做編輯內容
    • 在命令模式中按「yy」為複制一行;「dd」為刪除一行;「p」為貼上複制內容;「2yy」為複制二行;「3dd」為刪除3行
    • 在命令模式中按「gg」為跳到第一行;「G」為跳到最後一行;「:12」為跳到第12行
    • 在命令模式中按「/」+字串為尋找字串;尋找模式中再按n為尋找下一個;再按N為尋找上一個

七、參考:

八、名詞解釋

  • 虛擬機器 : 在電腦平台中安裝虛擬系統,在虛擬系統中安裝其他執行環境,虛擬機器會共用電腦平台的資源
  • Host(主機端) : 你目前使用的作業系統
  • Guest(客戶端) : 你於 VirtualBox 上安裝的作業系統
  • KVM : 即鍵盤、顯示器、滑鼠的英文首字母縮寫(Keyboard、Video、Mouse)

使用PHPUnit進行單元測試

一、說明

  • 使用PHPUnit進行單元測試,並產生測試報告

二、環境

  • Ubuntu 16.04
  • Nginx
  • PHP 7

三、安裝

3.1 PHP 檔案包(PHAR) - 全域安裝

將PHPUnit安裝到Linux開發環境中,以免在各專案中重複安裝

1
2
3
4
5
6
7
8
9
10
11
12
$ # 抓取phar檔案包
$ wget https://phar.phpunit.de/phpunit-6.5.phar

$ # 加入可執行屬性
$ chmod +x phpunit-6.5.phar

$ # 搬移至命令路徑 /usr/local/bin/ 下,並更名為 phpunit
$ sudo mv phpunit-6.5.phar /usr/local/bin/phpunit

$ # 查看版本
$ phpunit --version
PHPUnit x.y.z by Sebastian Bergmann and contributors.

PHPUnit 6.x 支援 PHP7.0 以上; PHPUnit 7.X 需 PHP7.1 以上

3.2 Composer安裝

1
$ composer require --dev phpunit/phpunit phpunit/php-invoker phpunit/dbunit

四、Xdebug安裝

如果要做程式碼覆蓋率報告,需安裝xdebug

1
$ sudo apt-get install php-xdebug

五、產生設定檔

5.1 指令:

1
$ phpunit --generate-configuration

5.2 設定目標:

1
2
3
4
5
6
7
8
9
PHPUnit 6.5.14 by Sebastian Bergmann and contributors.

Generating phpunit.xml in /var/www/html/my/TimePeriodHelper

Bootstrap script (relative to path shown above; default: vendor/autoload.php):
Tests directory (relative to path shown above; default: tests):
Source directory (relative to path shown above; default: src):

Generated phpunit.xml in /var/www/html/my/TimePeriodHelper
  • autoload檔案:vendor/autoload.php
  • 測試目錄:tests
  • 來源目錄:src

5.3 修改設定-程式碼覆蓋率

1
$ vi phpunit.xml
  • 全部統計覆蓋率,不使用@covers標籤聲明統計範圍

    1
    forceCoversAnnotation="false"
  • 增加程式碼覆蓋率報告參數

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <logging>
    <log type="coverage-html"
    target="./report"
    charset="UTF-8"
    highlight="false"
    lowUpperBound="35"
    highLowerBound="70" />
    </logging>
    <filter>
    <blacklist>
    <directory>/path/to/.composer</directory>
    </blacklist>
    </filter>

    logging 與 log 表示要使用的報告格式; filter 是過濾覆蓋的範圍,可以用 blacklist 子標籤 來去掉不想覆蓋的目錄或檔案。

六、編寫PHPUnit 測試

要點:

  • 針對類別Class的測試寫在類別ClassTest中。
  • ClassTest(通常)繼承自 PHPUnit\Framework\TestCase
  • 測試都是命名為test*的公開(public)方法。
      也可以在方法的文檔註釋塊(docblock)中使用@test標註將其標記為測試方法。
  • 在測試方法內,類似於assertSame()(參見appendixes.assertions )這樣的斷言方法用來對實際值與預期值的匹配做出斷言。

Example 2.1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
use PHPUnit\Framework\TestCase;

class StackTest extends TestCase
{
public function testPushAndPop()
{
$stack = [];
$this->assertEquals(0, count($stack));

array_push($stack, 'foo');
$this->assertEquals('foo', $stack[count($stack)-1]);
$this->assertEquals(1, count($stack));

$this->assertEquals('foo', array_pop($stack));
$this->assertEquals(0, count($stack));
}
}

七、執行測試

1
2
3
4
5
# 有設定好phpunit.xml檔案,直接執行phpunit即可
$ phpunit

# 使用參數執行
$ phpunit -c phpunit.xml --coverage-html reportPatch/

執行結果

1
2
3
4
5
6
7
8
9
10
PHPUnit 6.5.14 by Sebastian Bergmann and contributors.

Runtime: PHP 7.0.33-0ubuntu0.16.04.4
Configuration: /var/www/html/my/TimePeriodHelper/phpunit.xml

. 1 / 1 (100%)

Time: 71 ms, Memory: 8.00MB

OK (1 test, 1 assertion)

八、程式碼覆蓋率分析

覆蓋率分析產生後,存放於 reportPatch/ 中,畫面如下圖

  • 總覽

  • 覆蓋率報告-程式檔總覽

  • 覆蓋率報告-覆蓋狀況

  • 覆蓋率報告-圖例

九、常用測試函式

  • assertEquals()
  • assertNotEquals()
  • assertEmpty()
  • assertInstanceOf()

十、參考

開發原則與程式結構

一、說明

良好的程式結構,有助不但容易閱讀理解,出問題時追蹤問題點也容易。什麼樣才算是良好的結構?其實只要尊守物件導向設計原則,並注意程式的層次性即可。

二、物件導向設計原則

單一功能原則

開閉原則

里氏替換原則

介面隔離原則

依賴反轉原則

DRY原則

Don’t repeat yourself (DRY)、Once and only once(OAOO)

是物件導向程式設計中的基本原則,程式設計師的行事準則。旨在軟體開發中,減少重複的資訊。

參考:Wiki 一次且僅一次

KISS原則

Keep It Simple, Stupid (KISS)

KISS 原則是指在設計當中應當注重簡約的原則,而不摻入非必要的複雜性,這樣的系統運作成效會取得最優。

參考:Wiki KISS原則

程式的層次性

參考

程式碼追蹤定位

一、說明

程式的開發及維護時,對程式掌握度的高低對開發維護的效率與質量影響很大,但我們又不可能將做過的程式永遠記得牢牢的,總會有其他人的改動,總會接觸其他人編寫的程式,所以個人對程式的閱讀追蹤能力很大一部份代表個人實力,在此介紹一些php web程式的閱讀追蹤方式。

二、程式結構與追蹤

2.1 web使用模型

網頁使用流程可解析成(MVC):

  1. User輸入網址
  2. Browser對Controller要求View頁面
  3. View對Controller發出ajax請求
  4. Controller透過Model存取Database
  5. Controller將資料ajax回應給View
  6. View渲染畫面給User

從流程中可看出,View是web系統的起點跟終點

2.2 字串搜尋方式追蹤

適用:想要找到畫面內容所在程式碼

  • 以 Yii 2 basic 預設頁面為例,假設這個頁面我從未接觸過

  • 我想將「Congratulations!」改成「Congratulations-trace」時,可直接從頁面原始碼中的目標附近找一個不太可能重複的字串,如「class=”jumbotron”」

  • 將字串「class=”jumbotron”」貼到IDE的全專案搜尋中尋找,如字串選得好,找到的可能目標會非常少,很容易確認目標。

  • 修改後查看狀況,完成!

使用本方法,需注意不要選可能是組合字、變數等字串,大略可用tag, id, class, style名稱/值 為搜尋字串

2.3 依流程追蹤

適用:想要找到畫面內容所在程式碼

  • 從網址列可以得知目標是Controller: site, Method: index

  • 打開目標程式,可以找到目標view檔案

  • 打開目標View,找到目標,修改後查看成果。完成!

使用本方法,需熟知framework運作流程及相關函式。

2.4 插行號/檔名/字串方式追蹤

適用:

  • 狀況一:當程式錯誤後會沒有任何輸出時
  • 狀況二:當想知道有哪些判斷式被執行時

狀況一:當程式錯誤後會沒有任何輸出時

如果要確定在執行哪段程式時發生錯誤,可在可能的錯誤點前插行號。

下例假設資料處理掛了且沒有錯誤輸出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 預設輸出
$opt = [
'code' => 200,
'message' => 'OK',
];

try {
// 取得輸入資料
$post = $input->post();
echo __LINE__."\n";
// 初始化
$users = \app\service\Users::getInstance();
$rule = \app\service\Rule::getInstance();
$timeoff = \app\service\Timeoff::getInstance();
$punch = \app\service\Punch::getInstance();
$attend = \app\service\Attendance::getInstance();

echo __LINE__."\n";
// 輸入資料整理
$userData = $users->prepare($post);

echo __LINE__."\n";
// 資料驗証
if (! $rule->validate($userData)) {
echo __LINE__."\n";
throw new Exceptioh('Input error', 400);
}

// 取得相關資料
$timeoffData = $timeoff->get($userData['id'], $startDate, $endDate);
$punchData = $punch->get($userData['id'], $startDate, $endDate);

echo __LINE__."\n"; // <=== 因資料處理掛了,所以本行為最後一個輸出的行號
// 資料處理
$attendData = $attend->workStatus($timeoff, $punch, $startDate, $endDate);

echo __LINE__."\n";
// 寫入資料
$attend->write($attendData);
} catch (Exception $e) {
// 錯誤處理
$opt = [
'code' => $e->getCode(),
'message' => $e->getMessage(),
];
}

// 輸出結果
return $output->json($opt);

上例可看出:

  • 最後一個輸出行號在資料處理之前
  • $rule->validate($userData)驗証有通過,因為沒有看到丟出例外前的那個行號
  • 當然如果不嫌麻煩,也可以不用__LINE__行號(魔術常量),而是自己定義輸出內容

狀況二:當想知道有哪些判斷式被執行時

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 取得$userID在$startDate至$endDate的出勤資料
$attendances = $attend->get($userID, $startDate, $endDate);


// 本行號用來確定有開始動作
echo __LINE__."\n";

// 出勤資料-落點 - 重複打卡、無效卡、缺卡、遲到、早退等狀況,會依打卡先後順序及落點判斷
foreach ($attendances as $key => $att) {

// 本行輸出為:定位loop迴圈數
echo '$key = '.$key."\n";

// 本行輸出為:印出迴圈內容
//echo '$att = '. var_export($att, true) ."\n";

// 本行號用來確定loop開始
echo __LINE__."\n";

try {
if ($att <= $workStart) {
// 上班前
echo __LINE__."\n";
// 略...
} elseif ($workStart < $att && $att < $restStart) {
// 上班後至休息前
echo __LINE__."\n";
// 略...
} elseif ($restStart <= $att && $att < $restEnd) {
// 休息時間
echo __LINE__."\n";
// 略...
} elseif ($restEnd < $att && $att < $workEnd) {
// 休息後至下班前
echo __LINE__."\n";
// 略...
} elseif ($workEnd <= $att) {
// 下班後
echo __LINE__."\n";
// 略...
}
} catch (Exception $e) {
// 例外處理
echo __LINE__."\n";
}

// 本行號用來確定loop結尾
echo __LINE__."\n";
}

// 本行號用來確定結尾
echo __LINE__."\n";
  • 從輸出的 行號定位loop迴圈數印出迴圈內容 可協助判斷程式碼執行狀況。
  • 當然如果不嫌麻煩,也可以不用__LINE__(行號魔術常量),而是自己定義輸出內容

2.5 追蹤呼叫歷程

適用: 想知道本函被呼叫的過程時,可使用函式debug_backtrace()取得呼叫歷程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Home extends CI_Controller
{
public function index()
{
// $options = 2;時顯示較少資訊 ; $limit 為向上追蹤層數,0為不限
$debug = debug_backtrace($options = 2, $limit = 0);
print_r($debug);
exit;
}
}

// 輸出結果:
// Array
// (
// [0] => Array
// (
// [file] => /var/www/html/system/core/CodeIgniter.php
// [line] => 532
// [function] => index
// [class] => Home
// [type] => ->
// )
//
// [1] => Array
// (
// [file] => /var/www/html/index.php
// [line] => 318
// [args] => Array
// (
// [0] => /var/www/html/system/core/CodeIgniter.php
// )
//
// [function] => require_once
// )
//
// )

2.6 查看輸入輸出資料變化

適用:輸入輸出值錯誤 (沒有語法錯誤)

1
2
3
4
5
6
7
8
9
// 查看參數值
echo '$id = '. $id ."\n";
echo '$date = '. $date ."\n";

// 資料取得函式
$data = $obj->getData($id, $date);

// 查看資料內容
echo '$data = ' . var_export($data, 1) . "\n";
  • 觀察輸入輸出值是否符合預期

Node.js安裝

一、說明

在Linux和Windows中安裝Node.js

二、安裝

2.1 Windows

2.2 Linux(Ubuntu 16.04LTS)

  • 安裝Node.js
    1
    $ sudo apt-get install nodejs nodejs-legacy npm

三、常用指令

3.1 查詢版本

1
2
$ nodejs -v
$ npm -v

四、常用套件

  • gulp 開源自動化構建工具
  • webpack 個開源的前端打包工具

五、參考

VS Code遠端開發套件:Remote-SSH

一、說明

使用VS Codey套件Remote-SSH實現遠端開發

二、安裝

  1. 下載並安裝VS Code

  2. 安裝VS Code套件Remote - SSH

  3. 建立SSH Key
    打開git bash,輸入下列指令

    1
    $ ssh-keygen -t ed25519 -C "your_email@example.com"
    • 注意: 如果您使用的是不支持ed25519算法的舊系統,請使用以下指令
      • $ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
    • your_email@example.com請替換為您的電子信箱,也可以不指定電子信箱
    • 產生ssh key時,如果有設定密碼,以後使用此ssh key時都要輸入密碼
    • 非共用電腦上使用的ssh key,密碼通常不設,自行保管好即可
  4. 設定SSH config
    In VS Code, run Remote-SSH: Open Configuration File

    1
    2
    3
    4
    5
    6
    # `~/.ssh/config`

    Host {dev.idv}
    User {user_name}
    HostName {dev.idv}
    #IdentityFile ~/.ssh/id_ed25519 # 金鑰如果使用預設路徑&檔名,可以不設定本行
    • {user_name} 替換成自己的帳號
    • Host {dev.idv} 中的 {dev.idv} 是伺服器的alias name (可以跟HostName不一樣)
    • HostName {dev.idv} 中的 {dev.idv} 是伺服器域名,也可以使用IP位址
    • IdentityFile 指定ssh私鑰路徑,金鑰如果使用預設路徑&檔名,可以不設定本行
  5. 安裝SSH Key至遠端

    1
    $ ssh-copy-id -i ~/.ssh/id_ed25519.pub {user_name}@{dev.idv}
  6. 連線至遠端
    In VS Code, run Remote-SSH: Connect to Host

    第一次連線會自動安裝遠端套件

  • VS Code 遠端開發說明
    • VS Code IDE 編輯器安裝在本機電腦上,透過 Remote-SSH 套件使用SSH遠端連線協定連線到遠端Ubuntu(Linux)伺服器上編輯程式檔,此為遠端開發。
  • SSH連線金鑰
    • 使用指令ssh-keygen建立SSH認証金鑰對id_ed25519, id_ed25519.pub
    • id_ed25519是私鑰,存放於本機端
    • id_ed25519.pub是公鑰,透過指令ssh-copy-id存放於遠端伺服器
    • 設定config指定連線參數與使用金鑰檔
    • 此時使用SSH連線到遠端伺服器時,就不需再輸入帳號密碼,所以需要妥善保管好SSH私鑰

三、VS Code環境設定

3.1 本地使用者 環境設定檔

  • [ctrl+,]選使用者,再選右上角**{}**符號

  • 貼上下列設定值後儲存(ctrl+s)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
{
"extensions.autoUpdate": true,
"update.enableWindowsBackgroundUpdates": false,
"update.mode": "manual",

"remote.SSH.defaultExtensions": [
"alefragnani.bookmarks",
"berublan.vscode-log-viewer",
"bmewburn.vscode-intelephense-client",
"bysabi.prettier-vscode-standard",
"eamodio.gitlens",
"kokororin.vscode-phpfmt",
"mhutchie.git-graph",
"ms-ceintl.vscode-language-pack-zh-hant",
"mutantdino.resourcemonitor",
"neilbrayfield.php-docblocker",
"rafamel.subtle-brackets",
"small.php-ci",
],

"editor.fontSize": 16,
"editor.wordSeparators": "`~!@#%^&*()=+-[{]}\\|;:'\",.<>/?,。",
"editor.renderControlCharacters": true,
"editor.renderWhitespace": "all",
"editor.matchBrackets": false,

"files.maxMemoryForLargeFilesMB": 5120,
"files.autoSave": "onFocusChange",
"files.eol": "\n",
"files.watcherExclude": {
"**/bs": true,
"**/ckeditor": true,
"**/vendor/**": true,
"**/outer/**": true,
},
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/uploads": true,
"**/tmp": true,
},

"breadcrumbs.enabled": true,
"diffEditor.ignoreTrimWhitespace": false,
"window.zoomLevel": 0,

"typescript.npm": "‪C:/Program Files/nodejs/npm.cmd",
"typescript.preferences.quoteStyle": "single",
"javascript.implicitProjectConfig.checkJs": true,
"javascript.preferences.quoteStyle": "single",
"javascript.referencesCodeLens.enabled": true,
"javascript.format.insertSpaceBeforeFunctionParenthesis": true,
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": true,

"[php]": {
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client"
},
"[javascript]": {
"editor.defaultFormatter": "bysabi.prettier-vscode-standard"
},

"git.confirmSync": false,
"gitlens.views.lineHistory.enabled": false,
"gitlens.hovers.annotations.over": "annotation",
"gitlens.advanced.fileHistoryFollowsRenames": false,
"gitlens.advanced.similarityThreshold": 50,
"gitlens.codeLens.scopes": [
"document"
],
"gitlens.advanced.messages": {
"suppressCommitHasNoPreviousCommitWarning": true,
"suppressShowKeyBindingsNotice": true
},
"gitlens.blame.highlight.locations": [
"gutter",
"line",
"overview"
],

"subtleBrackets.style": {
"color": "#528BFF",
"borderWidth": "1px",
"borderStyle": "none none solid none"
},

"phpfmt.smart_linebreak_after_curly": true,
"phpfmt.passes": [
"GeneratePHPDoc",
"PHPDocTypesToFunctionTypehint",
"PrettyPrintDocBlocks",
"ReindentSwitchBlocks",
"DocBlockToComment",
"EliminateDuplicatedEmptyLines",
"PSR2EmptyFunction",
"AlignPHPCode",
"RTrim",
"IndentTernaryConditions",
"MergeElseIf",
],

"intelephense.completion.fullyQualifyGlobalConstantsAndFunctions": true,
"intelephense.files.maxSize": 5242880,
"intelephense.files.exclude": [
"**/.git/**",
"**/.svn/**",
"**/.hg/**",
"**/CVS/**",
"**/.DS_Store/**",
"**/node_modules/**",
"**/bower_components/**",
"**/vendor/**/{Test,test,Tests,tests}/**",
"**/outer/**",
],

"prettier.singleQuote": true,
"prettier.parser": "flow",
"prettier.trailingComma": "es5",
"prettier.printWidth": 100,

"terminal.integrated.shell.windows": "C:\\Program Files\\Git\\bin\\bash.exe",
"terminal.integrated.env.windows": {"LC_ALL":"en_US.UTF-8"},
"explorer.confirmDelete": false,
"terminal.external.windowsExec": "C:\\Program Files\\Git\\git-bash.exe",
}

Gunter.Chou提供

3.2 遠端 環境設定檔

  • [ctrl+,]選遠端[SSH:XXXX],再選右上角**{}**符號
  • 貼上下列設定值後儲存(ctrl+s)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"php.validate.executablePath": "/usr/bin/php7.0",
"typescript.npm": "/usr/local/bin/npm",
"CI.other": [
"system/core/Common.php",
"system/database/DB_query_builder.php",
"application/models/My_model.php",
],
"resmon.show.battery": false,
"logViewer.watch": [
{
"title": "CI Log",
"pattern": "./application/logs/*.php"
},
],
}

Gunter.Chou提供

四、常用快速鍵

  • 開啟工作資料夾: ctrl+k ctrl+o (雙組合鍵)
  • 切換工作資料夾: ctrl+r (已開過間切換)
  • 呼叫/關閉終端機: ctrl+`
  • 打開/關閉檔案總管: ctrl+b
  • 程式碼格式化: alt+shift+f
  • 專案內檔案尋找: ctrl+p
  • 整列註解切換: ctrl+/
  • 成對括號尋找: ctrl+shift+\
  • 增加選取列縮排: ctrl+]
  • 減少選取列縮排: ctrl+[]
  • 刪除整列: ctrl+shift+k
  • 複制: ctrl+c
  • 剪下: ctrl+x
  • 還原: ctrl+z
  • 重做: ctrl+y
  • 搜尋: ctrl+f
  • 取代: ctrl+h
  • 行數跳躍: ctrl+g
  • 分割視窗: ctrl+\
  • 選取同一段字詞: ctrl+d

五、參考

六、Log

  • 2019-05-27 Mars Hung編輯
  • 2019-07-03 加入Gunter Chou提供環境設定
  • 2019-07-24 加入Gunter Chou提供環境設定

程式碼寫作風格標準-CodeStyle

目錄


一、說明

團隊開發時,為提升可讀性、降低閱讀成本,程式碼寫作風格的統一是非常重要的,本文件為聲明尊循的業界標準及訂定合乎本團隊使用的標準。


二、公開標準-PSR

PHP Standards Recommendations

2.1. PSR-1

  • 本篇規範制定了代碼基本元素的相關標準,以確保共享的 PHP 代碼間具有較高程度的技術互通性。
  • 本文件中的 必須,不得,需要,應,不應,應該,不應該,推薦,可能 和 可選 等能願動詞按

總覽

  • 檔案只能使用 <?php 和 <?= 標籤
  • 檔案字元編碼只能用 UTF-8 檔首無 BOM
  • 檔案應該只宣告符號 (class、function、constant)或是造成副作用(side-effects,例如產生輸出、修改 .ini 檔之類)兩者擇一不應該兩個都做
  • Namespace 和 Class 必須遵循”自動載入(PSR-4)”的規範
  • Class 命名必須用首字母大寫駝峰式(StudlCaps)
  • Class 常數必須全部用大寫字母命名,多個單字之間用 _ (下底線)連接
  • Method 命名必須用首字母小寫駝峰式(camelCase)

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

namespace app\libraries;

class ExampleLibrary extends NueipLibrary
{
const CONSTANT_VARIABLE = 'test';

public function functionExample()
{

}
}

參考資料:PSR-1基礎編碼規範

2.2. PSR-2

  • 本篇規範是 PSR-1 基本代碼規範的繼承與擴展。
  • 本規范希望通過制定一系列規範化 PHP 代碼的規則,以減少在瀏覽不同作者的代碼時,因代碼風格的不同而造成不便。
  • 當多名程序員在多個項目中合作時,就需要一個共同的編碼規範,
    而本文中的風格規范源自於多個不同項目代碼風格的共同特性,
    因此,本規範的價值在於我們都遵循這個編碼風格,而不是在於它本身。
  • 本文件中的 必須,不得,需要,應,不應,應該,不應該,推薦,可能 和 可選 等能願動詞按

規範

  • 程式碼必須遵循 PSR-1
  • 程式碼必須用 4 個空格做縮排,而不是 tab
  • 一定不能硬性規定一行字元長度,軟性限制必須在 120 個字以內,一行應該在 80 個字元以內
  • 在宣告 namespace 和 use 的區塊後方一定要空一行
  • class 的開始左大括弧必須要換下一行,結束右大括弧必須要換到程式碼下一行
  • method 開始左大括弧必須要換下一行,結束右大括弧必須要換到程式碼下一行
  • 所有的 property 和 method 都必須要宣告可視範圍,abstract 和 final 必須要宣告在可視範圍前,static 必須宣告在可視範圍之後
  • 控制結構關鍵字後面必須要有一個空格,呼叫 method 或 function 時一定不要有空格
  • 控制結構的開始左大括弧必須要在同一行,結束右大括弧必須要換到程式碼下一行
  • 控制結構的開始左小括號後面絕對不要有空格,結束右小括號前面絕對不要有空格

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

namespace app\libraries;

use app\helpers\FileHelper;

class ExampleLibrary extends NueipLibrary
{
const CONSTANT_VARIABLE = 'test';

protected $property;

public function functionExample($score)
{
if ($score >= 90) {
self::visibilityExample($arg1, $arg2);
} elseif ($score >= 60) {
// do something
} else {
// do something
}
}

final public static function visibilityExample()
{
// body
}
}

參考資料:PSR-2 編碼風格規範

2.3. PSR-4

  • PSR-4 描述了從文件路徑中 自動加載 類的規範。它擁有非常好的兼容性,並且可以在任何自動加載規範中使用。 PSR-4 規範也描述了放置 autoload 文件(就是我們經常引入的 vendor/autoload.php)的位置。

規範

  • PSR 明確描述 classes 如何由檔案路徑載入
  • 完全符合規則的命名空間要符合以下格式(class 代表 classes, interfaces, traits)
    1
    \<命名空間>(\<子命名空間>)*\<類別名稱>
    • 完整的 class 名稱必須有最高層級的 namespace,像是大家熟知的 “vendor”
    • 完整的 class 名稱可以有一或多個子命名空間
    • 完整的 class 名稱最後必須要有一個 class
    • 完整的 class 名稱內底線不具有任何特殊含意
    • 完整的 class 名稱內大小寫字母可以任意組合
    • 所有的 class 名稱必須大小寫敏感
  • 使用符合規範的 class 名稱載入檔案時…
    • 在完整的 class 名稱裡不包含最前面的分割符號,後續作為命名空間前綴的一個或多個命名空間和子命名空間必須對應至少一個基本目錄
    • 在命名空間前綴之後的子命名空間對應基本目錄內的子目錄,每一個命名空間分隔符號代表目錄分隔符號,子目錄名稱必須完全符合子命名空間大小寫
    • 最後一個的 class 名稱必須符合檔案名稱且大小寫要相符
  • 自動載入絕對不能拋出錯誤,絕對不能提升至任何錯誤層級,並且不應該有回傳值

使用範例

1
\app\helpers\FileHelper::filterFilename($filename);

參考資料:PSR-4 自動加載規範

2.4. PHPDoc

  • 文檔塊(DocBlock)
    • 短介紹
      • 開始於第一行,以一個空行或一個句號(這裡是指英文句號,下同)結束。單詞中的句號(例如 example.com 或 0.1%)會被忽略。如果短介紹長度超過三行,則只有第一行有效。
    • 長描述
      • 則以任意多行繼續,而且可以包含 用于格式化顯示的 HTML 標記。
      • 下面是 phpDocumentor 所支持的標籤節錄:
        • <br> hard line break, may be ignored by some converters
        • <li> list item
        • <ol> ordered list
        • <p> If used to enclose all paragraphs, otherwise it will be considered text
        • <pre> Preserve line breaks and spacing, and assume all tags are text (like XML’s CDATA)
        • <ul> unordered list
    • 標籤(Tags)
  • 標籤(Tag)
標籤 說明
@abstract 記錄抽像類,類變量或方法。
@access 變數可存取的權限 (Example: Public or Private)
@api 為第三方來源的變數
@author 函數建立者名稱 (Example: @author Mars author@gmail.com)
@category 函數的分類別名,可能某些工具會利用這個來分類你的方法,使好幾個方法歸為某一類,方便做辨識使用
@copyright 函數的版權宣告 (Example: @copyright 隨手寫有限公司 github.com)
@deprecated 代表不建議使用的函數,未來可能會移除這個方法使用到的某個變數,或整個方法都被刪除
@example 代表這個函數使用方式可以參考某個資料,可以使用檔案位置或網址 (Example: @example https://github.com/)
@exception 記錄方法拋出的異常 - 另見@throws
@filesource 這個函數所需的來源
@global 函數內有使用的全域變數註解 (Example: @global Number $user_id)
@ignore 代表這個函數或區域可以被忽略,通常會加上說明
@internal 代表這個函數或區域可能只給予內部使用
@license 此函數可能是含有某個版權或許可 (Example: @license http://opensource.org/licenses/gpl-license.php GNU Public License)
@link 可能與某個網站有關係 (Example: @link https://github.com/)
@method 函數有使用的方法 (Example: @method Array @this->getCategories() or @method String getUserName())
@name 指定變量的別名。 例如,$ GLOBALS [‘myvariable’]變為$ myvariable
@package 利用這個註解來達到細部分層結構 (Example: @package PSR\Documentation\API or @package PSR\Documentation\Doc)
@param 函數要帶入的參數 (Example: @param String,Number $username)
@property 如果這是一個類別的函數,在類別建構時通常會指定初始化參數,而這個函數可能會使用到某些初始化後的參數,稱之為屬性 (Example: @property Resource,Boolean $mysql_connect)
@return 函數最後的回傳值或形態 (Example: @return Array,Object,Boolean)
@see 函數參照或關聯的方法 (Example: @see Class User or @see GuihubBlogs)
@since 函數內某個使用的變數由哪個版本變動 (Example: @since v1.3376a $user_nickname )
@source 這個比較特別,在函數中可以標示從 m 至 n 行 是做什麼事情 (Example: @source 14 21 Get user data)
@static 靜態變數的註解 (Example: @static String $lang = ‘zh_TW’)
@subpackage 利用這個註解來達到細部分層子結構,通常會同時使用 @package,可以參考上面的@package (Example: @package PSR @subpackage Documentation\API)
@throws 例外處理的註解,有多種例外處理的方式,每種方式都不同 (Example: @throws InvalidArgumentException if the provided argument is not of type ‘array’ @throws Exception other…)
@todo 計劃要進行的項目描述,一般應該會使用文字描述
@uses 代表某個元素可能與其它結構有利用關係 (Example: @uses MyClass::$items to retrieve the count from)
@var 變數(物件成員變數)的形態或描述 (Example: @var Boolean)
@version 函數的版本 (Example: v1.3258c)

參考資料:維基-PHPDoc


三、團隊標準

3.1. 函式內部註解

對函式內部重要的邏輯、規則、做法、未做的事註解,以達到說明、記憶、提醒、搜尋定位功能

3.1.1. 功能群組註解

  • 當一組動作共同完成一個任務時,可稱為「功能群組」,對該組動作的任務內容註釋,應放在動作最前方

  • 功能群組註解格式 1:

    1
    // === 功能群組註解 ===

    雙斜線 + 空格 + 三個=號 + 空格 + 註解文字 + 空格 + 三個=號

  • 功能群組註解格式 2:

    1
    /* === 功能群組註解 === */

    單斜線 + *號 + 空格 + 三個=號 + 空格 + 註解文字 + 空格 + *號 + 單斜線

  • 功能群組註解格式 3:

    1
    2
    3
    4
    /*
    * === 功能群組註解 ===
    * 功能群組註解詳細說明
    */

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* 取得使用者資料
*
* @param array|string $userList 使用者pk清單
* @param string $sDate 開始日期
* @param string $eDate 結束日期
* @param string $format 格式,如為default時,抓取物件預設格式
*/
public function getUserData($userList, $sDate, $eDate, $format = 'default')
{
// === 參數整理 ===
$userList = is_array($userList) ? $userList : array_map('trim', explode(',', $userList));
$format = $format == 'default' ? self::$format : $format;

// === 資料庫載入 ===
$UserInfo = UserInfo::singleton();
$DeptInfo = DeptInfo::singleton();
$TitleInfo = TitleInfo::singleton();
$Rule = Rule::singleton();

// === 資料取得 ===
// 取得使用者資料
$userData = $UserInfo->getDataByPK($userList);
ArrayHelper::indexBy($userData, 'id');
// 取得部門資料
$deptData = $DeptInfo->getDataByPK();
ArrayHelper::indexBy($deptData, 'id');
// 取得職稱資料
$titleData = $TitleInfo->getDataByPK();
ArrayHelper::indexBy($titleData, 'id');

// === 規則對映表建構 ===
// 取得規則資料-所屬部門歷程
$deptMap = $Rule->deptMapBuilder($userList, $sDate, $eDate);
// 取得規則資料-職稱歷程
$titleMap = $Rule->titleMapBuilder($userList, $sDate, $eDate);
// 取得規則資料-假日
$holidayMap = $Rule->holidayMapBuilder($userList, $sDate, $eDate);

/*
* === 待處理 ===
* TODO: 20190508 本註解為問題描述,如下方程式有問題暫時無法解決時描述、可能是待優化,可全專案搜尋「待處理」、「TODO」
*/

/*
* === 規則 ===
* RULE: 20190508 本註解為特殊規則描述,如下方程式碼規則特殊,可用此方法記錄
*/
}

3.1.2. 單行註解

  • 適用:
    • 變數定義
    • 函式呼叫
    • 行為解釋
    • 搜尋定位
  • 單行註解格式:
    1
    // 單行註解內容

3.2. 命名規則

3.2.1. 原則

  • 使用PSR-1駝峰式命名法

  • 如果在表示SQL來的變數值,可以直接用SQL欄位名 (SQL命名為小寫底線分隔),如:

    1
    2
    // 取得PK為$u_sn使用者的欄位user_id值
    $user_id = $SQL->getUser($u_sn, 'user_id');

    看到此種格式變數即得知與SQL欄位相關,可增加辨識度。

  • 單獨的「名稱」沒有意義,而是要配上「來源/意義」、「說明」,如:

    1
    2
    // 取得員工資料
    $userData = $this->Users_info_model->getUserList();

    變數的「名稱」、「來源/意義」、「說明」可稱為記憶的鐵三角,彼此可互相聯想

  • 在有足夠的辨識度下,名稱愈短愈好

    • 名稱愈短,閱讀、辨識、記憶 愈容易
    • 名稱要的是辨識度,不需完全敘述出作用,敘述是註釋的工作

3.2.2. 辨識度探討

  • 閱讀習慣:以字分段,一句一句來。比較下列辨識度優劣:

    • Rule_detail_value_model
    • RuleDetailValueModel
    • 規則資料
    • 規則詳細資料
    • 規則詳細資料值
      • 英文辨識度:底線分隔 優於 駝峰命名
      • 中文辨識度:因為是母語關系,在一眼可看到整行字時,三者辨識度差不多
  • 名稱長度與辨識度:

    • 優:10字母/2單字 以下
      • 太短的名子,不建議再縮寫,如$ruleData,不宜再縮寫為$rData
      • 如果函式處理流很簡單(行數少),取超短名也不會有誤解/遺忘的狀況
    • 優:10 ~ 20字母/2 ~ 4單字
      • 名稱兼顧易讀性與釋意
    • 劣:20字母/4單字 以上

    名稱定義:

    • 長名稱:20字母/4單字 以上
    • 短名稱:20字母/4單字 之下
    • 完整名稱:很容易從變數命名中看出意義,但有可能很長
      • 例:$userExperienceInformation
    • 縮寫名稱:需輔以命名習慣、註釋理解意義,但短
      • 例:帳號經歷資訊 $userExpInfo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    // === 長名稱 ===
    // 取得規則檔頭 - 佳 (短名稱)
    $ruleData = $this->Rule_model->read();
    // 取得規則資料 - 可 (短名稱)
    $ruleDetailData = $this->Rule_detail_model->read();
    // 取得子規則資料 - 劣 (長名稱)
    $ruleDetailValueData = $this->Rule_detail_value_model->read();

    /*
    * === 短名稱-縮寫名稱 ===
    * 依賴習慣、約定命名方式縮寫,但對習慣跳者看Code的人要回頭看註釋/定義
    */
    // 取得規則檔頭 - 可 (短名稱)
    $rData = $this->Rule_model->read();
    // 取得規則資料 - 可 (短名稱)
    $rdData = $this->Rule_detail_model->read();
    // 取得子規則資料 - 可 (短名稱)
    $rdvData = $this->Rule_detail_value_model->read();


    // === 長名稱 vs 短名稱 ===
    // 取得子規則資料 - function內容少時$rdvData較優
    $ruleDetailValueData = $this->Rule_detail_value_model->read();
    $rdvData = $this->Rule_detail_value_model->read();

    // 打卡規則處理 - 長名稱
    foreach ($ruleDetailValueData as $key => $ruleDetailValue) {
    // 問題4
    $ruleDetailValueQuest4 = ruleDetailValue[4];
    // 問題5
    $ruleDetailValueQuest5 = ruleDetailValue[5];

    // 使用者打卡資料
    $userPunch = $userInfo['punch'][$ruleDetailValueQuest4];
    }

    // 打卡規則處理 - 短名稱 - 依賴習慣、約定命名方式
    foreach ($rdvData as $k => $rdv) {
    // 問題4
    $rdvQ4 = $rdv[4];
    // 問題5
    $rdvQ5 = $rdv[5];

    // 使用者打卡資料
    $userPunch = $userInfo['punch'][$rdvQ4];
    }

3.2.3. 命名流派比較

A. 短名稱+說明+來源/意義

例:

1
2
3
// 取得子規則資料  ⇦ 說明
$rdvData = $this->Rule_detail_value_model->read();
⇧ 名稱 ⇧ 來源/意義

特點分析:

  • 名稱:等號左邊的內容,用來辨識為主,理解為輔
  • 說明:註釋,對名稱的敘述,補完對名稱的理解
  • 來源/意義:等號右邊的內容,變數真正的意義由來
  • 有三個記憶/聯想元素
  • 程式處理過程中,短名稱易閱讀、易辨識、易記憶
    • 較佳做法
    • 有時名稱不縮寫就很短了,則不縮寫

B. 長名稱+來源/意義

例:

1
2
$ruleDetailValueData = $this->Rule_detail_value_model->read();
⇧ 名稱 ⇧ 來源/意義

特點分析:

  • 名稱:等號左邊的內容,用名稱盡量描述完變數的意義後,不寫註釋
  • 來源/意義:等號右邊的內容,變數真正的意義由來
  • 有二個記憶/聯想元素
  • 程式處理過程中,長名稱不易閱讀、不易辨識、不易記憶
  • 如看Code喜歡跳著看,或喜歡寫出長長的Code讓變數活很久的人,較喜歡長名稱
    • 較差做法

3.2.4. 常見的命名名詞

  • 動詞:常放在字頭
    • get, set, read, add(create), edit(update), del(delete), remove(rm), find, do, re(重做), allow, deny
  • 判斷詞:常放在字頭
    • is, no(not), had(has), need
  • 性質:常放在字尾
    • data, list, info, map(table), rule, setting(profile), flag, config, type, old, new
    • builder, parser, provider, converter, reader, writer, refactor, reform, reformat, render
  • 複數:字尾
    $salarySnList, $salarySNs
    $userList, $users
  • 常用縮寫:
    • dept(department), sn(series number), id(identification), info(information), del(delete)

四、檔案結構

  • 定義目錄結構及其意義,以利團隊作業
  • 功能需給予分類命名,以提供相似功能下的子文件統一存放目錄

CodeIgniter

  • WebRoot
    • application
      • controllers
        • {功能群組名稱}
          • {功能Controller.php}

            將相關功能劃分為同群組,依群組分類Controller
            CodeIgniter的Controller路徑、檔名會作用於網址,不要用camelcase

      • helpers
        • {Helper函式庫名稱.php}

          放在本目錄的函式庫,皆使用PSR-4方式載入

      • interfaces
        • {功能群組名稱}
          • {功能interface.php}

            放在本目錄的函式庫,皆使用PSR-4方式載入

      • language(語係)
      • libraries
        • {功能群組名稱}
          • {功能Library.php}

            放在本目錄的函式庫,皆使用CI->load->library()方式載入

      • models
        • {資料表Model.php}

          放在本目錄的函式庫,皆使用CI->load->model()方式載入

      • presenters

        放在本目錄的函式庫,皆使用PSR-4方式載入

      • services
        • {函式庫功能}
          • {函式庫.php}

            放在本目錄的函式庫,皆使用PSR-4方式載入

      • vendor (Composer目錄)
      • views
        • {功能群組名稱}
          • {頁面View.php}

            放在本目錄的函式庫,皆使用CI->load->view(), 自定義涵式載入

      • composer.json (Composer安裝檔)
    • assets
      • css
        • inner
          • {功能群組名稱}
            • {頁面CSS.css}

              放在本目錄的函式庫,皆使用 手動方式、自定義涵式 載入

        • outer
      • js
        • inner
          • {功能群組名稱}
            • {頁面JS.js}

              放在本目錄的函式庫,皆使用 手動方式、自定義涵式 載入

        • outer
      • img
        • {功能群組名稱}

          放在本目錄的函式庫,皆使用 手動方式、自定義涵式 載入

    • index.php (網站入口)

Github Blog

一、說明

利用Hexo+Github建立前端型Blog

二、安裝

1.依賴

  • Hexo
    • nodejs
  • GitHub Pages(github.io)

2.安裝環境

3.初始化工作目錄

  • 在D碟建立工作目錄workspace
  • 工作目錄上按右鍵,打開 Git Bash

4.安裝Hexo

Git bash中,透過 npm 即可完成 Hexo 的安裝。

1
$ npm install -g hexo-cli

5.初始化Hexo

一旦 Hexo 完成後,請執行下列指令,Hexo 會在指定資料夾中建立所有您需要的檔案。

1
2
3
$ hexo init blog-src
$ cd blog-src
$ npm install

6.設定Hexo

網站 配置 檔案,您可以在此配置大部分的設定。

1
2
3
4
5
6
7
8
9
10
11
title: Mars's Blog
subtitle: Mars's learning record
description: Record Mars's learning experiences and experiences
author: Mars Hung
url: https://marshung24.github.io
new_post_name: :year-:month-:day-:title.md # File name of new posts
post_asset_folder: true
theme: bootstrap-blog
deploy:
type: git
repo: git@github.com:marshung24/marshung24.github.io.git

deploy git 需設定global git上傳ssh key資料

7.安裝測試伺服器

1
$ npm install hexo-server --save

8.下載主題

1
$ git clone https://github.com/cgmartin/hexo-theme-bootstrap-blog.git themes/bootstrap-blog

9.修改主題設定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File: themes/bootstrap-blog/_config.yml

# Header
navbar_brand: false
menu:
Home: index.html
Archives: archives/
GitHub: https://github.com/marshung24

# Content
excerpt_link: Read More
fancybox: false

# Sidebar
widgets:
- recent_posts
- archive
- category
- tag

# widget behavior
archive_type: 'monthly'
show_count: true

# visitors count
counter: true

訪問計數

1
2
3
4
5
6
7
8
# File: themes/bootstrap-blog/layout/_partial/footer.ejs

<% if (theme.counter) { %>
<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<span id="busuanzi_container_site_pv">總訪問量<span id="busuanzi_value_site_pv"></span>次</span>
<span class="post-meta-divider">|</span>
<span id="busuanzi_container_site_uv">訪客數<span id="busuanzi_value_site_uv"></span>人</span>
<% } %>

三、指令

1.建立新文件

1
$ hexo new [layout] <title>

2.產生靜態檔案

1
hexo generate

3.清除快取

清除快取檔案 (db.json) 和已產生的靜態檔案 (public)。

1
$ hexo clean

4.啟動伺服器

1
hexo server -p 80

5.查看結果

前往 http://localhost 查看建構後的結果

6.部署網站

上傳至Github

1
$ hexo deploy -g

四、其他應用

添加文章目錄

添加toc.ejs文件

  • 在主題目錄下layout/_partial文件夾中新建toc.ejs文件,存放文章目錄的代碼。
    1
    2
    3
    4
    5
    <% if (post.toc != false) { %>
    <div id="toc">
    <%- toc(post.content, {list_number: false}) %>
    </div>
    <% } %>

    這裡的toc()函數就是Hexo官方提供的輔助函數

添加到頁面中

主題目錄文章佈局文件layout/_partial/article.ejs,尋找代碼:

1
<%- post.content %>

在前面添加了一行 <%- partial(‘toc’) %> 引用了目錄代碼,變成:

1
2
<%- partial('toc') %>
<%- post.content %>

隱藏ol li前方數字

在source/css/custom.css增加樣式:

1
2
3
#toc ol li{ 
list-style-type:none;
}

錄自 「HEXO小書-添加文章目錄」

五、參考資料

開發及測試要點

開發及測試要點

一、說明

軟體開發/維護,離開不了測試,產品品質不但直接影響用戶體驗,更代表著企業形像、信譽,極為重要。

二、RD開發及測試要點

  • 必需知道「原由」 (故事)
  • 必需有明確「開發目標」 (案例)
  • 必需拆解出「工作項目」
  • 每個「工作項目」應有對應的「測試項目」
  • 測試項目應需有明確敘述
    • 環境: 如有前置環境,請描述如何建構測試環境
    • 5W1H: 對象(What)、目的(Why)、位置(Where)、時間/步驟(When)、人員(Who)、方法(How)
    • 對與錯: 測試正確性時,什麼狀況為「正確」;測試錯誤狀態時,什麼狀況為「錯誤」
  • 所有改動的影響,必需親自測試一次
    • QA難以測試的,需在RD內部做「白箱測試」
    • 需大量資料比對的,需編寫測試工具

故事 <=> 案例 <=> 工作項目 <=> 測試項目

三、QA測試要點

  • 必需知道「原由」 (故事)
  • 必需有明確「測試目標」 (案例)
  • 需了解「原由」、「測試目標」、「工作項目」、「測試項目」之間的關係
  • 精確測試與模糊測試
    • 精確測試
      • 必需要求RD條列測試項目,如果測試複雜,還要步驟化環境建置
      • 必需要求RD說明改動影響
        • 功能、頁面、按鈕、表單欄位、錯誤訊息、流程
    • 模糊測試
      • 自行評估可能影響並設計測試項目
      • 請「限時」測試

        精確測試必需100%完成,模糊測試取重點完成

  • 評估本案對使用者影響或使用者可能遇到的狀況

QA是RD與User間的橋樑,不但需要求程式正確性,還需注意使用者體驗
QA的全名為Quality Assurance engineer,即品質保證工程師,其中最核心的任務並非是測試,而是保證產品交付的品質

四、測試分類

  • 邏輯行為正確性
    • 步驟(Create, Read, Update, Delete, Search, Export, Import)
  • 資料正確性
    • 資料內容、計算規則、計算數值
  • 畫面正確性
    • 前端顯示內容、版面、順序、習慣
  • 錯誤訊息
    • 錯誤訊息處理&顯示
  • 自動化測試
    • 前端自動化測試
      • 行為測試(BDD)
    • 後端自動化測試
      • 單元測試(unit test)
      • 功能測試(function test)

五、參考

六、Log

  • 2019-04: Mars Hung編寫於HackMD
  • 2019-08-01: Mars Hung重製成md檔