简体   繁体   中英

Removing a function at runtime in PHP (without the runkit extension)

I know this question seems hacky and weird, but is there a way to remove a function at runtime in PHP?

I have a recursive function declared in a "if" block and want that function to be "valid" only in that "if" block. I don't want this function to be callled outside this block.

I found out runkit_function_remove but runkit isn't enabled on my Web host. Is there another way to do that?

BTW I only support PHP 5.1.0.

Edit: I knew my question was hacky but here is the exact thing I want to do:

if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc())
{
    function stripslashes_deep($value)
    {
        return is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value);
    }

    $_POST = array_map('stripslashes_deep', $_POST);
    $_GET = array_map('stripslashes_deep', $_GET);
    $_COOKIE = array_map('stripslashes_deep', $_COOKIE);
    $_REQUEST = array_map('stripslashes_deep', $_REQUEST);

    //runkit_function_remove('stripslashes_deep');
}

Since "stripslashes_deep" will only live when Magic Quotes are ON, I wanted to get rid of it when I'm done with it. I don't want people to rely on a function that isn't always there . I hope it's clearer now. Non-hacky solution suggestions are welcome too!

From the PHP Manual on user-defined Functions :

All functions and classes in PHP have the global scope - they can be called outside a function even if they were defined inside and vice versa. [...] PHP does not support function overloading, nor is it possible to undefine or redefine previously-declared functions.

The exception is through runkit . However, you could define your function as an anonymous function and unset it after you ran it, eg

if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc()) {
    $fn = create_function('&$v, $k', '$v = stripslashes($v);'); 
    array_walk_recursive(array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST), $fn);
    unset($fn);
}

Some commentors correctly pointed out (but not an issue any longer in PHP nowadays), you cannot call an anonymous function inside itself. By using array_walk_recursive you can get around this limitation. Personally, I would just create a regular function and not bother about deleting it. It won't hurt anyone. Just give it a proper name, like stripslashes_gpc_callback .

Note: edited and condensed after comments

As of PHP 5.3 you can use an anonymous function:

$anonymous_function = function($value){
  // do stuff
};

Call it like this:

$returned_value = $anonymous_function('some parameter');

To delete the function, just unset the variable:

unset($anonymous_function);

Here is an example of how to implement your function:

$stripslashes_deep = function (&$object){
  global $stripslashes_deep;
  if(is_array($object)) 
    foreach($object as &$value){ 
      if(is_array($value)) 
        $stripslashes_deep($value);
      else
        $value = stripslashes($value);
    }
};

anonymous functions has no name. So you can't use array_map , as it will only take a function name as parameter.

anonymous functions has the variable scope. So you have to declare the variable containing the function as global, to reach it from within a recursive function.

Unfortunately if you define a regular function inside the anonymous function, it will be globally available, even after you unset the variable. (I don't really see the benefit of that. I hope it's a blunder to be corrected later :-)

Initial function definition looks like:

// includes/std_functions.php
if (! function_exists('the_function')) {
  function the_function() {
    global $thefunction;
    return call_user_func_array($thefunction, func_get_args());
  }
  $GLOBALS['thefunction'] = function() {
    echo 'foo';
  };
}

if you have a test condition that allows you to rewrite code:

// somewhere in your code
if (<replacethefunctioncondition>) {
  $GLOBALS['thefunction'] = function() {
    echo 'bar';
  };
}

if you are overloading in another include that will be loaded before or after the original file:

// includes/custom_functions.php
if (! function_exists('the_function')) {
  function the_function() {
    global $thefunction;
    return call_user_func_array($thefunction, func_get_args());
  }
}
$GLOBALS['thefunction'] = function() {
  echo 'bar';
};

Simple answer: no.

However, you can try placing the function inside a namespace, a class or even within another function - but I think that's not what you're looking for.

One other option you have is to use debug_backtrace() inside the said function to check what file / line / etc... is calling it - it's hackish I know, but so is runkit_function_remove() .


Edit - Too bad you don't run PHP 5.3+, otherwise you could just do:

if (get_magic_quotes_gpc())
{
    $_GET = json_decode(stripslashes(json_encode($_GET, JSON_HEX_APOS)), true);
    $_POST = json_decode(stripslashes(json_encode($_POST, JSON_HEX_APOS)), true);
    $_COOKIE = json_decode(stripslashes(json_encode($_COOKIE, JSON_HEX_APOS)), true);
    $_REQUEST = json_decode(stripslashes(json_encode($_REQUEST, JSON_HEX_APOS)), true);
}

For older versions of PHP you still have this option :

if (get_magic_quotes_gpc()) {
    $process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
    while (list($key, $val) = each($process)) {
        foreach ($val as $k => $v) {
            unset($process[$key][$k]);
            if (is_array($v)) {
                $process[$key][stripslashes($k)] = $v;
                $process[] = &$process[$key][stripslashes($k)];
            } else {
                $process[$key][stripslashes($k)] = stripslashes($v);
            }
        }
    }
    unset($process);
}

No functions there, and the code is not that long. =)

No. But removing a function definition is silly.
Either you're defining the function differently in an else block and need the definition to change based on program state, effectively making your project closer and closer to impossible to debug, or code that calls that function will crash and burn if it didn't happen to get defined at runtime.

You should put this function in a class:

class foo {

    public function bar() {

        if( /* some condition */ ) {
            $this->baz();
        } else {
            $this->bazzer();
        }

    }

    private function baz() {

        /* if the if condition was met */

    }

    private function bazzer() {

        /* if the if condition failed */

    }

}

or, if you only want the condition tested once,

class foo {

    private $bar_function = NULL;

    public function __construct() {

        if( /* some condition */ ) {
            $this->bar_function = baz;
        } else {
            $this->bar_function = bazzer;
        }

    }

    public function bar() {

        $this->$bar_function();

    }
        ...

I don't know what you are trying to do or why you want to remove a function definition but hopefully this can help you do it in a cleaner way.

I too see very little reason to let a function "live" or "not live" depending on a condition, but to answer the question, it's sort of possible using anonymous functions. @Gordon already outlined how it's done. Starting with PHP 5.3.0, you can also use anonymous functions as follows. (There is no functional difference to the create_function() approach.)

if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc())
{
    $stripslashes_deep = function($value)
    {
        return is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value);
    }

    $_POST = array_map($stripslashes_deep, $_POST);
    $_GET = array_map($stripslashes_deep, $_GET);
    $_COOKIE = array_map($stripslashes_deep, $_COOKIE);
    $_REQUEST = array_map($stripslashes_deep, $_REQUEST);

    unset ($stripslashes_deep);
}

There may be another solution for replacing functions by changing usage of them like this ;

function function1(){
 echo "1";
}

function function2(){
 echo "2";
}

if(FUNCTION_CHOOSER_CONDITION) $the_function = "function2";
else $the_function="function1";
$the_function(); //  displays 2 if condition is TRUE;

Maybe this not for you in this situation as you wanted to destroy the function but i though this is right place to talk about replacement of functions.

It's impossible without runkit_function_remove. Runkit is the extension intended to do things like this.

Are you sure that the function lives after the if-block is completed? I would have thought that if it were defined within the if-block, that it would become inaccessible afterward.

I agree with Alix. You can, however, avoid the function being called from anywhere else by getting rid of the function. Rewrite the recursive function as an iterative loop. You'll have the added benefit of reduce memory usage.

UPDATE: with your code example, I'm not sure why you feel the need to prevent this function from being called again. It simply formats some strings in an array recursively... again, you could do this iteratively and avoid having a function in the first place. Otherwise, just leave it there and don't call it again.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM