PHP 學習筆記

# 前言

這是我的 PHP 學習筆記, 學什麼記什麼!




# 安裝 extension

自從 2018 年的 4 月, Homebrew 已經不再是 PHP 在 macOS 上的套件管理器, 所以所有的 PHP Extension 之後都應該使用 pecl 來安裝

# 第一步 - 刪除可能衝突的套件

brew rm php php@5.6 php@7.0 php@7.1
brew rm imagemagick


# 第二步 - 更新 Xcode command line 工具, 以及取得 build 套件

  • 確定已經安裝 Xcode command line 工具

    xcode-select --install
  • 到 AppStore 去更新 Xcode 以及套件

  • 安裝 homebrew building 工具

    brew install pkg-config


# 第三步 - 安裝 ImageMagick

brew install imagemagick

為什麼上面說了 brew 已不支援安裝 extension, 而這邊又使用 brew 來安裝呢? ImageMagick 是一個開源套件, 用 C 寫的, 跟 PHP 沒關係。
而下面安裝的 Imagick 才是 PHP 的 extension, 算是 PHP 跟 ImageMagick 之間的 binding


# 第四步 - 使用 Homebrew 安裝 PHP

brew install php --build-from-source

--build-from-source 會安裝 PHP-FPM


# 判別 Apache based PHP 或 Homebrew based PHP

type php
  • /usr/local/...anything.../php 表示你執行的為 homebrew based 的 PHP
  • /usr/bin/php 表示你執行的為 Apache based PHP


# 第五步 - 安裝 Imagick

pecl install imagick


# 記得重啟你的 Web server

  • 使用 Apache 記得重啟 Apache
  • 使用 Nginx 記得重啟 Nginx
  • 若是使用 valet, macOS 開發, 記得重啟 valet




# 疑難雜症篇

# 環境為 macOS, 使用 pecl 安裝 extension 時遇到以下的錯誤

Warning: mkdir(): File exists in System.php on line 294
PHP Warning: mkdir(): File exists in /usr/local/Cellar/php/7.3.3/share/php/pear/System.php on line 294

Warning: mkdir(): File exists in /usr/local/Cellar/php/7.3.3/share/php/pear/System.php on line 294
ERROR: failed to mkdir /usr/local/Cellar/php/7.3.3/pecl/20180731

看起來該是無法在該位置建立資料夾, 那就手動建立一個吧

  • 首先, 取得 pecl 的 extension 資料夾位址, 並複製

    pecl config-get ext_dir|pbcopy
  • 建立該資料夾

    mkdir -p copiedValueFromLastCommand
  • 這樣一來, 應該就解決了


# PHP 無法找到模組, 我的心慌慌

  • 先找出 pecl 將模組放在哪

    pecl config-get ext_dir|pbcopy
  • 打開目前被使用的 php.ini

    vim "$(php-config --ini-path)/php.ini"
  • 打開設定檔

    ; Directory in which the loadable extensions (modules) reside.
    ; http://php.net/extension-dir
  • 加入上面得到的模組位置

    extension_dir = "/usr/local/lib/php/pecl/XXXXXX"
  • 上面的做法適用於 homebrew 安裝的 PHP


# 優化

# opcache

; 是否啟用 opcache, 設為 0 時為不啟用
opcache.enable = 1

; PHP 7.4, 預設為 128 MB, 可視你的需求調整大小。
; 可使用 `opcachegetstatus()` function 取得實際上
; 使用了多少 opcache memory, 如果已經很接近上限了, 就可以調整上限
opcache.memory_consumption = 256

; PHP 使用一種名為 "字串駐留" (string interning) 的技術以增進效能
; 比方說, 你在單次的 request 中使用了字串 "foo" 500 次
; PHP 會在第一次使用時將這個字串存為一個不變的變數, 而之後的 499 次使用
; 只會使用一個 pointer 來指向這個變數。 原本這個變數只可被單個 php-fpm process 使用
; 但 opcache 允許將這個變數放到一個共享的區域, 可被所有的 php-fpm 存取
; 而這個選項可以設定這個共享區域的允許大小
; PHP 7.4 預設為 8 MB
opcache.interned_strings_buffer = 16

