竹磬网-邵珠庆の日记 生命只有一次,你可以用它来做些更多伟大的事情–Make the world a little better and easier


111月/150

PHP魔术方法和魔术常量介绍及使用

发布在 邵珠庆

PHP中把以两个下划线__开头的方法称为魔术方法,这些方法在PHP中充当了举足轻重的作用。 魔术方法包括:

  • __construct(),类的构造函数
  • __destruct(),类的析构函数
  • __call(),在对象中调用一个不可访问方法时调用
  • __callStatic(),用静态方式中调用一个不可访问方法时调用
  • __get(),获得一个类的成员变量时调用
  • __set(),设置一个类的成员变量时调用
  • __isset(),当对不可访问属性调用isset()empty()时调用
  • __unset(),当对不可访问属性调用unset()时被调用。
  • __sleep(),执行serialize()时,先会调用这个函数
  • __wakeup(),执行unserialize()时,先会调用这个函数
  • __toString(),类被当成字符串时的回应方法
  • __invoke(),调用函数的方式调用一个对象时的回应方法
  • __set_state(),调用var_export()导出类时,此静态方法会被调用。
  • __clone(),当对象复制完成时调用

__construct()__destruct()

构造函数和析构函数应该不陌生,他们在对象创建和消亡时被调用。例如我们需要打开一个文件,在对象创建时打开,对象消亡时关闭

<?php 
class FileRead
{
    protected $handle = NULL;

    function __construct(){
        $this->handle = fopen(...);
    }

    function __destruct(){
        fclose($this->handle);
    }
}
?>

这两个方法在继承时可以扩展,例如:

<?php 
class TmpFileRead extends FileRead
{
    function __construct(){
        parent::__construct();
    }

    function __destruct(){
        parent::__destruct();
    }
}
?>

__call()__callStatic()

在对象中调用一个不可访问方法时会调用这两个方法,后者为静态方法。这两个方法我们在可变方法(Variable functions)调用中可能会用到。

<?php
class MethodTest 
{
    public function __call ($name, $arguments) {
        echo "Calling object method '$name' ". implode(', ', $arguments). "\n";
    }

    public static function __callStatic ($name, $arguments) {
        echo "Calling static method '$name' ". implode(', ', $arguments). "\n";
    }
}

$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context');
?>

__get()__set()__isset()__unset()

当get/set一个类的成员变量时调用这两个函数。例如我们将对象变量保存在另外一个数组中,而不是对象本身的成员变量

<?php 
class MethodTest
{
    private $data = array();

    public function __set($name, $value){
        $this->data[$name] = $value;
    }

    public function __get($name){
        if(array_key_exists($name, $this->data))
            return $this->data[$name];
        return NULL;
    }

    public function __isset($name){
        return isset($this->data[$name])
    }

    public function unset($name){
        unset($this->data[$name]);
    }
}
?>

__sleep()__wakeup()

当我们在执行serialize()unserialize()时,会先调用这两个函数。例如我们在序列化一个对象时,这个对象有一个数据库链接,想要在反序列化中恢复链接状态,则可以通过重构这两个函数来实现链接的恢复。例子如下:

<?php
class Connection 
{
    protected $link;
    private $server, $username, $password, $db;

    public function __construct($server, $username, $password, $db)
    {
        $this->server = $server;
        $this->username = $username;
        $this->password = $password;
        $this->db = $db;
        $this->connect();
    }

    private function connect()
    {
        $this->link = mysql_connect($this->server, $this->username, $this->password);
        mysql_select_db($this->db, $this->link);
    }

    public function __sleep()
    {
        return array('server', 'username', 'password', 'db');
    }

    public function __wakeup()
    {
        $this->connect();
    }
}
?>

__toString()

对象当成字符串时的回应方法。例如使用echo $obj;来输出一个对象

<?php
// Declare a simple class
class TestClass
{
    public function __toString() {
        return 'this is a object';
    }
}

$class = new TestClass();
echo $class;
?>

这个方法只能返回字符串,而且不可以在这个方法中抛出异常,否则会出现致命错误。

__invoke()

调用函数的方式调用一个对象时的回应方法。如下

<?php
class CallableClass 
{
    function __invoke() {
        echo 'this is a object';
    }
}
$obj = new CallableClass;
var_dump(is_callable($obj));
?>

__set_state()

调用var_export()导出类时,此静态方法会被调用。

<?php
class A
{
    public $var1;
    public $var2;

    public static function __set_state ($an_array) {
        $obj = new A;
        $obj->var1 = $an_array['var1'];
        $obj->var2 = $an_array['var2'];
        return $obj;
    }
}

$a = new A;
$a->var1 = 5;
$a->var2 = 'foo';
var_dump(var_export($a));
?>

__clone()

当对象复制完成时调用。例如在设计模式详解及PHP实现:单例模式一文中提到的单例模式实现方式,利用这个函数来防止对象被克隆。

<?php 
public class Singleton {
    private static $_instance = NULL;

    // 私有构造方法 
    private function __construct() {}

    public static function getInstance() {
        if (is_null(self::$_instance)) {
            self::$_instance = new Singleton();
        }
        return self::$_instance;
    }

    // 防止克隆实例
    public function __clone(){
        die('Clone is not allowed.' . E_USER_ERROR);
    }
}
?>

魔术常量(Magic constants)

