40

If I have a function:

function this($a){
   return $a;
}

If I wanted to redefine the function, would it be as simple as rewriting it?

function this($a, $b){  //New this function
   return $a * $b;
}
Charles
  • 50,943
  • 13
  • 104
  • 142
Michael
  • 5,505
  • 4
  • 20
  • 12
  • Why do you want this? A function should be named to describe what it does. What's wrong with having 2 functions? – Bart S. Apr 14 '10 at 21:01
  • 1
    I'm modifying a core script which has a defined function and rather than edit the function directly, I'd like to include a custom file that I could use to simply redefine the function to my needs. – Michael Apr 16 '10 at 07:27
  • 7
    There are plenty of cases where one might want to modify an already existing function. For instance when writing test doubles (or mocks); or monkey patching libraries at runtime (without having to change the actual source code of the library). I actually find this limitation to be one of the most frustrating of PHP, as opposite to more dynamic programming languages such as Ruby or JavaScript, where this is not only possible, but is also done quite often. – Andrea Fiore May 30 '11 at 14:26
  • possible duplicate of [Is it possible to replace a function in php (such as mail) and make it do something else?](http://stackoverflow.com/q/1837184/), [Is it possible to replace (monkeypatch) PHP functions?](http://stackoverflow.com/q/530649/) – outis Feb 10 '12 at 17:16
  • That's what namespaces are for. Unfortunately many libraries seem hell-bent on screwing over users by escaping the function name to force a global call instead of first checking the current namespace for it. That's just one of the bullshit practices going on at the moment where language features are practically made useless by how libraries and composer/autoloading works at the moment. For example, extending any class not directly written by yourself is a royal pain in the ass - involving composer plugins or a local copy of the entire class or library, instead of just overriding a method. – Christoffer Bubach Aug 18 '21 at 08:52
  • Reflection trickery during runtime or stream filtering for patching PHP include():ed source might be your best option.. :/ – Christoffer Bubach Aug 18 '21 at 08:54

9 Answers9

25

Nope, that throws an error:

Fatal error: Cannot redeclare foo()

The runkit provides options, including runkit_function_rename() and runkit_function_redefine().

Schism
  • 275
  • 7
  • 15
Annika Backstrom
  • 13,937
  • 6
  • 46
  • 52
  • Note: runkit is so far not updated for PHP 7.0. See https://github.com/zenovich/runkit/issues/87 – SOFe Jan 07 '17 at 15:24
  • 1
    runkit for PHP 7 can now be found here: https://github.com/runkit7/runkit7 – Mahn Dec 06 '20 at 18:22
22

If you mean overloading in a Java sense, then the answer is no, this is not possible.

Quoting the PHP manual on functions:

PHP does not support function overloading, nor is it possible to undefine or redefine previously-declared functions.

You could use the runkit extension but usage of runkit in production scenarios is generally considered doubtful practice. If you want to exchange algorithms at runtime, have a look at the Strategy pattern or Anonymous functions instead.

If by redefine you mean add to an existing userland function, refactor, substitute or rewrite, then yes: it is as simple as you've shown. Just add the additional code to the function, but make sure you set a default for backwards compatibility.

Another option would be to use http://antecedent.github.io/patchwork

Patchwork is a PHP library that makes it possible to redefine user-defined functions and methods at runtime, loosely replicating the functionality runkit_function_redefine in pure PHP 5.3 code, which, among other things, enables you to replace static and private methods with test doubles.

Tivie
  • 18,864
  • 5
  • 58
  • 77
Gordon
  • 312,688
  • 75
  • 539
  • 559
  • Sorry, I misread your answer. I thought you said the opposite. The two other downvotes seem to indicate that I'm not the only person who made that mistake – Casebash Oct 07 '11 at 23:18
  • @Casebash thanks for taking your time to reread the answer. appreciated. – Gordon Oct 08 '11 at 08:00
  • @Gordon, These `runkit` extensions work deadly well. How is it "generally considered doubtful practice"? – Pacerier Mar 25 '15 at 15:03
  • @Pacerier because it is unobvious when function suddenly behave differently from their original definition. Especially when it comes to native functions which developers know well. You have to know that something changes this somewhere. – Gordon Mar 25 '15 at 15:26
11

You can't redefine or 'undefine' a function in PHP (without resorting to third-party modules). However, you can define a function conditionally.

So, if you know function A can be defined elsewhere, but not always, you can wrap it like this:

if (!function_exists('A')) {
    function A() {
        // default A implementation
    }
}

Then you only need to make sure the implementation you want is encountered first:

function A() {
    // another A implementation
}
rustyx
  • 80,671
  • 25
  • 200
  • 267
  • With the further [caveat](http://us2.php.net/manual/en/function.function-exists.php#110163) that **calls come [after](http://www.php.net/manual/en/functions.user-defined.php) definitions**. (Recent PHP doesn't care about call/define order for unconditionally defined functions.) – Bob Stein Jul 05 '13 at 22:50
2

I have good news and bad news.

The good news
It is possible (link(s) below).

The nadnews
There are 2 bad news:

By default, only userspace functions may be removed, renamed, or modified. In order to override internal functions, you must enable the runkit.internal_override setting in php.ini.

And the second bad news: You havbe to sacrifice code readability.

Example:

<?php
function this($a){
   return $a;
}

echo this(0);

$f_name = 'this';
$f_args = '$a';
$f_code = 'return $a*$b;';
runkit_function_redefine($f_name, f_args, f_code);

echo this(1,3);

Oh, and one more thing, using this as a name for a function may create confusion, due to the methods of a object of a class being able to use this.something to reffer to the variable something that is in the method and have the same name as the variable something from the object itself. Here is an example

<?php
class theclass{
  $a = 'a';
  function a($a){
    echo $a;
    $a = this.$a;
  }
}
theclass $object = new theclass();
$object -> a('b'); // will echo: ab
SapioiT
  • 21
  • 1
  • 5
  • What about $b parameter in replacing function? Also missing some $ signs in the runkit redeclaring function call. – Jackie Degl'Innocenti Apr 23 '19 at 15:38
  • May worth reading the PHP doc of the second example illustrated there https://php.net/manual/en/function.runkit-function-redefine.php that supports closure function definition too. – Jackie Degl'Innocenti Apr 23 '19 at 15:42
  • @GaretClaborn In what version of PHP does it not? I'm asking because both php7 AND php8 changed a lot of things, which means that some scripts might not be forwards-compatible. – SapioiT Mar 19 '21 at 06:59
1

I've got a library of functions that sometimes I just don't want invoked while I'm testing (typically database updates). If I have, for example, a few different db update functions that are all over the code. instead of commenting out the code, I just create a special class (e.g. class foo {}). Define a global variable (e.g., $DEBUG) and a dummy function (e.g., function dummy {}). Inside foo define all the (public static) functions you need to mimic as

$fn = isset($DEBUG) ? 'dummy' : 'real function'; return call_user_func_array($fn,func_get_args());

Plus you have the advantages of now doing other things, like logging the calls and parameters.

Then simply replace all your calls to real_function(...) with foo::real_function(...). Usually just a simple search/replace (or leave it there; depending on what's going on in the function and how often it's getting called the overhead may be irrelevant).

Cuse70
  • 271
  • 3
  • 5
1

You cannot redeclare functions, without runtime hacking, but in various situations you may, in fact, redefine them.

Namely, if they are stored in a variable. Though, under the hood this is really reassigning the symbol to a new function.


$action['doSomething'] = function($arguments){
    return 'false';
};

$action['doSomething'] = function($arguments){
    return var_export($arguments,true);
};

echo $action['doSomething']('Hello world');

There is also the case of inheritance.

namespace CoolCorp\AwesomeGame;

class World{
   const TILE_SIZE = 8;    // size in pixels of an N x N square
   const GAME_TICK = 25;   // milliseconds between action sequence frames
   const UP = 1;
   const DOWN = 2;
   const LEFT = 3;
   const RIGHT = 4;
    .
    .
}
class Character2D{
    const WALK = 1;
    const RUN = 2;
    
    public $mode=1;
    public $x=0;
    public $y=0;
    public $next_x=0;
    public $next_y=0;

    .
    .

    public function move($direction){
        $selected_mode = self::$mode == self::RUN ? 
            __NAMESPACE__ . '\RunMode' 
          : __NAMESPACE__ . '\WalkMode';

        class_alias($selected_mode, 'game_movement');

        switch($direction){
            case UP: game_movement::up($this); break;
            case DOWN: game_movement::down($this); break;
            case LEFT: game_movement::left($this); break;
            case RIGHT: game_movement::right($this); break;
        }

        $this->AnimateNextPositionAsync(WORLD::GAME_TICK);
    }


    public function move_Alternative($direction){
        $game_movement = self::$mode == self::RUN ? 
            __NAMESPACE__ . '\RunMode' 
          : __NAMESPACE__ . '\WalkMode';

        switch($direction){
            case UP: $game_movement::up($this); break;
            case DOWN: $game_movement::down($this); break;
            case LEFT: $game_movement::left($this); break;
            case RIGHT: $game_movement::right($this); break;
        }

        $this->AnimateNextPositionAsync(WORLD::GAME_TICK);
    }
    .
    .
}
class WalkMode{
    public static function up($actor){ 
        $actor->next_y -= World::TILE_SIZE; 
    };
    public static function down($actor){ 
        $actor->next_y += World::TILE_SIZE; 
    };
    public static function left($actor){ 
        $actor->next_x -= World::TILE_SIZE; 
    };
    public static function right($actor){ 
        $actor->next_x += World::TILE_SIZE; 
    };
}
class RunMode extends WalkMode{
    public static function up($actor){ 
        $actor->next_y -= World::TILE_SIZE*2; 
    };
    public static function down($actor){ 
        $actor->next_y += World::TILE_SIZE*2; 
    };
    public static function left($actor){ 
        $actor->next_x -= World::TILE_SIZE*2; 
    };
    public static function right($actor){ 
        $actor->next_x += World::TILE_SIZE*2; 
    };
}

A bit contrived but illustrative of how you may redefine a function in a child class and use to achieve a goal. The second example assumes you are not using an ancient PHP version.

0

You can't have both functions declared at the same time, that will give an error.

googletorp
  • 33,075
  • 15
  • 67
  • 82
0

You can't redeclare it. If your question is just about overloading that example, how about:

function this($a, $b=1)
{
    return $a * $b;
}
Tom
  • 22,301
  • 5
  • 63
  • 96
0

Setting an appropriate default to any new arguments that you add might help for backwards compatibility, i.e.:

function this($a, $b=1){  //New this function with a sane default.
    return $a * $b;
}

I also recommend, for clarity, generally avoiding using this for function/variable names.

Kzqai
  • 22,588
  • 25
  • 105
  • 137