Przeciążenie funkcji PHP


195

Pochodzący z C ++;)
Jak mogę przeciążać funkcje PHP?

Jedna definicja funkcji, jeśli istnieją argumenty, a druga, jeśli nie ma argumentów? Czy jest to możliwe w PHP? Czy powinienem użyć jeśli jeszcze, aby sprawdzić, czy są jakieś parametry przekazane z $ _GET i POST? i powiązać je?


1
Możesz przeciążać tylko metody klas, ale nie funkcje. Zobacz php.net/manual/en/language.oop5.overloading.php
Spechal

1
Możesz utworzyć funkcję, która jawnie sprawdza liczbę argumentów i wykonuje inną funkcję, korzystając ze wstępnie określonego zestawu. Jednak lepiej przeprojektuj swoje rozwiązanie lub skorzystaj z klas implementujących interfejs
kolypto

2
Jak mówi php.net/manual/en/language.oop5.overloading.php , definicja przeciążenia PHP jest inna niż typowy język OOP. Odnoszą się tylko do magicznych metod, które pozwalają na dynamiczne kierowanie właściwości i funkcji w oparciu o X.
Edwin Daniels

Dla przyszłych czytelników: To, o czym mówi @Spechal, ma inne znaczenie dla tego słowa overloading, niż jest zadawane w pytaniu. (Aby uzyskać więcej informacji, zobacz zaakceptowaną odpowiedź).
ToolmakerSteve

2
Coś się zmieniło od PHP 7? : o
nawfal

Odpowiedzi:


218

Nie można przeciążać funkcji PHP. Podpisy funkcji oparte są tylko na ich nazwach i nie zawierają list argumentów, więc nie można mieć dwóch funkcji o tej samej nazwie. Przeciążanie metod klasowych różni się w PHP niż w wielu innych językach. PHP używa tego samego słowa, ale opisuje inny wzorzec.

Można jednak zadeklarować funkcję wariadyczną, która przyjmuje zmienną liczbę argumentów. Użyłbyś func_num_args()i, func_get_arg()aby przekazać argumenty i użyć ich normalnie.

Na przykład:

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

8
Być może zbyt dużo zajmowałem się programowaniem w C ++, ale sugerowałbym wskazówkę, że dzieje się to w parametrach funkcji, takich jak myFunc(/*...*/).
doug65536,

4
@ doug65536, PHP 5.6+ będzie obsługiwał to „...” jako token składniowy , dla naszej wielkiej ulgi. ;)
Sz.

Lub zobacz odpowiedź Adila , która jest bliższa przeładowaniu C ++ - tak blisko, jak to możliwe, w luźno napisanym języku, takim jak php. Jest to jeszcze bardziej odpowiednie w php 7, ponieważ możesz podać wskazówki dotyczące typów parametrów, jeśli są one tego samego typu we wszystkich twoich przeciążeniach.
ToolmakerSteve,

78

PHP nie obsługuje przeciążania tradycyjnych metod, jednak jednym ze sposobów, w jaki możesz osiągnąć to, co chcesz, byłoby użycie __callmagicznej metody:

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

    }

}

20
Nie widziałem tego zastosowania __call()wcześniej. Dość kreatywny (choć trochę gadatliwy)! +1
BoltClock

Naprawdę godne podziwu użycie __call ()
Abhishek Gupta

2
W rzeczywistości nie mogę się z tym zgodzić i muszę zachować powściągliwość przy tej sugestii. Po pierwsze, użycie __call () jest anty-wzorcem. Po drugie, możliwe jest przeładowanie w PHP dla metod klas, które mają poprawną widoczność. Nie możesz jednak - przeciążać funkcji zwykłych.
Oddman

1
Czy możesz wyjaśnić, dlaczego uważasz, że użycie __call () jest anty-wzorcem? Przeciążenie metod PHP nie jest tym, czego szuka OP - chcą mieć możliwość posiadania wielu podpisów metod o tej samej nazwie, ale różnych danych wejściowych / wyjściowych: en.wikipedia.org/wiki/Funkcja_przeładowania
Stephen

20
Nie ma potrzeby używania __call (). Zamiast tego zadeklaruj metodę o pożądanej nazwie bez wymienionych parametrów i użyj func_get_args () w tej metodzie, aby wysłać do odpowiedniej prywatnej implementacji.
FantasticJamieBurns

30

