顯示具有 PHP 標籤的文章。 顯示所有文章
顯示具有 PHP 標籤的文章。 顯示所有文章

2019/01/16

PHP 5.6 new release

官方原本要在 2018/12 中止對 PHP 5.x 的支援,但 2019/01 還是發布了 5.6 的更新,自動內容大致如下:

  • GD library 的 security fix
  • mbstring 的 bug fix
  • Phar 的 security fix
  • xmlrpc 的 security fix
還沒更新上 PHP 7.x 的人記得更新。

2018/10/30

近期撰寫程式碼的量

以前大多用 3rd-party library 來拼拼湊湊出自己要使用的功能,近一年狀況比較特殊,幾乎所有東西都要自幹:
$ cd /project/root/src
$ du -sh
148K    .

一年的 code 就 150 KB,算是自開始寫程式以來的個人記錄了吧。

希望這 150 KB 的程式是好的程式,而不是垃圾。

2018/10/17

PHP 5.6 將在 2018 年底中止維護

目前 PHP 5 最後一個承載維護的版本:5.6,將於今年年底中止維護。也就是不會再有新功能,且就算有錯誤、安全漏洞也不會再繼續修補。

所以建議大家盡快升級手邊的 PHP 專案至 PHP 7。



個人的經驗,可以跳過 7.0 直接上 7.1,畢竟 7.1 主要是新增一些 7.0 還不支援的功能,沒有他太大的異動。7.2 主要是對效能調整,和新增一些語法,要直接跳 7.2 也不會花太多時間改程式。

2018/10/15

PHP server connector 一些 un-documented behaviour

今天是測試 Memcache::connect() 踩到的問題。

先來看看文件上的說明:
Returns TRUE on success or FALSE on failure.
所以我的程式就這樣寫:
$c = new Memcache();
$stat = $c->connect('localhost');

if (false === $stat) {
    echo 'connection failed';
} else {
    echo 'connected to cache server';
}

測試時把 Memcached shutdown 來看看是否會偵測到連線失敗。執行時卻噴了一堆錯誤訊息:
Warning: Memcache::connect(): Can't connect to localhost:11211, Connection refused (111) in /home/u/he5702/tmp/asd.php on line 4

Call Stack:
    0.0001     355600   1. {main}() /home/u/he5702/tmp/asd.php:0
    0.0001     355640   2. Memcache->connect() /home/u/he5702/tmp/asd.php:4

connection failed

文件上面並沒有提到 library 除了回傳 false 以外,還會噴一堆錯誤訊息。萬一這隻是 JSON API 就一定會導致 client parse error。

所以應變方法用「@」來隱藏錯誤訊息:
$stat = @$c->connect('localhost');



寫程式真的很怕遇到 undefine behavior,如果文件上都寫清楚的話,在 dev / staging 上都可以事先檢查、防範。但連文件都沒有寫,就只能等個被廣大的客戶客訴到死 ....。

2018/08/29

PHP SplPriorityQueue .... WTF!

我還以為 PHP SPL 的類別應該實作上應該有比較嚴謹,但 ... 我錯了 QQ

先來看一下 SplPriorityQueue 的官方文件定義:
public void insert ( mixed $value , mixed $priority )
priority 和我預期的不一樣,原本以為只能放數字 (numbers) 但這邊的 priority type hint 卻標示為「mixed」,表示 priority 可以是任何資料型態 ...?


先從最一般的 case 來測試吧:
$q = new SplPriorityQueue();

$q->insert(1, 'A');
$q->insert(2, 'B');
$q->insert(3, 'C');
$q->insert(4, 'D');
$q->insert(5, 'E');

while ($q->count() > 0) {
    echo $q->extract() . PHP_EOL;
}

output:
5
4
3
2
1

看起來會用 ASCII 數值來比較,數值大的優先 dequeue。看起來沒問題。



再來看一下遇到相同 priority 中有多個數值時,到底會發生什麼事:
$q = new SplPriorityQueue();

$q->insert(1, 1);
$q->insert(2, 1);
$q->insert(3, 1);
$q->insert(4, 1);
$q->insert(5, 1);

while ($q->count() > 0) {
    echo $q->extract() . PHP_EOL;
}

output:
1
5
4
3
2

「5, 4, 3, 2」還算正常,但是為什麼「1」會突然跑到最前面!?



