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 example 中, person2 的 output 是? 如何不 reference 同一個 amount object?
  • Example:
    <?php
    class Person
    {
        private $name;
        private $age;
        private $id;
        public  $account;
        public function __construct(string $name, int $age, Account $account)
        {
            $this->name = $name;
            $this->age  = $age;
            $this->account = $account;
        }
        public function setId(int $id)
        {
            $this->id = $id;
        }
        public function __clone()
        {
            $this->id   = 0;
        }
    }

    $person = new Person("bob", 44, new Account(200));
    $person->setId(343);
    $person2 = clone $person;
    // give $person some money
    $person->account->balance += 10;
    // $person2 sees the credit too
    print $person2->account->balance;
  • Answer:
    210
    function __clone()
      {
          $this->id   = 0;
          $this->account = clone $this->account;
      }
以下的 Laravel example code 的意思是?
  • Example:
    <?php
    class Person
    {
        private $writer;
        public function __construct(PersonWriter $writer)
        {
            $this->writer = $writer;
        }
        public function __call(string $method, array $args)
        {
            if (method_exists($this->writer, $method)) {
                return $this->writer->$method($this);
            }
        }
    }

    $person = new Person(new PersonWriter());
    $person->writeName();
  • Answer:
    使用 __call delegation, 直接使用 PersonWriter 中的 method
PHP 中, __callStatic($method, $arg_array) method 觸發的時機是?

當嘗試呼叫一個 undefined static method

PHP 中, __call($method, $arg_array) method 觸發的時機是?

當嘗試呼叫一個 undefined non-static method

PHP 中, __isset($property) method 觸發的時機是?

當嘗試使用 isset() 在一個 undefined property 時

PHP 中, __unset($property) method 觸發的時機是?

當嘗試使用 unset() 在一個 undefined property 時

PHP 中, __set($property, $value) method 觸發的時機是?

當嘗試 assign value 到一個 undefined property 時

PHP 中, __get($property) method 觸發的時機是?

當嘗試存取未定義的 property 時

以下的 PHP example 的意思是?
  • Example:
    <?php
    class IllegalCheckout extends Checkout
    {
        final public function totalize()
        {
            // change bill calculation
        }
    }
    // PHP Fatal error:  Cannot override final method popp\ch04\batch14\Checkout::totalize() ...
  • Answer:
    final method 無法被覆寫
以下的 PHP example 的意思是?
  • Example:
    <?php
    final class Checkout
    {
        // ...
    }
  • Answer:
    final class 無法被繼承
以下的 PHP example 的意思是?
  • Example:
    class UtilityService extends Service
    {
        use PriceUtilities {
            PriceUtilities::calculateTax as private;
        }
    }
  • Answer:
    使用 trait 時, 可使用 as operator 來改變該 method 在 UtilityService 中的 access level
    即一般 use 該 trait 可能為 public, 但在 UtilityService 中為 private
以下的 PHP example 的意思是?
  • Example:
    trait PriceUtilities
    {
        function calculateTax(float $price): float
        {
            // better design.. we know getTaxRate() is implemented
            return (($this->getTaxRate() / 100) * $price);
        }
        abstract function getTaxRate(): float;
        // other utilities
    }
  • Answer:
    trait 中可使用 abstruct method
以下的 PHP example 的意思是?
  • Example:
    class UtilityService extends Service
    {
        use PriceUtilities, TaxTools {
            TaxTools::calculateTax insteadof PriceUtilities;
            PriceUtilities::calculateTax as basicTax;
        }
    }
    $u = new UtilityService();
    print $u->calculateTax(100) . "\n";
    print $u->basicTax(100) . "\n";
  • Answer:
    當兩個 trait 有相同的 method 時, 可使用 insteadof 來指定要使用哪一個 method
    然後使用 as 來命名另外一個同名 method, 這樣兩個都可存取ㄕ
