zoukankan      html  css  js  c++  java
  • Laravel学习笔记之PHP反射(Reflection) (上)

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

    3

    说明: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()方法来判断指定类是否存在,如IlluminateDatabaseConnection::isDoctrineAvailable()的源码:

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

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

    namespace MyRightCapitalContainerTests;
    
    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 MyRightCapitalContainerTests;
    
    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很多地方用到了该函数,如IlluminateFoundationAuthRedirectsUsers::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, 'MyRightCapitalContainerTestsAliasTestClassExists');
            $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 MyRightCapitalContainerTests;
        // 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 MyRightCapitalContainerTests;
        // 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只有一处用到了该方法IlluminateDatabaseEloquentModel::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中只有一处使用到IlluminateMailJobsHandleQueuedMessage::__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的测试代码:

    <?php
    
    namespace MyRightCapitalContainerTests;
    
    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, 'MyRightCapitalContainerTestsAliasTestClassExists');
            $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的使用,到时见。

    欢迎关注Laravel-China

  • 相关阅读:
    维护需求与新增需求
    LoadRunner如何在注册业务脚本中设置参数化唯一性
    常用的功能测试方法
    应用代码性能诊断分析之内存泄漏
    (52)ElasticSearch之字符串排序问题
    (51)ElasticSearch之query string查询及copy_to的使用
    (50)ElasticSearch之分页查询中的deep paging问题
    (49)ElasticSearch之多index,多type查询
    (48)ElasticSearch之查询结果分析
    (47)ElasticSearch之bulk语法格式解析
  • 原文地址:https://www.cnblogs.com/mouseleo/p/10982468.html
Copyright © 2011-2022 走看看