最後,priority 真的可以放其他資料型態嗎?為了更清楚的看到執行結果,我這邊改使用 psysh 來操作:
Psy Shell v0.8.18 (PHP 7.1.18 — cli) by Justin Hileman
Unable to check for updates
>>> $q = new SplPriorityQueue();
=> SplPriorityQueue {#201
     heap: [],
   }
>>> $q->insert(1, [1, 2, 3]);
=> true
>>> $q->insert(2, []);
=> true
>>> $q->insert(3, new Exception())
=> true
靠北啊,Exception 也可以當作 priority 使用 ....

>>> while ($q->count() > 0) {
... echo $q->extract() . PHP_EOL;
... }
3
1
2
.... 我不想講了。

總之,若你要使用 SplPriorityQueue,請注意 undefined behavior。


Reference:

2018/07/30

PHP array_keys() 會自動轉換資料型態

給定一個 array:
$list = [
    '200' => 'OK',
    '404' => 'not found',
    '500' => 'internal server error',
];

使用 array_keys() 取得 keys 以後,key 的資料型態若可以被轉為 int 則會被自動轉換:
$keys = array_keys($list);

// array(3) {
//   [0]=>
//   int(200)
//   [1]=>
//   int(404)
//   [2]=>
//   int(500)
// }

文件中可以看到,透過第三個參數「$strict」可以要求保留原始資料型態,但無法避開第二個參數「$search_value」。所以第三個參數基本上是放好看的 (WTF)

如果要處理的資料是比較敏感的,建議在 key 前面加個 prefix,或是取得 keys 以後再手動轉換資料型態。

2018/06/14

PHP 的 Memcache 與 Memcached 函式庫無法共用資料

剛接觸的應該會被命名搞的一頭誤水,畢竟名字相同、不然就是幾乎相同 XD

負責提供記憶體讀寫快取的服務叫做 Memcached,這個服務和 PHP 八竿子打不著關係,就算你不是寫 PHP 也這樣可以使用他。

再來要說的是 PHP 底下的二個函式庫:MemcacheMemcached。這二個函式庫,主要是提供一個方法讓 PHP 連接前面提到的 Memcached 讀寫資料。另外要注意的是 Memcache 和 Memcached 函式庫雖然名字很接近,但卻是二個完全不同的函式庫,無法同時使用,只能二選一。

以下透過 psysh 實際操作,來看一下同時使用會發什麼事情。

