该篇属于《Laravel底层核心技术实战揭秘》这一课程《laravel底层核心概念解析》这一章的扩展阅读。由于要真正学好laravel底层,有些PHP相关的知识必须得了解,考虑到学员们的基础差异,为了避免视频当中过于详细而连篇累牍,故将一些laravel底层实现相关的PHP知识点以文章形式呈现,供大家预习和随时查阅。

Introspection本意是内省、自我检查、内向检查的意思,目前尚无统一明确的中文翻译,本文暂且取“内向检查”之意,并简称之为“内检”。

内检(Introspection)可谓是编程语言里的一个常见功能,通常用来检查、检验某个实例(object)或类(class),取得相应的信息以便于进一步操作。比如说当你在程序设计的时候,不知道当下有哪些方法可以执行,或者说不知道当下在操作哪个类,这个时候使用内检(Introspection)相关的功能,就会特别有用。

PHP里的内检(Introspection)可以用来检验classes、interfaces、properties和methods,接下来让我们通过几个例子来看看PHP里都有哪些内检(Introspection)相关的函数或功能。

示例一

相关函数预览:

  1. class_exists() ——检查某个类是否存在,或者说是否被定义,返回true或false
  2. get_class() ——返回一个实例对象所对应的类
  3. get_parent_class()——返回一个实例对象或类所对应的父类
  4. is_subclass_of() ——检查某个实例对象所对应的类,是否扩展(extends)或继承(implements)了某个类
<?php
class Introspection
{
    public function description() {
        echo "I am a super class for the Child class.n";
    }
}

class Child extends Introspection
{
    public function description() {
        echo "I'm " . get_class($this) , " class.\n";
        echo "I'm " . get_parent_class($this) , "'s child.\n";
    }
}

if (class_exists("Introspection")) {
    $introspection = new Introspection();
    echo "The class name is: " . get_class($introspection) . "\n"; 
    $introspection->description();
}

if (class_exists("Child")) {
    $child = new Child();
    $child->description();

    if (is_subclass_of($child, "Introspection")) {
        echo "Yes, " . get_class($child) . " is a subclass of Introspection.\n";
    }
    else {
        echo "No, " . get_class($child) . " is not a subclass of Introspection.\n";
    }
}

以上代码的输出如下:

The class name is: Introspection
I am a super class for the Child class.
I'm Child class.
I'm Introspection's child.
Yes, Child is a subclass of Introspection.

实战tips:

  1. class_exists()
  • 第一个参数是要检查的类名,必传,如果是namespace的时候,不能用别名;
  • 第二个参数可选,传true或false,以设置在检查的时候是否调用自动加载机制,说白了也即是否默认调用__autoload()spl_autoload_register()函数
  1. get_class()
  • 如果你传进去的不是一个对象(object),那么会默认报E_WARNING
  • 当然你也可以什么都不传,比如在一个类里面可以不传参直接调用get_class(),这里需要注意的是,当涉及到继承关系时,get_class()get_class($this)并不是一回事;
  • 从PHP 7.2.0开始,你不能往里传NULL,否则会报E_WARNING,而在之前的版本,传NULL就相当于什么也没传;
  • 在使用了namespace的情况下,返回的是带有namespace的path,如果你只是需要一个结尾的类名,那就需要额外处理一下;
  1. get_parent_class()
  • 参数可以是一个object,也可以是一个class;
  • 如果是在一个object的方法里,可以不传任何参数,但是如果不是在一个object的方法里,而且又没有传参,就会返回false
  • 如果传进去的object或Class没有父类,那么就默认返回false
  1. is_subclass_of(mixed $object , string $class_name [, bool $allow_string = TRUE ] )
  • 第三个参数可选,如果是默认的true,那么第一个参数,既可以传一个object,也可以传一个字符形式的类名,如果第三个参数是false,那么第一个参数必须是一个object;
  • 涉及到多重的继承关系时,比如B extends AC extends B,那么is_subclass_of($foo = new C,'A')返回的也是true

示例二

相关函数预览:

  1. get_declared_classes()——以array的形式返回当下已经定义的所有类名
  2. get_class_methods()——返回一个class下的所有方法名
  3. get_class_vars()——返回一个class的默认属性
  4. interface_exists() ——查看相应的interface是否存在
  5. method_exists()——查看实例或class当中是否存在某个方法
<?php
interface ICurrencyConverter
{
    public function convert($currency, $amount);
}

class GBPCurrencyConverter implements ICurrencyConverter
{
    public $name = "GBPCurrencyConverter";
    public $rates = array("USD" => 0.622846,
                          "AUD" => 0.643478);
    protected $var1;
    private $var2;

    function __construct() {}

