PHP、MySQL 語法基礎


Posted by saffran on 2021-02-25

環境建置

XAMPP 官網:https://www.apachefriends.org/zh_tw/index.html

會在 htdocs 資料夾裡面寫程式
檔案路徑都是從 htdocs 這個資料夾底下開始算

例如:
檔案路徑是 htdocs/saffran/index.php

網址就會是 http://localhost:8080/saffran/index.php
或是用 XAMPP 的 IP 位置也可以連到同一個位置去 http://192.168.64.2/saffran/index.php

當需要重新安裝 XAMPP 時,要注意:

詳細可參考 MacOSX启动XAMPP-VM报错:Error starting "XAMPP" stack: cannot calculate MAC address: signal: killed

情境如下:
我的 mac 升級版本之後,點擊 XAMPP 的 app icon 都毫無反應,因此需要重新安裝 XAMPP

正確做法:安裝前需要先刪除 ~/.bitnami/stackman/helpers/hyperkit

用此種方式重新安裝 XAMPP,在 htdocs 裡面原有的檔案都會完整保留下來

步驟如下:

  1. 打開 Finder,按下 cmd+shift+G,輸入檔案路徑 ~/.bitnami/stackman/helpers/hyperkit
  2. 把「hyperkit」檔案刪除
  3. 「hyperkit」檔案刪除後,再去 XAMPP 官網 重新安裝 XAMPP

錯誤做法:安裝前,沒有先刪除「hyperkit」檔案

如果沒有先刪除「hyperkit」檔案,就重新安裝了 XAMPP,在 MacOS 啟動 XAMPP 時會報錯(無法啟動):
Error starting "XAMPP" stack: cannot calculate MAC address: signal: killed

PHP 語法基礎

  • PHP 每一行「敘述句」的結尾都要加上 ;
  • 變數的前面一定要加上 $

變數、輸出內容

  • PHP 的變數不需要宣告,可以直接設定一個變數並賦予變數的值,變數會以 $ 來識別
  • 在使用變數時,變數前面也要加上 $
    $a = 789;
    echo $a;
    // output: 789
    

. 來拼接字串

注意!PHP 不是用 + 來拼接字串

$a = "sunday";
$b = "morning";
echo $a . $b;
// output: sundaymorning

if else 判斷式

範例一:

$score = 5;
if ($score >= 60) {
  echo "pass!";
} else {
  echo "fail";
}
// output: fail

範例二:

$score = 95;
if ($score >= 60 && $score < 80) {
  echo "pass!";
} else if ($score >= 80) {
  echo "great!";
} else {
  echo "fail";
}
// output: great!

迴圈

for ($i=1; $i<=10; $i++) {
  echo $i;
}
// output: 12345678910

換行要用 <br>

for ($i=1; $i<=10; $i++) {
  echo $i . "<br>";
}

output:

1
2
3
4
5
6
7
8
9
10

檢視原始碼:

1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>

陣列

$arr = array('s', 'u', 'n', 'd', 'a', 'y');

echo "length:" . sizeof($arr) . "<br>"; // 印出陣列的長度
echo $arr[4]; // 印出 index 是 4 的元素

output:

length:6
a

印出比較複雜的資料結構(例如物件、陣列)

例如我想要印出 $arr 這個陣列
寫成 echo $arr; 是沒有用的,會出現錯誤「Array to string conversion」
意思是:
要自己主動先把 array 轉成 string,才能用 echo 輸出

$arr = array('s', 'u', 'n', 'd', 'a', 'y');

echo $arr;

解決方式:

var_dump() 或是 print_r() 印出比較複雜的資料結構(不是單純的 number, string 這些)

var_dump() 這個 function 來輸出陣列裡面每個元素的「type 和 value」

$arr = array("sunday", 50, "dog", 33, 8);

var_dump($arr);

output:

array(5) { [0]=> string(6) "sunday" [1]=> int(50) [2]=> string(3) "dog" [3]=> int(33) [4]=> int(8) }

print_r() 這個 function 來輸出陣列裡面每個元素的「value」

$arr = array("sunday", 50, "dog", 33, 8);

print_r($arr);

output:

Array ( [0] => sunday [1] => 50 [2] => dog [3] => 33 [4] => 8 )

function

function add($a, $b) {
  return $a + $b;
}

echo add(5, 7);
// output: 12

include、require、include_once、require_once 的差別

參考資料