以下的 PHP example 的意思是?
  • Example:
    class UtilityService extends Service
    {
        use PriceUtilities, TaxTools {
            TaxTools::calculateTax insteadof PriceUtilities;
        }
    }
    // listing 04.30
    $u = new UtilityService();
    print $u->calculateTax(100) . "\n";
  • Answer:
    當兩個 trait 有相同的 method 時, 可使用 insteadof 來指定要使用哪一個 method
以下的 PHP example 的結果是?
  • Example:
    $a = true ? 0 : true ? 1 : 2;
  • Answer:
    // (true ? 0 : true) ? 1 : 2 = 2
以下的 PHP example 的結果是?
  • Example:
    $a = 3 * 3 % 5;
  • Answer:
    4
以下的 PHP example 中, 輸出分別是?
  • Example:
    <?php
    $a=1;
    $b=1
    $c=++$a;
    $d=$b++;
    echo $c;
    echo $d;
  • Answer:
    $c=2, 取++後的值, $d=1, 取++前的值
PHP 中, 以下的常量分別代表的意思是?
  • Example:
    __LINE__
    __FILE__
    __DIR__
    __FUNCTION__
    __CLASS__
    __TRAIT__
    __METHOD__
    __NAMESPACE__
  • Answer:
    __LINE__: 文件中的當前行號
    __FILE__: 文件完整路徑及文件名, 若被包含在文件中, 則返回被包含的文件名
    __DIR__: 文件所在的目錄。如果用在被包括文件中,則返回被包括的文件所在的目錄。它等同於 dirname(FILE)。除非是根目錄,否則目錄中名不包括末尾的斜杠。
    __FUNCTION__: 當前函數的名稱。匿名函數則為 {closure}
    __CLASS__: 當前 class 的名稱。class 名稱包括其被聲明的作用區域(例如 Foo\Bar)。注意自 PHP 6.4 起 CLASS 對 trait 也起作用。當用在 trait 方法中時,CLASS 是調用 trait method 的 class 的名字。
    __TRAIT__: Trait 的名字。 Trait 名包括其被聲明的作用區域(例如 Foo\Bar)
    __METHOD__: class 的 method 名稱
    __NAMESPACE__: 當前 namespace 的名稱
以下位於 PHP.ini 的 configuration example 的意思是?
  • Example:
    apc.ttl=0
  • Answer:
    讓 apc cache 永不過期, 預設是 3600s (1 小時)
以下的 Laravel example code 的意思是?
  • Example:
    <?php
    class CallableClass
    {
    public function __invoke($x)
    {
    var_dump($x);
    }
    }
    $obj = new CallableClass;
    $obj(5);
    var_dump(is_callable($obj));

    // output
    int(5)
    bool(true)
  • Answer:
    當嘗試以 function 的方式來呼叫一個 object 時, 就會觸發 invoke()
PHP 套件中, mbstring 是什麼?

multi bytes string, PHP 預設是使用 ASCII 編碼, 若要支援 UNICODE, 需要安裝 mbstring 來處理 multi bytes string (多個 bytes 代表一個 character)

以下的 example command 的意思是?
  • Example:
    ps aux | grep php-fpm
  • Answer:
    確認 php-fpm 是否有在運行中
以下的 PHP example code 的意思是?
  • Example:
    <?php
    var_dump(25/7);
    var_dump((int) (25/7));
    var_dump(round(25/7));
  • Answer:
    <?php
    var_dump(25/7); // float(3.5714285714286)
    var_dump((int) (25/7)); // int(3)
    var_dump(round(25/7)); // float(4)
    ?>
以下的 PHP example code 的意思是?
  • Example:
    <?php
    $a = 1234;
    $a = 0123;
    $a = 0x1A;
    $a = 0b11111111;
    $a = 1_234_567;
    ?>
  • Answer:
    <?php
    $a = 1234; // 十进制数
    $a = 0123; // 八进制数 (等于十进制 83)
    $a = 0x1A; // 十六进制数 (等于十进制 26)
    $a = 0b11111111; // 二进制数字 (等于十进制 255)
    $a = 1_234_567; // 整型数值 (PHP 7.4.0 以后)
    ?>
PHP 當中, float 跟 double 一樣嗎

一樣

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

×