81

Can you declare a function like this...

function ihatefooexamples(){
  return "boo-foo!";
};

And then redeclare it somewhat like this...

if ($_GET['foolevel'] == 10){
  function ihatefooexamples(){
    return "really boo-foo";
  };
};

Is it possible to overwrite a function that way?

Any way?

Mark Lalor
  • 7,820
  • 18
  • 67
  • 106
  • possible duplicate of [Php redefine Class Methods OR Class](http://stackoverflow.com/q/137006/), [Is it possible to replace a function in php (such as mail) and make it do something else?](http://stackoverflow.com/q/1837184/) – outis Feb 10 '12 at 17:14
  • 1
    ... [Is it possible to replace (monkeypatch) PHP functions?](http://stackoverflow.com/q/530649/) – outis Feb 10 '12 at 17:21
  • It would be wonderful if PHP were extended so that 'unset' could be used to delete a function definition preparatory to redefine it using 'function'. – David Spector Jun 05 '19 at 12:51

10 Answers10

110

Edit

To address comments that this answer doesn't directly address the original question. If you got here from a Google Search, start here

There is a function available called override_function that actually fits the bill. However, given that this function is part of The Advanced PHP Debugger extension, it's hard to make an argument that override_function() is intended for production use. Therefore, I would say "No", it is not possible to overwrite a function with the intent that the original questioner had in mind.

Original Answer

This is where you should take advantage of OOP, specifically polymorphism.

interface Fooable
{
    public function ihatefooexamples();
}

class Foo implements Fooable
{
    public function ihatefooexamples()
    {
        return "boo-foo!";
    }
}

class FooBar implements Fooable
{
    public function ihatefooexamples()
    {
        return "really boo-foo";
    }
}

$foo = new Foo();

if (10 == $_GET['foolevel']) {
    $foo = new FooBar();
}

echo $foo->ihatefooexamples();
Peter Bailey
  • 105,256
  • 31
  • 182
  • 206
  • 23
    +1 for solving the actual problem, rather than simply trying to answer the exact question... – ircmaxell Sep 01 '10 at 17:52
  • 2
    The only change I would make would to put the `$foo = new Foo()` in an `else` block, in case either the constructor (a) is expensive, or (b) has side-effects. – Austin Hyde Sep 01 '10 at 18:02
  • @Austin - if `b` ever happens, then you've developed your class wrong. Instantiating objects via `new` should be idempotent. And I'd say `a` is extremely rare. – Peter Bailey Sep 01 '10 at 18:33
  • In the case where the output stays "foo-boo" and has just "really" added to it, I'd rather use a Decorator than a completely new Strategy – Gordon Sep 01 '10 at 20:10
  • 2
    @Gordon You're over-analyzing. I think it's pretty clear that this is just an example. – Peter Bailey Sep 01 '10 at 20:34
  • @Peter: I completely agree. It's just that not everybody makes their classes "right", or maybe there is actually a legitimate reason for side-effects (the only example that comes to mind is an instance counter). In my code, I overwrite variables like that all the time, but for public consumption, I would do it "not lazy" and use an `else` block. – Austin Hyde Sep 01 '10 at 22:30
  • @Peter well, maybe I am. But IMO the OP could have picked a more different example then. It's not too far fetched to suggest Decorator here. Not saying Strategy is wrong though. – Gordon Sep 01 '10 at 22:35
  • 10
    I disagree, with this answer, the question clearly says "function", not "method", it is clear that the context is not a Class. I came here from a Google search, at the moment I'm using a non-OO legacy library and this answer is just useless. – mastazi Aug 18 '15 at 03:36
  • 1
    Not answering the question. I'm using a framework with global functions declared. I don't want them, and want to replace them. With this answer this is completely useless. – Thomas Cheng Oct 07 '16 at 08:37
  • 5
    @mastazi Thank you for pointing out to me that this is a top answer given by google searches on this question. I have updated the answer with information that should help future answer seekers with similar goals to your own. – Peter Bailey Oct 11 '16 at 14:17
  • 1
    @ThomasCheng As I just let mastazi know, this answer has been updated to better suit folks like yourself. Cheers. – Peter Bailey Oct 11 '16 at 14:18
  • @Peter Bailey thank you for the update, I just re-read my comment and realised it wasn't very friendly, sorry for that! – mastazi Oct 17 '16 at 05:40
87

Monkey patch in namespace php >= 5.3

A less evasive method than modifying the interpreter is the monkey patch.

Monkey patching is the art of replacing the actual implementation with a similar "patch" of your own.

Ninja skills

Before you can monkey patch like a PHP Ninja we first have to understand PHPs namespaces.

Since PHP 5.3 we got introduced to namespaces which you might at first glance denote to be equivalent to something like java packages perhaps, but it's not quite the same. Namespaces, in PHP, is a way to encapsulate scope by creating a hierarchy of focus, especially for functions and constants. As this topic, fallback to global functions, aims to explain.

If you don't provide a namespace when calling a function, PHP first looks in the current namespace then moves down the hierarchy until it finds the first function declared within that prefixed namespace and executes that. For our example if you are calling print_r(); from namespace My\Awesome\Namespace; What PHP does is to first look for a function called My\Awesome\Namespace\print_r(); then My\Awesome\print_r(); then My\print_r(); until it finds the PHP built in function in the global namespace \print_r();.

You will not be able to define a function print_r($object) {} in the global namespace because this will cause a name collision since a function with that name already exists.

Expect a fatal error to the likes of:

Fatal error: Cannot redeclare print_r()

But nothing stops you, however, from doing just that within the scope of a namespace.

Patching the monkey

Say you have a script using several print_r(); calls.

Example:

<?php
     print_r($some_object);
     // do some stuff
     print_r($another_object);
     // do some other stuff
     print_r($data_object);
     // do more stuff
     print_r($debug_object);

But you later change your mind and you want the output wrapped in <pre></pre> tags instead. Ever happened to you?

Before you go and change every call to print_r(); consider monkey patching instead.

Example:

<?php
    namespace MyNamespace {
        function print_r($object) 
        {
            echo "<pre>", \print_r($object, true), "</pre>"; 
        }

        print_r($some_object);
        // do some stuff
        print_r($another_object);
        // do some other stuff
        print_r($data_object);
        // do more stuff
        print_r($debug_object);
    }

Your script will now be using MyNamespace\print_r(); instead of the global \print_r();

Works great for mocking unit tests.

nJoy!

nickl-
  • 8,417
  • 4
  • 42
  • 56
  • 4
    A really valuable approach! Thanks for sharing – Nico Haase Mar 21 '16 at 14:54
  • 1
    This is an incredibly helpful answer for those who are stuck with legacy, non-oo libraries! – mastazi Oct 11 '16 at 21:32
  • 2
    I can't make this work when functions A, B, and C are defined in an existing large include file (I don't want to change these, because other pages use them) and I want to override just function B in the main file, where B is defined in a separate include file. I guess namespaces don't work in this case. – David Spector Oct 22 '18 at 14:03
  • @DavidSpector you need to namespace where it is redeclared which you want to do in a separate include file ex. `namespace SeparateInclude {}` then in the main file you can namespace again `namespace MainFile {}`, include the new file create monkey patch `function B()` again in main file which calls `\SeparateInclude\B();`. When you call `B();` in main file now it calls `\MainFile\B();` which calls `\SeparateInclude\B();` successfully avoiding `\B();` from the large/original include file. – nickl- Jun 05 '19 at 05:18
  • 1
    Does it actually move down the namespaces because it doesn't seem to? It seems A\B\C trying to use an "f" will check for \A\B\C\f or \f ("top level f") only, The \ are LITERALLY names with the \ at the start only having special meaning (like concatenating or not the current namespace as a prefix, or not doing this) - can you confirm? Because it'd make sense if it did! But it's PHP so they wont do that (I've tried and read extensively, this gave me a glimmer of hope - crush or or show me what I missed please! PHP5 would be great for the answer BTW) – Alec Teal Sep 07 '19 at 19:54
  • Is there a way to tell if a call is monkey patched? Let's say your intern is helping on the project and he can't figure out why all of his statements have `pre` tags around them. He's pulling out his hair. He then gives up programming and lives as a boodist monk. One day he's meditating and it occurs to him that the function call could be spaced by its name. What do you say to him? – 1.21 gigawatts Aug 23 '20 at 23:45