PHP中的常量大部分都是不变的,但是有8个常量会随着他们所在代码位置的变化而变化,这8个常量被称为魔术常量。

  • __LINE__,文件中的当前行号
  • __FILE__,文件的完整路径和文件名
  • __DIR__,文件所在的目录
  • __FUNCTION__,函数名称
  • __CLASS__,类的名称
  • __TRAIT__,Trait的名字
  • __METHOD__,类的方法名
  • __NAMESPACE__,当前命名空间的名称

这些魔术常量常常被用于获得当前环境信息或者记录日志。

287月/113

PHP类中的魔术方法+动态类导学

发布在 邵珠庆

PHP不知道为什么,钟爱“魔术”这个词,什么都弄魔术。其实想想也非常形象,就拿PHP类中的魔术方法来说吧,你不需要去调用
他们的任何一个方法,他们却可以执行,影响你的程序。

在学习之前我要说两句,首先你不能漠视魔术方法,不要认为你不用这世界上用的人就非常少,作用就小。其实 PHP的魔术方法是PHP的一大特征,既然你学了一种语言,连它最基本的特征都没学,还叫学习了这个语言了吗?其次,不要认为看完我这篇文章就可以深入的 理解魔术方法了,我只是带您入门,以后的路还得您自己走。最后,腾讯曾经出了个面试题,就是关于魔术方法的,如果你学习完我这篇文章,应付这道题应该不成 问题了。还有一点,学习魔术方法一个最重要的地方就是学习它的用法,也就是它在什么情况下被调用。这是重点中的重点,请在下面的学习中注意这点。

首先学习PHP4中就有的魔术方法——__sleep()和__wakeup()。

其实这两个方法用的非常非常的少,少到在我接触PHP1年半的前提下,如果不去主动学习他们根本不知道他们的存在,但是本着对
学术严谨的态度,多学点没什么坏处,用不了多少时间,所以你也可以跟着我花几分钟的时间熟悉一下这两个方法。

我刚才说他们用的非常非常的少,我用了两个非常,大家可以想想一下他们的程度。不光我们要知道他们用的少,重要的是知道为
什么用的少。我先写一段代码,你看看你是否见到过或者曾经写过,我见识比较少,至少我之前是没有见过这么写的。

[php]
class Test {
        public $testStr;
        public function fun() {
                //搞点小事情
        }
}
$test = new Test();
echo serialize($test);  //输出  O:4:"Test":1:{s:7:"testStr";N;}
[/php]

它竟然把一个类的给序列化了,也就是把一个类转换成了一个字符串,可以传输或者保存下来。我以前可没有接触过这么神奇的事情。

下面我修改一下上面的代码,上面不是要搞点小事情吗?那我现在就搞给你看!

[php]
class Test {
        public $testStr;
        public function __construct($str) {
                $this->testStr = $str;
        }
}
$test = new Test("Skiyo.cn");
echo serialize($test);  //输出   O:4:"Test":1:{s:7:"testStr";s:8:"Skiyo.cn";}
[/php]

大家可以看到,$testStr被设置后,序列化后也对应了相应的值,但是现在有个问题,比如我这个变量是个秘密呢?而且我又得把这个类序列化传给别的地方呢?
看下面的代码

[php]
class Test {
        public $mySecret;  //我的秘密不想让人知道
        public function __construct($secret) {
                $this->mySecret = $secret;
        }
}
$test = new Test("我的心里话  我爱某某某");
echo serialize($test);  //输出  O:4:"Test":1:{s:8:"mySecret";s:32:"我的心里话  我爱某某某";}
[/php]

大家可以看到,我的秘密序列化后还是存在的,可是我不想我的心里话被别人看到。这个时候PHP很贴心,她知道你的问题,所以设置了魔术方法。

__sleep() 就表示当你执行serialize()这个序列化函数之前时的事情,就像一个回调函数,所以在这个回调函数里面我们就可以做点事情,来隐藏我的秘密。

[php]
class Test {
        public $mySecret;  //我的秘密不想让人知道
        public function __construct($secret) {
                $this->mySecret = $secret;
        }
        public function __sleep() {
                $this->mySecret = "你休想知道我的秘密!";
                return array('mySecret');  //一定要返回变量,不然返回的是个空,所以序列化也就没有东西了。
        }
}
$test = new Test("我的心里话  我爱某某某");
echo serialize($test);  //输出  O:4:"Test":1:{s:8:"mySecret";s:28:"你休想知道我的秘密!";}
[/php]

大家看到了吧?我的心里话被加密了,这个就是__sleep()的作用。至于__wakeup()和__sleep()大同小异,只不过是反序列化之前进行的回调函数。我不详细说了,大家看下下面的代码就明白了。

[php]
class Test {
        public $mySecret;  //我的秘密不想让人知道
        public function __construct($secret) {
                $this->mySecret = $secret;
        }
        public function __sleep() {
                $this->mySecret = "你休想知道我的秘密!";
                return array('mySecret');  //一定要返回变量,不然返回的是个空,所以序列化也就没有东西了。
        }
        public function __wakeup() {
                $this->mySecret = "我的秘密又回来了,哈哈!";
                //反序列化就不用返回数组了,就是对字符串的解密,字符串已经有了不用其他的了。
        }
}
$test = new Test("我的心里话  我爱某某某");
print_r(unserialize(serialize($test)));  //输出  Test Object ( [mySecret] => 我的秘密又回来了,哈哈! )
[/php]