; 這個選項限制 opcache 可以緩存多少個 PHP 文件
; 這個選項必須設定大於你的 project 中的 PHP 文件總數
; 可只用 command "find yourProjectDirectory -type f -print | grep php | wc -l"
; 來計算你的 project 中 PHP 文件的數量
; 可參考官方文件 "https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.max-accelerated-files"
opcache.max_accelerated_files = 7963

; 如果啟用, opcache 會根據下面的另外一個選項 opcache.revalidate_freg 設置的秒數去檢查
; 文件的 timestamp, 目的為確認文件是否有更新, 若有更新, 則清除舊的並且重新緩存
; 在正式環境建議設為 0, 開發 / 測試環境設為 1
opcache.validate_timestamps = 1

; 這個選項為, opcache 該多久檢查你的程式碼是否有改變, 如果有改變, PHP 會重新編譯, 生成新的 opcode
; 並且緩存。 若設為 0 時, 表示每次 request 都會檢查, 這會大量使用 stat system call, 並且
; 在檢查過程中, 為了確認位於硬碟中文件的 timestamp, process 也會進到 wating state 等待 I/O
; 這些 system call 的調用, process state 的切換都會消耗一些時間以及系統的資源
; 建議開發環境設為 0, 正式環境中, 因為 opcache.validate_timestamps 會設為 0, 所以這個選項就
; 不會生效, 自然也就不用管它了
opcache.revalidate_freg = 5

; 這個選項若啟用, 會在每個 request 的最後更快速地釋放資源, 讓 response 以及 workers
; 再次調用的速度加快, 1 啟用, 0 不啟用
opcache.fast_shutdown = 1

; 這個選項會 cache comment, 建議開啟, 因為有些 Library 會使用到 comment
; 若關閉可能可以節省一點點 RAM, 開啟利大於弊
opcache.save_comments=1


# PHP.ini

; 設定單個 PHP process 可以使用的系統記憶體最大值, 默認 128M, 若是單主機單 PHP process
; 的話可以依據主機等級設置高一點, 因為可使用的系統記憶體越多, 代表 PHP-FPM 的 process 可以
; 負擔越多的。 但若是透過容器化啟用, 像是 k8s 架構, 那可以設小一點
; 可使用 top | grep php 查看 PHP 每個 process 使用的記憶體量
; 也可使用 memory_get-peak_usage() function 取得使用記憶體
; 假如分配了 512M, 每個 PHP-FPM 使用了 10M 記憶體, 那相當於可以負擔 51 個 PHP-FPM process
memory_limit = 512M

; 允許上傳文件
file_uploads = 1

; 允許的文件大小上限
upload_max_filesize = 10M

; 允許的最大上傳文件數量
max_file_uploads = 3

; 以上設定, 允許上傳, 最多 1 次的 request 中上傳 3 個文件, 每個文件最大 10MB
; 若要允許大的文件上傳, 在 Nginx 中的 client_max_body_size 也要修改

; 單個 PHP-FPM 可允許執行的時間, 默認 30 秒, 建議改成 5 秒, 5 秒在一個 request 來說算是很久了
max_execution_time = 5




# 參考文章

stackoverflow
Make your Laravel App Fly with PHP OPcache
PHP 及 Laravel 上線優化
PHP-Late Static Bindings
CSDN lamp_yang_3533 的部落格
程式狂想筆記




# Questions and Answers