使用 Memcache 寫入一筆資料:
>>> $m = new Memcache
=> Memcache {#201}
>>> $m->connect('localhost')
=> true
>>> $m->set('test', ['Hello', 'World'])
=> true
>>> $m->get('test')
=> [
     "Hello",
     "World",
   ]

再來使用 Memcahed 函式庫,連到 localhost 的 Memcached server 看看會發生什麼事:
>>> $d = new Memcached
=> Memcached {#198}
>>> $d->addServer('localhost', 11211)
=> true
>>> $d->getAllKeys()
=> [
     "test",
   ]
>>> $d->get('test')
=> 0

可以看出這二個函式庫提供的 methods 看起來很接近,但是底層處理資料的方式不一樣,只能挑其中一個來使用。

2018/05/31

由網頁觸發、執行 PHPUnit

通常都是在 console 底下執行 PHPUnit 跑測試:
$ phpunit Test.php 
... Run by vendor/bin/phpunit ...
PHPUnit 7.1.5 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 30 ms, Memory: 4.00MB

OK (1 test, 1 assertion)

但若開發環境無法使用 console 的話,只能靠寫 PHP script 來 require PHPUnit library 來執行。在這邊發現其實 PHPUnit 有保留 interface 讓使用者可以不透過 console 來操作 PHPUnit,可以在 PHP script 裡面 require library 來使用。

先使用 composer 把 library 拉下來:
composer require 'phpunit/phpunit'

在 PHPUnut/TextUI 底下,有實作幾個 controller 來模擬 console 的操作。以下使用 Command 來執行 PHPUnit:
<?php

require __DIR__ . '/vendor/autoload.php';

use PHPUnit\TextUI\Command;

$cmd = new Command();

$cmd->run([
    'phpunit',  // 1st arg
    'Test.php', // 2nd arg
]);

如果執行上面這段 PHP script,會得到與在 console 執行 PHPUnit 一樣的結果。
不過要注意的是測試結果會直接輸出到 std out,若另外處理的話,記得用 output buffer 把資料拉回來,直接在瀏覽器看的話會破版。

備註:PHPUnit 不同版本的路徑不同,類別名稱也會不同。

2018/05/24

PHP 7.1 的 Symmetric Array Destructuring

PHP 7.1 對 list() 語法做了一些調整,讓 list() 操作使用起來更簡便。

原本的 list() 語法:
list($a, $b) = [1, 2];
// $a = 1, $b = 2

在 7.1 以後可以使用陣列來表示:
[$a, $b] = [1, 2];

這個語法稱為 Symmetric Array Destructuring

有了這個 feature 以後,不用另外宣告暫存變數也可以對調變數 (swap):
[$b, $a] = [$a, $b];

2018/05/07

PHP array 新增 element 的方法和效率

新增 element 到 PHP array 的寫法有二種,一種是用 array_push(),一種是使用 $list[] 直接新增。

跑個迴圈塞一千萬筆資料進 array,看看哪一種寫法的速度比較快。

這個是 array_push() 的:
$list = [];

$start = microtime(true);

for ($a = 0; $a < 10000000; $a++ ) {
    array_push($list, 1);
}

$end = microtime(true);

var_dump($end - $start);  // double(7.2292509078979)


同上,若將語法換掉的話:
$list = [];

$start = microtime(true);

for ($a = 0; $a < 10000000; $a++ ) {
    $list[] = 1;
}

$end = microtime(true);

var_dump($end - $start);  // double(2.0703480243683)


小量資料的話,其實二種作法在速度上不會差很多,但資料量變大時,速度差異就會變得很明顯。

2018/03/09

PHP 中 「...」(three dots) 也可以用在 argument provider

看了 PHPUnit 的 source code,發現有個很有趣的寫法
function assertSame($expected, $actual, string $message = ''): void
{
    Assert::assertSame(...\func_get_args());
}

最初以為「...」只能用在定義 variable-length function argument/param,重新看了文件才發現之前文件沒有看完,也可以拿來把陣列分別當作 argument/param 傳入:
function add($a, $b) {
    return $a + $b;
}

add(...[1, 2]);  // $a = 1, $b = 2

// or ...

$args = [1, 2];
add(...$args);

2018/03/08

PHPUnit 中「backupGlobals」的作用

複習 PHPUnit 順手寫了小程式做實驗。

PHPUnit 為了讓各個不同的 test case 不會因為 super globals 變數而互相影響,設計了 「backupGlobals」這個功能。先來看範例程式:
class MyTest extends TestCase
{
    public function testOne()
    {
        $GLOBALS['test'] = 123;

        $this->assertArrayHasKey('test', $GLOBALS);
    }

    public function testTwo()
    {
        $this->assertArrayHasKey('test', $GLOBALS);
    }
}

範例中 testOne() 修改了 $GLOBALS 變數的內容,如果在不使用「backupGlobals」的情況下,在 testTwo() 中的 $GLOBALS 也會保留 testOne() 修改過的內容,使 testTow() 抓得到「test」這個 key:
$ phpunit MyTest.php 
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 40 ms, Memory: 4.00MB

OK (2 tests, 2 assertions)

也就表示 testOne() 影響到了 testTwo() 的測試結果,這在測試時是不樂見的,各個測試應該有獨立的測試環境,不應該互相影響。

使用「backupGlobals」功能,PHPUnit 會在 test case 執行之前,使用 serialize() 對 super globals 做備份,並在測試結束以後 unserialize() 復原,所以 testOne() 中做的修改就不會影響到 testTwo() 了:
$ phpunit --globals-backup MyTest.php 
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 52 ms, Memory: 4.00MB

There was 1 failure:

1) MyTest::testTwo
Failed asserting that an array has the key 'test'.

/home/johnroyer/tmp/MyTest.php:16

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

重點來了,因為是使用 serialize() 和 unserialize(),所以只能針對 primitive type 進行備份和還原,如果使用了 object 或是 singleton 時,「backupGlobals」就派不上用場了。這個時候要使用殺手鐧「runTestsInSeparateProcesses」:
/**
 * @runTestsInSeparateProcesses
 */
class MyTest extends TestCase
{
    ....
}

使用這個 annotation 會讓 PHPUnit 為每個 test case 分別 fork 出新的 proccess 進行測試,因此不同 test case 就可以在完全獨立的環境下執行不受影響:
$ phpunit MyTest.php 
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 303 ms, Memory: 4.00MB

There was 1 failure:

1) MyTest::testTwo
Failed asserting that an array has the key 'test'.

