静态方法和属性

  • 我们将类当做是对象的模板,对象作为活动的组件
  • 我们可以用类这个工厂生产的对象去做事情,自然也可以通过类本身来做事情
  • static 这个关键字帮助我们来完成

1. 类调用静态方法和静态属性

  • 前面的我们可以在子类中通过 parent:: 父类的方法和属性
  • 静态方法是以当前类作为作用域的,所以静态方法不能访问这个类的普通属性,普通属性也就是我们所说的非静态的属性它是为了对象服务的,而静态的属性是为了类而服务的
  • 如果在本类中访问静态的属性或者是方法,需要使用关键字 self::
  • self 指向当前类,而$this 这个伪变量指向当前类的对象
  • 只有在使用parent关键字的时候,才能对一个非静态的方法进行静态式的调用


  • 静态方法和静态属性又被称之为类变量和属性,所以我们不能用对象调用静态方法
  • 也不能在静态方法中使用伪变量 $this
  1. <?php
  2. class StaticExample
  3. {
  4. public static $aNum = 0;
  5. public static function sayHello()
  6. {
  7. print 'hello <br>';
  8. }
  9. }
  10. // 类名调用静态方法
  11. StaticExample::sayHello(); // hello
  12. // 类名调用静态属性
  13. echo StaticExample::$aNum; // 0

常量属性

1. 类中的常量属性

  • 常量的特性

    • 值不变
    • 字母或者下划线开头
    • 全局变量,作用域贯穿这个文件
    • 我们习惯于用 大写英文字母和下划线来对常量命名
  • 类中的常量 const

    • PHP 5.3 只能够在类中定义 const ,之后的版本外部也可以。

2. const 和 define 的比较

  • const 是一个语言结构 , define 是一个函数,所以性能上 const更好
  • define() 函数中 可以设置第三个参数为true来改变大小写敏感问题
  • define() 不可以用于类中。
  • 我个人更倾向于使用 const,这个更加直观

抽象类

  • 抽象类(abstract class)不能够直接被实例化,它只是定义部分实现,让子类去实现这些方法。
  1. <?php
  2. abstract class ShopProductWrite
  3. {
  4. protected $products = array();
  5. public function addProduct (Shop Product $shopProduct)
  6. {
  7. $this->products[] = $shopProduct;
  8. }
  9. }
  • 抽象类至少包含一个抽象方法 我们同样使用关键字 (abstract)
  1. <?php
  2. abstract class ShopProductWrite
  3. {
  4. protected $products = array();
  5. public function addProduct (Shop Product $shopProduct)
  6. {
  7. $this->products[] = $shopProduct;
  8. }
  9. abstract public function write()
  10. }

创建抽象方法后,要确保所有的子类都实现了该方法

  1. <?php
  2. class ErrorredWriter extends ShopProductWrite{
  3. ...
  4. ...
  5. // 实现抽象方法
  6. }

接口

  • 接口:定义一组开发规范
    • 抽象类为我们提供了具体实现的标准,而接口(interface)则是纯粹的模板
    • 接口只是定义了功能,不包含任何实现的内容
  1. <?php
  2. interface Chargeable
  3. {
  4. public function getPrice();
  5. }
  • 任何实现接口的类,都必须实现接口中的所有方法,除非该类是抽象类
  1. <?php
  2. class ShopProduct implements Chargeable
  3. {
  4. // ...
  5. public function getPrice()
  6. {
  7. return $this->price * $this->discount;
  8. }
  9. // ...
  10. }
  • 类是单继承的,而接口可以是多实现的
  • 子类可以同时继承一个父类并实现多个接口
  1. <?php
  2. class Consultance extends TimeService implements Bookable, Changreable
  3. {
  4. // ...
  5. }

