PHP - Introduction

PHP - Introduction

LAVI

PHP

先來聽首 PHP 之歌吧

PHP 是全世界最好的語言

圖片來源

然後 PHP 有個梗,他官方文件裡面曾經寫過 「PHP 是最好的 web 開發語言,那其他的語言如何呢?」,聽起來超囂張的 XD
結果後來就被華語圈的程式設計師以訛傳訛變成 「世界上最好的語言」

Introduction

什麼是 PHP?
PHP 的前身是一個用來跟蹤訪問主業的人們的信息,叫做 Personal Home Page Tools
但是後來被重寫後,現在改稱 PHP: Hypertext Preprocessor

因為 PHP 他長得像一隻大象,所以吉祥物是 elePHPhant,聽說有在賣周邊,在前面 PHP 之歌裡面出現,有點想買

PHP 是一個應用範圍很廣的語言,特別是在網路程式開發方面,你可以透過執行 PHP 的程式碼來產生網頁,提供瀏覽器讀取

PHP 也是一種腳本語言,縮短了傳統編寫編譯的過程的電腦語言,常用於自動化流程,通常簡單易學也容易使用

弱型別


圖片來源

PHP 是一種弱型別語言,弱型別就是這個語言型別系統對於型別檢查的嚴格程度不是很嚴謹

譬如說,如果今天用 PHP 寫說要讓數字 123 加上字串 456,那今天你得到的結果會是 579 ,因為 PHP 會很貼心的自動幫你發現說 “哦字串 456 可以轉成數字耶”,然後就很貼心的幫你把 456 轉成數字然後跟 123 相加

沒有甚麼好或不好,這種貼心很方便省事,但是有時候你可能真的打錯 code,PHP 會不知道你打錯,一樣自作聰明的幫你轉,結果你程式可能到最後都順利輸出了,但是答案是錯的,然後你要 debug 就會 de 很久

動態語言

動態語言指的是你可以在執行的時候改變他的結構、物件或型別的語言,你可以有新的函數、新的對象之類的,簡單來說就是你程式在運行的時候可以根據某些條件改變自身結構

後端語言


圖片來源

PHP 是常用的後端語言,後端由 server,後端語言、資料庫組成

Syntax

基礎的 PHP 語法

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

// 單行註解

/*
多行註解
*/

echo "Hello World\n";
ECHO "heLLo World\n";
eChO "Hello wORlD\n";

?>

PHP 的 script 可以在你 document 的任何地方去寫他,然後他的 script 開頭會是 <?php 這樣,結尾會是 ?>,中間就是你的 code

PHP 檔案存檔就跟你一般存檔的時候一樣,你的 filename 巴拉巴拉然後副檔名最後 .php,PHP 註解的方式和 C 一樣

最後他輸出的方式是寫 echo,而像是 echo 這類的關鍵字、保留字,在 PHP 的 Case Sensitivity 是大小寫沒差的,但是自己取的變數名稱就有差,只要變數中其中一個字元設不同的就會視為是不同變數

PHP 結尾要記得加分號!!

Data Types

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

$a = 123;
$_a = -123;
$b = 0123;
$_b = -0123;
$c = 0x123;
$_c = -0x123;
$f = 123.45;
$_f = -123.45;

?>

由於 PHP 的動態特性,動態語言的變數本身只有參考資料,並不會像靜態語言那樣還帶有著資料型態,所以所有變數都是 $ 錢字號開頭,就不像 c 那樣還要寫成 int a 等等,不需要為他賦予變數型態

任每次期中期末必考的那樣,PHP 的變數型態開頭也不能是數字,只能是單字或是 _ 底線開頭,且變數名稱整體只能包含數字、英文字母和底線而且,變數名稱的大小寫有差,是 Case Sensitivity 這點和 c 語言也是一樣的哦

接著,PHP 的存取範圍,在 32 bit 的系統整數可以表示的範圍是 -2147483648 ~ 2147483648
(我自己記大概都是記 20 億,也就是一個2後面9個零),而 PHP 的存取範圍在 64 bit 的系統是和 long long int 的範圍是一樣的,只要超過範圍就會改用浮點數型態儲存

String

string 的寫入在 PHP 裡面就像平常在寫 c 一樣,但是有單引號和雙引號的區別

如果在雙引號中寫入變數,那輸出字串時會自動將變數轉譯成變數值

1
2
3
4
$str = "NISRA!!!";
echo "I love $str!\n";

// I love NISRA!!!

但如果是在單引號中寫入變數,那他會把這整串都當作真的只是字串,就不會轉譯變數