[PHP教學] - 初學者最易混淆的include、include_once、require、require_once之比較
簡單談談PHP中的include、include_once、require以及require_once語句
深入理解require與require_once與include以及include_once的區別

include()

include() 會將指定的檔案讀入,並執行檔案裡面的程式碼。

include_once()

include_once() 和 include() 的作用幾乎相同,但是 include_once() 會先檢查要匯入的檔案是否已經被匯入過了,如果有的話就不會再重複匯入該檔案。
這個檢查有時候是很重要的,例如:在要匯入的檔案裡面有包含很多變數、自定義的函式,如果是用 include() 重複匯入該檔案,第二次匯入時就會發生錯誤,因為 PHP 不允許同名的變數、函式被重複宣告。

require()

require() 會將目標檔案的內容讀入,並把自己本身替換成這些讀入的內容。

require_once()

require(), require_once() 的差別也是在於: require_once() 會先檢查要匯入的檔案是否有被匯入過了,如果有的話就不會再重複匯入該檔案。

綜合比較

include(), include_once() 適合用來引入動態的程式碼。如果找不到要引入的檔案會出現錯誤訊息,但程式不會停止執行。

require(), require_once() 適合用來引入靜態的檔案內容。如果找不到要引入的檔案會出現錯誤訊息,且程式會停止執行。

Apache 與 PHP 原理簡介

執行 PHP 從頭到尾的流程如下:

request (test.php) => Apache (server) => PHP (執行 php test.php 這段指令) => output => Apache => response

我在 test.php 這個檔案發送 request,request 會先到 Apache 這個 server 去 => Apache 會幫我執行 php test.php 這段指令 => php test.php 這段指令會產生一個「output」 => 這個 output 會回到 Apache => 最後 Apache 再把 output 當作 response 傳回到 client 端

  • Apache 是 server(扮演最重要的角色),所要做的工作可以看成是這樣:
function run(request){
  response = php(request) // 把 request 拿進來後送到 PHP
  send response // 把拿回的 output 當作 response 傳回到 client 端
}
  • PHP 的工作只負責幫我「把 request 轉成我要輸出的 output 形式」

因此,在跑 Apache 這個 server 時,其實也可以跑其他的檔案,例如 index.html 或是 index.js 或是 all.css,Apache 都會幫我處理好

server 就只是一個程式(這個程式專門處理網路相關的 request 和 response)

可以想成是:
server 會接收一個 request => 中間做一些處理後 => 再把 response 傳回來

function server(request){
  ...... // 中間做一些處理
  return response;
}

PHP 這端在做什麼

test.php:

$num = 5;
echo "<br>" . $num . "<br>";

在 CLI 輸入 php test.php 就可以看到「被 PHP 轉化後的輸出」:
(因為執行環境不是在瀏覽器,所以 <br> 標籤不會被解析為「換行」,而是會用純文字顯示出來)

<br>5<br>

因此,整個流程就可以想成是:
Apache 收到 request 後會幫我執行 php test.php 這段指令 => 執行 php test.php 這段指令會輸出 <br>5<br> => Apache 再幫我把 <br>5<br> 當作 response 整包傳回來,傳回來到瀏覽器後,瀏覽器就會把 <br>5<br> 這些 HTML 標籤解析成我看到的畫面

因此

如果 Apache 掛掉:Apache 無法把 request 送到 PHP,也無法傳回任何 response

如果 PHP 掛掉,會有兩種情況:

  • 第一種情況:PHP 輸出了錯誤的 output,因此傳回來的 response 就可能都是一些錯誤訊息
  • 第二種情況:PHP 根本沒有輸出任何 output,因此 Apache 回傳的 response 就會是 PHP 的原始碼(如下),而不會是經過 PHP 處理後的結果 <br>5<br>
    • PHP 的原始碼:
$num = 5;
echo "<br>" . $num . "<br>";

Apache 預設的規則:網址就會是「檔案路徑」

這是 Apache 預設的規則:
PHP 的網址會跟資料夾的結構相符,例如 http://localhost:8080/saffran/test.php

這個規則是可以去 server 更改的
因此,幾乎所有網站的網址後面都不會看到有什麼 facebook.com/user.php 之類的,而會是像 facebook.com/user/528183237 這種自訂的網址,就是因為有去更改 server 預設的設定

資料庫系統簡介

不需要把資料庫系統想的過於複雜

前面有講到,server 就只是一個程式(這個程式專門處理網路相關的 request 和 response)
「資料庫系統」也只是一個程式(這個程式專門處理資料)

