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

  • 相关阅读:
    PHP 大小写转换、首字母大写、每个单词首字母大写转换相关函数
    【论文学习4】BiSample: Bidirectional Sampling for Handling Missing Data with Local Differential Privacy
    【论文学习3】Local Differential Privacy for Deep Learning
    【论文学习2】 Differential Privacy Reinforcement Learning
    深度学习中的优化算法
    Spatial crowdsourcing
    “pip install tensorflow ”出现错误
    python或pip'不是内部或外部命令”
    pip install torch出现错误
    打不开gitHub的解决方法
  • 原文地址:https://www.cnblogs.com/mouseleo/p/10982468.html
Copyright © 2011-2022 走看看