到此为止我们的__sleep()和__wakeup()两个魔术函数就学习完毕了。

即使你曾经对魔术方法没有任何接触,经过上节课的学习,那么我相信你也对魔术方法有一定的了解了。有了上节课的基础,我们的学习将会非常简单,因为魔术方法都是大同小异的东西。

今天我们要学习的是四个非常有用的魔术方法:__set() __get() __isset() __unset() ,特别是其中的 __set() 和 __get() ,在以后的面向对象的编程中经常会遇到,所以也是我们今天的重点。

我们还是从一个代码的例子出手。我们先来按照Java中类的思想写一个类。

[php]
class Test {

    //私有属性

    private $a;
        private $b;
        private $c;
        private $d;
        private $e;
        private $f;
        private $g;
        private $h;
        private $i;
        private $j;
    //还有很多很多....

    //Getters & Setters

        public function getA() {
                return $this->a;
        }

        public function getB() {
                return $this->b;
        }

        public function getC() {
                return $this->c;
        }

        public function getD() {
                return $this->d;
        }

        public function getE() {
                return $this->e;
        }

        public function getF() {
                return $this->f;
        }

        public function getG() {
                return $this->g;
        }

        public function getH() {
                return $this->h;
        }

        public function getI() {
                return $this->i;
        }

        public function getJ() {
                return $this->j;
        }

        public function setA($a) {
                $this->a = $a;
        }

        public function setB($b) {
                $this->b = $b;
        }

        public function setC($c) {
                $this->c = $c;
        }

        public function setD($d) {
                $this->d = $d;
        }

        public function setE($e) {
                $this->e = $e;
        }

        public function setF($f) {
                $this->f = $f;
        }

        public function setG($g) {
                $this->g = $g;
        }

        public function setH($h) {
                $this->h = $h;
        }

        public function setI($i) {
                $this->i = $i;
        }

        public function setJ($j) {
                $this->j = $j;
        }
    //还有很多很多...
}
[/php]

请不要认为我这是在凑字数,因为你得相信我,我没有稿费,所以不用故意拖这么多字数。

在一般的面向对象的编程中,这种情况是相当常见的,我曾经用Struts2写过一个音乐分享的网站,单单一个注册,因为注册要获取的字段很多,类似PHP 的$_POST,Struts2的机制就是给每个Form元素都设定一个Getter和Setter,这样下来,单单一个注册的类就有多的数不清的 Getters和Setters。这是个非常艰巨的任务,程序员是写程序的,不是抄程序的,所以强大的Eclipse就为我们提供了一个简单的方法,只要 你设定了类的属性,你就可以通过鼠标点击来生成相应的Getter和Setter,所以你不要认为上面的代码是我傻乎乎写的,我是个懒人,十分会偷懒 的:)

扯远了,PHP为了解决这个问题,而且又有魔术方法的基础,所以PHP创造了 __set() 和 __get() 。你只需要写一个方法,就可以代表上面大部分的Getters和Setters。

[php]
class Test {
        private $a;

        public function __set($key, $value) {  //必须是两个参数,第一个是属性,第二个是设置属性的值。
                $this->$key = $value;
                //这里不明白为什么是$this->$key ?? 一般不是这样写吗? $this->key ? 请看我下面的分解。
        }

        public function __get($key) {  //一个参数,要获得的属性
                return $this->$key;
        }
}
$test = new Test();
$test->__set('a', '我是A');  //给 属性a 设置一个值
echo $test->__get('a');  //打印a   输出  我是A
[/php]

通过上面的例子,我们就给A设置了一个值,并且打印了出来。

现在我来说上面为什么是$this->$key。这要说明白也是个不小的话题,PHP比较神奇,她可以设置一个变量的变量,什么意思呢?来看下面的例子。

[php]
$a = 'b';
$b = 'c';
echo $$a;  //输出c
[/php]

哇好神奇,竟然有连个$,好多的钱啊。下面我给你分解下。

[php]
$a = 'b';
$b = 'c';
echo ${$a};  //这样看的话是不是就明白多了呢? $a='b',所以大括号中就是b了,加上前面的$,就变成变量$b了。
[/php]

回到上面的类中,你可以注意到,$key只是我们需要传进来的一个内部属性,不是我们真正需要的,如果$this->key这样就会编程$key这 个属性的值了,而我们需要设置的是$this->a的值,所以这里就需要使用变量的变量了。明白了吗?有点绕,好好想想:)

经过这么折腾,我想你已经会一点 __get() 和 __set() 的使用方法了。但是上面的代码还不能用于实际的代码中,因为在实践中,往往内部属性的个数是不定的,你想到了什么解决办法?对,就是数组,PHP中数组的 长度是不定的,所以我们就可以给他无限增加元素。

[php]
class Test {
   public $values = array();  //存放属性的数组,以键值对的形式存在的。

   public function __get($name){
       return $this->values[$name];
   }

   public function __set($name, $value){
       $this->values[$name] = $value;
   }
}
$test = new Test();
$test->__set('a', '我是a');
$test->__set('b', '我是b');
echo $test->__get('a');  //输出  我是a
echo $test->__get('b');  //输出  我是b
[/php]

这样的话,如果是注册的页面,我们就可以灵活的获得和设置属性了。但是在实际运用中,我们知道传过来的值是不可靠的,我们得需要HTML转义,Mysql转义等等,所以我们可以写一个简单的回调函数,做数据验证的作用。

[php]
class Test {
   public $values = array();

   public function __get($name){
       return $this->values[$name];
   }

   public function __set($name, $value){
       $this->values[$name] = $this->validate($value);
   }
   private function validate($value){
       return htmlspecialchars(addslashes($value));
       //等等
   }
}
$test = new Test();
$test->__set('a', '<a>我是a</a>');
echo $test->__get('a');  //输出  &lt;a&gt;我是a&lt;/a&gt; 被转义啦...
[/php]

但是有的人不需要这样的,比如我前面提交的页面就有用户名和密码,不想再要别的了。当然,你如果用两个私有属性也可以解决,但是我说一下用一个数组的方法。

[php]
class Test {
        public $values = array('name' => '名字', 'passwd' => '密码');  //提前规定私有属性只有name和passwd

        public function __get($name){
                if (isset($this->values[$name])) {  //判断一下
                        return $this->values[$name];
                } else {
                        echo '没有此属性!';
                }
        }

        public function __set($name, $value){
                if (isset($this->values[$name])) {  //这里也判断一下
                        $this->values[$name] = $this->validate($value);
                } else {
                        echo '没有此属性!';
                }
        }
        private function validate($value){
                return htmlspecialchars(addslashes($value));
                //等等
        }
}
$test = new Test();
$test->__set('name', '<a>我是name</a>');  //设置name 无输出
$test->__set('我也不知道这个是什么', '<a>看看是我的值是多少</a>');  //输出 没有此属性!
echo $test->__get('name');  //输出  &lt;a&gt;我是a&lt;/a&gt;
echo $test->__get('随便看看');  //输出 没有此属性!
[/php]

到这里,我们算是基本掌握了 __set() 和 __get() 了,其实要学精通还是需要你自己去努力的,我只是带你入门而已。下面我们来看看 __isset() 和 __unset() 。

其实__isset() 和 __unset() 与 __sleep()和__wakeup() 是有点像的,__isset() 和 __unset() 是在触发isset()和unset()函数前的回调函数。我们继续来做例子。

[php]
class Test {
        public $a;

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

        public function __isset($name) {
                echo "你小子偷看我??";
        }

        public function __unset($name) {
                echo "你没权利宰了我!";
        }
}
$test = new Test("我是a");
isset($test->a); //无任何输出
[/php]

执行上面的代码,结果无任何输出,但是刚才我说了,只要执行isset()就要执行__isset()这个回调函数的啊,回调函数里面输出 “你小子偷看我??” ,但是为什么结果却没有输出呢?难道是我说错了??我不能欺骗大家啊,所以我得给大家说清楚,不是我错了,也不是程序错了,是PHP的问题!

在PHP中关于 __isset() 和 __unset() 有个约定,就是当用 isset() 函数测试成员属性的时候,如果这个属性为public属性或者默认属性时,就不回调,当为protected或者private时就回调。我语言表达能力 差,还是用代码表示吧。

[php]
class Test {
        var $a;  //这样就不输出
        public $a;  //这样也不输出

    protected $a;  //输出 你小子偷看我??
    private $a;  //输出 你小子偷看我??

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

        public function __isset($name) {
                echo "你小子偷看我??";
        return isset($this->a);  //返回真是的信息
        }

        public function __unset($name) {
                echo "你没权利宰了我!";
        }
}
$test = new Test("我是a");
isset($test->a);
[/php]

这样明白了吧?__unset()是一样的,这两个函数其实也有大作用,比如你做一个网游(我只是比喻,现实不是这样的),有个怪物类中有个属性是怪物的 血,你不能想unset()就unset(),也就是说你不能等怪物的血没完了就使他死了。我还用例子说明。这个例子我自认为相当的好,自恋一下:)把上 面的内容都囊括了,希望你可以好好执行下,分析下,为什么会出这样的结果。

[php]
class Monster {
        /**
         * 怪物的属性表
         *
         * @var array
         */
        private $state = array('hp' => 100, 'mp' => 100);

        /**
         * 打击怪物
         *
         * @access public
         * @return void
         *
         */
        public function hit() {
                $this->state['hp'] = $this->state['hp'] - rand(1, 10);
        }

        /**
         * 设置怪物属性
         *
         * @param string $key
         * @param integer $value
         * @access public
         * @return void
         */
        public function __set($key, $value) {
                if (isset($this->state[$key])) {
                        $this->state[$key] = $this->validate($value);
                } else {
                        echo '没有此属性!';
                }
        }

        /**
         * 获得怪物属性
         *
         * @param string $key
         * @access public
         * @return integer
         */
        public function __get($key) {
                if (isset($this->state[$key])) {
                        return $this->state[$key];
                } else {
                        echo '没有此属性!';
                }
        }

        /**
         * 验证设定怪物的血是否合法
         *
         * @param integer $value
         * @access private
         * @return void
         */
        private function validate($value){
                if (is_int($value) && $value > 0) {
                        return $value;
                } else {
                        return 0;
                }
        }

        /**
         * 查看是否存在怪物的状态
         *
         * @param string $key
         * @return boolean
         */
        public function __isset($key) {
                return isset($this->state[$key]);
        }

