說明:Laravel中常用PHP的反射特性來設計代碼,本文主要學習PHP的反射特性,來提升寫代碼時的設計質量。PHP提供一套檢測class, interface, trait, property, method
的兩個工具包:Introspection Functions
和Reflection API
,相似於探針同樣的東西來探測這些一等公民。本文先看下Introspection Functions
的使用。php
開發環境: Laravel5.3 + PHP7
laravel
Introspection Functions
是用來操做object class的一些函數,PHP提供了大量的Introspection Functions
來操做class, interface, trait, method, property
:bootstrap
class_exists()api
interface_exists()數組
method_exists()app
property_exists()ide
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()
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()是用來檢查接口是否存在,寫個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 { }
檢查類的方法(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() { } }
檢查該屬性(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是否存在,寫下PHPUnit測試,爆綠燈:
// class IntrospectionTest public function testTraitExists() { // Arrange // Actual $test_trait_exists = trait_exists(TestTraitExists::class); // Assert $this->assertTrue($test_trait_exists); } trait TestTraitExists { }
給指定類取別名,Laravel中只有一處使用了class_alias(),用來給config/app.php中$aliases[ ]註冊別名,可看下Laravel5.3之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()獲取對象的類名,這個函數在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()是用來獲取類的父類名,目前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()獲取後期靜態綁定類即實際調用類的名稱,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()用來獲取類的方法名組成一個數組(測試只能是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()只會讀取類的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()只會讀取對象的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()用來判斷給定類對象是不是另外一給定類名的子類,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_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的測試代碼:
<?php 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 { }
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的使用,到時見。