博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Laravel学习笔记之PHP反射(Reflection) (上)
阅读量:7058 次
发布时间:2019-06-28

本文共 22161 字,大约阅读时间需要 73 分钟。

说明:Laravel中经常使用PHP的反射特性来设计代码,本文主要学习PHP的反射特性,来提高写代码时的设计质量。PHP提供一套检测class, interface, trait, property, method的两个工具包:Introspection FunctionsReflection API,类似于探针一样的东西来探测这些一等公民。本文先看下Introspection Functions的使用。

开发环境: Laravel5.3 + PHP7

Introspection Functions

Introspection Functions是用来操作object class的一些函数,PHP提供了大量的Introspection Functions来操作class, interface, trait, method, property:

  1. class_exists()

  2. interface_exists()

  3. method_exists()

  4. property_exists()

  5. trait_exists()

  6. class_alias()

  7. get_class()

  8. get_parent_class()

  9. get_called_class()

  10. get_class_methods()

  11. get_class_vars()

  12. get_object_vars()

  13. is_subclass_of()

  14. is_a()

class_exists()

Laravel源码中好多个地方使用到class_exists()方法来判断指定类是否存在,如\Illuminate\Database\Connection::isDoctrineAvailable()的源码:

public function isDoctrineAvailable()    {        return class_exists('Doctrine\DBAL\Connection'); // Doctrine\DBAL\Connection::class类是否存在,大小写不敏感    }

写个PHPUnit测试下(爆绿灯,说明是正确的,这里不截图了。后面所有Introspection的测试都放在IntrospectionTest这个单元测试里):

namespace MyRightCapital\Container\Tests;class IntrospectionTest extends \PHPUnit_Framework_TestCase{    public function testClassExists()    {        // Arrange                // Actual        $class_exists = class_exists(TestClassExists::class);        // Assert        $this->assertTrue($class_exists);    }}class TestClassExists{    }

interface_exists()

interface_exists()是用来检查接口是否存在,写个PHPUnit测试下,爆绿灯:

namespace MyRightCapital\Container\Tests;class IntrospectionTest extends \PHPUnit_Framework_TestCase{    public function testInterfaceExists()    {        // Arrange                // Actual        $interface_exists = interface_exists(TestInterfaceExists::class);        // Assert        $this->assertTrue($interface_exists);    }}interface TestInterfaceExists{    }

method_exists()

检查类的方法(private,protected,public)是否存在于指定的类对象或类名中,Laravel中很多处用到了这个函数,如Application中的register()检查service provider中register是否存在,和bootProvider()中检查service provider中boot()方法是否存在:

public function register($provider, $options = [], $force = false){    ...        if (method_exists($provider, 'register')) {            $provider->register();    }        ...}protected function bootProvider(ServiceProvider $provider){    if (method_exists($provider, 'boot')) {        return $this->call([$provider, 'boot']);    }}

这里写个PHPUnit测试下,爆绿灯:

public function testMethodExists()    {        // Arrange        $test_class_exists = new TestClassExists();                // Actual        $object_method_exists1    = method_exists($test_class_exists, 'testPrivateMethodExists');        $object_method_exists2    = method_exists($test_class_exists, 'testProtectedMethodExists');        $object_method_exists3    = method_exists($test_class_exists, 'testPublicMethodExists');        $classname_method_exists1 = method_exists(TestClassExists::class, 'testPrivateMethodExists');        $classname_method_exists2 = method_exists(TestClassExists::class, 'testProtectedMethodExists');        $classname_method_exists3 = method_exists(TestClassExists::class, 'testPublicMethodExists');                // Assert        $this->assertTrue($object_method_exists1);        $this->assertTrue($object_method_exists2);        $this->assertTrue($object_method_exists3);        $this->assertTrue($classname_method_exists1);        $this->assertTrue($classname_method_exists2);        $this->assertTrue($classname_method_exists3);    }            class TestClassExists    {        private function testPrivateMethodExists()        {        }                protected function testProtectedMethodExists()        {        }                public function testPublicMethodExists()        {        }    }

property_exists()

检查该属性(private, protected, public)是否存在于类对象或类名中,Laravel很多地方用到了该函数,如\Illuminate\Foundation\Auth\RedirectsUsers::redirectPath()源码:

public function redirectPath()    {        return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home';    }

写个PHPUnit测试下该函数,爆绿灯:

// class IntrospectionTest    public function testPropertyExists()    {        // Arrange        $test_class_exists = new TestClassExists();                // Actual        $private_property1   = property_exists($test_class_exists, 'testPrivatePropertyExists');        $private_property2   = property_exists(TestClassExists::class, 'testPrivatePropertyExists');        $protected_property1 = property_exists($test_class_exists, 'testProtectedPropertyExists');        $protected_property2 = property_exists(TestClassExists::class, 'testProtectedPropertyExists');        $public_property1    = property_exists($test_class_exists, 'testPublicPropertyExists');        $public_property2    = property_exists(TestClassExists::class, 'testPublicPropertyExists');                // Assert        $this->assertTrue($private_property1);        $this->assertTrue($private_property2);        $this->assertTrue($protected_property1);        $this->assertTrue($protected_property2);        $this->assertTrue($public_property1);        $this->assertTrue($public_property2);    }            class TestClassExists    {        private   $testPrivatePropertyExists;        protected $testProtectedPropertyExists;        public    $testPublicPropertyExists;    }

trait_exists()

检查trait是否存在,写下PHPUnit测试,爆绿灯:

// class IntrospectionTest    public function testTraitExists()    {        // Arrange                // Actual        $test_trait_exists = trait_exists(TestTraitExists::class);                // Assert        $this->assertTrue($test_trait_exists);    }        trait TestTraitExists    {        }

class_alias()

给指定类取别名,Laravel中只有一处使用了class_alias(),用来给config/app.php中$aliases[ ]注册别名,可看下,看下Laravel中如何使用的:

public function load($alias)    {        if (isset($this->aliases[$alias])) {            return class_alias($this->aliases[$alias], $alias);        }    }

写个PHPUnit测试,爆绿灯:

public function testClassAlias()    {        // Arrange        class_alias(TestClassExists::class, 'MyRightCapital\Container\Tests\AliasTestClassExists');        $test_class_exists = new TestClassExists();                // Actual        $actual = new AliasTestClassExists();                //Assert        $this->assertInstanceOf(TestClassExists::class, $actual);        $this->assertInstanceOf(AliasTestClassExists::class, $test_class_exists);    }

get_class()

get_class()获取对象的类名,这个函数在Laravel中大量地方在用了,如Application::getProvider($provider)方法,是个很好用的方法:

public function getProvider($provider)    {        $name = is_string($provider) ? $provider : get_class($provider);        return Arr::first($this->serviceProviders, function ($value) use ($name) {            return $value instanceof $name;        });    }

写个PHPUnit测试,爆绿灯:

public function testGetClass()    {        // Arrange        $test_class_exists = new TestClassExists();                // Actual        $class_name = get_class($test_class_exists);                // Assert        $this->assertSame(TestClassExists::class, $class_name);    }

get_parent_class()

get_parent_class()是用来获取类的父类名,目前Laravel中还没用到这个函数,传入的可以是子类对象或者子类名,写个PHPUnit测试下:

// namespace MyRightCapital\Container\Tests;    // class IntrospectionTest extends \PHPUnit_Framework_TestCase    public function testGetParentClass()    {        // Arrange        $child_class = new ChildClass();                // Actual        $parent_class1 = get_parent_class($child_class);        $parent_class2 = get_parent_class(ChildClass::class);                // Assert        $this->assertSame(ParentClass::class, $parent_class1);        $this->assertSame(ParentClass::class, $parent_class2);    }        class ChildClass extends ParentClass     {            }        class ParentClass    {            }

get_called_class()

get_called_class()获取后期静态绑定类即实际调用类的名称,Laravel中还没使用到该函数,不妨写个测试看下如何使用:

// namespace MyRightCapital\Container\Tests;    // class IntrospectionTest extends \PHPUnit_Framework_TestCase    public function testGetCalledClass()    {        // Arrange        $child_class  = new ChildClass();        $parent_class = new ParentClass();                // Actual        $child_called_class = $child_class->testGetCalledClass();        $parent_called_class = $parent_class->testGetCalledClass();                // Assert        $this->assertSame(ChildClass::class, $child_called_class);        $this->assertSame(ParentClass::class, $parent_called_class);    }        class ChildClass extends ParentClass    {            }        class ParentClass    {        public function testGetCalledClass()        {            return get_called_class();        }    }

get_class_methods()

get_class_methods()用来获取类的方法名组成一个数组(测试只能是public),Laravel只有一处用到了该方法\Illuminate\Database\Eloquent\Model::cacheMutatedAttributes() :line 3397,这里写个PHPUnit测试,爆绿灯:

public function testGetClassMethod()    {        // Arrange        $get_class_methods1 = get_class_methods(ChildClass::class);        $get_class_methods2 = get_class_methods(new ChildClass());                // Actual                // Assert        $this->assertFalse(in_array('testPrivateGetClassMethod', $get_class_methods1, true));        $this->assertFalse(in_array('testPrivateGetClassMethod', $get_class_methods2, true));        $this->assertFalse(in_array('testProtectedGetClassMethod', $get_class_methods1, true));        $this->assertFalse(in_array('testProtectedGetClassMethod', $get_class_methods2, true));        $this->assertTrue(in_array('testPublicGetClassMethod', $get_class_methods1, true));        $this->assertTrue(in_array('testPublicGetClassMethod', $get_class_methods2, true));        $this->assertTrue(in_array('testGetCalledClass', $get_class_methods1, true));        $this->assertTrue(in_array('testGetCalledClass', $get_class_methods2, true));    }        class ChildClass extends ParentClass    {        private function testPrivateGetClassMethod()        {        }                protected function testProtectedGetClassMethod()        {        }                public function testPublicGetClassMethod()        {        }    }

get_class_vars()

get_class_vars()只会读取类的public属性组成一个数组,类似于get_class_methods(),若属性没有默认值就为null,目前Laravel中还未使用,看下PHPUnit测试:

public function testGetClassVars()    {        // Arrange                // Actual        $class_vars = get_class_vars(ChildClass::class);                // Assert        $this->assertArrayNotHasKey('privateNoDefaultVar', $class_vars);        $this->assertArrayNotHasKey('privateDefaultVar', $class_vars);        $this->assertArrayNotHasKey('protectedNoDefaultVar', $class_vars);        $this->assertArrayNotHasKey('protectedDefaultVar', $class_vars);        $this->assertEmpty($class_vars['publicNoDefaultVar']);        $this->assertEquals('public_laravel', $class_vars['publicDefaultVar']);    }        class ChildClass extends ParentClass    {        private   $privateNoDefaultVar;        private   $privateDefaultVar   = 'private_laravel';        protected $protectedNoDefaultVar;        protected $protectedDefaultVar = 'protected_laravel';        public    $publicNoDefaultVar;        public    $publicDefaultVar    = 'public_laravel';   }

get_object_vars()

get_object_vars()只会读取对象的public属性组成一个数组,类似于get_class_vars(), get_class_methods(),且属性没有默认值就是null,Laravel中只有一处使用到\Illuminate\Mail\Jobs\HandleQueuedMessage::__sleep() :line 78,写个PHPUnit测试下,爆绿灯:

public function testGetObjectVars()    {        // Arrange        $get_object_vars = new TestGetObjectVars(1, 2, 3);                // Actual        $object_vars = get_object_vars($get_object_vars);                // Assert        $this->assertArrayNotHasKey('x', $object_vars);        $this->assertArrayNotHasKey('y', $object_vars);        $this->assertEquals(3, $object_vars['z']);        $this->assertArrayNotHasKey('dot1', $object_vars);        $this->assertArrayNotHasKey('dot2', $object_vars);        $this->assertArrayNotHasKey('circle1', $object_vars);        $this->assertArrayNotHasKey('circle2', $object_vars);        $this->assertEquals(10, $object_vars['line1']);        $this->assertEmpty($object_vars['line2']);    }        class TestGetObjectVars    {        private   $x;        protected $y;        public    $z;        private   $dot1    = 10;        private   $dot2;        protected $circle1 = 20;        protected $circle2;        public    $line1   = 10;        public    $line2;                public function __construct($x, $y, $z)        {                        $this->x = $x;            $this->y = $y;            $this->z = $z;        }    }

is_subclass_of()

is_subclass_of()用来判断给定类对象是否是另一给定类名的子类,Laravel中有用到,这里写下PHPUnit测试,爆绿灯:

public function testIsSubclassOf()    {        // Arrange        $child_class = new ChildClass();                // Actual        $is_subclass = is_subclass_of($child_class, ParentClass::class);                // Assert        $this->assertTrue($is_subclass);    }

is_a()

is_a()用来判定给定类对象是否是另一给定类名的对象或是子类,和is_subclass_of()有点类似,只是is_a()还可以判定是不是该类的对象,is_a()类似于instanceof操作符,Laravel中还没用到这个方法,这里写个PHPUnit测试,爆绿灯:

public function testIsA()    {        // Arrange        $child_class = new ChildClass();                // Actual        $is_object   = is_a($child_class, ChildClass::class);        $is_subclass = is_a($child_class, ParentClass::class);                // Assert        $this->assertTrue($is_object);        $this->assertTrue($is_subclass);    }

最后,给下整个PHPUnit的测试代码:

assertTrue($class_exists); } public function testInterfaceExists() { // Arrange // Actual $interface_exists = interface_exists(TestInterfaceExists::class); // Assert $this->assertTrue($interface_exists); } public function testMethodExists() { // Arrange $test_class_exists = new TestClassExists(); // Actual $object_method_exists1 = method_exists($test_class_exists, 'testPrivateMethodExists'); $object_method_exists2 = method_exists($test_class_exists, 'testProtectedMethodExists'); $object_method_exists3 = method_exists($test_class_exists, 'testPublicMethodExists'); $classname_method_exists1 = method_exists(TestClassExists::class, 'testPrivateMethodExists'); $classname_method_exists2 = method_exists(TestClassExists::class, 'testProtectedMethodExists'); $classname_method_exists3 = method_exists(TestClassExists::class, 'testPublicMethodExists'); // Assert $this->assertTrue($object_method_exists1); $this->assertTrue($object_method_exists2); $this->assertTrue($object_method_exists3); $this->assertTrue($classname_method_exists1); $this->assertTrue($classname_method_exists2); $this->assertTrue($classname_method_exists3); } public function testPropertyExists() { // Arrange $test_class_exists = new TestClassExists(); // Actual $private_property1 = property_exists($test_class_exists, 'testPrivatePropertyExists'); $private_property2 = property_exists(TestClassExists::class, 'testPrivatePropertyExists'); $protected_property1 = property_exists($test_class_exists, 'testProtectedPropertyExists'); $protected_property2 = property_exists(TestClassExists::class, 'testProtectedPropertyExists'); $public_property1 = property_exists($test_class_exists, 'testPublicPropertyExists'); $public_property2 = property_exists(TestClassExists::class, 'testPublicPropertyExists'); // Assert $this->assertTrue($private_property1); $this->assertTrue($private_property2); $this->assertTrue($protected_property1); $this->assertTrue($protected_property2); $this->assertTrue($public_property1); $this->assertTrue($public_property2); } public function testTraitExists() { // Arrange // Actual $test_trait_exists = trait_exists(TestTraitExists::class); // Assert $this->assertTrue($test_trait_exists); } public function testClassAlias() { // Arrange class_alias(TestClassExists::class, 'MyRightCapital\Container\Tests\AliasTestClassExists'); $test_class_exists = new TestClassExists(); // Actual $actual = new AliasTestClassExists(); //Assert $this->assertInstanceOf(TestClassExists::class, $actual); $this->assertInstanceOf(AliasTestClassExists::class, $test_class_exists); } public function testGetClass() { // Arrange $test_class_exists = new TestClassExists(); // Actual $class_name = get_class($test_class_exists); // Assert $this->assertSame(TestClassExists::class, $class_name); } public function testGetParentClass() { // Arrange $child_class = new ChildClass(); // Actual $parent_class1 = get_parent_class($child_class); $parent_class2 = get_parent_class(ChildClass::class); // Assert $this->assertSame(ParentClass::class, $parent_class1); $this->assertSame(ParentClass::class, $parent_class2); } public function testGetCalledClass() { // Arrange $child_class = new ChildClass(); $parent_class = new ParentClass(); // Actual $child_called_class = $child_class->testGetCalledClass(); $parent_called_class = $parent_class->testGetCalledClass(); // Assert $this->assertSame(ChildClass::class, $child_called_class); $this->assertSame(ParentClass::class, $parent_called_class); } public function testInArray() { $this->assertTrue(in_array('a', ['a', 'b', 1], true)); } public function testGetClassMethod() { // Arrange $get_class_methods1 = get_class_methods(ChildClass::class); $get_class_methods2 = get_class_methods(new ChildClass()); // Actual // Assert $this->assertFalse(in_array('testPrivateGetClassMethod', $get_class_methods1, true)); $this->assertFalse(in_array('testPrivateGetClassMethod', $get_class_methods2, true)); $this->assertFalse(in_array('testProtectedGetClassMethod', $get_class_methods1, true)); $this->assertFalse(in_array('testProtectedGetClassMethod', $get_class_methods2, true)); $this->assertTrue(in_array('testPublicGetClassMethod', $get_class_methods1, true)); $this->assertTrue(in_array('testPublicGetClassMethod', $get_class_methods2, true)); $this->assertTrue(in_array('testGetCalledClass', $get_class_methods1, true)); $this->assertTrue(in_array('testGetCalledClass', $get_class_methods2, true)); } public function testGetClassVars() { // Arrange // Actual $class_vars = get_class_vars(ChildClass::class); // Assert $this->assertArrayNotHasKey('privateNoDefaultVar', $class_vars); $this->assertArrayNotHasKey('privateDefaultVar', $class_vars); $this->assertArrayNotHasKey('protectedNoDefaultVar', $class_vars); $this->assertArrayNotHasKey('protectedDefaultVar', $class_vars); $this->assertEmpty($class_vars['publicNoDefaultVar']); $this->assertEquals('public_laravel', $class_vars['publicDefaultVar']); } public function testGetObjectVars() { // Arrange $get_object_vars = new TestGetObjectVars(1, 2, 3); // Actual $object_vars = get_object_vars($get_object_vars); // Assert $this->assertArrayNotHasKey('x', $object_vars); $this->assertArrayNotHasKey('y', $object_vars); $this->assertEquals(3, $object_vars['z']); $this->assertArrayNotHasKey('dot1', $object_vars); $this->assertArrayNotHasKey('dot2', $object_vars); $this->assertArrayNotHasKey('circle1', $object_vars); $this->assertArrayNotHasKey('circle2', $object_vars); $this->assertEquals(10, $object_vars['line1']); $this->assertEmpty($object_vars['line2']); } public function testIsSubclassOf() { // Arrange $child_class = new ChildClass(); // Actual $is_subclass = is_subclass_of($child_class, ParentClass::class); // Assert $this->assertTrue($is_subclass); } public function testIsA() { // Arrange $child_class = new ChildClass(); // Actual $is_object = is_a($child_class, ChildClass::class); $is_subclass = is_a($child_class, ParentClass::class); // Assert $this->assertTrue($is_object); $this->assertTrue($is_subclass); }}class TestGetObjectVars{ private $x; protected $y; public $z; private $dot1 = 10; private $dot2; protected $circle1 = 20; protected $circle2; public $line1 = 10; public $line2; public function __construct($x, $y, $z) { $this->x = $x; $this->y = $y; $this->z = $z; }}class ChildClass extends ParentClass{ private $privateNoDefaultVar; private $privateDefaultVar = 'private_laravel'; protected $protectedNoDefaultVar; protected $protectedDefaultVar = 'protected_laravel'; public $publicNoDefaultVar; public $publicDefaultVar = 'public_laravel'; private function testPrivateGetClassMethod() { } protected function testProtectedGetClassMethod() { } public function testPublicGetClassMethod() { }}class ParentClass{ public function testGetCalledClass() { return get_called_class(); }}class TestClassExists{ private $testPrivatePropertyExists; protected $testProtectedPropertyExists; public $testPublicPropertyExists; private function testPrivateMethodExists() { } protected function testProtectedMethodExists() { } public function testPublicMethodExists() { }}interface TestInterfaceExists{ }trait TestTraitExists{ }

图片描述

PHP不仅提供了检测class, interface, trait, property, method这些函数Introspection Functions,还提供了一整套的API即反射来检测class, interface, trait, property, method,这些API是好几个类组成的,提供了很多好用的方法。限于篇幅,下篇再聊下反射API。

总结:本文主要聊了下PHP提供的一套检测class, interface, trait, property, method的两个工具包:Introspection Functions和Reflection API,这里先聊到Introspection Functions。下篇再聊下Reflection API的使用,到时见。

欢迎关注。

招聘

转载地址:http://bmgol.baihongyu.com/

你可能感兴趣的文章
java 使用hibernate时创建session的辅助类
查看>>
T-Mobile CEO为视频节流辩护 声称他支持网络中立
查看>>
【沉淀】来到阿里云后,SRS大神杨成立说:“终于可以不用装大神了”
查看>>
CDN的夏天来了?
查看>>
报告称到2020年73%的企业将运行SaaS
查看>>
创建动态链接库Dll及测试用例
查看>>
明晰监管范围保护信息安全
查看>>
超融合架构:主数据存储使命之外
查看>>
澳大利亚电信公布其可编程网络计划
查看>>
《Excel数据可视化:一样的数据不一样的图表》——3.2 用项目规则显示隐藏在计算机中的数据...
查看>>
诺基亚将在 MWC 上发布低成本 Android 手机
查看>>
《Outlook时间整理术》一不是电子邮件的问题,而是我们应如何处理它
查看>>
《Adobe Premiere Pro CS5经典教程》——第1课 Adobe Premiere Pro CS5概述 1.1 Adobe Premiere Pro CS5中的新功能...
查看>>
设计师是不是真正的用户
查看>>
《CCIE路由和交换认证考试指南(第5版) (第1卷)》——1.2节以太网第1层:线缆、速率和双工...
查看>>
补丁不起作用:Mac平台安全漏洞仍然存在
查看>>
《Spark核心技术与高级应用》——导读
查看>>
首席技术官 (CTO) 比普通程序员强在哪
查看>>
《交互式程序设计 第2版》一1.4 艺术与交互
查看>>
《脱颖而出——成功网店经营之道》一2.2 进货攻略
查看>>