PHP5.4新特性:traits

从PHP 5.4开始实现了代码重用的的新方法traits。

traits是一种单继承语言(比如PHP)的代码重用机制,是为了解决单继承语言的局限性,在避免多重继承的复杂性的同时,又兼顾多重继承的代码重用的优点。

traits类似于class,traits不能被实例化,不能被继承,只能被横向的组合(使用use)。

trait实例:

<?php
trait ezcReflectionReturnInfo {
    function getReturnType() { /*1*/ }
    function getReturnDescription() { /*2*/ }
}

class ezcReflectionMethod extends ReflectionMethod {
    use ezcReflectionReturnInfo;
    /* ... */
}

class ezcReflectionFunction extends ReflectionFunction {
    use ezcReflectionReturnInfo;
    /* ... */
}
?>

优先权

如果一个从基类继承的方法和一个使用trait组合的方法名如果一样的化,那么trait中的方法将会覆盖从基类继承的方法。

<?php
class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
?>

上面程序会输出:

Hello World!

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

class TheWorldIsNotEnough {
    use HelloWorld;
    public function sayHello() {
        echo 'Hello Universe!';
    }
}

$o = new TheWorldIsNotEnough();
$o->sayHello();
?>

上面程序输出:

Hello Universe!

组合多个trait

使用逗号可以组合多个trait

<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World';
    }
}

class MyHelloWorld {
    use Hello, World;
    public function sayExclamationMark() {
        echo '!';
    }
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>

以上程序输出:

Hello World!

冲突

如果在一个类中插入多个trait,并且trait中定义的方法有重名的情况,将会产生一个致命错误,如果没有显示定义引入的方法。

解决一个类中插入trait重名的方法,可以使用insteadof操作符来定义具体引用哪个trait中的方法。

使用insteadof只能引入一个方法,可以同时使用as操作符给同名的方法换一个方法名。

<?php
trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
        B::bigTalk as talk;
    }
}
?>

改变方法的可见性

使用as语法,可以改变方法的可见性

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// Change visibility of sayHello
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// Alias method with changed visibility
// sayHello visibility not changed
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}
?>

trait中包含trait

通类一样,在trait中同样可以使用use语法来组合多个trait成为一个新的trait。

<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World!';
    }
}

trait HelloWorld {
    use Hello, World;
}

class MyHelloWorld {
    use HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
?>

上面代码输出:

Hello World!

抽象的trait成员

traits支持使用抽象(abstract)方法。

<?php
trait Hello {
    public function sayHelloWorld() {
        echo 'Hello'.$this->getWorld();
    }
    abstract public function getWorld();
}

class MyHelloWorld {
    private $world;
    use Hello;
    public function getWorld() {
        return $this->world;
    }
    public function setWorld($val) {
        $this->world = $val;
    }
}
?>

静态trait成员

在trait的方法中可以使用静态(static)变量,方法也可以定义为静态方法,但是不能定义为trait的静态变量。

静态变量实例

<?php
trait Counter {
    public function inc() {
        static $c = 0;
        $c = $c + 1;
        echo "$c\n";
    }
}

class C1 {
    use Counter;
}

class C2 {
    use Counter;
}

$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>

静态方法实例

<?php
trait StaticExample {
    public static function doSomething() {
        return 'Doing something';
    }
}

class Example {
    use StaticExample;
}

Example::doSomething();
?>

属性

traits同样支持属性

<?php
trait PropertiesTrait {
    public $x = 1;
}

class PropertiesExample {
    use PropertiesTrait;
}

$example = new PropertiesExample;
$example->x;
?>

如果trait中定义一个属性,那么引用这个trait的类中就不能定义相同的属性名,如果类中定义的同名属性和trait中定义的属性值相同将会引发一个E_STRICT,值不一样的化将会引发一个致命错误。

PHP5.4中while和for的性能对比

测试代码:

<?php

require 'Benchmark/Iterate.php';

$benchmark = new Benchmark_Iterate();


function fun_while() {
    $i = 0;
    while($i<=10000) {
        $i++;
    }
}

function fun_for() {
    for($i=0; $i<=10000;$i++) {}
}

$benchmark->run(50, 'fun_while');

print_r($result = $benchmark->get());

$benchmark->run(50, 'fun_for');

print_r($result = $benchmark->get());

结果:

while执行时间:0.00026299476623535

for执行时间:0.00044032096862793

PHP 5.4简单性能测试

测试PHP版本分别为:PHP 5.3.10和 PHP 5.4.3,PHP 5.3使用apt-get安装,PHP 5.4使用编译安装,测试页面代码,只有一行phpinfo(); php设置均为默认,没有调整。

测试环境:

