静态方法和属性
- 我们将类当做是对象的模板,对象作为活动的组件
- 我们可以用类这个工厂生产的对象去做事情,自然也可以通过类本身来做事情
- static 这个关键字帮助我们来完成
1. 类调用静态方法和静态属性
- 前面的我们可以在子类中通过 parent:: 父类的方法和属性
- 静态方法是以当前类作为作用域的,所以静态方法不能访问这个类的普通属性,普通属性也就是我们所说的非静态的属性它是为了对象服务的,而静态的属性是为了类而服务的
- 如果在本类中访问静态的属性或者是方法,需要使用关键字 self::
- self 指向当前类,而$this 这个伪变量指向当前类的对象
- 只有在使用parent关键字的时候,才能对一个非静态的方法进行静态式的调用
- 静态方法和静态属性又被称之为类变量和属性,所以我们不能用对象调用静态方法
- 也不能在静态方法中使用伪变量 $this
<?phpclass StaticExample{public static $aNum = 0;public static function sayHello(){print 'hello <br>';}}// 类名调用静态方法StaticExample::sayHello(); // hello// 类名调用静态属性echo StaticExample::$aNum; // 0
常量属性
1. 类中的常量属性
常量的特性
- 值不变
- 字母或者下划线开头
- 全局变量,作用域贯穿这个文件
- 我们习惯于用 大写英文字母和下划线来对常量命名
类中的常量 const
- PHP 5.3 只能够在类中定义 const ,之后的版本外部也可以。
2. const 和 define 的比较
- const 是一个语言结构 , define 是一个函数,所以性能上 const更好
- define() 函数中 可以设置第三个参数为true来改变大小写敏感问题
- define() 不可以用于类中。
- 我个人更倾向于使用 const,这个更加直观
抽象类
- 抽象类(abstract class)不能够直接被实例化,它只是定义部分实现,让子类去实现这些方法。
<?phpabstract class ShopProductWrite{protected $products = array();public function addProduct (Shop Product $shopProduct){$this->products[] = $shopProduct;}}
- 抽象类至少包含一个抽象方法 我们同样使用关键字 (abstract)
<?phpabstract class ShopProductWrite{protected $products = array();public function addProduct (Shop Product $shopProduct){$this->products[] = $shopProduct;}abstract public function write()}
创建抽象方法后,要确保所有的子类都实现了该方法
<?phpclass ErrorredWriter extends ShopProductWrite{......// 实现抽象方法}
接口
- 接口:定义一组开发规范
- 抽象类为我们提供了具体实现的标准,而接口(interface)则是纯粹的模板
- 接口只是定义了功能,不包含任何实现的内容
<?phpinterface Chargeable{public function getPrice();}
- 任何实现接口的类,都必须实现接口中的所有方法,除非该类是抽象类
<?phpclass ShopProduct implements Chargeable{// ...public function getPrice(){return $this->price * $this->discount;}// ...}
- 类是单继承的,而接口可以是多实现的
- 子类可以同时继承一个父类并实现多个接口
<?phpclass Consultance extends TimeService implements Bookable, Changreable{// ...}
延迟静态绑定
- 先看下面的代码:我们创建了一个抽象类 DomainObject,它的两个子类都包含静态方法 create,用于生产各自类的实例
<?phpabstract class DomainObject{}class User extends DomainObject{public static function create(){return new User();}}class Document extends DomainObject{public static function create(){return new Document();}}
- 我感觉有重复的代码,然后我把 create() 方法放在 DomainObject类中
- 看起来是很美好,但是
- self 关键字指的是解析上下文,而不是调用上下文。和 $this 不同
- 所以下面的带吗运行就会报错。
- self 解析定义的create() 为 DocumentObject,而不是你所想的 Document类,哈哈
<?php<?phpabstract class DomainObject{public static function create(){return new self();}}class User extends DomainObject{}class Document extends DomainObject{}// 然后我们在使用的时候$document1 = Document::create();var_dump($document1);
- PHP在5.3 后引入了延迟静态绑定的概念 static ,static指的是被调用的类而不是包含的类 ,就像Document::create() 生成的一个新的对象。所以我们可以这么写
<?php<?phpabstract class DomainObject{public static function create(){// 静态上下文中使用继承关系return new static();}}class User extends DomainObject{}class Document extends DomainObject{}// 然后我们在使用的时候$document1 = Document::create();var_dump($document1);/*** F:\phpStudy\PHPTutorial\WWW\Numb\Basic\test5.php:21:* object(Document)[1]*/
- static 关键字不仅仅可以用于实例化,同样的可以和 self parent 一样作为静态方法的调用符
- 我惊奇的发现PHP中竟然可以重写父类的静态方法,我靠惊呆了。
<?phpabstract class DomainObject{private $group;public function __construct(){$this->group = static::getGroup();}public static function getGroup(){return 'defaulf';}public static function create(){return new Static();}}class User extends DomainObject{}class Document extends DomainObject{public static function getGroup(){return 'document';}}class SpreadSheep extends Document{}// User Object ( [group:DomainObject:private] => defaulf )print_r(User::create());// SpreadSheep Object ( [group:DomainObject:private] => document )print_r(SpreadSheep::create());
错误处理
1. 异常
- PHP5 引入了异常(exception)
- 异常是从PHP5 内置的类 Exception 或它的子类实例化得到的特殊对象
| 方法 | 描述 |
|---|---|
| getMessage() | 获取传递给构造方法的消息字符串 |
| getCode() | 获取传递给构造方法的错误代码 |
| getFile() | 获取产生异常的文件 |
| getLine() | 获取产生异常的行号 |
| getPrevious() | 获取一个嵌套的异常对象 |
| getTrace() | 获取一个多维数组,这个数组追踪导致异常的方法调用,包含方法、类、文件和参数数据 |
| getTraceAsString | 获取getTrace的字符串版本 |
| __toString() | 在字符串中使用Exception对象是自动调用,返回一个描述异常细节的字符串 |
2. 抛出异常
- 可以联合使用 throw 关键字和 Exception对象来抛异常
- 这个会停止当前正在执行的方法,将错误返回给调用代码
<?php// ...function __construct(){$this->file = $fileif (! file_exists($file)){throw new Exception("file '$file' does not exist");}}
- try catch
<?phptry {$conf = new Conf( dirname(__FILE__) . "/conf01.xml");print "user : " . $conf->get('user') . "\n";print "host : " . $conf->get('host') . "\n";$conf->set("pass", "newpass");$conf->write();} catch (Exception", $e) {die( $e->__toString() );}
3. 异常的子类化
- 我们可以创建用户自定义异常,通过继承Exception类
- ① 扩展异常类的功能
- ② 子类定义了新的异常类型,可以进行自己特有的错误处理
- 实际上就算定义了多个try catch,我们一直使用一个 try 就够了
<?php// ....
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()
- 它只是在对象从内存中删除之前,自动调用。
- 可以利用这个方法进行必要的清理工作
<?phpclass Person{private $name;private $age;private $id;function __construct($name, $age){$this->name = $name;$this->age = $age;}function setID($id){$this->id = $id;}function __destruct(){if (! empty($this->id)) {// 保存Person数据echo 'saving person <br>';}}}$person = new Person('bob', 44);$person->setID(343);unset($person); // saving person
克隆对象
- PHP 中对象的赋值和传递都是通过引用进行的,像是下面的两个变量指向同一个对象的引用
<?phpclass CopMe{}$first = new CopyMe();$second = $first;// php4:是两个完全不相同的对象// php5之后:指向同一个对象
- 如果我们想要一个副本,可以使用 clone 关键字。
- clone 使用“值复制”的方式新生成一个对象
<?phpclass CopMe{}$first = new CopyMe();$second = clone $first;
- 我们可以控制复制什么 __clone
<?phpclass Person{private $name;private $age;private $id;function __construct($name, $age){$this->name = $name;$this->age = $age;}function setID($id){$this->id = $id;}function __clone(){$this->id = 0;}}$p1 = new Person('xs', 25);$p1->setID(19960118);$p2 = clone $p1;/*** p2:* name:xs* age:25* id:0*/
- 假设我们希望更为准确的克隆,有一个账户副本并不会去有单独这个属性的引用
<?php<?phpclass Account{public $balance;function __construct($balance){$this->balance = $balance;}}class Person{private $name;private $age;private $id;public $account;function __construct($name, $age, Account $account){$this->name = $name;$this->age = $age;$this->account = $account;}function setID($id){$this->id = $id;}function __clone(){$this->id = 0;$this->account = clone $this->account;}}$p1 = new Person('xs', 25, new Account(200));$p1->setID(19960118);$p2 = clone $p1;$p1->account = 1000;echo '<pre>';print_r($p1);print_r($p2);echo '</pre>';/*Person Object([name:Person:private] => xs[age:Person:private] => 25[id:Person:private] => 19960118[account] => 1000)Person Object([name:Person:private] => xs[age:Person:private] => 25[id:Person:private] => 0[account] => Account Object([balance] => 200))*/
定义对象的字符串值
1. __toString()
- PHP5 引入的这个方法,是受到了Java的启发
- 通过 __toString() 方法,你可以控制字符串输出的格式
- 我们常常在日志和错误报告用于这个方法
<?php<?phpclass Person{private $name;private $age;function __construct($name, $age){$this->name = $name;$this->age =$age;}function __toString(){$desc = 'name: ' . $this->name;$desc .= '; age: ' . $this->age;return $desc;}}$person1 = new Person('xs', 25);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 测试:匿名函数变量
<?php<?phpecho '<pre>';echo '测试1:匿名函数变量 <br>';$fun = function ($a, $b){echo ($a+$b);};$res1 = is_callable($fun, true, $call_able_name1);var_dump($res1); // boolean truevar_dump($call_able_name1); // string 'Closure::__invoke' 闭包::调用echo '-----------';$res2 = is_callable($fun, false, $call_able_name2);var_dump($res2); // boolean truevar_dump($call_able_name2); // string 'Closure::__invoke' 闭包::调用echo '<pre>';
1.2 测试:普通函数
<?phpecho '<pre>';echo '测试2:普通函数 <br>';function fun1($num){echo '__' . $num . '__';}$res1 = is_callable('fun1', true, $call_able_name1);var_dump($res1); // truevar_dump($call_able_name1); // fun1echo '-----------';$res2 = is_callable('fun1', false, $call_able_name2);var_dump($res2); // truevar_dump($call_able_name2); // fun1echo '<pre>';
1.3 测试:类方法
<?phpnamespace OOP;class Person{public static function get($a){echo $a;}protected function _set(){echo 1;}}$re1 = is_callable([new Person(), 'get'], false, $callable_name1);var_dump($re1); // truevar_dump($callable_name1); // 'OOP\Person::get'$re2 = is_callable([new Person(), 'get'], true, $callable_name2);var_dump($re2); // truevar_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()
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]); 但是我暂时没感觉这么做的意义所在,我们这么做不是多此一举吗?暂时保留我一个菜鸡的疑惑