PHP-FPM 中, 如果要從 `LISTEN PORT 改成 LISTEN SOCKET, 該修改哪個檔案?

pool 的 config 檔

以下的 PHP terminal command 的意思是?
  • Example:

    php -S IP:Port -t Directory
  • Answer:
    使用 PHP 內建 Web server

以下的 PHP example code 的輸出為?
  • Example:

    <?php
    class A {
    public static function get_self() {
    return new self();
    }

    public static function get_static() {
    return new static();
    }
    }

    class B extends A {}

    echo get_class(B::get_self()); // ?
    echo get_class(B::get_static()); // ?
    echo get_class(A::get_self()); // ?
    echo get_class(A::get_static()); // ?
  • Answer:

    <?php
    echo get_class(B::get_self()); // A
    echo get_class(B::get_static()); // B
    echo get_class(A::get_self()); // A
    echo get_class(A::get_static()); // A
    // 總結, self 表示 method 所在的 class, static 表示呼叫的 class 本身
SplStack 是什麼?

Stand PHP Library 已實作完成的 stack structure class

SplLinkedList 是什麼?

Stand PHP Library 已實作完成的 linked list structure class

以下的 PECL terminal command 的意思是?
  • Example:

    pecl config-get php_ini
  • Answer:
    使用 pecl 取得 php.ini 位置, 當然, 你的 PHP 必須是要使用 PECL 安裝的

以下的 Brew terminal command 的意思是?
  • Example:

    brew info php
  • Answer:
    可取得 php.ini 位置, 當然, PHP 需要是使用 brew 安裝

以下的 PHP terminal command 的意思是?
  • Example:

    php-config --ini-path
  • Answer:
    可取得 php.ini 位置, PHP 版本為 7.4.9

以下的 PECL terminal command 的意思是?
  • Example:

    pecl config-get ext_dir
  • Answer:
    取得 PECL 模組安裝的位置

以下的 PHP terminal command 的意思是?
  • Example:

    php -m
  • Answer:
    查詢目前已載入哪些模組

Brew 安裝的 PHP 是如何使用 PECL 安裝的 module?

當使用 brew 安裝 PHP 時, 其 php.ini 文件內 extention_dir 參數可以指定 extension 資料夾路徑, 因為是使用 PECL 安裝的, 在 Mac 上通常路徑是 /usr/local/lib/php/pecl/<日期版本號>
也就是說, 如果你用 brew 重新安裝了 PHP, 那新安裝的 PHP 的 extension_dir 會指向新的日期版本, 所以務必要更新 extension_dir 的指向, 最好是使用 PECL uninstall packageName, 然後在重新安裝 extension pecl install packageName, 最後再將舊的 extension_dir 移除 rm -rf oldExtensionDir

什麼是 Zend Engine?

為一虛擬機, PHP 的核心

什麼是 Opcode?

Zend Engine 解析 PHP code 之後得到的執行碼, 可直接被 Zend 虛擬機執行

PECL 全寫是?

PHP Extension Community Library

PEAR 全寫是?

PHO Extension and Application Repository

以下的 PHP example code 回傳的值是?
  • Example:

    <?php
    function test()
    {
    static $var;
    $var += 1;
    echo $var . PHP_EOL;
    }


    function testtest()
    {
    static $var;
    $var += 1;
    echo $var . PHP_EOL;
    }

    // 以下的輸出是?
    test();
    test();
    test();
    testtest();
    testtest();
    testtest();
    ?>
  • Answer:

    <?php
    // static variable 在不同的 function scope 內會累加
    test(); // 1
    test(); // 2
    test(); // 3
    testtest(); // 1
    testtest(); // 2
    testtest(); // 3
以下的 PHP example code 回傳的值是?
  • Example:

    <?php
    class A
    {
    static $var;

    public static function test()
    {
    self::$var += 1;
    echo self::$var . PHP_EOL;
    }


    public static function testtest()
    {
    self::$var += 1;
    echo self::$var . PHP_EOL;
    }

    }

    A::test(); // 輸出是?
    A::test(); // 輸出是?
    A::test(); // 輸出是?
    A::testtest(); // 輸出是?
    A::testtest(); // 輸出是?
    A::testtest(); // 輸出是?
    ?>
  • Answer:

    <?php
    class A
    {
    static $var;

    public static function test()
    {
    self::$var += 1;
    echo self::$var . PHP_EOL;
    }


    public static function testtest()
    {
    self::$var += 1;
    echo self::$var . PHP_EOL;
    }

    }

    A::test(); // 1
    A::test(); // 2
    A::test(); // 3
    A::testtest(); // 4
    A::testtest(); // 5
    A::testtest(); // 6
    ?>
以下的 PHP example code 回傳的值是?
  • Example:

    <?php
    class A {

    protected $name = 'A';
    static $alias = 'a';
    const HASH = 'md5';

    public function dd() {
    echo $this->name; echo '--';
    echo static::$alias; echo '--';
    echo static::HASH; echo '--';
    echo self::$alias; echo '--';
    echo self::HASH; echo '--';

    var_dump(new self); echo '--';
    var_dump($this); echo '--';
    var_dump(new static); echo '<br>';
    }
    }

    class B extends A {
    protected $name = 'B';
    static $alias = 'b';
    const HASH = 'sha1';
    }

    (new A)->dd();
    (new B)->dd();
  • Answer:

    <?php
    // $this->name = A, $this 代表當前 instance, 而當前 instance 為
    // class A 的 instance, 故為 class A 中的 protected variable

    // static::$alias = a, static 為 forwarding call, static 的 forwarding call
    // 下面會詳述。 呼叫的 instance 的 class 為 class A, 所以取 class A 中定義的 static variable

    // static::HASH = md5, 原理同上

    // self::$alias = a, self 表示當前 method 歸屬的 class, 此處為 class A,
    // 因此使用 class A 中定義的 static variable

    // self::HASH = md5, 原理同上

    // new self = class A, 如同上面提到的, 當前 method 所在的 class 為 class A

    // $this = class A, 原理同上, $this 為 class A 的 instance, 所以是 class A

    // new static = 同上, 原始被呼叫的 class 為 class A, 所以為 class A
    (new A)->dd();
    // 輸出為: A--a--md5--a--md5--object(A)#2 (1) { ["name":protected]=> string(1) "A" }
    // --object(A)#1 (1) { ["name":protected]=> string(1) "A" }
    // --object(A)#2 (1) { ["name":protected]=> string(1) "A" }

    // $this->name = B, $this 代表當前 instance, 也就是 class B

    // static::$alias = b, static 為 forwarding call, static 的 forwarding call 下面會詳述。
    // 呼叫的 instance 的 class 為 class B, 所以取 class B 中定義的 static variable

    // static::HASH = sha1, 原理同上

    // self::$alias = a, self 表示當前 method 歸屬的 class, 因 class B 使用的 test() method 是
    // 繼承自 class A 的, 實際上 self 為 class A, 所以使用定義在 class A 的 alias

    // self::HASH = md5, 原理同上

    // new self = class A, 如同上面提到的, 當前 method 定義於 class A

    // $this = class B, 原理同上, $this 為 class B 的 instance, 所以是 class B

    // new static = 同上, 原始被呼叫的 class 為 class B, 所以為 class B

    (new B)->dd();
    // 輸出為: B--b--sha1--a--md5--object(A)#2 (1) { ["name":protected]=> string(1) "A" }
    // --object(B)#1 (1) { ["name":protected]=> string(1) "B" }
    // --object(B)#2 (1) { ["name":protected]=> string(1) "B" }
以下的 PHP example code 回傳的值是?
  • Example:

    <?php
    class A {
    public static function who() {
    echo __CLASS__;
    }
    public static function test() {
    self::who();
    }
    }

    class B extends A {
    public static function who() {
    echo __CLASS__;
    }
    }

    B::test(); // 輸出是?
    ?>
  • Answer:

    <?php
    // 輸出為 A
    // 因為 B 本身沒有 test(), 所以調用 A 的 test(), 而 test() 中的
    // self 代表 test() 歸屬的 class, 即 A, 所以會調用 A 的 who()
以下的 PHP example code 回傳的值是?
  • Example:

    <?php
    class A {
    public static function who() {
    echo __CLASS__;
    }
    public static function test() {
    static::who();
    }
    }

    class B extends A {
    public static function who() {
    echo __CLASS__;
    }
    }

    B::test(); // 輸出是?
    ?>
  • Answer:

    <?php
    // 輸出為 B
    // static 代表著上一個調用 non-forwarding call 的 class, 即 B
    // 下面會針對 forwarding call 以及 non-forwarding call 詳述
以下的 PHP example code 回傳的值是?
  • Example:

    <?php
    class A {
    public static function foo() {
    static::who();
    }

    public static function who() {
    echo __CLASS__."\n";
    }
    }

    class B extends A {
    public static function test() {
    A::foo();
    parent::foo();
    self::foo();
    }

    public static function who() {
    echo __CLASS__."\n";
    }
    }
    class C extends B {
    public static function who() {
    echo __CLASS__."\n";
    }
    }

    C::test();
    ?>
  • Answer:

    <?php
    class A {
    public static function foo() {
    // who() 為 echo called class name, 因此 static 代表什麼, 決定著輸出
    static::who();
    }

    public static function who() {
    echo __CLASS__."\n";
    }
    }

    class B extends A {
    public static function test() {
    // 上面有提到 A:: 算 non-forwarding call, 所以假如後面有呼叫 static:: 的話, static 就會代表 A
    A::foo();
    // 上面有提到, parent:: 算 forwarding call, 而上一個 non-forwarding call
    // 為 C::test(), 因此如果後面有使用 static::, static 表示 C
    parent::foo();
    // 同上
    self::foo();
    }

    public static function who() {
    echo __CLASS__."\n";
    }
    }
    class C extends B {
    public static function who() {
    echo __CLASS__."\n";
    }
    }

    C::test();
    // A C C
    // A::foo() 輸出 A, 如上所述, A:: 為 non-forwarding call,
    // 因此 static::who() 這邊的 static 代表 A

    // parent::foo() 輸出 C, 如上所述, parent:: 為 forwarding call,
    // 因此 foo() 中的 static 還是代表上一個 non-forwarding call, 即 C

    // self::foo() 輸出為 C, 同上
    ?>
PHP 中, forwarding call 與 non-forwarding call 分別是?

forwarding call:

  • parent::
  • self::
  • static::
  • forward_static_call()

non-forwarding call:
A::test()

PHP 中, self 跟 static 的差異是?
  • self 代表當前 method 所歸屬的 class
  • static 代表上一個 non-forwarding 調用的 class
PHP 中, 在 non-static context 環境中, static 的調用順序是?

它指向的 class 中找尋 private method ==> public method ==> 調用 static 的 method 所在的 class 中尋找 private method ==> public method, 如果存在就調用, 並停止找尋。

PHP 中, 在 non-static context 環境中, $this 的調用順序是?

調用 $this 的 method 所在的 class 中尋找 private method ==> 它指向的 instance 的 class 中尋找 private method ==> 然後是 public method ==> 調用 $this 的 method 所在的 class 中尋找 public method, 只要找到就調用, 並停止找尋

以下的 PHP example code 的輸出是?
  • Example

    <?php
    class A {
    private function foo() {
    echo __class__ . PHP_EOL;
    }
    public function test() {
    $this->foo();
    static::foo();
    }
    }

    class B extends A {
    }

    class C extends A {
    private function foo() {
    }
    }

    $b = new B(); // 輸出是?
    $b->test(); // 輸出是?
    $c = new C(); // 輸出是?
    $c->test(); // 輸出是?
    ?>
  • Answer:

    <?php
    class A {
    private function foo() {
    echo __class__ . PHP_EOL;
    }
    public function test() {
    $this->foo();
    static::foo();
    }
    }

    class B extends A {
    // foo() 是從 A copy 過來 B 的, 因此它的 scope 依然是 A
    }

    class C extends A {
    private function foo() {
    // 這個 foo() 覆蓋了 class A 的 foo(), 所以新的 scope 為 C
    }
    }

    $b = new B();

    // $this->foo() 輸出為 A, 如上所述, class B 本身沒有 test(), 所以會調用 class A 的
    // 而 $this 會先到 "調用 $this->foo()" 的 method, 即 test(), 所在的 class, 即 class A,
    // 中尋找 private method, 你可以試試在 class B 中新增一個一模一樣的 private method foo(),
    // 結果還是會先調用 class A 的 foo()

    // static::foo() 輸出為 A, 同上, 但 class B 並沒有 foo(), 所以調用了 class A 的 foo()
    // 可以試試在 class B 新增一個 private method foo(), 這樣會報錯, 因為 static 會先從實際調用
    // class, 即 class B, 尋找 private method, 這樣就變成從 class A 去呼叫 class B 的 private
    // method, private method 只可從所屬的 class 中才可調用, 當然, 如果改成 public method,
    // 那輸出就會變成 B
    $b->test();

    $c = new C();

    // $this->foo() 輸出是 A, 這點同上, 不加贅述
    // static::foo() 會 fail, 因為 static 會到實際調用的 class 中尋找 private method, 而
    // class C 中確實定義了 private method foo(), 這樣就變成了從 class A 的 scope 去調用
    // class C 的 private method, 所以錯了
    // 可以試試把 class C 中的 foo() 改成 public, 且輸入跟 class A foo() 一樣的內容
    // 這樣輸出就會變成 C 了
    $c->test();
    ?>
Laravel 學習筆記 Kubernetes 學習筆記

留言

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×