  • 操作系统:ubuntu 12.04
  • CPU:Pentium(R) Dual-Core CPU E6600 @ 3.06GHz × 2
  • 内存:4GB
  • HTTP服务器:Apache 2.2
  • 测试工具:ab (ab -n 1000 -c 100 url)

测试数据:

PHP 5.3 PHP 5.4
Time taken for tests 0.939 seconds 0.577 seconds
Complete requests 1000 1000
Failed requests 0 105(Length: 105)
Requests per second 1065.53 [#/sec] (mean) 1733.73 [#/sec] (mean)
Time per request 93.850 [ms] (mean) 57.679 [ms] (mean)

从数据中可以看到5.4比5.3确实快了不少,有60-70%左右的提高,但是不知道怎么会有105个失败,ab显示的失败信息如下:

Failed requests:        105
(Connect: 0, Receive: 0, Length: 105, Exceptions: 0)

不知道这个Length什么意思?

ps:关于Failed requests的Length

只要出現 Failed requests 就會多出現一行要求失敗的各原因的數據統計,分別有 Connect, Length, 與 Exception 三種,分別代表的意義為:

  • Connect 無法送出要求、目標主機連接失敗、要求的過程中連線被中斷
  • Length 回應的內容長度不一致 ( 以 Content-Length 標頭值為判斷依據 )
  • Exception 發生無法預期的錯誤

而從上述說明就可以很明顯看出所有的 Failed requests 都落在 Length 這個類別上,原來這是因為受測網站的首頁是動態的內容,當第一次發出 HTTP request 與後續發出的 HTTP request 所得到回應的 HTML 長度都是不同大小的 ( 每次回應的 Content-Length 大小不一致 ),才會引發 Failed requests 的 Length 問題的失敗,因此這類 Length 不一致的失敗在進行「動態網頁」壓力測試時是合理的,可以不予理會。

這裡的 Length 是以 “第 1 次” 取得的 Content-Length 為主,如果第 2 次以後的 HTTP Request 所得到的 HTTP Response Header 得到的 Content-Length 與第 1 次取得的長度不一致,就會得到 Length 的錯誤。

PHP5.4向下不兼容的改变

升级到PHP5.4,虽然大多数现有的PHP 5中的代码应该不会改变,请注意一些向后不兼容的变化:

  • 不再支持安全模式。任何依赖安全模式的应用程序都需要在安全方面进行调整。

  • 魔术引号被删除。依赖此功能的应用程序需要更新,以避免安全问题。get_magic_quotes_gpc()和get_magic_quotes_runtime()始终返回false。set_magic_quotes_runtime()会抛出E_CORE_ERROR错误。

  • 从php.ini中删除register_globals和register_long_arrays参数。

  • Call-time pass by reference has been removed.

  • break和continue不再支持可边的参数(例如break 1 + foo() * $bar;)。静态参数仍然被支持,如break 2;

  • date和time扩展中,不再支持TZ环境变量设置,你必须指定一个时区,在php.ini中设置date.timezone或者使用date_default_timezone_set()方法。如果timezone没有设置,PHP将不再尝试猜测当前时区,默认使用UTC,同时引发一个E_WARNING。

  • 非数字的字符串偏移量,例如$a['foo'] $a是一个字符串,isset()返回false,empty返回true,如果使用他们将会产生一个E_WARNING。bool和null类型的偏移量会产生一个E_NOTICE。数字字符串(例如$a['2'])仍将工作。请注意,’12.3′和’5 foobar’偏移量视为非数字,会产生一个E_WARNING,但经过转换后得到12和5仍将向后兼容。注意,下面的代码返回不同的结果,$str=’abc’; vardump(isset($str['x'])); //php5.4(包含5.4)以后的版本得到false,5.3(包含5.3)之前的版本得到true

  • 数组转换为字符串,将产生一个E_NOTICE错误,但返回的结果仍然是一个字符串”Array”。

  • NULL,FALSE或者一个空字符串添加一个对象属性,将会产生一个E_WARNING错误,而不是E_STRICT。

  • 参数名称使用全局的变量会得到致命的错误。禁止这样的函数foo($_GET,$_POST){}

  • Salsa10和Salsa20哈希算法已被删除。

  • array_combine()函数的2个参数为空数组的时候将会返回一个array()代替FALSE。原本如果array_combine()的2个参数为空数组或者单元数不同时会返回FALSE。

  • 若果你使用htmlentities()参数为亚洲字符串,他的工作类似于htmlspecialchars(),这是以前PHP版本的处理情况,但是现在将会引发一个E_STRICT错误。

以下关键字作为保留关键字,不得用于函数、类的名字

  • trait
  • callable
  • insteadof

下面的方法从PHP中删除:

  • define_syslog_variables()
  • import_request_variables()
  • session_is_registered(), session_register() and session_unregister().
  • The aliases mysqli_bind_param(), mysqli_bind_result(), mysqli_client_encoding(), mysqli_fetch(), mysqli_param_count(), mysqli_get_metadata(), mysqli_send_long_data(), mysqli::client_encoding() and mysqli_stmt::stmt().

PHP 5.4.3和PHP 5.3.13发布

2012年5月8日PHP开发团队宣布提供可用的PHP5.4.3和PHP5.3.13。推荐所有的用户都升级到PHP5.4.3或PHP5.3.13

此版本修复了基于CGI-based(CVE-2012-2311)的漏洞。注:mod_php和php-fpm不受影响。

PHP5.4.3修复apache_request_headers()(CVE-2012-2329)的缓冲区溢出漏洞。 PHP5.3系列不受影响。