【问题标题】:PHP function overloadingPHP函数重载
【发布时间】:2011-06-09 12:31:43
【问题描述】:

来自 C++ 背景 ;)
如何重载 PHP 函数?

如果有参数,一个函数定义,如果没有参数,另一个函数定义? 在PHP中可以吗?或者我应该使用 if else 检查是否有从 $_GET 和 POST 传递的任何参数?并将它们联系起来?

【问题讨论】:

  • 只能重载类方法,不能重载函数。见php.net/manual/en/language.oop5.overloading.php
  • 您可以创建一个函数,从一组预定义的参数中显式检查参数的数量并执行另一个函数。但是你最好重新设计你的解决方案,或者使用实现你的接口的类
  • 正如php.net/manual/en/language.oop5.overloading.php 所说,PHP 对重载的定义不同于典型的OOP 语言。它们只是指允许基于 X 动态路由属性和函数的魔术方法。
  • 对于未来的读者:@Spechal 所指的是 overloading 这个词的不同含义,而不是在问题中提出的。 (有关详细信息,请参阅已接受的答案。)
  • 自 PHP 7 以来有什么变化吗? :o

标签: php arguments overloading


【解决方案1】:

您不能重载 PHP 函数。函数签名仅基于它们的名称,不包括参数列表,因此您不能有两个具有相同名称的函数。类method overloading 在PHP 中与在许多其他语言中不同。 PHP 使用相同的词,但它描述了不同的模式。

但是,您可以声明一个variadic function,它接受可变数量的参数。您将使用func_num_args()func_get_arg() 来获取传递的参数,并正常使用它们。

例如:

function myFunc() {
    for ($i = 0; $i < func_num_args(); $i++) {
        printf("Argument %d: %s\n", $i, func_get_arg($i));
    }
}

/*
Argument 0: a
Argument 1: 2
Argument 2: 3.5
*/
myFunc('a', 2, 3.5);

【讨论】:

  • 也许我一直在做 C++ 开发太多了,但我会建议在函数参数中做这个提示,比如 myFunc(/*...*/)
  • @doug65536,PHP 5.6+ 将支持 "..." as a syntax token,让我们大为松了一口气。 ;)
  • 或者参见Adil's answer,它更接近于 C++ 的重载——尽可能接近,使用像 php 这样的松散类型语言。它在 php 7 中更合适,因为您可以为参数提供类型提示,如果它们在所有重载中都是相同的类型。
  • 使用可变参数函数很好,只要它没有被过度使用和滥用(仅在需要时使用)但模拟 C++ 行为是不好的。因为它总是涉及使用 switch case 或 if/else 检查条件,这会导致开销。这可能不是一个巨大的性能成本,但是当一个函数经常被调用时,它就会加起来。坦率地说,尽管性能成本很小,但我不相信它比仅使用多个方法名称更容易接受。
【解决方案2】:

PHP 不支持传统的方法重载,但是您可能能够实现所需的一种方法是使用 __call 魔术方法:

class MyClass {
    public function __call($name, $args) {

        switch ($name) {
            case 'funcOne':
                switch (count($args)) {
                    case 1:
                        return call_user_func_array(array($this, 'funcOneWithOneArg'), $args);
                    case 3:
                        return call_user_func_array(array($this, 'funcOneWithThreeArgs'), $args);
                 }
            case 'anotherFunc':
                switch (count($args)) {
                    case 0:
                        return $this->anotherFuncWithNoArgs();
                    case 5:
                        return call_user_func_array(array($this, 'anotherFuncWithMoreArgs'), $args);
                }
        }
    }

    protected function funcOneWithOneArg($a) {

    }

    protected function funcOneWithThreeArgs($a, $b, $c) {

    }

    protected function anotherFuncWithNoArgs() {

    }

    protected function anotherFuncWithMoreArgs($a, $b, $c, $d, $e) {

    }

}