        /**
         * 查看怪物的属性是否为0
         *
         * @param string $key
         * @access public
         * @return void
         */
        public function __unset($key) {
                if ($this->state[$key] > 0) {
                        echo '你没权利宰了我!';
                } else {
                        unset($this->state[$key]);
                        echo '我死了!';
                }
        }
}

$monster = new Monster();
$monster->__set('hp', 100);  //设定怪物的血和蓝
$monster->__set('mp', 100);
$monster->__set('血', 200);   //输出 没有此属性!
while (isset($monster->hp)) {  //当怪物还存在的时候
        $monster->hit();           //开始打怪
        unset($monster->hp);       //消灭怪物
        echo $monster->__get('hp');  //输出怪物剩余血量 当怪物死后输出 没有此属性!
}
[/php]

写网游一直是儿时的梦想,但是现在比较现实了,根本不可能,所以我就在这里写个虚假的类来YY下吧,大家不要BS我:)

好了,今天的笔记到此为止,明天我们将要学习 __call() 。构造函数和析构函数我不准备说了,思想不是PHP里特有的,看些OOP的教程都有讲。
明天要毕业答辩,不知道笔记会不会按时发布,请期待。

 

昨天答辩实在曲折,到现在还没有开始,废话不说了,感觉继续奉上教程笔记。

今天我们要说的是__call() 和其他一些不常用的模式方法,我也许只会点到一下,如果你想搞的非常透彻还需要自己下功夫。

__call() 是个非常重要的魔术方法,可以说非常非常的重要。至于为什么,相信你学习完今天的笔记就会明白了。

当我们实例化一个类的对象后,调用类中的一个方法,比如$test->fun();。系统就会从这个类中去找fun()这个方法,如果找到了就去执行它,如果没有找到就去调用 __call()。
我们来看下例子:

[php]
class Test {

        public function __call($fun, $args) {  //第一次参数是方法名,第二个参数是传过来的参数,是以数组的方式传过来的。
                echo "你在调用".$fun."方法";
                print_r($args);
        }
}
$test = new Test();
$test->fun("a","b");  //调用一个不存在的方法 参数是a和b .
//输出  你在调用test方法Array ( [0] => a [1] => b )
[/php]

从上面的例子,我们可以清晰的分析出__call()方法的执行过程。但是我们想,如果类中存在一个同名的private方法,PHP会如何处理呢?我们来看下面的例子。

[php]
class Test {

        public function __call($fun, $args) {
                echo "你在调用".$fun."方法";
                print_r($args);
        }

        private function fun($a, $b) {
                echo "你再调用类中的private方法";
        }
}
$test = new Test();
$test->fun("a","b");  //调用类中存在的private方法
//输出  Fatal error: Call to private method Test::fun() from context '' in PHPDocument3 on line 14
[/php]

结果输出了错误,看来这种时候,魔术方法也不起作用。系统直接报错了。

现在我们再来想,如果调用本类中不存在的方法,而父类中存在的方法,是先调用本类的__call()呢还是先调用父类中存在的方法呢?我们继续来做实验。

[php]
class Test {

        public function __call($fun, $args) {
                echo "你在调用父类的".$fun."方法";
                print_r($args);
        }

        public function fun() {
                echo "你在调用父类的test方法";
        }
}
class Test2 extends Test {

        public function __call($fun, $args) {
                echo "你在调用子类的".$fun."方法";
                print_r($args);
        }
}
$test2 = new Test2();
$test2->fun("a","b");  //调用父类存在的方法  输出  你在调用父类的test方法
$test2->abc();  //调用都不存在的方法  输出  你在调用子类的abc方法Array ( )
[/php]

结果,我们可到这样的现象,子类跳过了__call()而直接寻找父类中的方法,如果调用一个都不存在的方法,就调用本类的__call()方法,这个是由于子类的__call()把父类的__call()给覆盖了。

所以我们就可以得出这样的结论,在PHP中,系统对__call()方法的调用顺序是这样的:

当我们实例化一个类的对象后,调用类中的一个方法,比如$test->fun();。系统就会从这个类中去找fun()这个方法,如果存在就调用 之,如果不存在就去父类中找这个方法,如果父类中也不存在就去找本类的__call()方法,如果本类中不存在__call()方法就去找父类中的方法, 以此类推。当然这个是在调用权限允许的范围内的。

下面说我为什么说__call()重要。

我们都知道,在PHP5中并没有真正的支持重载。但是我们可以想办法去实现它,借助的工具就是__call()。我们来看例子。

[php]
class Test {

        public function __call($fun, $argps) {
                if (method_exists($this, $fun.count($argps))) {
                        return call_user_func_array(array(&$this, $fun.count($argps)), $argps);
                      
                } else {
                        throw new Exception('调用了未知方法:'.get_class($this).'->'.$fun);  //不存在的方法
                }
        }
    //一个参数的方法
        public function fun1($a) {
                echo "你在调用一个参数的方法,参数为:".$a;
        }
    //两个参数的方法
        public function fun2($a, $b) {
                echo "你在调用两个参数的方法,参数为:".$a."和".$b;
        }
}
$test = new Test();
$test->fun("a");  //输出  你在调用一个参数的方法,参数为:a
$test->fun("a","b");  //输出  你在调用两个参数的方法,参数为:a和b
$test->fun("a","b","c");  //输出 Fatal error: Uncaught exception 'Exception' with message '调用了未知方法:Test->fun'
[/php]