Aby przeładować funkcję, po prostu podaj domyślnie parametr 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');

4
Nie uważam parametru domyślnego za przeciążenie funkcji. przeciążenie funkcji [lub metody] ma więcej wspólnego z wywołaniem innej implementacji na podstawie typu przekazanego argumentu. Użycie parametrów domyślnych pozwala tylko wywołać tę samą implementację z wygodą mniejszej liczby parametrów.
Skalowalny

1
tak, możesz również manipulować nim na podstawie typu, ale tak jakbyś zna luźno napisany język php i radzenie sobie z nim wymaga rozwiązania tego problemu.
Adil Abbasi,

1
Wolę tę odpowiedź od zaakceptowanej, ponieważ wyjaśnia ona, jaka powinna być minimalna i maksymalna liczba parametrów. (Nie podawaj wartości domyślnej dla wymaganych parametrów.) @Scalable - Zgadzam się z Adilem, że skoro php jest luźno wpisany, to właściwie wszystko, co może oznaczać w php dla overloadfunkcji - niemniej jednak robisz użyteczny punkt o których czytelnicy powinni wiedzieć.
ToolmakerSteve

11

Dla niektórych może to być hacking, ale nauczyłem się w ten sposób, jak Cakephp wykonuje niektóre funkcje i dostosowałem go, ponieważ podoba mi się elastyczność, jaką zapewnia

Chodzi o to, że masz różnego rodzaju argumenty, tablice, obiekty itp., A następnie wykrywasz to, co przeszedłeś i stamtąd

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

1
Nie, nie widzę w tym nic dziwnego, PHP robi to dla wielu swoich wbudowanych funkcji.
BoltClock

Ponieważ php jest luźno wpisany, tak właśnie należy poradzić sobie z tą sytuacją. Jego „niezbędna hackactwo” w php.
ToolmakerSteve,

11
<?php   
/*******************************
 * author  : hishamdalal@gmail.com 
 * 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;
}
//----------------------------------------------------------

4
Czy możesz dodać wyjaśnienie, jak korzystać z tej klasy?
Justus Romijn

1- stwórz nową klasę 2- rozszerza przeciążalność. 3- twórz funkcje takie jak funcname_ () => bez argumentów lub jak funcname_s ($ s) => ciąg arg </li>
Hisham Dalal

1
To całkiem fajne rozwiązanie. Dlaczego robisz $ o = new $ obj ()? Jeszcze tego nie wypróbowałem, ale myślę, że powinno to być \ $ o = \ $ this?
over_optimistic

Dziękuję za tę ważną wiadomość i użyję odwrotnego ukośnika, ale działa on z odwrotnym ukośnikiem i bez niego! - Używam phpEazy jako serwera lokalnego.
Hisham Dalal,


3

W PHP 5.6 można użyć operatora ikona ... jako ostatni parametr i pozbyć się func_get_args()i func_num_args():

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

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

Możesz go również użyć do rozpakowania argumentów:

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

Jest równa:

example(1, 2, 3);

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);
    ?>

Spróbuj edytować pytanie i użyj formatowania. Sprawi, że Twoja odpowiedź będzie bardziej czytelna i przyciągnie więcej użytkowników.
Kashish Arora


0

PHP na razie nie obsługuje przeładowywania. Mam nadzieję, że zostanie to zaimplementowane w innych wersjach, takich jak inne języki programowania.

Zapoznaj się z tą biblioteką, to pozwoli ci użyć przeciążenia PHP pod względem zamknięć. https://github.com/Sahil-Gulati/Overloading


1
jeśli zamierzasz złożyć takie oświadczenie, naprawdę powinno ono zawierać wersje, o których mowa, w ten sposób ludzie wiedzą, jak nieaktualny jest twój komentarz, kiedy zobaczą go w przyszłości
MikeT

0

Niestety nie ma przeciążenia w PHP, jak dzieje się to w C #. Ale mam małą sztuczkę. Deklaruję argumenty z domyślnymi wartościami null i sprawdzam je w funkcji. W ten sposób moja funkcja może robić różne rzeczy w zależności od argumentów. Poniżej znajduje się prosty przykład:

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

1
Przeczytaj wszystkie istniejące odpowiedzi, zanim napiszesz nowy rok później. Technikę tę pokazano już dwukrotnie w powyższych odpowiedziach. Raz w 2013 r. I ponownie w 2014 r.
ToolmakerSteve
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.