1
2
3
4
$str = "NISRA!!!";
echo 'I love $str!\n';

// I love $str!\n

PHP 的 strlen() 是回傳多少這個字串有 bytes 而不是多少字元

1
2
echo strlen("strabc123"); 
// 9

PHP 裡面,英文字母的 . 用來串接字串

1
2
echo "123" . '4' . ".5\n"; 
// 1234.5

如果我們今天用加號相加呢,前面有題到,PHP 是弱型別語言,所以今天如果我們的字串是數字的話,PHP 是允許這個字串和 int、float 型別的數字做相加的
執行以後會發現輸出結果是 127.56,也正體現了弱型別的特性

1
2
echo "123" + 4 + 0.56; 
// 127.56

用字串做相加,譬如說我們把剛剛的 4 和 0.56 都改成字串型態
執行以後會發現,他的輸出結果一樣是 127.56,也就是這些字串都被判斷為 int 而做相加

1
2
echo "123" + "4" + "0.56"; 
// 127.56

C 的字串結尾最後會有空字元,但 PHP 沒有

Conditions

簡單的 PHP if elseif else 的範例

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

$t = date("H");

if("6" <= $t && $t < "12"){
echo "Good morning!";
}
elseif("12" <= $t && $t < "18"){
echo "Good afternoon!";
}
else{
echo "Good night!";
}

?>

$t 是一個我們自己設的變數,date("H") 是回傳 24 小時制的字串,這個 H 是固定的
那我們現在把他丟進 if 判斷式裡面判斷,而這邊要用字串型態判斷是因為我們這邊 date 是用 “H” 字串作為 $t 的型態,而可以用字串型態去比較數字的大小是因為 PHP 是弱型別語言

Loops

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
<?php

$i = 1;
while($i <= 5){
echo $i++;
}

$j = 1;
do{
echo $j++;
}while($j <= 5);

for($k = 1; $k <= 5; $k++){
if($k == 5){
break;
}
elseif($k == 3){
continue;
}
echo $k;
}

$num = array(1, 2, 3, 4, 5);

foreach($num as $n){
echo "$n";
}

?>

array 的寫法,可以直接宣告 $var = array();
而如果要宣告二維陣列的話,會宣告 $array = array(array());,此種寫法,會讓陣列包含一個 [0] => array() 陣列頭的元素
下面有詳細說明

for each 一般來說並不太常被使用,但是如果要寫的話可以這樣寫 foreach($array as $value)
前面是你剛剛設為 array 的參數名稱,as 後面是你現在設定要遍歷整個陣列的變數名稱
我自己的理解上,我覺得他的判斷式有點像 c++ 的 for(auto i : array)

Array

在 PHP 裡面,array 的宣告是長這樣

1
2
3
<?php
$f = array();
?>

如果要初始化放值在 array 裡面,就在括號裡面用雙引號填寫值,中間用逗號隔開

1
2
3
<?php
$f = array("A", "B");
?>

當我們想要表示陣列的大小時,在 PHP 我們會寫 count(f),中間的括號放我們的陣列變數,代表你的陣列中總共有多少元素

1
2
3
4
5
6
7
<?php
$f = array("A", "B");
for($i = 0; $i < count($f); $i++){
echo $f[$i] . " ";
}
?>
// A B

一樣可以用 . 來對陣列和字串做串接

1
2
echo $f[0] . " is " . $f[1] . "'s friend\n";
// A is B's friend

Associative

PHP 的 array 有的 Associative 特性
我們可以用一個自己設定的 key 值去當作該陣列的索引值,有點像 c 的 STL map
譬如說我現在把 key 值設為字串 LAVI,那我可以用像這樣,像箭頭一樣的符號 等於大於 => 然後接我想要放入的值
那我們輸出來看看,這邊一樣,取值的話也一樣是在陣列裡面放 key 值

1
2
3
$age = array("LAVI" => "19");
echo "LAVI is " . $age['LAVI'] . " years old.\n";
// LAVI is 19 years old

也可以直接用像對陣列賦值一樣的寫法,直接把字串索引的 key 寫在陣列的中括號裡面

1
2
3
$age['LAVI'] = "19";
echo "LAVI is " . $age['LAVI'] . " years old.\n";
// LAVI is 19 years old