这样我们基本就可以完成了重载了。

另外提下下面不常用的两个魔术方法。
第一个是__toString() 。这个著名的方法在Java中是非常常见的,但是到PHP这里我基本没见多少人用过了。原因是这样的,我们看下例子:

[php]
class Test {
        public $a;
        public function fun(){
                echo "我只是一个方法而已.";
        }
}
$test = new Test();
echo $test;  //输出错误 Catchable fatal error: Object of class Test could not be converted to string
[/php]

如果我们想打印出一个对象,就需要调用__toString()这个魔术方法了,我们给他加上一个__toString()就不会出错了。

[php]
class Test {
        public $a;
        public function fun(){
                echo "我只是一个方法而已.";
        }
        public function __toString() {
                return "你在打印一个对象";  //这个方法必须返回一个字符串
        }
}
$test = new Test();
echo $test;  //输出  你在打印一个对象
[/php]

这个比较简单,我不多说了,还有一个方法,在Google搜索都搜不到几个有价值的东西,可见其少用的程度,汗一下。这个魔术方法就是:__set_state();
这个方法其实也比较简单,就是var_export()的回调函数。我们来看下例子。

[php]
class Test {
        public $a;
        public function fun(){
                echo "我只是一个方法而已.";
        }
}
$test = new Test();
var_export($test);  //输出  Test::__set_state(array( 'a' => NULL, ))
[/php]

注意a是NULL,没有赋值,下面我写个回调

[php]
class Test {
        public $a;
        static function __set_state($array) {  //必须为静态方法.参数是个数组
                $tmp = new Test();
                $tmp->a = "abc";  //我直接赋值,
                return $tmp;  //必须返回一个对象.可以是其他类的对象
        }
}
$test = new Test();
eval( '$b = '  . var_export($test, true).  ';' );
var_dump($b);  //得到的$b就是Test的一个对象.并且a="abc"
[/php]

有人问这有什么用?是没多大用,最大的作用可以复制一个对象。只需改下代码而已。

[php]
class Test {
        public $a;
        static function __set_state($array) {  //必须为静态方法.参数是个数组
                $tmp = new Test();
                $tmp->a = $array['a'];
                return $tmp;  //必须返回一个对象.可以是其他类的对象
        }
}
$test = new Test();
$test->a = "我是$test";
eval( '$b = '  . var_export($test, true).  ';' );
var_dump($b);  //得到的$b就是$test的复制.并且a="abc"
[/php]

有人又会问了,克隆可以直接clone()方法啊!!那么好,这样的情况请问你如何克隆,我们来看下代码:

[php]
class Test {
        public $a;
        static function __set_state($array) {  //必须为静态方法.参数是个数组
                $tmp = new Test();
                $tmp->a = str_replace('$test','$b',$array['a']);  //!important
                return $tmp;  //必须返回一个对象.可以是其他类的对象
        }
}
$test = new Test();
$test->a = '我是$test';
eval( '$b = '  . var_export($test, true).  ';' );
var_dump($b);  //得到的$b就是$test的复制.但是b做相应的改变b='我是$b'
[/php]

这样的话,我们虽然克隆了一个$test,但是我们又做了相应的改变。请允许我称为这种方法为“进化”。进化就是在克隆的基础上做一些自己相应的改变。
有的人问,你为什么不用__clone进行回调呢?这正是我想说的地方,个人认为__clone() 没有 __set_state()强大,因为__clone()没有可以接受的参数,局限了“进化”的范围,我用个例子说明。

[php]
class Test {
        public $a;

        function __clone(){
                $this->a = str_replace("a","克隆a",$this->a);  //无法通过参数改变a,但是我们还是可以想办法的:)
        }
}
$test = new Test();
$test->a = '我是a';
$b = clone $test;
var_dump($b);  //得到的$b就是$test的复制.并且a='我是克隆a'
[/php]

真累啊,基本方法都提到了,但是我只是带大家入门,更多更深的内容还需要大家自己挖掘。有些地方我还没有提到,所以大家要学会去主动学习。

另外,顺便提下,这些魔术方法都必须被定义为public方式的。因为你要在所有的地方都得调用它们。

好了。这个还没完,下此会有更神奇的东西,改变你对PHP的看法,请关注。

[php]

<?php

class Test {
    private $mySecret;  //我的秘密不想让人知道
    public function __construct($secret) {
        $this->mySecret = $secret;
    }
    public function __sleep() {
        $this->mySecret = "这是个秘密!";    // 外部不可见.必须调用成员方法输出值.
        echo '不让你知道';  

    }
    public function __wakeup() {
        
                 
      

    }
       
        public function get(){
       
                return $this->mySecret;
        }
}

$obj = new Test('i like php !');
echo $obj->get();
echo '<br />';

serialize($obj);          // __sleep()
echo $obj->get();
echo '<br />';

unserialize($obj);   // __wakeup()
echo $obj->get();

$obj = new Test('php');
echo $obj->get();

?>

[/php]

大家等我教程等着急了吧?这几天事情比较多。

看到很多人看完上面的笔记还是有很多人不明白,不清楚,或者说有疑问的地发。不要着急,我今天绝对打消您的疑问。您瞧好吧。

今天我们利用前几天学习的知识,学习如何写一个动态类,其实是就是一个具有柔韧性的类。