    function convert($currency, $amount) {
        return $rates[$currency] * $amount;
    }
}

if (interface_exists("ICurrencyConverter")) {
    echo "ICurrencyConverter interface exists.\n";
}

$classes = get_declared_classes();
echo "The following classes are available:\n";
print_r($classes);

if (in_array("GBPCurrencyConverter", $classes)) {
    print "GBPCurrencyConverter is declared.\n";
 
    $gbpConverter = new GBPCurrencyConverter();

    $methods = get_class_methods($gbpConverter);
    echo "The following methods are available:\n";
    print_r($methods);

    $vars = get_class_vars("GBPCurrencyConverter");
    echo "The following properties are available:\n";
    print_r($vars);

    echo "The method convert() exists for GBPCurrencyConverter: ";
    var_dump(method_exists($gbpConverter, "convert"));
}

以上代码的输出如下:

ICurrencyConverter interface exists.
The following classes are available:
Array
(
    [0] => stdClass
    [1] => Exception
    [2] => ErrorException
    [3] => Closure
    [4] => DateTime
    [5] => DateTimeZone
    [6] => DateInterval
    [7] => DatePeriod
    ...
    [154] => GBPCurrencyConverter
)
GBPCurrencyConverter is declared.
The following methods are available:
Array
(
    [0] => __construct
    [1] => convert
)
The following properties are available:
Array
(
    [name] => GBPCurrencyConverter
    [rates] => Array
        (
            [USD] => 0.622846
            [AUD] => 0.643478
        )
)
The method convert() exists for GBPCurrencyConverter: bool(true)
  1. get_declared_classes()
  • 不接收任何参数;
  • 返回的类里面会包含PHP预定义的一些class,比如上例中的stdClassExceptionErrorException等;同时取决于你所用的库(libraries)或扩展(extensions),当它们已被编译或者加载到PHP中,也会显示出来;
  1. get_class_methods()
  • 接收的参数可以是一个Class的名字,也可以是一个实例对象(object)
  • 返回的是一个array,如果期间有错误,则返回null
  • 涉及到继承关系时,返回的不仅是当前class下的方法,也包括继承过来的方法
  1. get_class_vars()
  • 接收的是字符形式的class名称,如果你已有的是一个object,我们通常先使用get_class(object)的方式取得其class名称,然后再调用get_class_vars()
  • 返回的是默认属性的默认值,比如即使你class的__construct()里对已有属性的值有操作,get_class_vars()返回的也依然是默认值;
  • 能够返回什么样的属性,也即涉及到属性的可见性(public、private、protected)时,这要取决于你调用get_class_vars()这个方法时的情境,如上例所示,返回的是一个array,其中privateprotected属性都被跳过了,原因呢是因为我们是在GBPCurrencyConverter这个class的外边来调用get_class_vars(),自然这个时候privateprotected属性就不可见了;那么如果你是在一个class内部来调用get_class_vars(),相当于说查看一下自己本身都有哪些属性,那么这个时候privateprotected属性也能被调出来;
  1. interface_exists(string $interface_name [, bool $autoload = true ])
  • 第二个参数可选,用来定义是否要调用自动加载机制,比如__autoload()spl_autoload_register()
  • 与前面说的get_declared_classes()类似,涉及到interface相关的还有一个get_declared_interfaces(),用于返回当下定义的所有interface,其中也会包含PHP自带的、以及你依赖中的interface;
  1. method_exists ( mixed $object , string $method_name )
  • 第一个参数可以是object,也可以是class名称

以上呢通过两个示例展示了PHP的部分内检函数,在我们日常的开发中,尤其是在写一些相对复杂的系统中,经常会用到它们,在我们查看laravel底层源码的时候也是频繁见到。PHP的内检函数呢,实际上就是关于类或者对象的函数,下面这个链接可以看到都有哪些及各自功能:

关于类或者对象的函数(//php.net/manual/zh/ref.classobj.php)

以上的这些内检函数呢,优点是简单直接不影响性能,缺点是各自为政,缺乏统一的规范,如果你在内检或反向解析这方面有特别的需求,比如说你在写一个供很多人使用的框架,那么这些内检函数可能对你来说就显得不够用,或者说用起来感到有些杂乱,鉴于此呢,从PHP5开始新增了一系列映射(reflection)功能,或者说反射功能,又经常称作是reflection api,极大地丰富了和规范了PHP的内检相关功能,而且使用起来也更加简单、清晰、明了,但代价是能耗比较高。

关于reflection api又是怎么一回事,更关键的是它与laravel又有啥关系,这个我们下一篇文章《Laravel自动依赖解析的背后实现——PHP映射解析(reflection api\reflection class)功能》介绍~