/home/johnroyer/tmp/MyTest.php:19

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

但使用這個殺手鐧也是要付出一點代價:時間。最前二次執行 PHPUnit 時,跑完二個 test case 只花費了 50ms 左右,而開 proccess 來進行測試則花了 300ms。

2018/03/02

PHP 7.x 與 HHVM 的效能比較

前幾天看到國外有人使用 PHP 7.x 和 HHVM 做了效能測試「The Definitive PHP 5.6, 7.0, 7.1, 7.2 & HHVM Benchmarks (2018)」,可以從比較上看到 PHP 7 以後速度有大幅的躍進,到了 7.1 與 7.2 已經和 HHVM 不相上下了。

新版的 Druple 還不支援 PHP 7.2,但 7.1 的效能也很接近 HHVM 了


目前用過跑起來最慢的 project 應該是 GalleryDruple (古早的版本),由上圖可以看到 PHP 7.x 幾乎快了 5.x 一倍,另外在 7.1 的效能也逼近 HHVM 了。

相較於 HHVM 在佈署之前還要經過編譯步驟,我想應該很多人會回來使用 PHP 7.x 吧。

2018/01/15

Silex EOL in June 2018

Symfony 團隊認為新版的 Symfony 4 的修改,已經能讓使用者有和 Silex 相同的使用者體驗,於是決定不再繼續維護 Silex

http://symfony.com/blog/the-end-of-silex

目前 Silex 將在 6 月結束維護 (EOL, End Of Life):
So, we've decided to not support Symfony 4 in Silex, or at least not add the new features added in 3.4. The current stable version of Silex is still maintained for bugs and security issues. But its end of life is set to June 2018.

2018/01/14

PHP 7.2 下使用 xdebug

我目前使用的 PHP APT 是「http://ppa.launchpad.net/ondrej/php/ubuntu」,不過看起來在 PHP 7.2 還沒有 xdebug 可以使用。

如果硬要使用 aptitude 安裝,只能裝到 PHP 7.1 的版本。

所以後來用 phpbrew 在硬幹,發現不能安裝是有原因的:目前 xdebug stable 版本只支援到 PHP 7.1,若要在 PHP 7.2 使用 xdebug,只能先安裝 dev 版本

所以開發機可能考慮先降版本回到 7.1 吧。

2018/01/05

PHP 近幾年生態圈一些可惜的地方


進幾年 Laravel framework 竄紅,大家也趨之若騖,相對其他的 framework 就被冷落了,實在有點可惜。

Laravel 有其特色,但個人用了幾個版號,覺得 Laravel 比較適合中大型專案、以及快速開發,在往後維護和升級難度都不低 (5.1 migrate 到 5.4 根本是地獄)。





而如雨後春筍般冒出來的 Laravel library 很多又只能供 Laravel 使用,很難在其他 framework 上使用。Laravel 開發出來的元件耦合度也不低,很難拉出來供其他人使用 (Symfony 的各個元件都是 library,可以透過 composer require 供需要者使用)。結果讓想要使用其他 framework 的人找不到資源可以使用,少了很多選擇。

個人比較想看到的是,各種 framework 都能有社群協助一起發展,PSR 把一些好的設計制定成開發規範,大家遵守便能讓一個 library 在多個平台使用,而不是讓 Laravel 獨大,讓 PHP 的生態系變得單調、沒有選擇。

最近因公司需要而翻了近十個 framework、以及 CRM,並從中截長補短撰寫適合公司使用的 framework,希望以後有時間可以整理成公版讓其他有類似需求的人也來用看看。(本來想再愚人節發布的,似乎來不及了 TAT)

2018/01/04

PHPStan 靜態分析工具

前陣子從 gslin 那邊看到 PHPStan 這個工具,剛好手上有需要測試的程式,就拿來掃一掃。常見的問題大多都可以抓到,甚至連 doc comment 有錯誤也會抓出來。

下面弄個個 demo code 讓大家稍微看一下實際狀況。

Hello.php:
<?php
namespace Zero;