【讨论】:

  • 我以前从未见过__call() 的这种用法。很有创意(如果有点冗长)! +1
  • 对__call()的使用真的很赞
  • 其实不能同意这个建议,必须克制一下这个建议。一方面,这种使用 __call() 是一种反模式。其次,可以在 PHP 中为具有正确可见性的类方法进行重载。但是,您不能 - 重载plain-jane 函数。
  • 你能解释一下为什么你认为使用 __call() 是一种反模式吗? PHP 方法重载不是 OP 正在寻找的 - 他们希望能够拥有多个具有相同名称但输入/输出不同的方法签名:en.wikipedia.org/wiki/Function_overloading
  • 不需要使用__call()。而是声明一个具有您想要的名称的方法,不列出任何参数,并在该方法中使用 func_get_args() 来调度到适当的私有实现。
【解决方案3】:

要重载函数,只需默认传递参数为 null,

class ParentClass
{
   function mymethod($arg1 = null, $arg2 = null, $arg3 = null)  
     {  
        if( $arg1 == null && $arg2 == null && $arg3 == null ){ 
           return 'function has got zero parameters <br />';
        }
        else
        {
           $str = '';
           if( $arg1 != null ) 
              $str .= "arg1 = ".$arg1." <br />";

           if( $arg2 != null ) 
              $str .= "arg2 = ".$arg2." <br />";

           if( $arg3 != null ) 
              $str .= "arg3 = ".$arg3." <br />";

           return $str;
         }
     }
}

// and call it in order given below ...

 $obj = new ParentClass;

 echo '<br />$obj->mymethod()<br />';
 echo $obj->mymethod();

 echo '<br />$obj->mymethod(null,"test") <br />';
 echo $obj->mymethod(null,'test');

 echo '<br /> $obj->mymethod("test","test","test")<br />';
 echo $obj->mymethod('test','test','test');

【讨论】:

  • 我不认为默认参数是函数重载。函数[或方法]重载更多地与根据传递的参数类型调用不同的实现有关。使用默认参数只允许您以更少的参数方便地调用相同的实现。
  • 是的,您也可以根据类型对其进行操作,但是就好像您知道 php 松散类型语言并处理它需要解决这个问题。
  • 我更喜欢这个答案而不是接受的答案,因为它明确了参数的最小和最大数量应该是多少。 (不要为所需的参数提供默认值。)@Scalable - 我同意 Adil 的观点,因为 php 是松散类型的,这实际上是 php 中对overload 一个函数的全部含义 - 永远不会,你做读者应该注意的一个有用的点。
  • 这与主题无关。传统方法重载的要点是允许允许具有相同名称的函数,只要它们具有不同的参数计数和/或参数类型。并且强制执行数字或论点与此背道而驰。但是您是对的,因此没有强制客户正确使用方法。出于几个原因,我认为在 PHP 中模拟这种行为是一个坏主意,应该使用不同名称的方法。
【解决方案4】:

这对某些人来说可能很老套,但我从 Cakephp 的一些功能中学到了这种方法并对其进行了调整,因为我喜欢它所创造的灵活性

这个想法是你有不同类型的参数、数组、对象等,然后你检测你被传递的内容并从那里开始

function($arg1, $lastname) {
    if(is_array($arg1)){
        $lastname = $arg1['lastname'];
        $firstname = $arg1['firstname'];
    } else {
        $firstname = $arg1;
    }
    ...
}

【讨论】:

  • 不,我不认为这是 hackish,PHP 的许多内置函数都是这样做的。
  • 因为 php 是松散类型的,这正是一个 必须 处理这种情况的方式。它在php中的“必要的hackishness”。
【解决方案5】:
<?php   
/*******************************
 * author  : hishamdalal@gmail.com 
 * version : 3.8
 * create on : 2017-09-17
 * updated on : 2020-01-12
 * download example:  https://github.com/hishamdalal/overloadable
 *****************************/

#> 1. Include Overloadable class

class Overloadable
{
    static function call($obj, $method, $params=null) {
        $class = get_class($obj);
        // Get real method name
        $suffix_method_name = $method.self::getMethodSuffix($method, $params);

        if (method_exists($obj, $suffix_method_name)) {
            // Call method
            return call_user_func_array(array($obj, $suffix_method_name), $params);
        }else{
            throw new Exception('Tried to call unknown method '.$class.'::'.$suffix_method_name);
        }
    }