我們也可以一次對整個陣列賦予很多 value,只要我們中間用逗點隔開就好,那我們接下來來試著用 foreach 迴圈輸出看看
這邊是因為如果用 i = 0、i < 陣列大小之類的,但因為我們現在索引值不是 0 1 2 3 那些了,所以不能那樣寫
那我們這邊的寫法是陣列名字變數 as 你取的變數名,此時它代表的意義是你的 key 值
後面接箭頭指向的另一個變數值也是你自己取的另一個變數,它代表的意義是你的 value
也順便同時來看看 key 值是多少

1
2
3
4
5
6
7
8
<?php
$age = array("A" => "15", "B" => "30", "C" => "20");

foreach($age as $i => $i_value) {
echo "Key = " . $i . ", Value = " . $i_value;
echo "\n";
}
?>

Sort

對這個 array 做排序,PHP 有內建的 sort 函式
我們可以簡單的寫個一維陣列,然後用 foreach 輸出

1
2
3
4
5
6
7
8
9
<?php
$numbers = array(4, 6, 2, 22, 11);
sort($numbers);

foreach($numbers as $i){
echo $i . " ";
}
?>
// 2 4 6 11 22

如果是字串呢
可以看到,他排序的方式是按照第一個字元的字典序作排列

1
2
3
4
5
6
7
8
9
<?php
$f = array("ABC", "XYZ", "BCDEF");
sort($f);

foreach($f as $i){
echo $i . " ";
}
?>
// ABC BCDEF XYZ

如果今天是 associative 的 array 呢
可以從結果看到,他 asort 這邊根據的是你的 value 值去做排序,而不是 key 值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$age = array("P" => "35", "B" => "37", "J" => "43");
asort($age);

foreach($age as $i => $i_value){
echo $i . " " . $i_value;
echo "\n";
}
?>
/*
P 35
B 37
J 43
*/

如果我們今天想要根據 key 值去做排序的話,可以用 ksort()

1
2
3
4
5
6
7
8
9
<?php
$age = array("P" => "35", "B" => "37", "J" => "43");
ksort($age);

foreach($age as $i => $i_value){
echo $i . " " . $i_value;
echo "\n";
}
?>

Multidimensional

關於多維陣列,PHP 的多維陣列有點像俄羅斯套娃,一層包一層那樣寫
那像如果我們今天要寫一個簡單的二維陣列 data[2][3],row 有兩層、column 有三層,那我們可以這樣寫
為了方便自己看的話,我們可以把他換行對齊

1
2
3
$data = array( array("LAVI", 160, 449),
array("TsaiTing", 165, 217),
);

Functions

PHP 的 function 寫法,是開頭先寫 function,放一個空白之後後面接你自己取名的 function name,再接一個括弧,這個括弧裡面看你裡面有沒有要放參數

=== 是除了本來值相同的等於外,參數型態也要相同,多了一個這樣的條件
function name 是 not case-sensitive,大小寫都可以 call 這個 function

1
2
3
4
5
6
7
8
9
10
11
12
13
function NISRA($arg){
if($arg === "name"){
echo "Network and Information Security Research Association\n";
}
elseif($arg === "year"){
return "since 2007\n";
}
}

echo nisra("name");
echo NISRA("year");
// Network and Information Security Research Association
// since 2007

function sum

假設我們今天寫一個 function sum,他有傳入兩個參數 x 和 y,我們可以對這兩個參數都寫入初始賦值
他有點像 constructor,今天如果你 call 了他但內部沒有放入任何參數的話
他就會用你原本賦的值,否則,如果你有放入參數的話,他就會用你放的參數的值
如果你放入的參數的值的量不是你全部要求的傳入參數的量的話,沒被傳入的參數,他也會用你初始賦的值

1
2
3
4
5
6
7
8
9
10
11
12
function sum(int $x = 1, int $y = 2) {
return $x + $y . "\n";
}

echo sum(2, 3);
echo sum(4);
echo sum();
/*
5
6
3
*/

我們正常的傳入 sum(2, 3),那他輸出的結果會是 5,也就是他用的是我們傳入的 2 3,不是 function 上賦值的 1 2

如果我們只傳入 sum(4),一個參數,那他輸出的結果是 6,也就是他 x = 4,是你傳入的參數,但 y = 2,是他初始賦的值

你傳入一個甚麼參數都沒有的 sum(),最後輸出結果是 3,就是你初始賦的值 x = 1 和 y = 2

1
2
echo sum(2, "3");
// 5

如果傳入的參數不是 int 呢 ? 如果傳入 sum(2, "3"),後面放字串,那我們會發現輸出值一樣是 5,因為 PHP 又幫你自動做型態轉換了

如果不想要他做型態轉換可以在最前面加上

1
declare(strict_types=1);

passing arguments by reference

