2022年12月27日 星期二

PHP 7.3.x 升級到 PHP 8.1.x

 [PHP 7.3 -> PHP 8.1升級步驟]

環境:CentOS 7、已安裝 remi repository 的 PHP 7.3

步驟:(參考 remi repository 官網的說明)

  1. 安裝(更新) EPEL repository 設置
    # yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
  2. 安裝(更新) Remi repository 設置
    # yum install https://rpms.remirepo.net/enterprise/remi-release-7.rpm
    我執行時發生錯誤
    Loaded plugins: fastestmirror
    Cannot open: https://rpms.remirepo.net/enterprise/remi-release-7.rpm. Skipping.
    Error: Nothing to do
    出現不能打開網址的錯誤,但在 windows 上用瀏覽器可以下載該網址檔案
    linux 上用 wget 測試
    # wget  https://rpms.remirepo.net/enterprise/remi-release-7.rpm
    --2022-07-25 01:02:03--  https://rpms.remirepo.net/enterprise/remi-release-7.rpm
    正在查找主機 rpms.remirepo.net (rpms.remirepo.net)... 109.238.14.107, 2a00:c70:1:109:238:14:107:1
    正在連接 rpms.remirepo.net (rpms.remirepo.net)|109.238.14.107|:443... 連上了。
    錯誤: cannot verify rpms.remirepo.net's certificate, issued by ‘/C=US/O=Let's Encrypt/CN=R3’:
      Issued certificate has expired.
    如果不想用安全模式連接 rpms.remirepo.net,請使用 ‘--no-check-certificate’ 選項
    cannot verify rpms.remirepo.net's certificate,應該是linux上的SSL憑證機構資料太舊,不認得發給rpms.remirepo.net憑證的機構
    更新(安裝) ca-certificates
    # yum install ca-certificates
    更新憑證機構資料後,便可以正常執行更新 Remi repository 設置的指令。

  3. 安裝(更新) yum-utils
    # yum install yum-utils
  4. 查看目前開啟(enabled)的 repository,PHP 是舊的 PHP7.3
    # yum repolist
    .....
    repo id             repo name                                                       status
    .....
    remi-php73          Remi's PHP 7.3 RPM repository for Enterprise Linux 7 - x86_64      446
    .....
  5. 查看所有 repository,可以發現多了PHP8.1(更新 Remi repository 設置後,即可看到新的 PHP 8.1)
    # yum repolist all
    .....
    remi-php73    Remi's PHP 7.3 RPM repository for Enterprise Linux 7 - x86_64    enabled:    446
    .....
    remi-php81    Remi's PHP 8.1 RPM repository for Enterprise Linux 7 - x86_64    disabled
    .....
  6. 關閉舊的 remi-php73,開啟 remi-php81
    # yum-config-manager --disable 'remi-php*'
    # yum-config-manager --enable   remi-php81

    查看目前開啟(enabled)的 repository,PHP 變成新的 PHP8.1
    # yum repolist
    ....
    remi-php81          Remi's PHP 8.1 RPM repository for Enterprise Linux 7 - x86_64      317
    .....
  7. 升級所有相關的php套件
    # yum update php\*

    重啟 PHP-FPM
    # systemctl restart php-fpm.service

    查看目前PHP版本
    # php -v
    Mon Jul 25 01:02:03 2022 (3137): Debug Loading blacklist file:  '/etc/php.d/opcache-default.blacklist'
    PHP 8.1.8 (cli) (built: Jul  5 2022 21:55:55) (NTS gcc x86_64)
    Copyright (c) The PHP Group
    Zend Engine v4.1.8, Copyright (c) Zend Technologies
        with Zend OPcache v8.1.8, Copyright (c), by Zend Technologies

    更新完,新裝的套件如下
    # yum list installed | grep php
    gd3php.x86_64                    2.3.3-7.el7.remi                    @remi
    oniguruma5php.x86_64             6.9.8-1.el7.remi                    @remi
    php.x86_64                       8.1.8-1.el7.remi                    @remi-php81
    php-bcmath.x86_64                8.1.8-1.el7.remi                    @remi-php81
    php-cli.x86_64                   8.1.8-1.el7.remi                    @remi-php81
    php-common.x86_64                8.1.8-1.el7.remi                    @remi-php81
    php-fpm.x86_64                   8.1.8-1.el7.remi                    @remi-php81
    php-gd.x86_64                    8.1.8-1.el7.remi                    @remi-php81
    php-mbstring.x86_64              8.1.8-1.el7.remi                    @remi-php81
    php-mysqlnd.x86_64               8.1.8-1.el7.remi                    @remi-php81
    php-opcache.x86_64               8.1.8-1.el7.remi                    @remi-php81
    php-pdo.x86_64                   8.1.8-1.el7.remi                    @remi-php81
    php-pecl-igbinary.x86_64         3.2.7-1.el7.remi.8.1                @remi-php81
    php-pecl-mcrypt.x86_64           1.0.5-1.el7.remi.8.1                @remi-php81
    php-pecl-memcached.x86_64        3.2.0-1.el7.remi.8.1                @remi-php81
    php-pecl-msgpack.x86_64          2.2.0~RC1-4.el7.remi.8.1            @remi-php81
    php-pecl-redis5.x86_64           5.3.7-1.el7.remi.8.1                @remi-php81
    php-soap.x86_64                  8.1.8-1.el7.remi                    @remi-php81
    php-sodium.x86_64                8.1.8-1.el7.remi                    @remi-php81
    php-xml.x86_64                   8.1.8-1.el7.remi                    @remi-php81

    原本舊的套件
    # yum list installed | grep php
    php.x86_64                       7.3.12-1.el7.remi                   @remi-php73
    php-bcmath.x86_64                7.3.12-1.el7.remi                   @remi-php73
    php-cli.x86_64                   7.3.12-1.el7.remi                   @remi-php73
    php-common.x86_64                7.3.12-1.el7.remi                   @remi-php73
    php-fpm.x86_64                   7.3.12-1.el7.remi                   @remi-php73
    php-gd.x86_64                    7.3.12-1.el7.remi                   @remi-php73
    php-json.x86_64                  7.3.12-1.el7.remi                   @remi-php73
    php-mbstring.x86_64              7.3.12-1.el7.remi                   @remi-php73
    php-mysqlnd.x86_64               7.3.12-1.el7.remi                   @remi-php73
    php-opcache.x86_64               7.3.12-1.el7.remi                   @remi-php73
    php-pdo.x86_64                   7.3.12-1.el7.remi                   @remi-php73
    php-pecl-igbinary.x86_64         3.0.1-2.el7.remi.7.3                @remi-php73
    php-pecl-mcrypt.x86_64           1.0.3-1.el7.remi.7.3                @remi-php73
    php-pecl-memcached.x86_64        3.1.4-1.el7.remi.7.3                @remi-php73
    php-pecl-msgpack.x86_64          2.0.3-1.el7.remi.7.3                @remi-php73
    php-pecl-redis4.x86_64           4.3.0-2.el7.remi.7.3                @remi-php73
    php-soap.x86_64                  7.3.12-1.el7.remi                   @remi-php73
    php-xml.x86_64                   7.3.12-1.el7.remi                   @remi-php73
  8. 原本有修改的設定擋會保留沿用,可查看一下跟新版設定檔的差異,通常是不需要修改
    # diff -y /etc/php.ini /etc/php.ini.rpmnew | less
    # diff -y /etc/php.d/10-opcache.ini /etc/php.d/10-opcache.ini.rpmnew | less
    .....