    static function getMethodSuffix($method, $params_ary=array()) {
        $c = '__';
        if(is_array($params_ary)){
            foreach($params_ary as $i=>$param){
                // Adding special characters to the end of method name 
                switch(gettype($param)){
                    case 'array':       $c .= 'a'; break;
                    case 'boolean':     $c .= 'b'; break;
                    case 'double':      $c .= 'd'; break;
                    case 'integer':     $c .= 'i'; break;
                    case 'NULL':        $c .= 'n'; break;
                    case 'object':
                        // Support closure parameter
                        if($param instanceof Closure ){
                            $c .= 'c';
                        }else{
                            $c .= 'o'; 
                        }
                    break;
                    case 'resource':    $c .= 'r'; break;
                    case 'string':      $c .= 's'; break;
                    case 'unknown type':$c .= 'u'; break;
                }
            }
        }
        return $c;
    }
    // Get a reference variable by name
    static function &refAccess($var_name) {
        $r =& $GLOBALS["$var_name"]; 
        return $r;
    }
}
//----------------------------------------------------------
#> 2. create new class
//----------------------------------------------------------

class test 
{
    private $name = 'test-1';

    #> 3. Add __call 'magic method' to your class

    // Call Overloadable class 
    // you must copy this method in your class to activate overloading
    function __call($method, $args) {
        return Overloadable::call($this, $method, $args);
    }

    #> 4. Add your methods with __ and arg type as one letter ie:(__i, __s, __is) and so on.
    #> methodname__i = methodname($integer)
    #> methodname__s = methodname($string)
    #> methodname__is = methodname($integer, $string)

    // func(void)
    function func__() {
        pre('func(void)', __function__);
    }
    // func(integer)
    function func__i($int) {
        pre('func(integer '.$int.')', __function__);
    }
    // func(string)
    function func__s($string) {
        pre('func(string '.$string.')', __function__);
    }    
    // func(string, object)
    function func__so($string, $object) {
        pre('func(string '.$string.', '.print_r($object, 1).')', __function__);
        //pre($object, 'Object: ');
    }
    // func(closure)
    function func__c(Closure $callback) {
        
        pre("func(".
            print_r(
                array( $callback, $callback($this->name) ), 
                1
            ).");", __function__.'(Closure)'
        );
        
    }   
    // anotherFunction(array)
    function anotherFunction__a($array) {
        pre('anotherFunction('.print_r($array, 1).')', __function__);
        $array[0]++;        // change the reference value
        $array['val']++;    // change the reference value
    }
    // anotherFunction(string)
    function anotherFunction__s($key) {
        pre('anotherFunction(string '.$key.')', __function__);
        // Get a reference
        $a2 =& Overloadable::refAccess($key); // $a2 =& $GLOBALS['val'];
        $a2 *= 3;   // change the reference value
    }
    
}

//----------------------------------------------------------
// Some data to work with:
$val  = 10;
class obj {
    private $x=10;
}

//----------------------------------------------------------
#> 5. create your object

// Start
$t = new test;

#> 6. Call your method

// Call first method with no args:
$t->func(); 
// Output: func(void)

$t->func($val);
// Output: func(integer 10)

$t->func("hello");
// Output: func(string hello)

$t->func("str", new obj());
/* Output: 
func(string str, obj Object
(
    [x:obj:private] => 10
)
)
*/

// call method with closure function
$t->func(function($n){
    return strtoupper($n);
});

/* Output:
func(Array
(
    [0] => Closure Object
        (
            [parameter] => Array
                (
                    [$n] => 
                )

        )

    [1] => TEST-1
)
);
*/

## Passing by Reference:

echo '<br><br>$val='.$val;
// Output: $val=10

$t->anotherFunction(array(&$val, 'val'=>&$val));
/* Output:
anotherFunction(Array
(
    [0] => 10
    [val] => 10
)
)
*/

echo 'Result: $val='.$val;
// Output: $val=12

$t->anotherFunction('val');
// Output: anotherFunction(string val)