什么是一个具有柔韧性的类?其实我也说不清楚,有的人更愿意称之为可以弯曲的类,因为这个类可以随你的意愿进行有特点的伸展。

我们上次提到了__set()这个魔术方法,又提到了,其实魔术方法之所以称之为魔术方法是由于我们不用主动去触发这个方法它就会自动的运行。

有的人会问了,在学习__set()的时候,我们是主动去调用它的啊。跟别的,比如__isset()方法有些不一样。其实在真正的运用中,__set()的使用方法不是上面那样的,请允许我为这篇文章埋下一个伏笔。今天我们就来学习__set()的最神奇的地方。

一个柔韧性的类的一个重要的特征应该就是属性具有柔韧度。如何制作一个具有伸缩属性的类?看下面的例子。

[php]
class Test {
        private $array;  //PHP数组就是具有伸缩性的这个特性

        public function __set($key, $value) {  //__set()魔术方法,设置$array的值。
                $this->array[$key] = $value;
        }

        public function get() {
                print_r($this->array);
        }
}
$test = new Test();
$test->a = 'a';  //这样系统就会自动调用__set()这个魔术方法
$test->b = 'b';  //这样下去,我们就可以设置无数个属性。
$test->c = 'c';  //你还可以设置$test->d 。没有人拦着你,谁拦你我拦他!
$test->get();  //输出  Array ( [a] => a => b [c] => c )
[/php]

这只是个简单的例子,如果你要限制一些属性,该如何设置呢?还记得前面的课程吗?

[php]
class Test {
        private $array;

        public function __construct() {  //在构造函数中我们可以设置一些属性。
                $this->array['a'] = null;
                $this->array['b'] = null;
        }

        public function __set($key, $value) {  //__set()魔术方法,进行一下简单的判断就OK
                if (array_key_exists($key, $this->array)) {
                        $this->array[$key] = $value;
                } else {
                        throw new Exception("没有此属性");
                }
        }

        public function get() {
                print_r($this->array);
        }
}
$test = new Test();
$test->a = 'a';
$test->b = 'b';
$test->c = 'c';  //当程序执行到这步的时候就会抛出异常。
$test->get();
[/php]

有人肯定又会说了,我靠。你耍我们玩啊,一会说要写个可以伸缩的类,然后又说要做限制,你怎么回事啊?

莫急莫急,我这就跟您慢慢解释。要写个具有伸缩性的类,上面我说了最简单的例子了,大家应该都明白了。

但是在实际的程序中,我们并不需要这样伸缩性的类,因为这样的类会变得不安全,未知因素也很多。这不是我们设计程序的初衷。

但是,在实际的运用中,我们又需要根据实际情况来设定一些变化的因素。这样就会有了矛盾了,既要伸缩性,但是在某种程度上又不允许程序具有伸缩性。

这是个非常棘手的问题,虽然我们绕弯避免。最简单的解决办法就是多申请几个属性,想用就用。但是我们既然学了魔术方法,就要使用魔术方法来做点事情。值得 注意的是,构造函数在PHP里也算是一个魔术方法。所以我们来写个要伸就伸,要缩就缩的类来。这种类的用处非常大,等下你就知道了。

[php]
class TestA {
        private $array = array();
        public function __construct($array) {  //在父类中创建一个具有伸缩性的构造方法
                foreach( $array as $key )
                $this->array[ $key ] = null;
        }
        public function __set($key, $value) {  //__set()魔术方法,进行一下简单的判断就OK
                if (array_key_exists($key, $this->array)) {
                        $this->array[$key] = $value;
                } else {
                        throw new Exception("没有此属性");
                }
        }
        public function get() {
                print_r($this->array);
        }
}
class TestB extends TestA {
        public function __construct($array) {  //只要重写构造函数就可以申请自己的属性值.
                parent::__construct($array);  //调用父类的构造方法
        }
}
$test1 = new TestB(array('a', 'b'));  //想要几个属性就要几个属性
$test1->a = 'a';
$test1->b = 'b';
$test1->get();  //输出  Array ( [a] => a => b )
$test2 = new TestB(array('a', 'b', 'c'));  //想要几个属性就要几个属性
$test2->a = 'a';
$test2->b = 'b';
$test2->c = 'c';
$test2->get();  //输出  Array ( [a] => a => b [c] => c )
[/php]

这下明白了吧?我们是通过继承来实现这种中性的伸缩的。这就叫伸缩自如。哈哈。

如果你体会到了其中的好处,你会发现,PHP的类在这方面会比Java要强大的多。我们下面试图用这种思路来写一个简单的数据库类。

[php]
class DB {
        public function connect($dbhost, $dbuser, $dbpwd, $dbname = '', $charset = 'utf8') {
                mysql_connect ( $dbhost, $dbuser, $dbpwd );
                mysql_query ( "SET NAMES '$charset'" );
                mysql_select_db ( $dbname );
        }
        public function insert($sql) {   //插入数据
                if (! ($query = mysql_query ( $sql ))) {
                        return -1;
                }
                return mysql_insert_id();
        }
}
[/php]

上面这是个最基础的类,我写的很垃圾,你可以再修改,但是我们现在最重要的是让他可以具有伸缩性。所以我这样修改。

