@levinzhang
2020-07-23T05:57:34.000000Z
字数 9320
阅读 583
by
在专门介绍PHP 7的第二篇文章中,我们将会继续探索PHP 7的新特性,本文中我们将会关注对面向对象编程的支持、类和接口。
在这个文章系列中,我们将会探索PHP 7的新特性。在第一篇文章中,我们准备好了环境并介绍了PHP 7,随后讨论了其与面向对象编程相关的新特性。在本文中,我们会讨论PHP在类和接口方面的改进。
有时候,短期使用、用后即可废弃的对象可以取代完整的类实例。
PHP 7.0添加了对匿名类的支持,它们非常易于实例化,即便只使用一次。匿名类和完整类很相似,它们能够扩展其他的类、实现接口、定义构造器等。
作为样例,我们会创建一个匿名类来为服务器日志处理日志消息。创建一个anonymous.php脚本并定义包含setMsg(string $msg)
函数的LogMsg
接口,该接口允许我们设置日志消息。另外,创建一个带有getter/setter方法getLogMsg(): LogMsg
和setLogMsg(LogMsg $logMsg)
的ServerLog类,这个类用来设置服务器日志。
<?php
interface LogMsg {
public function setMsg(string $msg);
}
class ServerLog {
private $logMsg;
public function getLogMsg(): LogMsg {
return $this->logMsg;
}
public function setLogMsg(LogMsg $logMsg) {
$this->logMsg = $logMsg;
}
}
$serverLog = new ServerLog;
$serverLog->setLogMsg(new class implements LogMsg {
public function setMsg(string $msg) {
echo $msg;
}
});
var_dump($serverLog->getLogMsg());
?>
创建ServerLog
类的实例并调用setLogMsg(LogMsg $logMsg)
函数,其中参数是以匿名类的形式提供的。
$serverLog = new ServerLog;
$serverLog->setLogMsg(new class implements LogMsg {
public function setMsg(string $msg) {
echo $msg;
}
});
如果运行脚本的话,var_dump
将会打印出我们传入到SetLogMsg
中的匿名类对象的引用。
object(class@anonymous)#2 (0) { }
如果在上面的样例中不使用匿名类的话,我们需要提供一个完整的实现了LogMsg
接口的类。如果不使用匿名类,相同功能的代码如下所示。
<?php
interface LogMsg {
public function setMsg(string $msg);
}
class ServerLogMsg implements LogMsg {
public function setMsg(string $msg) {
echo $msg;
}
}
class ServerLog {
private $logMsg;
public function getLogMsg(): LogMsg {
return $this->logMsg;
}
public function setLogMsg(LogMsg $logMsg) {
$this->logMsg = $logMsg;
}
}
$serverLog = new ServerLog;
$serverLog->setLogMsg(new ServerLogMsg());
var_dump($serverLog->getLogMsg());
?>
从同一个匿名类声明实例化的所有对象都是该类的实例并且彼此是完全相同的。相同比较是使用===操作符执行的,它表明要对比的对象相等并且具有相同的类型。使用===进行身份对比(identity comparison)可能一开始会让人觉得疑惑。那我们从相等操作符==开始。如果两个对象具有相同的属性和值并且是同一个类的实例,那么它们是相等的(使用==操作符进行对比)。而身份对比操作符(===)判定两个对象相同,仅在它们引用了同一个类的同一个实例时才能成立。
为了充分理解这一点,我们创建一个anonymous-class-objects.php脚本并定义一个返回匿名类对象的函数。现在,我们借助get_class
函数,调用这个函数两次,获取所实例化的两个不同的匿名对象,然后得到它们的类名并进行比较。如下所示,两个名字是相同的。
<?php
function a_class()
{
return new class {};
}
if(get_class(a_class())===get_class(a_class())){
echo "Objects are instances of same class ".get_class(a_class());
}else{
echo "Objects are instances of different classes";
}
echo "</br>";
var_dump(get_class(a_class()));
echo "</br>";
var_dump(get_class(a_class()));
?>
运行anonymous-class-objects.php将会生成一条输出消息,表明对象是同一个类的实例,它的名字以class@anonymous
开头。这里返回了相同的实例class@anonymousC:\PHP7.4\php-7.4-ts-windows-vc15-x64-r6c9821a\scripts\sumints.php000000000626B031
,这表明是两个相同的类。匿名类的名字是由PHP引擎分配的,依赖于实现。
Objects are instances of same class class@anonymousC:\PHP7.4\php-7.4-ts-windows-vc15-x64-r6c9821a\scripts\sumints.php000000000626B031
string(98) "class@anonymousC:\PHP7.4\php-7.4-ts-windows-vc15-x64-r6c9821a\scripts\sumints.php000000000626B031"
string(98) "class@anonymousC:\PHP7.4\php-7.4-ts-windows-vc15-x64-r6c9821a\scripts\sumints.php000000000626B031"
匿名类也可以使用extends扩展其他的类。
为了阐述这一点,创建一个anonymous-extend-class.php脚本,定义带有$msg
字段及其get/set函数的LogMsg
类。现在,定义带有getLogMsg(): LogMsg
和setLogMsg(LogMsg $logMsg)
函数的ServerLog
类。最后,创建ServerLog
类的实例并调用setLogMsg(LogMsg $logMsg)
函数,将扩展了LogMsg的一个匿名类提供给LogMsg
参数:
$serverLog = new ServerLog;
$serverLog->setLogMsg(new class extends LogMsg {
public function setMsg(string $msg) {
$this->msg = $msg;
}
});
anonymous-extend-class.php脚本如下所示:
<?php
class LogMsg {
private $msg;
public function getMsg() {
return $msg;
}
}
class ServerLog {
private $logMsg;
public function getLogMsg(): LogMsg {
return $this->logMsg;
}
public function setLogMsg(LogMsg $logMsg) {
$this->logMsg = $logMsg;
}
}
$serverLog = new ServerLog;
$serverLog->setLogMsg(new class extends LogMsg {
public function setMsg(string $msg) {
$this->msg = $msg;
}
});
var_dump($serverLog->getLogMsg());
?>
运行脚本并检查输出。我们会看到LogMsg
类型的msg
字段被设置成了NULL
。
object(class@anonymous)#2 (1) { ["msg":"LogMsg":private]=> NULL }
正如我们所预期的,匿名类的构造器可以传入参数。
为了阐述该功能,创建anonymous-extend-class-add-constructor.php脚本,并定义了像前面样例那样的LogMsg
和ServerLog
类。唯一的差异在于有个参数传递到了匿名类的构造器之中:
$serverLog->setLogMsg(new class('Log Message') extends LogMsg {
public function __construct($msg)
{
$this->msg = $msg;
}
…
}
anonymous-extend-class-add-constructor.php脚本如下所示。
<?php
class LogMsg {
private $msg;
public function getMsg() {
return $msg;
}
}
class ServerLog {
private $logMsg;
public function getLogMsg(): LogMsg {
return $this->logMsg;
}
public function setLogMsg(LogMsg $logMsg) {
$this->logMsg = $logMsg;
}
}
$serverLog = new ServerLog;
$serverLog->setLogMsg(new class('Log Message') extends LogMsg {
public function __construct($msg)
{
$this->msg = $msg;
}
public function setMsg(string $msg) {
$this->msg = $msg;
}
});
var_dump($serverLog->getLogMsg());
?>
运行脚本并校验传递给传递给匿名类构造器的日志消息,它会被getLogMsg()
返回并打印出来。
object(class@anonymous)#2 (2) { ["msg":"LogMsg":private]=> NULL ["msg"]=> string(11) "Log Message" }
匿名类可能会被其他类嵌套,但是它不能使用外部类的protected或private函数或属性。要想使用外部类的private属性,我们要像上面的例子那样将属性作为参数传递到匿名类的构造器中。
为了阐述该功能,创建一个inner-class-private.php脚本并定义一个外部类Outer
,它有一个私有属性。添加一个inner()
函数,该函数返回一个匿名类对象。来自Outer
类的私有属性传递到了匿名类构造器中,并设置为匿名类的private属性。现在,使用在匿名类中定义的函数,我们就能返回从Outer
类传递到匿名内部类的private属性的值:
return new class($this->a) extends Outer {
private $a;
public function __construct($a)
{
$this->a = $a;
}
public function getFromOuter()
{
echo $this->a;
}
};
要打印由Outer
传递给匿名内部类的private属性的值,我们需要创建一个Outer
类的实例并调用inner()函数,该函数会创建匿名类,然后调用匿名类中返回private属性值的函数:
echo (new Outer)->inner()->getFromOuter();
inner-class-private.php脚本如下所示。
<?php
class Outer
{
private $a = 1;
public function inner()
{
return new class($this->a) {
private $a;
public function __construct($a)
{
$this->a = $a;
}
public function getFromOuter()
{
echo $this->a;
}
};
}
}
echo (new Outer)->inner()->getFromOuter();
?>
运行脚本,检查私有属性的值(1)从Outer
传递到了内部类中并打印到了浏览器上。
接下来,我们要阐述Outer
类的protected函数如何在匿名类中进行调用。为了调用外部类中定义的protected函数,匿名内部类需要扩展这个外部类。
为了阐述该功能,创建一个inner-class-protected.php脚本并定义名为Outer
的外部类,该类包含一个protected字段和protected函数。现在,定义另外一个函数,该函数会创建一个扩展Outer类的匿名类,并在匿名类中定义一个函数,让该函数调用我们最早定义的外部类的protected函数。因为匿名类扩展了Outer类,所以它继承了Outer类的protected字段和函数,这就意味着可以使用this
访问protected的函数和字段。和前面一样,匿名类的函数可以通过首先创建Outer
类的实例来进行调用:
echo (new Outer)->inner()->getFromOuter();
inner-class-protected.php脚本如下所示:
<?php
class Outer
{
protected $a = 1;
protected function getValue()
{
return 2;
}
public function inner()
{
return new class extends Outer {
public function getFromOuter()
{
echo $this->a;
echo "<br/>";
echo $this->getValue();
}
};
}
}
echo (new Outer)->inner()->getFromOuter();
?>
运行脚本并检查Outer
类的protected字段以及Outer
类的函数所返回的值,如下面的输出所示。
1
2
我们使用了两个样例。分别阐述了如何从嵌入式的匿名类中调用外部类的private字段以及如何调用protected的字段与函数。我们可以将这两个样例合并到一起,让匿名的嵌套类扩展外部类,便于继承外部类protected的字段和函数,同时传递外部类的private字段到匿名类的构造器中,如下所示:
return new class($this->prop) extends Outer {
…
}
为了阐述该功能,我们创建inner-class.php脚本。
<?php
class Outer
{
private $prop = 1;
protected $prop2 = 2;
protected function func1()
{
return 3;
}
public function func2()
{
return new class($this->prop) extends Outer {
private $prop3;
public function __construct($prop)
{
$this->prop3 = $prop;
}
public function func3()
{
return $this->prop2 + $this->prop3 + $this->func1();
}
};
}
}
echo (new Outer)->func2()->func3();
?>
运行脚本将会输出6,这是通过调用外部类的字段和函数实现的。
PHP 7.0引入了名为IntlChar
的新类,它提供了多个工具方法用来访问Unicode字符的信息。注意,要使用IntlChar
类,需要安装Intl
扩展,这可以通过在php.ini
配置文件中解除对如下代码行的注释来实现:
extension=intl
IntlChar类中的一些方法如下表所示。
IntlChar类的方法
方法 | 描述 |
---|---|
IntlChar::charFromName |
根据名称返回Unicode字符的代码点(code point)的值。 |
IntlChar::charName |
返回unicode字符的名称. |
IntlChar::charType |
返回unicode代码点的通用类别的值。例如,对于标题大小写字符种类,会返回IntlChar::CHAR_CATEGORY_TITLECASE_LETTER 。对于十进制数字种类,返回IntlChar::CHAR_CATEGORY_DECIMAL_DIGIT_NUMBER 。如果字符不在任何预定义的类别中的话,那么返回的种类将是IntlChar::CHAR_CATEGORY_UNASSIGNED 。 |
IntlChar::chr |
根据代码点的值返回Unicode字符。 |
IntlChar::getNumericValue |
返回unicode代码点的数字值。 |
IntlChar::isdefined |
返回boolean值以表明某个字符是否已定义。 |
我们现在创建一个Intlchar.php脚本来测试其中的一些方法。在如下的样例中,我们通过 IntlChar::UNICODE_VERSION
常量输出unicode的版本,探查LATIN CAPITAL LETTER B
的unicode代码点,并检查\u{00C6}
是否已定义。脚本Intlchar.php如下所示。
<?php
printf('Unicode Version : ');
echo "<br/>";
echo IntlChar::UNICODE_VERSION;
echo "<br/>";
echo IntlChar::charFromName("LATIN CAPITAL LETTER B");
echo "<br/>";
var_dump(IntlChar::isdefined("\u{00C6}"));
?>
运行脚本将会产生如下的输出:
Unicode Version :
12.1
66
bool(true)
PHP 7还废弃了一些特性。
在PHP 7.0.x废弃的属性中,包括PHP 4、“老式”风格的构造器,也就是构造器方法和类的名字是相同的。
举例来讲,创建constructor.php脚本并复制如下的代码清单到文件中。
<?php
class Catalog {
function Catalog() {
}
}
?>
脚本声明了一个Catalog
类,并且带有一个名称同样为Catalog
的方法。运行脚本将会看到如下的输出:
**Deprecated**: Methods with the same name as their class will not be constructors in a future version of PHP; Catalog has a deprecated constructor
除此之外,在PHP 7.0.0中,以静态方式调用非静态方法也被废弃了。我们创建一个static.php脚本并复制如下的代码清单到文件中,我们声明了一个带有非静态函数getTitle()
的类,现在尝试对这个函数进行静态调用:
<?php
class Catalog {
function getTitle() {
}
}
Catalog::getTitle();
?>
运行脚本将会看到输出如下的消息:
**Deprecated**: Non-static method Catalog::getTitle() should not be called statically
在PHP 7.1.x中,mcrypt
扩展被废弃了。PHP 7.2废弃的特性包括unquoted strings
、__autoload()
方法、create_function()
、强制转换为unset
、不带第二个参数使用parse_str()
、gmp_random()
函数、each()
函数、带有字符串参数的assert()
以及read_exif_data()
函数。PHP 7.3废弃的特性包括大小写不敏感的常量以及在命名空间中声明assert()
。
作为阐述废弃大小写不敏感常量的样例,运行如下的样例,其中define()
在调用的时候,将case_insensitive
参数设置成了true:
<?php
define('CONST_1', 10, true);
var_dump(CONST_1);
var_dump(const_1);
?>
这将会输出如下的消息:
Deprecated: define(): Declaration of case-insensitive constants is deprecated on line 2
int(10)
Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "CONST_1" on line 4
在关于PHP 7系列的第二篇文章中,我们探讨了类和接口方面的新特性。最值得关注的新特性就是支持匿名类。通过一个新的类IntlChar, Unicode也得到了提升,该方法可以用来获取关于Unicode字符的信息。
在下一篇文章中,我们将会探讨PHP类型系统方面的新特性。
Deepak Vohra是一位Sun认证的Java程序员和Sun认证的Web组件开发人员。Deepak在WebLogic Developer's Journal、XML Journal、ONJava、java.net、IBM developerWorks、Java Developer’s Journal、Oracle Magazine和devx上都发表过Java和Java EE相关的技术文章。Deepak还出版过五本关于Docker的书,他是Docker导师。Deepak还发表了多篇关于PHP的文章,以及一本面向PHP和Java开发人员的Ruby on Rails图书。