延迟静态绑定

  • 先看下面的代码:我们创建了一个抽象类 DomainObject,它的两个子类都包含静态方法 create,用于生产各自类的实例
  1. <?php
  2. abstract class DomainObject
  3. {
  4. }
  5. class User extends DomainObject
  6. {
  7. public static function create()
  8. {
  9. return new User();
  10. }
  11. }
  12. class Document extends DomainObject
  13. {
  14. public static function create()
  15. {
  16. return new Document();
  17. }
  18. }
  • 我感觉有重复的代码,然后我把 create() 方法放在 DomainObject类中
  • 看起来是很美好,但是
    • self 关键字指的是解析上下文,而不是调用上下文。和 $this 不同
    • 所以下面的带吗运行就会报错。
    • self 解析定义的create() 为 DocumentObject,而不是你所想的 Document类,哈哈
  1. <?php
  2. <?php
  3. abstract class DomainObject
  4. {
  5. public static function create()
  6. {
  7. return new self();
  8. }
  9. }
  10. class User extends DomainObject
  11. {
  12. }
  13. class Document extends DomainObject
  14. {
  15. }
  16. // 然后我们在使用的时候
  17. $document1 = Document::create();
  18. var_dump($document1);
  • PHP在5.3 后引入了延迟静态绑定的概念 static ,static指的是被调用的类而不是包含的类 ,就像Document::create() 生成的一个新的对象。所以我们可以这么写
  1. <?php
  2. <?php
  3. abstract class DomainObject
  4. {
  5. public static function create()
  6. {
  7. // 静态上下文中使用继承关系
  8. return new static();
  9. }
  10. }
  11. class User extends DomainObject
  12. {
  13. }
  14. class Document extends DomainObject
  15. {
  16. }
  17. // 然后我们在使用的时候
  18. $document1 = Document::create();
  19. var_dump($document1);
  20. /**
  21. * F:\phpStudy\PHPTutorial\WWW\Numb\Basic\test5.php:21:
  22. * object(Document)[1]
  23. */
  • static 关键字不仅仅可以用于实例化,同样的可以和 self parent 一样作为静态方法的调用符
  • 我惊奇的发现PHP中竟然可以重写父类的静态方法,我靠惊呆了。
  1. <?php
  2. abstract class DomainObject
  3. {
  4. private $group;
  5. public function __construct()
  6. {
  7. $this->group = static::getGroup();
  8. }
  9. public static function getGroup()
  10. {
  11. return 'defaulf';
  12. }
  13. public static function create()
  14. {
  15. return new Static();
  16. }
  17. }
  18. class User extends DomainObject
  19. {
  20. }
  21. class Document extends DomainObject
  22. {
  23. public static function getGroup()
  24. {
  25. return 'document';
  26. }
  27. }
  28. class SpreadSheep extends Document
  29. {
  30. }
  31. // User Object ( [group:DomainObject:private] => defaulf )
  32. print_r(User::create());
  33. // SpreadSheep Object ( [group:DomainObject:private] => document )
  34. print_r(SpreadSheep::create());

错误处理

1. 异常

  • PHP5 引入了异常(exception)
  • 异常是从PHP5 内置的类 Exception 或它的子类实例化得到的特殊对象
方法 描述
getMessage() 获取传递给构造方法的消息字符串
getCode() 获取传递给构造方法的错误代码
getFile() 获取产生异常的文件
getLine() 获取产生异常的行号
getPrevious() 获取一个嵌套的异常对象
getTrace() 获取一个多维数组,这个数组追踪导致异常的方法调用,包含方法、类、文件和参数数据
getTraceAsString 获取getTrace的字符串版本
__toString() 在字符串中使用Exception对象是自动调用,返回一个描述异常细节的字符串

2. 抛出异常

  • 可以联合使用 throw 关键字和 Exception对象来抛异常
  • 这个会停止当前正在执行的方法,将错误返回给调用代码
  1. <?php
  2. // ...
  3. function __construct()
  4. {
  5. $this->file = $file
  6. if (! file_exists($file)){
  7. throw new Exception("file '$file' does not exist");
  8. }
  9. }
  • try catch
  1. <?php
  2. try {
  3. $conf = new Conf( dirname(__FILE__) . "/conf01.xml");
  4. print "user : " . $conf->get('user') . "\n";
  5. print "host : " . $conf->get('host') . "\n";
  6. $conf->set("pass", "newpass");
  7. $conf->write();
  8. } catch (Exception", $e) {
  9. die( $e->__toString() );
  10. }

3. 异常的子类化

  • 我们可以创建用户自定义异常,通过继承Exception类
    • ① 扩展异常类的功能
    • ② 子类定义了新的异常类型,可以进行自己特有的错误处理
  • 实际上就算定义了多个try catch,我们一直使用一个 try 就够了
  1. <?php
  2. // ....