[php]
class DB {
        private $table = '';  //定义表
        private $field = array();  //表里面的字段
        public function __construct($table, $field) {  //在父类中创建一个具有伸缩性的构造方法
                $this->table = $table;
                foreach( $field as $key ) {
                        $this->$field[ $key ] = null;
                }
        }
        public function connect($dbhost, $dbuser, $dbpwd, $dbname = '', $charset = 'utf8') {
                mysql_connect ( $dbhost, $dbuser, $dbpwd );
                mysql_query ( "SET NAMES '$charset'" );
                mysql_select_db ( $dbname );
        }
        public function insert() {  //插入一个数据
                $fieldName = join(", ", array_keys($this->field));  //获得字段名
                array_walk($this->field, array(&$this, 'filter'));  //过滤字段的值.可以看下下面的回调函数
                $fieldValue = join(", ", $this->field);  //转换成**,**,**这种形式
                $sql = "INSERT INTO %s(%s) VALUES(%s)";
                $sql = sprintf($sql, $this->table, $fieldName, $fieldValue);
                if (!(mysql_query ( $sql ))) {
                        return -1;
                }
                return mysql_insert_id();
        }
        public function filter(&$value) {  //过滤字段的值
                if (!is_numeric($value)) {    //如果是数字就不管.如果是其他类型必须加入''才可以正常插入到数据库中
                        $value =  "'".addslashes($value)."'";
                }
        }
}
[/php]

你看完后,肯定会说,好麻烦啊,你为什么把一个简单的插入数据搞得这么复杂啊。你傻吧你!

很客观的告诉你,我不傻,这么做是有好处的。如果我写了一个这样类,那么面向用户是非常好的,扩展性非常好,并且容易操作。我们来看下,当你把我这个类当做父类后,看下你的操作是多么的简单。

[php]
class TableA extends DB {
        public function __construct() {  //只要重写构造函数就可以申请自己的属性值.
                parent::__construct('tablea',array('id', 'key', 'value'));  //调用父类的构造方法
        }
}
$tablea = new TableA();
$tablea->id = 123;
$tablea->key = '我是key';
$tablea->value = '我是value';
$tablea->insert();
[/php]

看到了吧?我们几乎可以使MySqL变成一个对象型的数据库,因为我们可以给每个表都写个类,一个类对应一个表,执行操作的时候不用写SQL语句,非常简单,不容易出错,这不是传说中大名鼎鼎的Hibernate吗?

说Hibernate有点言过其实了,主要是我们改变了PHP与MySql的一般操作方式。但是这样的问题就在于父类不好写,比较麻烦。对于一般的新手还是非常困难的,所以希望个大牛能写出个通用的父类来,感激不尽啊。

好了,拖了这么长时间,笔记终于算写完了,但是PHP中魔术方法的内容远远没有这么少,所以要学习还得靠自己。我以后还会不断的出类似这样的教程笔记,希望大家关注,谢谢,再次感谢您能看完我这么多字的胡扯。


觉得set之所以称为魔术方法应该是这个原因
<?php
class test
{
        public $a;
        public function __set($name,$value)
        {
                $this->a[$name]=$value;
        }
}
$a=new test();
$a->b=2;
echo $a->a['b'];


[php]

<?php
class Test {
    private $a;

    public function __set($name, $value) {  //必须是两个参数

        $this->$name= $value;
        //这里不明白为什么是$this->$name ?? 一般不是这样写吗?

    }

    public function __get($name) {  //一个参数,要获得的属性
        if( isset($this->$name))
                {
                  echo "{$name} 的值是:";       
                  return $this->$name;       
                  
               
                } else {
               
       
                 $this->$name=0;
                 return "{$name} 未设置, {$name} 被初始化为0.<br />";
                 
                }
    }
}
$test = new Test();
$test->__set('a', '我是A');  
echo $test->__get('a');
echo '<br />';
echo $test->__get('b');
echo $test->__get('b');
?>`

[/php]


[php]

<?php

class Test {
    //var $a;  //这样就不输出
   // public $a;  //这样也不输出

    //protected $a;  //输出 你小子偷看我??
    private $a;  //输出 你小子偷看我??

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

    public function __isset($name) {
        echo "你小子偷看我??";
        return ($name);  //返回真是的信息
    }

    public function __unset($name) {
        echo "你没权利宰了我!";
                unset($name);                       // 将name 改为$this->a  才消除a??  
    }

        public function get(){
       
                return $this->a;
       
        }

}
$test = new Test("我是a");
isset($test->a);
unset($test->a);               // isset 和 __isset() 之间的执行关系是怎样的????
echo $test->get();

?>

[/php]


[php]
<?php

class Test {
    //var $a;  //这样就不输出
   // public $a;  //这样也不输出

    //protected $a;  //输出 你小子偷看我??
    private $a;  //输出 你小子偷看我??

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

    public function __isset($name) {
        echo "你小子偷看我??";
        return isset($name);  //  这里需要返回一个bool类型.应该这样写  如果不想让别人知道就直接 return false;
    }

    public function __unset($name) {
        echo "你没权利宰了我!";
        unset($name);                       // $name为形参.当然不能删除$this->a
    }

    public function get(){
   
        return $this->a;
   
    }

}
$test = new Test("我是a");
isset($test->a);
unset($test->a);               // 当执行isset()的时候.系统就去找是否存在__isset() 如果存在就去执行.就是isset()的一个回调.
echo $test->get();

?>

[/php]