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,值不一样的化将会引发一个致命错误。

curl, file_get_contents和fsockopen速度比较

测试环境:

  • Apache 2.2
  • PHP 5.3

测试代码:

function curl_test() {
    $url = 'http://www.google.com.hk/';

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $data = curl_exec($ch);
    curl_close($ch);
    return $data;
}

function file_get_content_test() {
    $url = 'http://www.google.com.hk/';
    $data = file_get_contents($url);
    return $data;
}

function fsockopen_test() {
    $url = 'http://www.google.com.hk/';
    $urls = parse_url($url);
    if(!isset($urls['port'])) {
        $urls['port'] = '80';
    }
    if(!isset($urls['path'])) {
        $urls['path'] = '/';
    }
    $path = $urls['path'];
    if(isset($urls['query'])) {
        $path .= '?'.$urls['query'];
    }

    $data = '';
    $fp = fsockopen($urls['host'], $urls['port'], $errno, $errstr, 10);
    if(!$fp) {
        echo "ERROR: $errno - $errstr <br />\n";
    }
    else {
        $put = "GET ".$path." HTTP/1.1\r\n";
        $put .= "Host: ".$urls['host']."\r\n";
        $put .= "Connection: Close\r\n\r\n";
        fwrite($fp, $put);
        while (!feof($fp)) {
            $data .= fgets($fp);
        }
        fclose($fp);
        if($data) {
            $data = substr($data, strpos($data, "\r\n\r\n")+4);
        }
    }

    return $data;
}

require 'Benchmark/Iterate.php';

$benchmark = new Benchmark_Iterate();

$funcs = array('curl_test', 'file_get_content_test', 'fsockopen_test');
foreach ($funcs as $func) {
    $benchmark->run(10, $func);
    $result = $benchmark->get();
    echo $func;
    var_dump($result);
}

测试结果:

curl_test
array (size=12)
  1 => string '0.439183' (length=8)
  2 => string '0.423215' (length=8)
  3 => string '0.416775' (length=8)
  4 => string '0.416882' (length=8)
  5 => string '0.414696' (length=8)
  6 => string '0.416986' (length=8)
  7 => string '0.418402' (length=8)
  8 => string '0.416467' (length=8)
  9 => string '0.415329' (length=8)
  10 => string '0.417352' (length=8)
  'mean' => string '0.419528' (length=8)
  'iterations' => int 10

file_get_content_test
array (size=12)
  1 => string '0.468215' (length=8)
  2 => string '0.469789' (length=8)
  3 => string '0.473641' (length=8)
  4 => string '0.471970' (length=8)
  5 => string '0.470277' (length=8)
  6 => string '0.472823' (length=8)
  7 => string '0.470583' (length=8)
  8 => string '0.470775' (length=8)
  9 => string '0.470803' (length=8)
  10 => string '0.470506' (length=8)
  'mean' => string '0.470938' (length=8)
  'iterations' => int 10

fsockopen_test
array (size=12)
  1 => string '0.384669' (length=8)
  2 => string '0.386607' (length=8)
  3 => string '0.388593' (length=8)
  4 => string '0.395739' (length=8)
  5 => string '0.390402' (length=8)
  6 => string '0.388805' (length=8)
  7 => string '0.388581' (length=8)
  8 => string '0.390463' (length=8)
  9 => string '0.388577' (length=8)
  10 => string '0.387762' (length=8)
  'mean' => string '0.389019' (length=8)
  'iterations' => int 10

PHP类中的$this和self的区别

$this代表当前对象(类)的实例,self代表当前类,当前类的实例很好理解,那么什么叫当前类呢? 看看下面例子:

<?php
class Person {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }

    public function getTitle() {
        return $this->getName()." the person";
    }

    public function sayHello() {
        echo "Hello, I'm ".$this->getTitle()."<br/>";
    }

    public function sayGoodbye() {
        echo "Goodbye from ".self::getTitle()."<br/>";
    }
}

class Geek extends Person {
    public function __construct($name) {
        parent::__construct($name);
    }

    public function getTitle() {
        return $this->getName()." the geek";
    }
}

$geekObj = new Geek("Ludwig");
$geekObj->sayHello();
$geekObj->sayGoodbye();

上面代码将会输出:

Hello, I'm Ludwig the geek
Goodbye from Ludwig the person

子类覆盖了父类的方法之后,在外面我们是无法访问父类被覆盖的方法,那么在子类中和在父类中是可以访问的:

  • 在子类中可以通过parent::method()访问
  • 在父类中可以通过self::method()访问

PHP Warning: Xdebug MUST be loaded as a Zend extension in Unknown on line 0

PHP Warning:  Xdebug MUST be loaded as a Zend extension in Unknown on line 0

使用pecl安装的xdebug,使用apache php5.3运行没问题,但是在使用cli运行php时出现上面的警告。

问题的根源在于使用pecl安装的xdebug会自动在你的php.ini中增加extension="xdebug.so" ,但是根据xdebug官方的安装文档说法,xdebug只能使用zend_extension加载扩展,使用extension会有问题的。所以修改php.ini中的extension="xdebug.so"zend_extension="/path/xdebug.so"即可。注意使用zend_extension需要指定xdebug.so的全路径。使用sudo find / -name xdebug.so即可找到你的xdebug.so文件的路径。

关于extension和zend_extension的区别可以看这里:extension和zend_extension的区别