Surcharge de la fonction PHP

195

Venant de l'arrière-plan C ++;)
Comment puis-je surcharger les fonctions PHP?

Une définition de fonction s'il y a des arguments, et une autre s'il n'y a pas d'arguments? Est-ce possible en PHP? Ou dois-je utiliser if else pour vérifier si des paramètres sont passés de $ _GET et POST ?? et les relier?

Vamsi Krishna B
la source
1
Vous ne pouvez surcharger que les méthodes de classe, mais pas les fonctions. Voir php.net/manual/en/language.oop5.overloading.php
Spechal
1
Vous pouvez créer une fonction qui vérifie explicitement le nombre d'arguments et exécute une autre fonction, à partir d'un ensemble prédéfini d'entre eux. Cependant, vous feriez mieux de
reconcevoir
2
Comme le dit php.net/manual/en/language.oop5.overloading.php , la définition de PHP de la surcharge est différente du langage OOP typique. Ils se réfèrent simplement à des méthodes magiques qui permettent un routage dynamique des propriétés et des fonctions basées sur X.
Edwin Daniels
Pour les futurs lecteurs: ce à quoi @Spechal fait référence, est un sens du mot différent de celui qui overloadingest demandé dans la question. (Voir la réponse acceptée pour plus de détails.)
ToolmakerSteve
2
Quelque chose a changé depuis PHP 7? : o
nawfal

Réponses:

218

Vous ne pouvez pas surcharger les fonctions PHP. Les signatures de fonction sont basées uniquement sur leurs noms et n'incluent pas de listes d'arguments, vous ne pouvez donc pas avoir deux fonctions avec le même nom. La surcharge des méthodes de classe est différente en PHP que dans de nombreux autres langages. PHP utilise le même mot mais il décrit un modèle différent.

Vous pouvez cependant déclarer une fonction variadique qui accepte un nombre variable d'arguments. Vous utiliseriez func_num_args()et func_get_arg()pour faire passer les arguments, et les utiliser normalement.

Par exemple:

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);
BoltClock
la source
8
J'ai peut-être fait trop de développement C ++, mais je suggérerais que cela se fasse dans les paramètres de fonction comme myFunc(/*...*/).
doug65536
4
@ doug65536, PHP 5.6+ supportera ce "..." comme jeton de syntaxe , pour notre plus grand soulagement. ;)
Sz.
Ou voir la réponse d'Adil , qui est plus proche de la surcharge de C ++ - aussi proche qu'il est possible d'obtenir, dans un langage vaguement typé comme php. C'est encore plus approprié dans php 7, car vous pouvez fournir des indices de type pour les paramètres, s'ils sont du même type dans toutes vos surcharges.
ToolmakerSteve
78

PHP ne prend pas en charge la surcharge de méthode traditionnelle, mais une façon dont vous pourriez être en mesure de réaliser ce que vous voulez, serait d'utiliser la __callméthode magique:

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) {

    }

}
Stephen
la source
20
Je n'ai jamais vu cette utilisation __call()auparavant. Assez créatif (si un peu bavard)! +1
BoltClock
Utilisation vraiment admirable de __call ()
Abhishek Gupta
2
En fait, je ne peux pas être d'accord avec cela et je dois faire un exercice de réintégration avec cette suggestion. D'une part, cette utilisation de __call () est un anti-modèle. Deuxièmement, il est possible de faire une surcharge en PHP pour les méthodes de classe qui ont la bonne visibilité. Cependant, vous ne pouvez pas surcharger les fonctions plain-jane.
Oddman
1
Pouvez-vous expliquer pourquoi vous pensez que l'utilisation de __call () est un anti-modèle? La surcharge de méthode PHP n'est pas ce que l'OP recherche - ils veulent avoir la possibilité d'avoir plusieurs signatures de méthode avec le même nom mais des entrées / sorties différentes: en.wikipedia.org/wiki/Function_overloading
Stephen
20
Il n'est pas nécessaire d'utiliser __call (). Au lieu de cela, déclarez une méthode portant le nom que vous désirez, sans aucun paramètre répertorié, et utilisez func_get_args () dans cette méthode pour la distribuer à l'implémentation privée appropriée.
FantasticJamieBurns
30