Final类和方法

  • final 修饰的类终止了类的继承
  • final 类不能有子类,方法不能被重写

拦截器

  • php 内置了拦截器(interceptor),它可以用来拦截发送到未定义的方法和属性
  • 也被称为重载(overload)
  • php支持如下的拦截器方法
方法 描述
__get($property) 访问未定义的属性时被调用
__set($property, $value) 给未定义的属性赋值时被调用
__isset($property) 对未定义的属性调用 isset()时被调用
__unset($property) 对未定义的属性调用 isset()时被调用
__call($method, $arg_array) 调用未定义的方法时被调用

析构方法

  • 我们在实例化对象时,construct() 方法会被自动调用,php还提供了一个应对的方法 destruct()
  • 它只是在对象从内存中删除之前,自动调用。
  • 可以利用这个方法进行必要的清理工作
  1. <?php
  2. class Person
  3. {
  4. private $name;
  5. private $age;
  6. private $id;
  7. function __construct($name, $age)
  8. {
  9. $this->name = $name;
  10. $this->age = $age;
  11. }
  12. function setID($id)
  13. {
  14. $this->id = $id;
  15. }
  16. function __destruct()
  17. {
  18. if (! empty($this->id)) {
  19. // 保存Person数据
  20. echo 'saving person <br>';
  21. }
  22. }
  23. }
  24. $person = new Person('bob', 44);
  25. $person->setID(343);
  26. unset($person); // saving person

克隆对象

  • PHP 中对象的赋值和传递都是通过引用进行的,像是下面的两个变量指向同一个对象的引用
  1. <?php
  2. class CopMe{}
  3. $first = new CopyMe();
  4. $second = $first;
  5. // php4:是两个完全不相同的对象
  6. // php5之后:指向同一个对象
  • 如果我们想要一个副本,可以使用 clone 关键字。
  • clone 使用“值复制”的方式新生成一个对象
  1. <?php
  2. class CopMe{}
  3. $first = new CopyMe();
  4. $second = clone $first;
  • 我们可以控制复制什么 __clone
  1. <?php
  2. class Person
  3. {
  4. private $name;
  5. private $age;
  6. private $id;
  7. function __construct($name, $age)
  8. {
  9. $this->name = $name;
  10. $this->age = $age;
  11. }
  12. function setID($id)
  13. {
  14. $this->id = $id;
  15. }
  16. function __clone()
  17. {
  18. $this->id = 0;
  19. }
  20. }
  21. $p1 = new Person('xs', 25);
  22. $p1->setID(19960118);
  23. $p2 = clone $p1;
  24. /**
  25. * p2:
  26. * name:xs
  27. * age:25
  28. * id:0
  29. */
  • 假设我们希望更为准确的克隆,有一个账户副本并不会去有单独这个属性的引用
  1. <?php
  2. <?php
  3. class Account
  4. {
  5. public $balance;
  6. function __construct($balance)
  7. {
  8. $this->balance = $balance;
  9. }
  10. }
  11. class Person
  12. {
  13. private $name;
  14. private $age;
  15. private $id;
  16. public $account;
  17. function __construct($name, $age, Account $account)
  18. {
  19. $this->name = $name;
  20. $this->age = $age;
  21. $this->account = $account;
  22. }
  23. function setID($id)
  24. {
  25. $this->id = $id;
  26. }
  27. function __clone()
  28. {
  29. $this->id = 0;
  30. $this->account = clone $this->account;
  31. }
  32. }
  33. $p1 = new Person('xs', 25, new Account(200));
  34. $p1->setID(19960118);
  35. $p2 = clone $p1;
  36. $p1->account = 1000;
  37. echo '<pre>';
  38. print_r($p1);
  39. print_r($p2);
  40. echo '</pre>';
  41. /*
  42. Person Object
  43. (
  44. [name:Person:private] => xs
  45. [age:Person:private] => 25
  46. [id:Person:private] => 19960118
  47. [account] => 1000
  48. )
  49. Person Object
  50. (
  51. [name:Person:private] => xs
  52. [age:Person:private] => 25
  53. [id:Person:private] => 0
  54. [account] => Account Object
  55. (
  56. [balance] => 200
  57. )
  58. )
  59. */

定义对象的字符串值