25

Have a look at override_function to override the functions.

override_function — Overrides built-in functions

Example:

override_function('test', '$a,$b', 'echo "DOING TEST"; return $a * $b;');
Sarfraz
  • 377,238
  • 77
  • 533
  • 578
  • 2
    this is a good point but if the function is large, its not nice to pass in strings that long! :(, another point is that this can only be used for built in functions and not user defined! – RobertPitt Sep 01 '10 at 17:39
  • @RobertPitt: Agreed this is what we have at the moment and your solution looks good but that depends on php version being used. – Sarfraz Sep 01 '10 at 17:41
  • Why would you not want to be on a php version < 5.3.0? unless your building a script that would be retailed/released to the public then you have a valid point. – RobertPitt Sep 01 '10 at 17:44
  • @RobertPitt: I am using 5.3 and hope OP is using the same too :) – Sarfraz Sep 01 '10 at 17:46
  • Also this requires that you install APD and I am not sure that is good for production apps, i mean why would you want a debugger running on production? That will effect performance. I am looking into this issue as well because wordpress plugins are using mysql_ when wordpress is using mysqli_ so i need to figure out how to map the calls. – Joseph Crawford Apr 14 '15 at 16:44
  • This in my opinion is better than the accepted answer since the OP mentions functions and not class methods. Thumbs up – mastazi Aug 18 '15 at 03:38
  • @JosephCrawford APD and override are just as good as XDebug for development, doesn't mean you put then on your prod sever. Nothing in the original question or Sarfaz's answer makes a reference to "production" applications. – amateur barista Oct 17 '16 at 07:21
  • @amateurbarista correct but that is providing that none of the code they add override_function calls to ends up in production. It would have to be temporary test code or wrapped with environment checks so that the code does not run on production. Otherwise it will try to run on production where APD is not installed and cause a ton of issues. That was my only concern, well that and not installing APD on production servers. – Joseph Crawford Oct 18 '16 at 13:40
  • 2
    I tried using override function but its a part of PECL and PECL needs to be configured first. –  Dec 28 '17 at 10:19
  • Is there a way to tell if a function has been over written? – 1.21 gigawatts Aug 23 '20 at 23:47
