tags:

views:

193

answers:

6

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!

+2  A: 

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. =)

Alix Axel
He is using 5.1.0, so no namespaces. He can still *try* though. Will throw an error for sure :)
Gordon
I've done that, Working on a project on my local server running PHP 5.3.0, uploading to a live server running PHP 5.2.1. Headaches, headaches.
Carson Myers
@Gordon: Oh, I didn't paid attention to that. My mistake.
Alix Axel
A: 

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.

Helgi Hrafn Gunnarsson
You were wrong. `if` statements don't have their own scope.
Alix Axel
Functions don't work like variables. Once they are defined they don't 'go out of scope' and they are available to the entire application, no matter where they are defined / declared.
Carson Myers
A: 

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.

bkuhns
And duplicated code hither and yon
Carson Myers
In a function that's apparently only being used in one location? I don't know for certain, but from the question it sure sounds like the function only exists to use recursion.
bkuhns
He may be doing it for the wrong reasons, but it's definitely right to break code into lots of functions, even if they're only used once.
Carson Myers
I couldn't agree more, but short of your answer to use classes (which could very well be "overkill" for what the op needs/wants), the other answers provided are mostly hacks. The *simplest* (in my opinion) solution to this question is to use iteration instead of recursion.
bkuhns
Edited OP added explaination of what I'm doing exactly...
AlexV
+6  A: 

From the PHP Manual on 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, e.g.

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, 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

Gordon
It's a "quirky" solution, but I like it. If the OP insists on using a function, this is the way to do it.
bkuhns
Is it possible to do a recursive anonymous function?
AlexV
Good point. I can't think of a way to make this work with recursion (without basically getting back to the code you already have). In that case, just listen to his (and my) edits and don't worry about removing the function.
bkuhns
Yeah I will leave it there instead of using strage hacks that are hard to understand years later :)
AlexV
True, recursion wont work. Updated again.
Gordon
Bleh, I should stop rephrasing my answers. Now it's a community wiki :(
Gordon
+3  A: 

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.

Carson Myers
+1, use a class and make it private
Paolo
Edited OP added explaination of what I'm doing exactly...
AlexV
You could still use OOP for this, like a Request class that gets the POST, GET, etc variables and optionally uses stripslashes on them. The stripslashes_deep function could be a private method that the rest of the application doesn't use and doesn't know about.
Carson Myers
A: 

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);
}
Pekka
Will the `array_map('stripslashes_deep', $value)` work?! Anyway, for PHP 5.3+ there is a more elegant solution - see my answer.
Alix Axel
Yup, will work (tested). Your solution is sneaky, well done! :)
Pekka
@Pekka: Thanks! I'm not a big fan of lambda functions but it's nice to know nonetheless.
Alix Axel