reflection php,Laravel学习笔记之PHP反射(Reflection) (上)

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

开发环境: Laravel5.3 + PHP7

Introspection Functions

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

class_exists()

interface_exists()

method_exists()

property_exists()

trait_exists()

class_alias()

get_class()

get_parent_class()

get_called_class()

get_class_methods()

get_class_vars()

get_object_vars()

is_subclass_of()

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学习笔记之bootstrap源码解析,看下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的测试代码:

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);

}

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

{

}

bVDqwx?w=4626&h=594

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的使用,到时见。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值