• PHP __callStatic() 拿不到参数的情况


    PHP 单例

    单例模式的三个要点:

    1 需要一个保存类的唯一实例的静态成员变量:private static $_instance;

    2 构造函数和克隆函数必须声明为私有的,防止外部程序 new 类从而失去单例模式的意义;

    3 必须提供一个访问这个实例的公共的静态方法,从而返回唯一实例的一个引用 。

    
    class Demo {
    	//静态变量私有化,防止类外修改
        private static $_instance;
        public $version;
    	
    	//构造函数私有化,类外不能直接新建对象
        private function __construct(){
            // self::$_instance = $this;
        }
    
    	//在__clone前用private修饰,用来禁止克隆
        private function __clone(){
        }
    
        public function test($ver = 1){
            $this->version = $ver;
        }
    
        // $new=true 时将传出一个新的实例
        public static function instance($new = false)
        {
            if ($new) {
                return new self();
            }
            if (empty(self::$_instance)) {
                self::$_instance = new self();
            }
            return self::$_instance;
        }
    }
    
    
    // 由于 $this 指向的是当前对象,  self 指向的是类, 因此  self::$_instance = $this; 会将之后的实例给覆盖掉
    
    $ins1 = Demo::instance();
    $ins1->test(3);
    echo $ins1->version . PHP_EOL;
    
    $ins3 = Demo::instance(true);
    $ins3->test();
    echo $ins3->version . PHP_EOL;
    
    $ins2 = Demo::instance();
    echo $ins2->version . PHP_EOL;
    
    echo $ins2->version . PHP_EOL;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    输出:

    3
    1
    3
    3
    
    • 1
    • 2
    • 3
    • 4

    由于我当时的画蛇添足, 在构造函数里加了一行 self::$_instance = $this; 导致同样的代码输出却是:

    3
    1
    1
    1
    
    • 1
    • 2
    • 3
    • 4

    后来想了想为什么, 回忆一下得出一个答案, 就是 $this和self 的区别:

    $this 可以说是当前对象的标识符,self 是当前类的标识符。

    self::$_instance = $this; 时, 代表着把当前对象覆盖到类的静态属性中,静态属性是和类一起共有同一片内存的,,那么当 ins3 里新生成对象的时候,把原有的类属性给覆盖掉了,因此之后再使用 Demo::instance() 时获得的对象就和 $ins3 之前的不一样了。

    从这里看 static 这个关键词使用要慎重, 使用场景只有读的情况可以随意使用, 还能节省一点内存(毕竟每个对象查到的属性都是从同一片内存中读的), 但是涉及到 读写 的时候, 就要想明白它的运行顺序了。

    php 的 __callStatic 函数

    现在很多框架中调用方法都是Foo::bar()这种格式的,但是他们真的是静态方法吗?

    larave 中尤其常见,但是开发过程中很明显这些有一部分不是静态的,比如你使用一个模型User,那么你每次实例化出来他都是一个全新的,互不影响,这里就用到了一个魔术方法__callStatic。

    举个栗子:

    
    class Test{
        public function __call($name, $arguments)
        {
            echo 'this is __call'. PHP_EOL;
        }
    
        public static function __callStatic($name, $arguments)
        {
            echo 'this is __callStatic:'. PHP_EOL;
        }
    }
    
    $test = new Test();
    $test->hello();
    $test::hi();
    //输出:this is __call:hello
    //输出:this is __callStatic:hi
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    当然魔术方法也是很耗性能的一种方式,每次调用的时候后会先扫一遍 class 没找到方法时才会调用它,而为了代码的整洁和抽象这个方法也能给很大的帮助,在这之间去要有个权衡。

    下面实现的 log 类,采用的就是这种方法,将方法解耦出来,只要符合规定的接口就能调用

    
    
    class Test{
        //获取 logger 的实体
        private static $logger;
    
        public static function getLogger(){
            return self::$logger?: self::$logger = self::createLogger();
        }
    
        private static function createLogger(){
            return new Logger();
        }
    
        public static function setLogger(LoggerInterface $logger){
            self::$logger = $logger;
        }
    
    
        public function __call($name, $arguments)
        {
            call_user_func_array([self::getLogger(),$name],$arguments);
        }
    
        public static function __callStatic($name, $arguments)
        {
            forward_static_call_array([self::getLogger(),$name],$arguments);
        }
    }
    
    interface LoggerInterface{
        function info($message,array $content = []);
        function alert($messge,array $content = []);
    }
    
    class Logger implements LoggerInterface {
        function info($message, array $content = [])
        {
            echo 'this is Log method info' . PHP_EOL;
            var_dump($content);
        }
    
        function alert($messge, array $content = [])
        {
            echo 'this is Log method alert: '. $messge . PHP_EOL;
        }
    }
    
    
    Test::info('喊个口号:',['好好','学习','天天','向上']);
    $test = new Test();
    $test->alert('hello');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    在这里插入图片描述

    PHP __callStatic() 拿不到参数的情况

    
    
    class Test
    {
        // 实例
        protected static $instance;
        
        public static function make($rules = [], $message = [], $field = [])
        {
    		/*
    		var_dump(self::$instance); null
    		var_dump($rules, $message, $field);
    		array(0) {
    		}
    		array(0) {
    		}
    		array(0) {
    		}
    		*/
            if (is_null(self::$instance)) {
                self::$instance = new self($rules, $message, $field);
            }
    		/*
    		var_dump($rules, $message, $field);
    		array(0) {
    		}
    		array(0) {
    		}
    		array(0) {
    		}
    		*/
            return self::$instance;
        }
        
        protected function is($value, $rule, $data = [])
        {
            var_dump($value, $rule, $data);
    		/*
    		int(555)
    		string(5) "email"
    		array(0) {}
    		*/
        }
        
        public static function __callStatic($method, $params)
        {
            /*
    		var_dump($method, $params);
    		array(2) {
    		  [0]=>
    		  int(555)
    		  [1]=>
    		  string(5) "email"
    		}
    		*/ 
            $class = self::make();
    		// var_dump(method_exists($class, $method)); true
            if (method_exists($class, $method)) {
                return call_user_func_array([$class, $method], $params);
            } else {
                throw new \BadMethodCallException('method not exists:' . __CLASS__ . '->' . $method);
            }
        }
    }
    
    Test::is(555, 'email');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    本地环境 php7.4.28

    成功获取参数
    
    array(2) { [0]=> int(555) [1]=> string(5) "email" }
    
    • 1
    • 2
    • 3

    生产环境 php 7.3.0

    获取参数失败
    
    array(2) { }
    
    • 1
    • 2
    • 3

    原因

    PHP 7 ChangeLog 7.3.2

    https://www.php.net/ChangeLog-7.php#7.3.2

    在这里插入图片描述
    在这里插入图片描述
    Bug #77339 __callStatic may get incorrect arguments
    https://bugs.php.net/bug.php?id=77339

    call_user_func_array (内置函数)

    call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数。

    语法

    call_user_func_array(callable $callback, array $args): mixed
    
    • 1

    把第一个参数作为回调函数(callback)调用,
    把参数数组作(args)为回调函数的的参数传入。

    参数

    callback
    被调用的回调函数。

    args
    要被传入回调函数的数组,这个数组得是索引数组。

    函数调用方式

     
    //单个参数
    function LovePhper($app){
    	echo 'Love for - '.$app;
    } 
    /方法名参数///   方法内多个参数使用数组
    call_user_func('LovePhper','PHP');
    
    ************************* 输出 *************************
    Love for - PHP
    
    //多个参数
    function LikePhper(... $app){
    	$str =  'Like for - ';
    	foreach ($app as $key => $value) {
    		echo $str,$value,PHP_EOL;
    	}
    } 
    call_user_func_array('LikePhper', ['PHP','JAVA','Python','Dart'])
    
    ************************* 输出 *************************
    Like for - PHP
    Like for - JAVA
    Like for - Python
    Like for - Dart
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    调用类的内部方法

     
    class Cladr{
    	static function LovePhper($app){
    		echo 'Love for - '.$app;
    	}
    }
    call_user_func_array(array('Cladr','LovePhper'),['PHP'])
    
    *************************输出*************************
    Love for - PHP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    命名空间使用

     
    namespace Lj;
    
    class Cladr{
    	static function LovePhper($app){
    		echo 'Love for - namespace - '.$app;
    	}
    }
    call_user_func_array(array(__NAMESPACE__.'\Cladr','LovePhper'),['PHP'])
    
    
     *************************输出*************************
     Love for - namespace - PHP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    完整的函数作为回调传入 call_user_func_array()

     
    $LovePhper = function($app){
    	return 'Love for - - '.$app;
    };
    echo call_user_func_array($LovePhper, ['PHP']);
    
     *************************输出*************************
     Love for - - PHP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    JavaScript流程控制-循环(循环(for 循环,双重 for 循环,while 循环,do while 循环,continue break))
    python re 使用非捕获组来忽略第一个value的匹配结果
    多线程新手村3--多线程代码案例
    以训辅教,以战促学 | 新版攻防世界平台正式上线运营!
    【多目标进化优化】 MOEA 测试函数
    【论文笔记】UniPAD: A Universal Pre-training Paradigm for Autonomous Driving
    高中生自学Python,这里给大家一些建议
    记一次To B开发普通的性能优化历程......报表优化
    Android Studio轮播图使用失败怎么办【已解决】
    (续)SSM整合之spring笔记(IOC 基于注解的自动装配@Autowired)(P091—P093)
  • 原文地址:https://blog.csdn.net/weiguang102/article/details/126351971