為什麼需要資料庫系統

在寫程式時,常常需要處理一些資料

假如沒有資料庫系統:
處理完的資料,可以存在變數、記憶體裡面,可是一旦我把程式關掉,這些資料就沒有了(資料無法被永久保存)

因此在早期,如果想要永久保存資料,就必須用「存檔」這種最陽春的方式:把資料存在檔案裡面,要用資料時就去讀取檔案。但存檔的缺點是「沒效率」:我必須自己手動去處理每一筆資料格式(例如:excel 的資料格式),會受到很多限制,很麻煩

因此就有人從「存檔」延伸出去開發了資料庫系統

資料庫系統,底層還是存在硬碟裡面,但我不用去在意它底層到底是怎麼存的
我只需要知道:資料庫系統提供了很友善的介面和指令,讓我可以用程式語言的語法很方便的去操作資料(把資料存進去、撈出來)

這就是資料庫系統的價值所在:讓「保存資料」變得更有效率、更好維護

資料庫系統主要有分為兩種類型

第一種是:關聯式資料庫 (Relational database)

存取的資料有一定的型態限制,例如:只能是 string, number

關聯式資料庫只是一個統稱,底下有許多真正實作出來的資料庫,其中,MySQL、Microsoft SQL Server 以及 PostgreSQL 就是最有名的幾個關聯式資料庫,這些資料庫在使用上其實都大同小異

第二種是:非關聯式的資料庫,統稱它為 NoSQL (全名是 Not only SQL)

存取的資料沒有型態限制,想要存成 array, object 這些都可以

比較有名的 NoSQL 有 MongoDB 這個資料庫


這兩種不同的資料庫系統,分別有不同的適用場合

在大部分情況下,關聯式資料庫是較常使用的

但當我想要存 log 時,就比較適合用 NoSQL,原因為 log 的一些特性:

  • log 資料很多
  • log 資料有可能會變,例如:我突然需要記錄 user 的手機型號,這時我就可以在 NoSQL 裡面用物件的方式,在同一個物件裡面新增一個 key 叫做 phoneModel 來記錄 user 的手機型號即可(我不需要額外新增一個欄位)=> 新增 key 即可,不用去更改原本的結構
{
  studentId: 3,
  score: [60, 85, 72],
  student: {
    name: peter,
    phoneModel: 'y528'
  }
}

但如果是在關聯式資料庫裡面,就一定得額外新增一個欄位(手機型號)才能存取新的資料,很麻煩

SQL(全名是 Structured Query Language)

我可以用 SQL 這個程式語言,來操作關聯式資料庫裡面的資料

如何管理資料庫? phpmyadmin 簡介

要連到資料庫有兩種方式:

  1. 用 CLI 直接輸入 SQL 指令來操作資料庫
  2. 用 GUI 來操作資料庫,例如 phpMyAdmin

phpMyAdmin 是一套「管理 MySQL 資料庫」的軟體

phpMyAdmin 其實就是一個 php 檔案,有人用 php 幫我寫好了這個網頁,讓我透過這個軟體介面更方便的去管理我的資料庫
運作機制就是:
例如我在 phpMyAdmin 新增一個表格,phpMyAdmin 就會在底層幫我下一些 SQL 的指令給資料庫,就可以在資料庫裡面建立一個表格

除了 phpMyAdmin 之外,也可以使用另一套軟體 Sequel Pro 來管理資料庫
Sequel Pro 不是用 php 寫的,而是一個直接跑在電腦上的程式

Table schema 簡介

schema 就是「結構」的意思

每一個 table 都有一個結構

對於比較短的字串,例如 username,type 會存成 VARCHAR

Primary key、Index、Unique 這些有什麼用?

Primary key 主鍵

主鍵,英文是 Primary key,簡稱為 PK
設定成主鍵的欄位,就是在整個 table 裡面最重要的欄位,必須滿足幾個條件:

  • 值不能為空
  • 值不能重複

假設我要設計一個員工的系統,就會把「員工編號」設為 PK,因為員工編號就代表每個員工,不會重複且是最重要的一個欄位

Unique 唯一

如果把此欄位設為 Unique,就不能再新增跟這個欄位同樣的值

使用時機:
例如我不希望不同的 user 使用同樣的 email

Index 索引

如果把此欄位設為 Index,在搜尋此欄位的資料時就可以比較快
也可以把兩個欄位設為一個 Index,例如 username 和 password,這樣我就可以同時查詢 username 和 password 的組合