1. __toString()

  • PHP5 引入的这个方法,是受到了Java的启发
  • 通过 __toString() 方法,你可以控制字符串输出的格式
  • 我们常常在日志和错误报告用于这个方法
  1. <?php
  2. <?php
  3. class Person
  4. {
  5. private $name;
  6. private $age;
  7. function __construct($name, $age)
  8. {
  9. $this->name = $name;
  10. $this->age =$age;
  11. }
  12. function __toString()
  13. {
  14. $desc = 'name: ' . $this->name;
  15. $desc .= '; age: ' . $this->age;
  16. return $desc;
  17. }
  18. }
  19. $person1 = new Person('xs', 25);
  20. print $person1; // name: xs; age: 25

回调、匿名函数、闭包

1. is_callable()

  • 作用:检测参数是否为合法的可调用结构
  • bool is_callable ( callable $name [, bool $syntax_only = false [, string &$callable_name ]] )
  • 参数:
    • 第一个参数:要检查的回调函数
    • 第二个参数:默认false,可选参数。
    • 第三个参数:方法的名称。
  • 返回值:
    • 可以调用返回 true
    • 不可以调用返回 false

1.1 测试:匿名函数变量

  1. <?php
  2. <?php
  3. echo '<pre>';
  4. echo '测试1:匿名函数变量 <br>';
  5. $fun = function ($a, $b)
  6. {
  7. echo ($a+$b);
  8. };
  9. $res1 = is_callable($fun, true, $call_able_name1);
  10. var_dump($res1); // boolean true
  11. var_dump($call_able_name1); // string 'Closure::__invoke' 闭包::调用
  12. echo '-----------';
  13. $res2 = is_callable($fun, false, $call_able_name2);
  14. var_dump($res2); // boolean true
  15. var_dump($call_able_name2); // string 'Closure::__invoke' 闭包::调用
  16. echo '<pre>';

1.2 测试:普通函数

  1. <?php
  2. echo '<pre>';
  3. echo '测试2:普通函数 <br>';
  4. function fun1($num)
  5. {
  6. echo '__' . $num . '__';
  7. }
  8. $res1 = is_callable('fun1', true, $call_able_name1);
  9. var_dump($res1); // true
  10. var_dump($call_able_name1); // fun1
  11. echo '-----------';
  12. $res2 = is_callable('fun1', false, $call_able_name2);
  13. var_dump($res2); // true
  14. var_dump($call_able_name2); // fun1
  15. echo '<pre>';

1.3 测试:类方法

  1. <?php
  2. namespace OOP;
  3. class Person
  4. {
  5. public static function get($a)
  6. {
  7. echo $a;
  8. }
  9. protected function _set()
  10. {
  11. echo 1;
  12. }
  13. }
  14. $re1 = is_callable([new Person(), 'get'], false, $callable_name1);
  15. var_dump($re1); // true
  16. var_dump($callable_name1); // 'OOP\Person::get'
  17. $re2 = is_callable([new Person(), 'get'], true, $callable_name2);
  18. var_dump($re2); // true
  19. var_dump($callable_name2); // // 'OOP\Person::get'

1.4 测试:普通变量

<?php

$str = 'xs';

$res1 = is_callable($str, false, $call_able1);
var_dump($res1); // false
var_dump($call_able1); // xs

$res2 = is_callable($str, true, $call_able2);
var_dump($res2); // xs
var_dump($call_able2); // true


$number = 24;
$res3 = is_callable($str, false, $call_able3);
var_dump($res3); // false
var_dump($call_able3);

$res4 = is_callable($str, false, $call_able4);
var_dump($res4); // false
var_dump($call_able4); // xs

1.5 测试:和method_exists()方法比较

<?php

class P
{
    public function A($a)
    {
        echo $a;
    }
    public static function A_A($a)
    {
        echo $a;
    }

    protected function B($a)
    {
        echo $a;
    }
    protected static function B_B($a)
    {
        echo $a;
    }

    private function C($a)
    {
        echo $a;
    }
    private static function C_C($a)
    {
        echo $a;
    }
}

$p = new P();

var_dump(is_callable([$p, 'A']));       // true
var_dump(is_callable([$p, 'A_A']));     // true
var_dump(is_callable([$p, 'B']));       // false
var_dump(is_callable([$p, 'B_B']));     // false
var_dump(is_callable([$p, 'C']));       // false
var_dump(is_callable([$p, 'C_C']));     // false