一般 PHP 裡我們都是用 passed by value
但我們這邊來寫寫看 passing arguments by reference,用位址來傳參數

1
2
3
4
5
6
7
8
9
function add(&$value) {
$value++;
}

$num = 1;
add($num);
echo $num;

// 2

這邊我們寫一個 function add,傳入參數的地方寫 &$value,對 value 取址,PHP 的取址一樣是用 & 符號
那我們這邊設立一個另一個參數 num 並賦予初始值 1,再來把它傳入 function add 中,而此時 value 是 num 的位址的值
那我們對 value 做加加,也就是對 num 的位址的值做加加,那就是對 1 做加加
所以我們最後輸出 num,輸出值是 2

Syntax 0x2

Comparison

在 PHP 裡面,我們有很多的比較運算元,也有分嚴格比較和寬鬆比較
這也是 PHP 弱型別的體現
像是 ==!= 這些都是寬鬆的比較,它只比較兩者的值,不會比較型態是否相同,PHP 會自動幫你轉換型態
那像 =! 是嚴格的比較,它不只比較值,也會比較型態

Loose Comparison

在寬鬆的比較下,也就是只有兩個等於的時候,
我們可以寫比較式來驗證看看它的 true or false 的狀態
這邊大家可以用 var_dump() 可以把 true or false 的變數資訊顯示出來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
var_dump(0 == "nisra");
var_dump(1 == "01");
var_dump("1" == "01");

/*
在 8.0 版本
bool(false)
bool(true)
bool(true)

在 7.0 版本
bool(true)
bool(true)
bool(true)
*/
?>

可以看到結果是 false、true、true,第二和第三個之所以也都是 true,是因為像這類的 Numerical String,數字字串,在比較時都會被轉成數字
但是第一串 0 == “nisra” 現在在的 PHP 8 下,會被驗證成是 false,可是在,因為 PHP 8 之後的 Numerical String 不會被亂轉,但是在版本 8 以前會被轉,因而產生非預期的結果

Strict Comparison

那我們現在把它轉成嚴格比較,===,那可以發現結果變成都是 false 了,因為它現在要求你型態也要一致
所以只有像這樣,比較 “01” === “01”,型態是字串跟字串內容皆相同時才會回傳 true

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
var_dump(0 === "nisra");
var_dump(1 === "01");
var_dump("1" === "01");
/*
bool(false)
bool(false)
bool(false)
*/

var_dump("01" === "01");
// bool(true)
?>

Others

那 PHP 的 library 裡面還有很多比較運算元,譬如說 <> 就像是 !=,當你今天在將兩邊比較者都換成數字後,如果兩邊不等於,才會回傳 true
像剛剛的例子修改過後,會發現它反而變成 true、false、false 這樣

1
2
3
4
5
6
7
8
9
10
11
<?php
var_dump(0 <> "nisra");
var_dump(1 <> "01");
var_dump("1" <> "01");

/*
bool(true)
bool(false)
bool(false)
*/
?>

Superglobals $GLOBALS

PHP 很多的 Super global variables,也就是那種很像全域變數之類的觀念,它讓你在你成是範圍中的無論何處,全部的範圍內都可以被讀取
他們是一種變數,這些字是保留字
那像 $GLOBALS,它的用途是讓你可以使用無論宣告在哪裡的變數,而被它使用過的變數也可以在無論何處都可以被使用
像這樣,我們可以隨便先設立兩個變數在隨便一個地方,然後我們來寫 function 用這兩個變數但不傳入
那此時我們就要用 $GLOBAL 來抓這兩個值,不然一般你是不能用 function 內沒有,你也沒傳入的變數
那寫法是在 $GLOBAL[] 的中括號內用單引號括起你要抓的變數名稱 'x'
而我們可以發現它輸出的 $z 結果是 100,也就是說我們在 function 裡把它變 global 後,在 function 外也可以 call 這個變數了

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$x = 75;
$y = 25;

function addition() {
$GLOBALS['z'] = $GLOBALS['x'] + $GLOBALS['y'];
}

addition();
echo $z;
?>
// 100

Superglobals $_SERVER

$_SERVER 這個指令它會儲存這個 request 或是這個 server 存存在的一些資訊
譬如說 PHP_SELF 會回傳給你目前腳本的檔名
SERVER_NAME 會回傳給你主機名
SCRIPT_NAME 會回傳目前腳本的路徑

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

echo $_SERVER['PHP_SELF'] . "\n";
echo $_SERVER['SERVER_NAME'] . "\n";
echo $_SERVER['SCRIPT_NAME'] . "\n";