class Hello
{
    /**
     * @param array $name
     */
    public function greeting(): void
    {
        $test = '123';
        echo "Hello $name\n";
    }
}

bootstrap.php:
<?php
require __DIR__ . '/../vendor/autoload.php';

$hello = new \Zero\Hello();
$hello->greeting(123);

然後執行 PHPStan 做 level 5 的分析:
 ------ ------------------------------------------------------ 
  Line   src/Hello.php                                         
 ------ ------------------------------------------------------ 
  10     PHPDoc tag @param references unknown parameter $name  
  14     Undefined variable: $name                             
 ------ ------------------------------------------------------ 

 ------ --------------------------------------------------------------------- 
  Line   src/bootstrap.php                                                    
 ------ --------------------------------------------------------------------- 
  7      Method Zero\Hello::greeting() invoked with 1 parameter, 0 required.  
 ------ ---------------------------------------------------------------------

PHPStan 給的錯誤訊息,就可以在執行 PHP 之前預防錯誤發生。

不過有一些比較可惜的地方:

  • PHPStan 一定要吃 autoload 規則,看起來是為了要解 PHP Refelction 問題,所以比較就的程式就 GG 了
  • Hello.php 裡面有個沒使用到的變數 $test 沒有被抓到,不然應該可以再把程式清的更乾淨

2017/12/21

PHP 將 HTTP POST field 的 . (dot) 改為 _ (underscore)

今天遇到很奇怪的行為,在 PostMan 將 HTTP POST data 丟給 PHP 以後,參數名稱裡面有 . (dot) 的字元,全部都被改為 _ (underscore) 了。像是 field 名稱為「article.title」的欄位名稱,在 PHP 底下取值必須使用 $_POST['article_title']。

原本以為 RFC 裡面有的定義 reserve word,但是找了半天找不到。最後原因是 PHP 為了支援 register_global,但是 PHP 變數名稱又不允許使用 . (dot) 當作名稱的一部分,最後就自動轉為 _ (underscore)。

這個行為不管有沒有啟動 register_global 都會出現,即使上了 PHP 7.2 都已經不支援了,這個行為仍然存在。

ref:




後來做了個簡單的測試,在 HTTP POST 依序送出二個 field data:
a.b=100&a_b=200

這時候再印出 $_POST:
var_dump($_POST);

array(1) {
  'a_b' =>
  string(3) "200"
}

所以後面的會打贏前面的 ... (所以「世界上的戰爭沒任何人贏了,只有戰爭贏了」)

2017/11/15

PHP 變數初始化 (?)

看到有 Java 工程師在學 PHP 遇到變數初始化的問題,覺得有趣,做個筆記順便複習 PHP 的特性。

Java 是強型別 (strong type) 的程式語言,所有的變數在使用之前都必須給資料型態以及初始值;PHP 則是弱型別,就算不給值、不給資料型態也沒差,interpreter 會在 runtime 的時候自動做型別轉換在繼續執行程式。我想 PHP 這一特性就會讓很多 Java 工程師決得很莫名其妙吧,沒定義的變數到底要怎麼用?執行的時候真的不會出事嗎? (其實就是會 XD)

先來看正常的寫法:
$x = 'hello';
var_dump($x);  // "hello"

如果變數沒有給值會發生什麼事?
var_dump($x);  // NULL

PHP 把沒有初始化的變數帶入 NULL 來使用,連錯誤訊息都沒噴耶?其實有,只是要調整一下 error reporting 的 level:
error_reporting(E_ALL);

var_dump($x);

// Notice: Undefined variable: x
// NULL

2017/10/09

PHP curl 的一些特性

curl 在 PHP 中是以 extension 形式存在,所以只能透過 resource reference 去操作 curl 行為,沒辦法透過 debug 工具摸清楚 curl 在背景到底做了哪些事情。

手癢用 memory_get_usage() 看了一下 curl 在 init、exec 以及 close 這幾個狀態的記憶體使用量,來猜測 curl 到底怎麼運作。

echo memory_get_usage() . "\n"   // 236136;

$ch = curl_init();
var_dump($ch);                   // resource(4) of type (curl)
echo memory_get_usage() . "\n";  // 237472

在 init_curl() 以後,會先在記憶體中 allocate 並放一些資料,所以吃掉大概 1 KB 左右的記憶體,再將 curl 的 reference 傳出來給 $ch。