資料型別

資料來源 1 CHAR Data Type
資料來源 2 VARCHAR Data Type
資料來源 3 TEXT, TINYTEXT, MEDIUMTEXT, LONGTEXT

編碼會使用 UTF8 是因為:可以支援多種語言

CHAR、VARCHAR、TEXT 都是「string」的資料型別

以下是個別的介紹與比較:

CHAR

資料型別如果使用 CHAR:該欄位的每個值都要是一個「固定長度的 string」

CHAR(30) 代表:長度是 30 個 characters 的字串

如果我的字串是「hello」,只有用到 5 個 characters,MySQL 會在後面自動加上 25 個空白來補滿 30 個 characters,所以在欄位裡的資料就會是:

'hello                 '

但是,當我使用 SELECT 語法把資料撈出來時,MySQL 會自動把空白去掉,所以我拿到的資料會是:

'hello'

MySQL mode 有很多種模式,如果我去把其中一個模式叫做 PAD_CHAR_TO_FULL_LENGTH 給打開,這樣當我使用 SELECT 語法把資料撈出來時,MySQL 就不會把空白去掉了,所以我拿到的資料就會是「有 25 個空白的字串」:

'hello                 '

CHAR 使用時機

當我很確定此欄位的字串長度,例如都會是 30 個字元時,使用 CHAR(30) 就是最節省空間的,因為 MySQL 不需要特別去記錄每筆資料的字串長度(就都是 30 個字元)

如果是使用 VARCHAR,MySQL 就要特別去記錄每筆資料的字串長度(因此每個字串都會佔用較多空間)

當我使用了 CHAR(30),但是此欄位的資料長度不是固定的,例如這筆資料只有 5 個字元,那就會浪費了 25 個字元的空間

CHAR 允許的長度

CHAR 的長度是限定在 0 ~ 255 個字元