Pour surcharger une fonction, passez simplement le paramètre à null par défaut,

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');
Adil Abbasi
la source
4
Je ne considère pas le paramètre par défaut comme une surcharge de fonction. la surcharge de fonction [ou méthode] a plus à voir avec l'appel d'une implémentation différente en fonction du type d'argument passé. L'utilisation des paramètres par défaut vous permet d'appeler la même implémentation avec la commodité de moins de paramètres.
Extensible
1
oui, vous pouvez également le manipuler en fonction du type, mais comme si vous connaissiez le langage typé php de manière lâche et que le traiter nécessite de s'y attaquer.
Adil Abbasi
1
Je préfère cette réponse à la réponse acceptée, car elle rend explicite le nombre minimal et maximal de paramètres. (Ne fournissez pas de valeur par défaut pour les paramètres requis.) @Scalable - Je suis d'accord avec Adil que, puisque php est vaguement tapé, c'est effectivement tout ce que cela peut signifier en php pour overloadune fonction - néanmoins, vous faites un point utile que les lecteurs doivent connaître.
ToolmakerSteve
11

Cela peut être hack pour certains, mais j'ai appris de cette façon de la façon dont Cakephp fait certaines fonctions et je l'ai adapté parce que j'aime la flexibilité qu'il crée

L'idée est que vous avez différents types d'arguments, de tableaux, d'objets, etc., puis vous détectez ce qui vous a été transmis et allez à partir de là

function($arg1, $lastname) {
    if(is_array($arg1)){
        $lastname = $arg1['lastname'];
        $firstname = $arg1['firstname'];
    } else {
        $firstname = $arg1;
    }
    ...
}
SeanDowney
la source
1
Non, je ne vois pas cela comme un hack, PHP le fait pour la plupart de ses fonctions intégrées.
BoltClock
Parce que php est vaguement tapé, c'est exactement comment on doit gérer cette situation. Son "hackishness nécessaire" en php.
ToolmakerSteve
11
<?php   
/*******************************
 * author  : [email protected] 
 * version : 3.8
 * create on : 2017-09-17
 * updated on : 2020-01-12
 *****************************/

#> 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;
}
//----------------------------------------------------------
Hisham Dalal
la source
4
Pourriez-vous ajouter quelques explications sur l'utilisation de cette classe?
Justus Romijn
1- créer une nouvelle classe 2- étend les surchargeables. 3- créer des fonctions comme funcname_ () => pas d'arguments ou comme funcname_s ($ s) => string arg </li>
Hisham Dalal
1
C'est une solution plutôt cool. Pourquoi faites-vous $ o = new $ obj ()? Je ne l'ai pas encore essayé, bien que je pense que ce devrait être \ $ o = \ $ this?
over_optimistic
Merci pour cet avis important, et je vais utiliser la barre oblique inverse, mais cela fonctionne avec et sans barre oblique inverse! - J'utilise phpEazy comme serveur local.
Hisham Dalal
4

Et ça:

function($arg = NULL) {

    if ($arg != NULL) {
        etc.
        etc.
    }
}
Marlin Ouverson
la source
Peut fonctionner, mais est moins lisible si la surcharge aura des paramètres différents avec des noms et des significations différents.
Mathias Lykkegaard Lorenzen
3

En PHP 5.6, vous pouvez utiliser l' opérateur splat ... comme dernier paramètre et supprimer func_get_args()et func_num_args():

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

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

Vous pouvez également l'utiliser pour décompresser les arguments:

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

Est équivalent à:

example(1, 2, 3);
mtpultz
la source
1
<?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);
    ?>
Raman Deep Bajwa
la source
Essayez de modifier la question et utilisez la mise en forme. Cela rendra votre réponse plus lisible et attirera plus d'utilisateurs.
Kashish Arora
Cette technique est présentée dans une réponse antérieure .
ToolmakerSteve
0

PHP ne prend pas en charge la surcharge pour l'instant. J'espère que cela sera implémenté dans les autres versions comme les autres langages de programmation.

Consultez cette bibliothèque, cela vous permettra d'utiliser la surcharge PHP en termes de fermetures. https://github.com/Sahil-Gulati/Overloading

Sahil Gulati
la source
1
si vous voulez faire une déclaration comme celle-ci, cela devrait vraiment inclure les versions auxquelles vous faites référence, de cette façon les gens savent à quel point votre commentaire est obsolète lorsqu'ils le voient à une date future
MikeT
0

Malheureusement, il n'y a pas de surcharge en PHP comme cela se fait en C #. Mais j'ai un petit truc. Je déclare des arguments avec des valeurs nulles par défaut et les vérifie dans une fonction. De cette façon, ma fonction peut faire différentes choses en fonction des arguments. Voici un exemple simple:

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);
Rovshan Mamedov
la source
1
Veuillez lire toutes les réponses existantes avant d'en rédiger une nouvelle une année plus tard. Cette technique a déjà été montrée deux fois dans les réponses ci-dessus. Une fois en 2013, et de nouveau en 2014.
ToolmakerSteve