PHP 7 入门:类和接口的增强

本文要点

  • PHP 7为一次性对象添加了匿名类的功能,这样的例子可能是值对象以及实现了一个接口用来进行依赖注入的对象。
  • 按照设计,匿名类是一次性使用的,不需要完整的类定义。
  • 匿名类就像完整的类那样,可以扩展其他类、实现接口、定义构造器等等。
  • PHP 7引入了IntlChar类来访问Unicode字符的信息。
  • PHP 7废弃了一些特性,比如PHP 4风格的构造器。

在这个文章系列中,我们将会探索PHP 7的新特性。在第一篇文章中,我们准备好了环境并介绍了PHP 7,随后讨论了其与面向对象编程相关的新特性。在本文中,我们会讨论PHP在类和接口方面的改进。

匿名类

有时候,短期使用、用后即可废弃的对象可以取代完整的类实例。

PHP 7.0添加了对匿名类的支持,它们非常易于实例化,即便只使用一次。匿名类和完整类很相似,它们能够扩展其他的类、实现接口、定义构造器等。

作为样例,我们会创建一个匿名类来为服务器日志处理日志消息。创建一个_anonymous.php_脚本并定义包含setMsg(string $msg)函数的LogMsg接口,该接口允许我们设置日志消息。另外,创建一个带有getter/setter方法getLogMsg(): LogMsgsetLogMsg(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(): LogMsgsetLogMsg(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_ 脚本,并定义了像前面样例那样的LogMsgServerLog类。唯一的差异在于有个参数传递到了匿名类的构造器之中:

$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,这是通过调用外部类的字段和函数实现的。

用于 Unicode 字符的新 IntlChar 类

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 图书。

原文链接:

Classes and Interfaces Improvements

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/H5av8rIy5WDy4dUagZWx
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券