[程式不相容地方修改]
測試時,將 OPcache 關閉,似乎在新版會產生 Deprecated 的寫法,只有第一次產生 OPcache 快取時,才會出現 Deprecated 訊息。
(且由產生Deprecated的語法修改前後,可發現 OPcache 產生的快取檔大小一樣)
  1. 查看各版本間升級差異注意事項
  2. 不向後兼容的變更、廢棄(Deprecated)的功能,需要仔細查看。
    使用到不向後兼容的寫法,會直接出現錯誤,非改不可。
    使用到廢棄功能的寫法,雖然可執行,但會出現 Deprecated 訊息,提示哪個地方的寫法已經廢棄。
    雖然可以隱藏 Deprecated 訊息,但還是建議修改,因為之後某個版本,這些提示 Deprecated 的寫法就會被移除了,Deprecated 可以想成要正式移除前,給的緩衝時間提示。
  3. PHP 7.3 ~ PHP 8.1,不向後兼容的變更、廢棄的功能不少。
    有些難用關鍵字準確搜尋出來檢查。
    以下是我依我平時寫程式習慣、編輯器自動排版格式,將覺得可能採到雷的地方,用正規表達式(regex)搜尋全部程式碼文件,盡量減少須檢查的地方。
    • 槽狀嵌套的三元運算子(Nested ternary operator),須明確使用括號:
      1 ? 2 : 3 ? 4 : 5;   // 7.4.x deprecated、8.0.x 不向後兼容
      (1 ? 2 : 3) ? 4 : 5; // ok
      1 ? 2 : (3 ? 4 : 5); // ok
      我習慣三元運算子,盡量不用槽狀嵌套,若用到也會用括號,但還是不放心,
      所以用 regex 規則「 \? [\S ]+ \? 」 搜尋,找出一行內,出現兩次「 ? 」的地方檢查。
    • implode(string $separator, array $array) 參數中 $separator、$array 不可以互調。
      7.4.x deprecated、8.0.x 不向後兼容。

      看了這點,我才知道原來這兩個參數寫反,可以正常執行,
      印象中,應該沒寫反過,但以前不知道寫反可以正常執行,
      以為參數寫反會出錯,也許自己寫反了也沒發現,安全起見搜尋看看看。
      用 regex 規則「implode\([^'"]」 搜尋,找出「implode(」後面緊接著,不是「"」、也不是「、」的地方。
    • 函數(方法)有預設值的參數,後面若有必要參數,則默認值無效。
      function test($a = [], $b) {} // Before,8.0.x deprecated,8.1.x 不向後兼容(ArgumentCountError)
      function test($a, $b) {}      // After
      
      印象中以前將舊方法增加參數時,應該有導致此情況。 用 regex 規則「function [\S ]+\([\S ]+ = [\S ]+, \$[^=]+,[\S ]+\) \{」 搜尋,
      找出「function」後面,有出現「 = 」,但後面還有參數「 $」,卻沒有「 = 」的地方。
    • 不可以使用大刮號存取陣列。
      $array{"key"}; // 7.4.x deprecated、8.0.x 不向後兼容
      自己都是用中刮號存取陣列,但安全起見還是搜尋看看
      用 regex 規則「\$[\w]+\{」 搜尋,找出變數後面緊接著大刮號「{」的地方

    • 數字(numbers)跟數字字串(numeric strings)、非數字字串(non-numeric strings=>空字串、只有空白的字串也是非數字字串)的「==」比較修改。
      =>PHP8.0後改為
      https://www.php.net/manual/en/types.comparisons.php
      • 數字(numbers)跟數字字串(numeric strings)比較,跟之前一樣,按數字比較。
      • 數字(numbers)跟非數字字串(numeric strings)比較,PHP 8.0.x 後數字先轉成字串,再按字串比較。
      var_dump(0 == "0"); //PHP7;true,PHP8:true(同 "0" == "0")
      var_dump("0" == "0"); //PHP7、PHP8 均為 true
      
      var_dump(0 == "0.0"); //PHP7;true,PHP8:true(同 "0" == "0.0")
      var_dump("0" == "0.0"); //PHP7、PHP8 均為 true
      
      var_dump(0 == "foo"); //PHP7;true,PHP8:false(同 "0" == "foo")
      var_dump("0" == "foo"); //PHP7、PHP8 均為 false
      
      var_dump(0 == ""); //PHP7;true,PHP8:false(同 "0" == "")
      var_dump("0" == ""); //PHP7、PHP8 均為 false
      
      
      var_dump(42 == " 42"); //PHP7;true,PHP8:true(同 "42" == " 42")
      var_dump("42" == " 42"); //PHP7、PHP8 均為 true
      
      var_dump(42 == "42foo"); //PHP7;true,PHP8:false(同 "42" == "42foo")
      var_dump("42" == "42foo"); //PHP7、PHP8 均為 false
      
      var_dump(0 < "0"); //PHP7;false,PHP8:false(同 "0" < "0")
      var_dump("0" < "0"); //PHP7、PHP8 均為 false
      
      var_dump(0 < "0.0"); //PHP7;false,PHP8:false(同 "0" < "0.0")
      var_dump("0" < "0.0"); //PHP7、PHP8 均為 false
      
      var_dump(0 < "foo"); //PHP7;false,PHP8:true(同 "0" < "foo")
      var_dump("0" < "foo"); //PHP7、PHP8 均為 true
      
      var_dump(0 > ""); //PHP7;false,PHP8:true(同 "0" > "")
      var_dump("0" > ""); //PHP7、PHP8 均為 true
      
      var_dump(42 < " 42"); //PHP7;false,PHP8:false(同 "42" < " 42")
      var_dump("42" < " 42"); //PHP7、PHP8 均為 false
      
      var_dump(42 < "42foo"); //PHP7;false,PHP8:true(同 "42" < "42foo")
      var_dump("42" < "42foo"); //PHP7、PHP8 均為 true
      

      這點算是大魔王,就看平時寫程式習慣了。
      還有沒提到的,兩邊都是字串的比較,PHP8 和PHP7 有些也有差異:
      var_dump("  42  " == "42"); //PHP7.3、PHP7.4:false,PHP8.0、PHP8.1:true
      var_dump("  42" == "42"); //PHP7、PHP8 均為 true
      var_dump("42  " == "42"); //PHP7.3、PHP7.4:false,PHP8.0、PHP8.1:true
      var_dump(" 42  " == "   42     "); //PHP7.3、PHP7.4:false,PHP8.0、PHP8.1:true
      var_dump(" " == "  "); //PHP7、PHP8 均為 false
    • PHP 8.1 有兩個 Deprecated 的項目,雖然可執行,但會出現 Deprecated,也找出來修改,避免之後版本移除,變成不相容
      • Passing null to non-nullable parameters of built-in functions
        =>將 null 傳給內建函式中,不能為 null 的參數
        =>例如,preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0)
        preg_match("/abc/", null),之前會默默執行,PHP8.1 會出現 Deprecated 訊息
        =>PHP 7.4新增的Null coalescing assignment operator,在此處很有用
        preg_match("/abc/", $aa ?? "");//當 $aa 是 null 時,將 $aa 改成是空字串
      • Implicit incompatible float to int conversions
        =>帶小數的浮點數,隱含轉換為整數
        =>例如,srand(int $seed = 0, int $mode = MT_RAND_MT19937)
        srand(10.123),之前會默默轉成整數執行,PHP8.1 會出現 Deprecated 訊息
        Implicit conversion from float to int loses precision
    • 嘗試以array方式訪問null、bool、int、float、resource
      例如 $null["key"],將拋出錯誤訊息,不向後兼容,7.4拋出notice、8.0拋出Warning 
      =>在PHP7.3,這些資料型態用陣列方式存取,結果會是null,沒有錯誤訊息。
      PHP7.4、8.0後,用陣列方式存取,結果雖然也是null,但多了notice、Warning訊息,
      所以若變數某些情況可能不會是array,例如可能是false,也要特別留意,才不會出現notice、Warning訊息。
      =>這點無法用關鍵字、正規式搜尋找出來,因通常出現在函式(方法)回應值,成功回傳array、失敗回傳false(null)..的情況
    • MySQL Driver(PHP8.1不向後相容)
      Integers and floats in result sets will now be returned using native PHP types instead of strings when using emulated prepared statements. This matches the behavior of native prepared statements.The previous behaviour can be restored by enabling the PDO::ATTR_STRINGIFY_FETCHES option.
      之前的版本(php-mysqlnd,PHP5.4後預設使用mysqlnd),若 PDO 設定 PDO::ATTR_EMULATE_PREPARES=>true(預設值),將使用 PHP 模擬的 prepare statement,而不是在資料庫做 prepare statement,但 PHP 模擬的行為,一直有一個問題:取資料庫中的 int、float 資料時,會變成字串形式返回。
      PHP 8.1 之後,不管有沒有啟用 PDO::ATTR_EMULATE_PREPARES,都將忠實的返回 int、float,不會再返回成字串。

      如果想維持返回的資料為字串,可以另外設定 PDO::ATTR_STRINGIFY_FETCHES => true
      以我的習慣,可能有影響的地方,大概是這類型的資料:
      "0.12" === $aa;
      "0" === $aa;
      "1" === $aa;
      
      $aa === "0.12";
      $aa === "0";
      $aa === "1";
      
      "0.12" !== $aa;
      "0" !== $aa;
      "1" !== $aa;
      
      $aa !== "0.12";
      $aa !== "0";
      $aa !== "1";
      分別寫4個正規式,搜尋找出檢查
      • [\"\']{1}[\d\.]+[\"\']{1} ===
      • === [\"\']{1}[\d\.]+[\"\']{1}』
      • [\"\']{1}[\d\.]+[\"\']{1} !==
      • !== [\"\']{1}[\d\.]+[\"\']{1}

      PDO::ATTR_EMULATE_PREPARES 說明
          Note: Only available for the OCI, Firebird, and MySQL drivers.
          Whether enable or disable emulation of prepared statements.
          Some drivers do not support prepared statements natively or have limited support for them.
          If set to true PDO will always emulate prepared statements, otherwise PDO will attempt to use native prepared statements.
          In case the driver cannot successfully prepare the current query, PDO will always fall back to emulating the prepared statement.

      PDO::ATTR_STRINGIFY_FETCHES 說明
          Whether to convert numeric values to strings when fetching. Takes a value of type bool: true to enable and false to disable.




參考:


沒有留言:

張貼留言