var_dump(method_exists($p, 'A'));       // true
var_dump(method_exists($p, 'A_A'));     // true
var_dump(method_exists($p, 'B'));       // true
var_dump(method_exists($p, 'B_B'));     // true
var_dump(method_exists($p, 'C'));       // true
var_dump(method_exists($p, 'C_C'));     // true

1.6 is_callable() 总结

  • method_exists()

    • 方法不会受到访问权限修饰符的限制,只要方法存在就返回 true
  • is_callable()

    • 方法会受到访问权限修饰符的限制,只可以接受public修饰的方法
    • is_callable 这个函数说到底是为了验证第一个参数,是否是一个合法的回调结构
      • 可以接受匿名函数变量
      • 可以接受普通函数
      • 可以接受类中public修饰的函数
    • 它的第二个参数也仅仅是一个限制,如果我们将默认的值false改为true
      • 字符串的话只会检查它是否是一个合法的字符串,而不会考虑他是否是一个合法的回调结构
    • 第三个参数也仅仅是将这个传入参数的名称和定义结构说明一下
  • 我们最后在使用这个 is_callable()的方法时,其实只需要传入第一个参数,第二个参数放宽了限制,而第三个参数几乎很少会用到。

  • 如果是验证类方法,第一个参数我们以数组的形式传入,第一个变量表示对象,第二个字符串表示要验证的方法

2. call_user_func() 和 call_user_func_array()

  • 作用:把第一个参数作为回调函数调用
  • mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )
  • 参数:
    • 第一个参数:我们将要调用的回调函数
    • 后续参数:0个或以上的参数,被传入回调函数
  • 返回值:返回回到函数的函数值

2.1 测试:调用匿名函数

<?php
<?php

// 调用匿名函数
$func = function ($a, $b)
{
    echo $a * $b.'<br>';
};
call_user_func($func, 3, 4); // 12

2.2 测试:调用普通函数

<?php
function fun1($a, $b) {
    echo $a * $b.'<br>';
}
call_user_func('fun1', 3, 4); // 12

2.3 测试:调用类的内部函数

<?php
class Test1
{
    public function A($a, $b)
    {
        echo $a * $b . '<br>';
    }
    public static function A_A($a, $b)
    {
        echo $a * $b . '<br>';
    }

    protected function B($a, $b)
    {
        echo $a * $b . '<br>';
    }
    protected static function B_B($a, $b)
    {
        echo $a * $b . '<br>';
    }

    private function C($a, $b)
    {
        echo $a * $b . '<br>';
    }
    private static function C_C($a, $b)
    {
        echo $a * $b . '<br>';
    }
}
$t = new Test1();
// 非静态方法测试
call_user_func([new Test1(), 'A'], 3, 4); // 12
call_user_func([new Test1(), 'B'], 3, 4); // 访问不了
call_user_func([new Test1(), 'C'], 3, 4); // 访问不了

// 静态方法的测试
call_user_func('Test1::A_A', 3, 4); // 12
call_user_func('Test1::B_B', 3, 4); // 访问不了
call_user_func('Test1::C_C', 3, 4); // 访问不了

2.4 区别:call_user_func()和call_user_func_array()

  • 前者不支持参数的引用地址,后者支持
  • 用法上有一点区别:后者将需要传入到回调函数的参数单独设置一个数组存放其中
<?php
function increment(&$var)
{
    $var ++;
}
$a = 0;
call_user_func('increment', $a);
echo $a; // 0 警告

call_user_func_array('increment', array(&$a)); // You can use this instead
echo $a; // 1

2.5 小结

  • 这两个参数的作用一直,都是为了将第一个参数作为回调函数去调用
  • 从使用上感觉是使用 call_user_func_array() 上感觉更为直观一点,第二个参数是一个数组,专门用来存放第一个参数需要的参数。
  • 这连个方法虽然还是在很多不规范的使用情况下会调用,但是记住不要这么干,应该规范的使用,否则你看PHP都告诉你 警告 不赞成。
  • 或许我们在类的内部可以这么去使用这些方法比如: call_user_func([$this, $method]); 但是我暂时没感觉这么做的意义所在,我们这么做不是多此一举吗?暂时保留我一个菜鸡的疑惑