14

short answer is no, you can't overwrite a function once its in the PHP function scope.

your best of using anonymous functions like so

$ihatefooexamples = function()
{
  return "boo-foo!";
}

//...
unset($ihatefooexamples);
$ihatefooexamples = function()
{
   return "really boo-foo";
}

http://php.net/manual/en/functions.anonymous.php

RobertPitt
  • 56,863
  • 21
  • 114
  • 161
  • Do you really need the `unset`? I know it isn't bad to do, just curious (I haven't tried this specific case)... – ircmaxell Sep 01 '10 at 17:55
  • 4
    @ircmax You don't have to use `unset` – NullUserException Sep 01 '10 at 18:01
  • I haven't a clue on anonymous functions I just know they how to create them but iv'e never played with them, i understand that unset can be costly if overused but depending on the size of the function it might be better ! – RobertPitt Sep 01 '10 at 18:35
  • 1
    I don't know why this didn't get the merit it required. It is the cleanest, usable and sensible approach. The OP didn't ask for an OOP approach (quite the contrary in fact), and the 3rd-party functionality simply does not work, hence this is the only solution. – Christian Sep 05 '11 at 01:09
  • This is not overriding any function it's merely changing the value of a variable. you would've had the same result leaving the closure (anonymous function) part out completely. This does not answer the question! – nickl- Aug 26 '12 at 06:17
11

You cannot redeclare any functions in PHP. You can, however, override them. Check out overriding functions as well as renaming functions in order to save the function you're overriding if you want.

So, keep in mind that when you override a function, you lose it. You may want to consider keeping it, but in a different name. Just saying.

Also, if these are functions in classes that you're wanting to override, you would just need to create a subclass and redeclare the function in your class without having to do rename_function and override_function.

Example:

rename_function('mysql_connect', 'original_mysql_connect' );
override_function('mysql_connect', '$a,$b', 'echo "DOING MY FUNCTION INSTEAD"; return $a * $b;');
  • 16
    This requires APD PECL extension, and is outdated. Latest version is from 2004. http://pecl.php.net/package/apd – jperelli Jul 26 '13 at 14:32
5

I would include all functions of one case in an include file, and the others in another include.

For instance simple.inc would contain function boofoo() { simple } and really.inc would contain function boofoo() { really }

It helps the readability / maintenance of your program, having all functions of the same kind in the same inc.

Then at the top of your main module

  if ($_GET['foolevel'] == 10) {
    include "really.inc";
  }
  else {
    include "simple.inc";
  }
Déjà vu
  • 28,223
  • 6
  • 72
  • 100
  • If it is required (doesn't seem so) one can add a `$iam = "really"` or `$iam = "simple"` at the top of the include files. – Déjà vu Sep 05 '11 at 15:45
  • 1
    I think this is the best non OOP answer on this question as it shows a procedural implementation of the State Pattern. (Although I would prefer an implementation where the ifs are replaced by a foreach loop.) I have used this pattern successfully. You can figure out which function is used by logging. The log messages are of a level that is not used anymore in production or acceptance environments. – Loek Bergman Jan 24 '16 at 08:23
4

You could use the PECL extension

but that is bad practise in my opinion. You are using functions, but check out the Decorator design pattern. Can borrow the basic idea from it.

Gordon
  • 312,688
  • 75
  • 539
  • 559
1

No this will be a problem. PHP Variable Functions

Chris
  • 11,780
  • 13
  • 48
  • 70
1

Depending on situation where you need this, maybe you can use anonymous functions like this:

$greet = function($name)
{
    echo('Hello ' . $name);
};

$greet('World');

...then you can set new function to the given variable any time

esud
  • 21
  • 3
1

A solution for the related case where you have an include file A that you can edit and want to override some of its functions in an include file B (or the main file):

Main File:

<?php
$Override=true; // An argument used in A.php
include ("A.php");
include ("B.php");
F1();
?>

Include File A:

<?php
if (!@$Override) {
   function F1 () {echo "This is F1() in A";}
}
?>

Include File B:

<?php
   function F1 () {echo "This is F1() in B";}
?>

Browsing to the main file displays "This is F1() in B".

David Spector
  • 1,520
  • 15
  • 21