echo 'Result: $val='.$val;
// Output: $val=36







// Helper function
//----------------------------------------------------------
function pre($mixed, $title=null){
    $output = "<fieldset>";
    $output .= $title ? "<legend><h2>$title</h2></legend>" : "";
    $output .= '<pre>'. print_r($mixed, 1). '</pre>';
    $output .= "</fieldset>";
    echo $output;
}
//----------------------------------------------------------

【讨论】:

  • 你能补充一些解释如何使用这个类吗?
  • 1- 创建新类 2- 扩展可重载。 3- 创建函数,如 funcname_() => no args 或 funcname_s($s) => string arg
  • 这是一个非常酷的解决方案。为什么要使用 $o = new $obj()?我还没有尝试过,虽然我认为它应该是 \$o = \$this?
  • 感谢您的重要通知,我将使用反斜杠,但它可以使用反斜杠,也可以不使用! - 我使用 phpEazy 作为本地服务器。
【解决方案6】:

这个呢:

function($arg = NULL) {

    if ($arg != NULL) {
        etc.
        etc.
    }
}

【讨论】:

  • 可以工作,但如果重载将具有不同名称和含义的不同参数,则可读性较差。
【解决方案7】:

在 PHP 5.6 中,您可以使用 splat operator ... 作为最后一个参数并取消 func_get_args()func_num_args()

function example(...$args)
{
   count($args); // Equivalent to func_num_args()
}

example(1, 2);
example(1, 2, 3, 4, 5, 6, 7);

您也可以使用它来解包参数:

$args[] = 1;
$args[] = 2;
$args[] = 3;
example(...$args);

相当于:

example(1, 2, 3);

【讨论】:

    【解决方案8】:
    <?php
    
        class abs
        {
            public function volume($arg1=null, $arg2=null, $arg3=null)
            {   
                if($arg1 == null && $arg2 == null && $arg3 == null)
            {
                echo "function has no arguments. <br>";
            }
    
            else if($arg1 != null && $arg2 != null && $arg3 != null)
                {
                $volume=$arg1*$arg2*$arg3;
                echo "volume of a cuboid ".$volume ."<br>";
                }
                else if($arg1 != null && $arg2 != null)
                {
                $area=$arg1*$arg2;
                echo "area of square  = " .$area ."<br>";
                }
                else if($arg1 != null)
                {
                $volume=$arg1*$arg1*$arg1; 
                echo "volume of a cube = ".$volume ."<br>";
                }
    
    
            }
    
    
        }
    
        $obj=new abs();
        echo "For no arguments. <br>";
        $obj->volume();
        echo "For one arguments. <br>";
        $obj->volume(3);
        echo "For two arguments. <br>";
        $obj->volume(3,4);
        echo "For three arguments. <br>";
        $obj->volume(3,4,5);
        ?>
    

    【讨论】:

    • 尝试编辑问题并使用格式。这将使您的答案更具可读性并吸引更多用户。
    • 这项技术是shown in an earlier answer
    【解决方案9】:

    遗憾的是,PHP 中没有像 C# 中那样的重载。但我有一个小技巧。我用默认的空值声明参数并在函数中检查它们。这样我的函数可以根据参数做不同的事情。下面是一个简单的例子:

    public function query($queryString, $class = null) //second arg. is optional
    {
        $query = $this->dbLink->prepare($queryString);
        $query->execute();
    
        //if there is second argument method does different thing
        if (!is_null($class)) { 
            $query->setFetchMode(PDO::FETCH_CLASS, $class);
        }
    
        return $query->fetchAll();
    }
    
    //This loads rows in to array of class
    $Result = $this->query($queryString, "SomeClass");
    //This loads rows as standard arrays
    $Result = $this->query($queryString);
    

    【讨论】:

    • 请通读所有现有答案,一年后再写一个新答案。这种技术已经在上面的答案中显示过两次。 2013 年一次,2014 年一次。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-14
    • 2010-12-03
    • 2011-12-05
    • 1970-01-01
    • 2014-06-18
    • 2012-02-21
    相关资源
    最近更新 更多