/*
/parser/temp/[...]

/parser/temp/[...]
*/

?>

Superglobals $_GET & $_POST

在網路傳資料常見的有兩種模式
第一種是 get,第二種是 post

$_GET

1
http://example.com/?user=nisra

今天如果你在你的網址上看見 ? 後面跟著甚麼等於甚麼 and 甚麼等於甚麼,那些就是你所謂的 get 變數
但如果今天它甚麼都沒有,但它還是傳了資料呢,那些資料就會被以 post 的方式傳出去
一個 http 封包,他一開始會告訴你它是用甚麼方式傳資料,像我們這邊可以看到這邊有個 /? 後面接 user=nisra,那它這整串就是一個 get 變數
那這邊的意義是你檔案的路徑,你前面可能會先有一個甚麼甚麼 .com,像這邊舉例是 example.com,後面才會接這串變數
那 get 也因為是直接把要傳遞的資訊都直接寫給你看,如此一來它可傳輸的資料量會受限,因為 URL 所能接受的長度是有限的
而且此種方法較不安全,因為我們可以直接從 URL 中看見 get 傳送的變數

$_POST

1
2
3
4
5
6
<?php

$name = $_GET['user'];
echo 'Hello, ' . $name;

?>

那如果我們今天用 post 傳的時候,它甚麼都不會寫,沒有 /? 甚麼甚麼,就只有一個斜線而已 /
因此,我們會說,使用 post 傳輸資料比用 get 更安全一點
POST 把要傳送的資料包起來放在內部,並不是直接露骨的把想幹嘛都寫在 URL 上
所以可以過濾掉那些不會使用封包、不知道怎麼攔截封包的人

$_GET & $_POST

總結來說
get 就像是寄名信片一樣,會把所有的資料都寫在 URL 上,不僅隱密性不夠而且傳輸的資料有限 (受限於 URL 所能接受的長度),安全性較低
post 則是像寄信或寄包裹一樣,將要傳送的地址寫在 URL 上,裡面再包資料或檔案,內容大小比 get 所能接受的大小大得多,也因為資料不會直接寫在 URL 上,就像你可以看見這裡有個包裹,但你看不到包裹裡面有甚麼,所以相對 get 比較起來安全一點
但是 get 它有它的方便性在

Protocols & Wrappers

PHP Supported

這邊要談的是 PHP 的偽協議
PHP 在處理協議的地方有很多種

  • file:// - Accessing local filesystem
  • http:// - Accessing HTTP(s) URLs
  • php:// - Accessing various I/O streams
  • phar:// - PHP Archive
  • 這些協議如果使用不當的話會造成
    • Local File Include,這個漏洞會造成這台伺服器本地端的檔案被你的 php 讀取,包括你一些重要的東西
    • Remote File Include,遠端東東西被 php 檔讀取
    • Remote Code Excution,它會讓遠端的使用者可以對你的主機進行指令操作,這些是很危險的嚴重漏洞

file://

那關於 file 這個協議
file 後面一定會跟一個路徑,那這個 file 它可以讀取你所有在 web root 上的檔案
那我們可以透過 file 來將那個檔案包含進來,然後顯示在網頁上
那顯示在網頁上會造成你可以隨意地看它主機內有甚麼檔案的危險性
那這邊大家回到剛剛的 index.php
我們來試試看讀取回傳檔案
我們這邊寫到 $_GET['path'],我們等等要用 get 取某個檔案的絕對路徑
那接著我們 echo file_get_contentsfile_get_contents吃檔名,接著會把檔案內容轉換成字串型態,最後回傳,所以回傳的東西可能不見得很漂亮

1
2
3
4
<?php
$p = $_GET['path'];
echo file_get_contents($p);
?>

php://

php 協議通常被攻擊者利用在要取得 PHP 原始碼的時候使用
因為 PHP 語法會被 web server 解析至前端時隱藏起來
那這個協議的編碼特性,可以將讀取到的原始碼經過編碼後輸出,而做到繞過的手段
那 php 這個協議中,他的寫法是先 php://,這邊一樣,他也自帶兩條斜線
這邊 <filter> 跟 parameters 都是可以做替換的
filter 可以換,變成 php://inputphp://filter 等等
parameters 可以是 read,它代表的意思是以甚麼方式讀取,它可以等於 base64、string 等等看你需求的方式去做調整,它可有可無,如果需要很多個中間可以用 | (或)隔開,就是在你 enter 上面的那個直線
另外可以寫 resource,代表你要讀取的檔案,它是必備的

Reference