iT邦幫忙

DAY 8
10

逐步提昇PHP技術能力系列 第 8

逐步提昇PHP技術能力 - PHP的語言特性 : 型別 / Type Juggling / Type Hint

型別是一個語言的基本...不過因為PHP是動態語言,所以往往不太會去深究,因為很少出問題。記得幾年前Rasmus Lerdorf來演講,當天晚上的party遊戲,他出的題目就是型別轉換規則。可見對於動態語言來說,自動型別轉換其實是非常重要的規則,有些地方即使不一定合理(像Javascript...)。根據程式的操作,PHP可能會自動轉換使用的型別,這個過程就叫做Type Juggling。

PHP5加入了一個Type Hinting機制,讓開發者可以指定函數的參數型別以及回傳的型別(純量之外)。PHP5其實已經將近十年了,不過還真的沒用過Type Hinting。
參考:
* PHP: Types - Manual
* PHP: Type Juggling - Manual
* PHP: PHP type comparison tables - Manual
* PHP: Type Hinting - Manual

* PHP的型別系統

手冊中把PHP的型別分成幾個類型:

  1. 純量(scalar)
    1a. boolean
    1b. integer
    1c. float
    1d. string
  2. 複合型別
    2a. array
    2b. object
  3. 特殊型別
    3a. resource
    3b. NULL
  4. 偽型別
    4a. mixed
    4b. number
    4c. callback

如果開發過PHP的extension,有一個資料結構應該會很熟悉:

typedef struct _zval_struct zval;
......
typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
} zvalue_value;

struct _zval_struct {
    /* Variable information */
    zvalue_value value;     /* value */
    zend_uint refcount__gc;
    zend_uchar type;    /* active type */
    zend_uchar is_ref__gc;
};

與外部Library介接時,通常要做的事情就是把C的型別與zval做轉換。純量主要是用long、double跟str來存放,陣列使用的是HashTable,其他物件則是zend_object_value。型別的資訊,則放在zval.type(_zval_struct.type)。

偽型別並不是真正的型別,通常是用來定義函數參數以及回傳值的一些狀況用的。mixed表示可以有多種型別,number表示可能是integer或float。PHP5.4定義了callable,在此之前是使用callback,來代表傳入的函數或方法。void則代表函數沒有返回值。...代表函數有不定數目的參數。

* Type Juggling

PHP有定義在程式中型別轉換的規則。例如字串轉換成數字:

$a = 1;
$a += "10 is a number."; //$a的值變成11
$a = "total customers: "+$a;//$a的值還是11
$a = "conver to string: ".$a;//變成字串

PHP手冊中對於Type Juggling有相當完整的解說,另外對於變數的比較與邏輯運算,有一個完整的對照表,建議參考一下,我就不多說。

* Type Hinting

從PHP5開始,可以強制函數及方法的參數必須使用的型別。可以用來當做Type Hint的有:Class及Interface名稱、array(PHP5.1)或是callable(PHP5.4),但是不能使用Resource以及純量作為Type Hint。

一個簡單的例子:

<?php
class a {
  public $name = 'fillano';
  function show() {
    echo $this->name."\n";
  }
}
class b {  
  public $name = 'hildegard';
  function show() {
    echo $this->name."\n";
  }
}
function testa(a $a) {
  $a->show();
}
testa(new a);
testa(new b);  

testa函數使用Type Hint強制定義參數必須是class a的實例,如果丟給他的是class b就會出錯。執行結果:

Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-7a.php 
fillano
PHP Catchable fatal error:  Argument 1 passed to testa() must be an instance of a, instance of b given, called in /Users/fillano/builds/ironman6/1-7a.php on line 22 and defined in /Users/fillano/builds/ironman6/1-7a.php on line 17

再來試一下陣列:

<?php
function testarr(array $a) {
  echo $a[0]."\n";
}
testarr(array('fillano'));
testarr(array('k'=>'v'));
testarr('string');

執行結果:

Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-7b.php 
fillano
PHP Notice:  Undefined offset: 0 in /Users/fillano/builds/ironman6/1-7b.php on line 3

PHP Catchable fatal error:  Argument 1 passed to testarr() must be an array, string given, called in /Users/fillano/builds/ironman6/1-7b.php on line 7 and defined in /Users/fillano/builds/ironman6/1-7b.php on line 2

第一次執行傳給函數陣列,可以正常執行。第二次傳給他HashTable,會因為找不到key出現警告,但是第三次傳給他字串,就會出現錯誤。

如果使用PHP5.4,就可以使用callable:

<?php
class a {
  var $name = "fillano\n";
  function show() {
    echo $this->name;
  }
}
function testcb(callable $f) {
  $f();
}
function show() {echo "fillano\n";}

testcb(function(){echo "fillano\n";});
testcb(array(new a, 'show'));
testcb('show');
testcb('string');

前三次是三種不同的callable的使用方式傳入函數,第四次傳入的字串,PHP找不到同名的函數,就會出錯。執行結果:

Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ phpbrew use php-5.4.20
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-7c.php 
fillano
fillano
fillano
PHP Catchable fatal error:  Argument 1 passed to testcb() must be callable, string given, called in /Users/fillano/builds/ironman6/1-7c.php on line 15 and defined in /Users/fillano/builds/ironman6/1-7c.php on line 8

Catchable fatal error: Argument 1 passed to testcb() must be callable, string given, called in /Users/fillano/builds/ironman6/1-7c.php on line 15 and defined in /Users/fillano/builds/ironman6/1-7c.php on line 8

不過即使定義了Type Hint,如果把預設值設為NULL,那傳給他NULL並不會出錯:

<?php
class a {
}
function testnull(a $a=NULL) {
  var_dump($a);
}
testnull(new a);
testnull(NULL);

執行結果:

Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-7d.php 
object(a)#1 (0) {
}
NULL

另外,使用Interface或是Class來定義Type Hint時,傳給他子類別的實例也沒問題:

<?php
interface a {
  function show();
}
class b implements a {
  var $name = "fillano\n";
  function show() {
    echo $this->name;
  }
}
class c {
  function show(a $c) {
    $c->show();
  }
}
$b = new b;
$c = new c;
$c->show($b);

執行結果:

Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-7e.php 
fillano

使用Type Hint,可以讓物件的關係更明確,也比較不會誤用。在組織比較嚴謹的類別架構時,應該是個不錯的幫手...雖然沒用過XD


上一篇
逐步提昇PHP技術能力 - PHP的語言特性 : PHP內建的interface與class
下一篇
逐步提昇PHP技術能力 - PHP的語言特性 : magic methods
系列文
逐步提昇PHP技術能力30
0
0
porkhere
iT邦新手 5 級 ‧ 2017-10-01 05:40:49

好文,雖然很少用型別提示,不過針對本文想做一個建議,類別中變數宣告的 var 可以拿掉了,或改成 public, var 這是很久很久以前的寫法,現在不需要這樣做了,寫 var 和 public 是一樣的。

fillano iT邦超人 1 級 ‧ 2017-10-01 19:19:31 檢舉

也是,var是PHP4的寫法XD,我來改一下,感謝...

我要留言

立即登入留言