0 個字元就是:8 bit 能放入的最少字元 (可以是 null、空字串 ''
255 個字元就是:8 bit 能放入的最多字元

十進位的 0 就是:二進位的 00000000
十進位的 255 就是:二進位的 11111111

VARCHAR

VARCHAR 跟 CHAR 的差別是:
VARCHAR 不會用空白去補字串長度

但是!使用 VARCHAR 的缺點是:因為每個字串長度都不同,所以 MySQL 會需要額外的空間去記錄每個字串的長度

  • 如果字串長度 <= 255 個字元,MySQL 會需要 1 byte 來記錄字串長度
  • 如果字串長度 > 255 個字元,MySQL 會需要 2 bytes 來記錄字串長度

在 UTF8 的編碼中,一個英文字母所佔的空間是 1 byte
其他語言(例如中文字),因為筆畫較多,一個字元在 MySQL 最多會佔 3 bytes 的空間

如果我的資料型別使用的是 VARCHAR(50)

當有一個字串 'hello',所佔的空間就是 5 bytes + 1 byte

VARCHAR 的字串長度限制

VARCHAR 可以放入字串的最大空間是:65535 bytes
而 MySQL 的 row limit 也剛好是 65535 bytes
(row limit 就是:每個 row 可以存東西的最大空間)

65535 bytes 換算成字串長度就是 21,843 個字元:

65535 bytes / 3 - 2 bytes = 21843
  • 「除以 3」是因為:一個字元在 MySQL 最多會佔 3 bytes 的空間
  • 「減掉 2 bytes」是因為:MySQL 最多會需要 2 bytes 來記錄字串長度

因此,保險起見,VARCHAR 的字串長度不要超過 20,000 個字元

TEXT

如果是用 VARCHAR,只要是 65535 bytes 以內的字串,我都可以存

但如果是用 TEXT,會根據不同的 size 又區分為四種型別:

TINYTEXT: 最多可以存 255 characters = 255 Bytes
TEXT: 最多可以存 65,535 characters = 64 KB
MEDIUMTEXT: 最多可以存 16,777,215 characters = 16 MB
LONGTEXT: 最多可以存 4,294,967,295 characters = 4 GB

參考資料 Understanding Storage Sizes for MySQL TEXT Data Types

VARCHAR 和 TEXT 的差別主要有三個

差別一:VARCHAR 可以設長度,但是 TEXT 不行設長度(這是最重要的差別)

  • 如果本來就知道大概會需要多少字元,就用 VARCHAR (才能節省空間)
  • 真的逼不得已東西很長(例如說要存文章)的時候才用 TEXT

差別二:MySQL 的 row limit

  • VARCHAR 會受到 MySQL 的 row limit 所限制(最大就是 65535 bytes)
  • TEXT 不受到 MySQL 的 row limit 所限制

原因為:
VARCHAR 是直接存在 table 裡面,所以會受到 row limit 的限制

但是 MySQL 會把 TEXT 存放在別的地方(不是直接存在 table 裡面),再用「reference」的方式去引入這個 TEXT
(無論存了多大的 TEXT,在 table 本身最多只會用到 12 bytes 而已)

因此,TEXT 可以比 VARCHAR 存更多的字元在欄位中

TEXT 這種資料型別也被稱為 CLOB(Character Large Object)

差別三:default value

  • VARCHAR 的 default value 可以是任何的值(包括 null 或是其他任何的值)
  • TEXT 的 default value 只能是 null

MySQL 語法基礎

查詢資料 Select

基本語法

  • * 是「欄位名稱」 (* 代表:所有欄位)
  • blog posts 是「table 名稱」
SELECT * FROM `blog posts`

查詢指定的欄位

只把 username, content 這兩個欄位的內容撈出來

SELECT username, content FROM `blog posts`

以別名來顯示欄位

原本的欄位名稱是 username,但我想要欄位名稱改為用 studentName 來顯示

SELECT username as studentName FROM `blog posts`

設定查詢條件

WHERE 來設定我要查詢的資料條件是 username = 'Harry'

注意!字串 'Harry' 要用單引號或雙引號包起來

SELECT username FROM `blog posts` WHERE username = 'Harry'

and 設定多個條件

多個條件之間用 and 來連接(需同時符合這幾個條件)

SELECT username FROM `blog posts` WHERE username = 'Harry' and id = 1

or 設定多個條件

多個條件之間用 or 來連接(只需符合其中一個條件即可)

SELECT username FROM `blog posts` WHERE username = 'Harry' or id = 2

新增資料 Insert

基本語法

  • blog posts 是 table 名稱
  • 第一個小括弧裡面放入:我要插入資料的欄位名稱
  • 第二個小括弧裡面放入:對應的資料
    • username 欄位新增一筆資料 Micky
    • content 欄位新增一筆資料 Hi, I'm a mouse.
INSERT INTO `blog posts`(`username`, `content`) VALUES ('Micky', "Hi, I'm a mouse.")

修改資料 Update

基本語法

  • blog posts 是 table 名稱
  • SET 後面接一系列我想要修改的欄位 + 資料,不同欄位之間用 , 分開
  • WHERE 後面接「我要限定的條件」,例如:我只要修改 id 是 2 的欄位

注意!如果沒有加上 WHERE id = 2,就會把整個 table 裡面每個 username 欄位的資料都改成 Katy,也會把整個 table 裡面每個 content 欄位的資料都改成 Check it out!

UPDATE `blog posts` SET username = 'Katy', content = 'Check it out!' WHERE id = 2

刪除資料 Delete

基本語法

  • blog posts 是 table 名稱
  • WHERE id = 2 代表:我想要刪除的是 id = 2 的這筆資料
DELETE FROM `blog posts` WHERE id = 2

is_deleted 來決定此資料是否要呈現在 user 面前

在很多情況下,系統並不會真的把資料刪掉
例如:
當 user 在前台選擇「刪除會員帳號」時,系統並不會真的把資料刪掉(因為 user 有可能是誤刪資料,如果真的把資料刪掉就無法救回來了)

系統的做法會是:
會在 table 最後新增一個欄位叫做 is_deleted,它的 type 會是 Boolean

  • 如果資料還沒被刪除,is_deleted 的值會是 0 (因為 0 代表 false)
  • 當 user 在前台選擇「刪除資料」時,系統並不會把資料刪除,只是把 is_deleted 的值改為 1 (因為 1 代表 true)來當作「資料已經被刪掉了」的意思

然後,user 在前台只會看到那些 is_deleted = 0 的資料而已,所以 user 就會認為那些 is_deleted = 1 的資料都是已經被刪掉的(但對資料庫來說,每一筆資料都還在)

SELECT * FROM `blog posts` WHERE is_deleted = 0

#PHP #MySQL







Related Posts

簡明程式解題入門 - 字串篇 I

簡明程式解題入門 - 字串篇 I

JavaScript 進階 05:this

JavaScript 進階 05:this

你改我修都不怕-初探專案管理軟體 git

你改我修都不怕-初探專案管理軟體 git


Comments