分类目录归档:PHP

PHP通过魔术方法实现多继承与重载

 PHP所提供的”重载”(overloading)是指动态地”创建”类属性和方法。我们是通过魔术方法(magic methods)来实现的。当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。本节后面将使用”不可访问属性”和”不可访问方法”来称呼这些未定义或不可见的类属性或方法。所有的重载方法都必须被声明为 public。

<?php
class Parent1 {
    public function printFunction ($args) {
        echo 'parent1->printfunction ' . $args . '<br>';
    }

    public function printFunction1 ($args) {
        echo 'parent1->printfunction1 ' . $args . '<br>';
    }
}

class Parent2 {
    public function printFunction2 ($args) {
        echo 'parent2->printfunction2 ' . $args . '<br>';
    }
}

class Child {
    protected $_parents = null;

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

    public function __call($method, $args) {
        foreach ($this->_parents as $p) {
            if (is_callable(array($p, $method))) {
                return call_user_func_array(array($p, $method), $args);
            }
        }
    }

    public function printFunction ($args) {
        echo 'child->printfunction ' . $args . '<br>';
    }
}

class A {
    protected $_parents = null;

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

    public function __call($method, $args) {
        foreach ($this->_parents as $p) {
            if (is_callable(array($p, $method))) {
                return call_user_func_array(array($p, $method), $args);
            }
        }
    }   
}

class B extends Child{
    public function __construct($parent){
        parent::__construct($parent);
    }
}
$obj = new Child(array(new Parent1(), new Parent2()));
$a = new A(array(new Child(array(new Parent1(), new Parent2()))));
$b = new B(array(new Parent1(), new Parent2()));

$obj->printFunction(123); // child->printfunction 123
$obj->printFunction1(456); // parent1->printfunction1 456
$obj->printFunction2(789); // arent2->printfunction2 789

$a->printFunction(123); // child->printfunction 123
$a->printFunction1(456); // parent1->printfunction1 456
$a->printFunction2(789); // arent2->printfunction2 789

$b->printFunction(123); // child->printfunction 123
$b->printFunction1(456); // parent1->printfunction1 456
$b->printFunction2(789); // arent2->printfunction2 789

当执行$obj->printFunction(123)时,由于在Child类中定义了此方法,所以直接调用Child中的printFunction方法。而执行$obj->printFunction1(456)和$obj->printFunction2(789)时,由于Child类中未定义printFunction1和printFunction2方法,就会自动调用__call方法,该方法的第一个参数是调用的不存在的方法的名称,是字符串类型,第二个参数是要传递的参数,是数组类型。在__call方法中查找引用的类的实例中是否存在该方法。类A和类B分别为实现不同的“继承”方法,他们的结果是一样的。用静态方式中调用一个不可访问方法时,__callStatic() 会被调用。

对于属性的重载也是同样的道理。

在给不可访问属性赋值时,__set() 会被调用。
读取不可访问属性的值时,__get() 会被调用。
当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
当对不可访问属性调用 unset() 时,__unset() 会被调用。

还请读者参照PHP文档进行具体实现,若要了解详细使用,可参考yii中yii\base\Object的property和yii\base\Component的behavior的具体实现。

PHP中的引用详解

我们首先看下官方英文文档的解释。

PHP中的引用意味着你可以用不同的变量名访问同一个变量内容。在PHP中变量名和变量内容是不同的,引用是符号表别名。引用不同于C语言中的指针,你不能对其进行数学运算。最形象的比喻就是Unix的文件系统,变量名对应文件系统中的文件名,而变量内容对应文件系统中实际的文件内容,引用即为系统中的硬链接。

那么PHP中的引用和C语言中的指针有什么不同呢?C语言中加入定义一个整形指针“int *p”,我们可以对指针进行加操作“p++”,那么它就指向了下一个内存地址。或者我们进行赋值操作,给指针p一个新的地址“p = n”,那么p就指向了n所指向的地址,他们都指向同一个地址。而在PHP中我们不可以对引用使用加减操作,因为通过引用赋值时,如“$b=&$a”,新的$b只是一个变量名而已,和$a是一样的,&的作用就是使$b的变量内容指向了$a的变量内容,$b终究只是一个变量名,用官方的说法是$a的别名。说的直白点就是,使用了“&”的话就不会分配新的内存空间,引用的结果是新变量指向现有的内存空间

PHP中引用的作用是什么呢?引用有三个基本的作用:引用赋值、引用传递和引用返回。

引用赋值

对于“$a=$b”,$a和$b是完全一样的,他们都指向同一个内容。

引用传递

PHP中函数参数的传递都是值传递,都是将实参的值拷贝一份传递到函数中。如果想在函数中修改函数外部的值,那么就需要引用。

引用返回

可以使用引用进行函数返回,而不是返回值的拷贝。

具体的作用介绍可参考http://php.net/manual/en/language.references.whatdo.php

需要我们注意的是,在PHP5中的对象变量,我们需要深刻理解。PHP5中的对象变量已经不再保存整个对象的值,只是保存一个标识符来访问真正的对象内容。当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。 可以参考下列代码对本篇文章进行理解。

<?php

$a = 1;
$b = $a;
$b = 4;
echo $a . '<br>'; // 1

$b = &$a;
$b = 4;
echo $a . '<br>'; // 4
$c = $b;
$c = 5;
echo $b . '<br>'; // 4
echo $a . '<br><br>'; // 4

$a= 1;
function func (&$args){
    $args = 6;
}
func($a);
echo $a; // 6
?>

对象变量的引用

<?php
class A {
    public $foo = 'empty';
}
class B {
    public $foo = 'empty';
    public $bar = 'hello';
}

function normalAssignment($obj) {
    $obj->foo = 'changed';
    $obj = new B;
}

function referenceAssignment(&$obj) {
    $obj->foo = 'changed';
    $obj = new B;
}

$a = new A;
normalAssignment($a);
echo get_class($a), "\n";
echo "foo = {$a->foo}\n";

referenceAssignment($a);
echo get_class($a), "\n";
echo "foo = {$a->foo}\n";
echo "bar = {$a->bar}\n";

/*
prints:
A
foo = changed
B
foo = empty
bar = hello
*/
?>

代码中可以看出,普通变量的变量内容存储的就是实际的变量内容,而对象变量的变量内容实际上是对象的标志符,而不是正真的对象。例如将上述代码中的$a赋值给$b,“$b=$a”所代表的意义是创建一个新的变量$b,$b的变量内容存储的值和变量$a的变量内容所存储的值是一样的,都是对象的标识符,而且这两个变量具有不同的变量内容存储空间。而引用赋值“$b=$a”所代表的意义是变量$b的变量内容指向变量$a的变量内容,这两个变量的变量内容都指向一个存储空间。“=”和“=&”操作得到的变量都可对对象的值进行修改,若想在函数中修改对象的值,则在函数定义时可不必加“&”操作符。PHP5中对象的赋值操作不会产生新的对象,要复制对象,使用clone关键字。