静态方法和属性
- 我们将类当做是对象的模板,对象作为活动的组件
- 我们可以用类这个工厂生产的对象去做事情,自然也可以通过类本身来做事情
- static 这个关键字帮助我们来完成
1. 类调用静态方法和静态属性
- 前面的我们可以在子类中通过 parent:: 父类的方法和属性
- 静态方法是以当前类作为作用域的,所以静态方法不能访问这个类的普通属性,普通属性也就是我们所说的非静态的属性它是为了对象服务的,而静态的属性是为了类而服务的
- 如果在本类中访问静态的属性或者是方法,需要使用关键字 self::
- self 指向当前类,而$this 这个伪变量指向当前类的对象
- 只有在使用parent关键字的时候,才能对一个非静态的方法进行静态式的调用
- 静态方法和静态属性又被称之为类变量和属性,所以我们不能用对象调用静态方法
- 也不能在静态方法中使用伪变量 $this
<?php
class 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)不能够直接被实例化,它只是定义部分实现,让子类去实现这些方法。
<?php
abstract class ShopProductWrite
{
protected $products = array();
public function addProduct (Shop Product $shopProduct)
{
$this->products[] = $shopProduct;
}
}
- 抽象类至少包含一个抽象方法 我们同样使用关键字 (abstract)
<?php
abstract class ShopProductWrite
{
protected $products = array();
public function addProduct (Shop Product $shopProduct)
{
$this->products[] = $shopProduct;
}
abstract public function write()
}
创建抽象方法后,要确保所有的子类都实现了该方法
<?php
class ErrorredWriter extends ShopProductWrite{
...
...
// 实现抽象方法
}
接口
- 接口:定义一组开发规范
- 抽象类为我们提供了具体实现的标准,而接口(interface)则是纯粹的模板
- 接口只是定义了功能,不包含任何实现的内容
<?php
interface Chargeable
{
public function getPrice();
}
- 任何实现接口的类,都必须实现接口中的所有方法,除非该类是抽象类
<?php
class ShopProduct implements Chargeable
{
// ...
public function getPrice()
{
return $this->price * $this->discount;
}
// ...
}
- 类是单继承的,而接口可以是多实现的
- 子类可以同时继承一个父类并实现多个接口
<?php
class Consultance extends TimeService implements Bookable, Changreable
{
// ...
}
延迟静态绑定
- 先看下面的代码:我们创建了一个抽象类 DomainObject,它的两个子类都包含静态方法 create,用于生产各自类的实例
<?php
abstract 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
<?php
abstract 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
<?php
abstract 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中竟然可以重写父类的静态方法,我靠惊呆了。
<?php
abstract 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 = $file
if (! file_exists($file)){
throw new Exception("file '$file' does not exist");
}
}
- try catch
<?php
try {
$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()
- 它只是在对象从内存中删除之前,自动调用。
- 可以利用这个方法进行必要的清理工作
<?php
class 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 中对象的赋值和传递都是通过引用进行的,像是下面的两个变量指向同一个对象的引用
<?php
class CopMe{}
$first = new CopyMe();
$second = $first;
// php4:是两个完全不相同的对象
// php5之后:指向同一个对象
- 如果我们想要一个副本,可以使用 clone 关键字。
- clone 使用“值复制”的方式新生成一个对象
<?php
class CopMe{}
$first = new CopyMe();
$second = clone $first;
- 我们可以控制复制什么 __clone
<?php
class 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
<?php
class 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
<?php
class 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
<?php
echo '<pre>';
echo '测试1:匿名函数变量 <br>';
$fun = function ($a, $b)
{
echo ($a+$b);
};
$res1 = is_callable($fun, true, $call_able_name1);
var_dump($res1); // boolean true
var_dump($call_able_name1); // string 'Closure::__invoke' 闭包::调用
echo '-----------';
$res2 = is_callable($fun, false, $call_able_name2);
var_dump($res2); // boolean true
var_dump($call_able_name2); // string 'Closure::__invoke' 闭包::调用
echo '<pre>';
1.2 测试:普通函数
<?php
echo '<pre>';
echo '测试2:普通函数 <br>';
function fun1($num)
{
echo '__' . $num . '__';
}
$res1 = is_callable('fun1', true, $call_able_name1);
var_dump($res1); // true
var_dump($call_able_name1); // fun1
echo '-----------';
$res2 = is_callable('fun1', false, $call_able_name2);
var_dump($res2); // true
var_dump($call_able_name2); // fun1
echo '<pre>';
1.3 测试:类方法
<?php
namespace 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); // true
var_dump($callable_name1); // 'OOP\Person::get'
$re2 = is_callable([new Person(), 'get'], true, $callable_name2);
var_dump($re2); // true
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()
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]); 但是我暂时没感觉这么做的意义所在,我们这么做不是多此一举吗?暂时保留我一个菜鸡的疑惑