1

I'm programming an app (php) which requires a very long list of similar yet different functions, which are being called by a set of keys:

$functions = [
    "do this" => function() {
        // does this
    },
    "do that" => function() {
        // does that
    }
] 
etc.

I've chosen to place the similar functions in an array because they are not similar enough - getting the same result with one big function which is full of conditional statements isn't gonna work. And I do need to be able to call them only by key, for example:

$program = ["do this", "do that", "do this"];
foreach ($program as $k => $v) {
    $functions[$v]();
}

Thing is this functions-array structure is causing many problems, for example I'm having a hard time calling one array function from within another array function, e.g. this doesn't work:

"do that" => function() {
    $functions["do this"]();
}

nor this:

"do that" => function() {
    global $functions;
    $functions["do this"]();
}

or this:

"do that" => function($functions) {
    $functions["do this"]();
}

$functions["do that"]($functions);

I guess I could have one giant function with a long switch statement:

function similar_functions($key) {
    switch ($key) {
        case "do this":
            // does this
        break;
        case "do that":
            // does that
        break;
    }
}

But that doens't really seem like good practice. Or maybe it is?

So, what are my alternatives? Should I go with the switch structure? Or is there another, better solution?

11
  • My guess is that there is a better way to go, but it's difficult to tell without more information. Could you post some more details about what you want the functions to accomplish? (i.e. some concrete examples)
    – paul
    Feb 11, 2014 at 13:29
  • I have no time to post a complete answer, but if you need the functions to call one another, you can just define them outside the array.
    – lortabac
    Feb 11, 2014 at 13:32
  • @paul: The main purpose of this structure is demonstrated in the $program code block above - I need to be able to pass an array of keys, that will trigger many different kinds of functions.
    – Roy
    Feb 11, 2014 at 13:37
  • @lortabac please note the comment I left above to paul, this might clear why defining the functions out side of an array would not suffice
    – Roy
    Feb 11, 2014 at 13:38
  • 1
    @Roy as lortabac says, even though you want to have them in an array, you should be able to define them outside of the array then construct the array with pointers to the functions instead of using definitions.
    – paul
    Feb 11, 2014 at 13:58

6 Answers 6

3

Expanding on @lortabac's comment:

You can pass around pointers to functions just by passing the function name, e.g.:

function fn1()
{
    // ...
}

function fn2($fn)
{
    $fn();
}

fn2(fn1);

So you should be able to populate your array like so:

$functions = [
    "do this" => fn1,
    "do that" => fn2
]

or, if you want to be able to have the functions be able to call each other using the references set up in the array, you can define the array first, then write the functions, then set the references to the functions in the array after that.

1

First of all, according to the DRY principle, you should try and make your functions even more similar, so they can share as much code as possible. This will make managing your codebase quite easy, which cannot be said about number of "similar yet different" functions.

The possible solution to your problem is to enclose all your functions in a class and make them publicly callable given only a name. The cleanest way to do it in your situation is to define method call, which will then call appropriate function, found using __get magic method. It's pretty and it's safe (well, depending on the implementation).

1

You're essentially asking when and how to branch. My general suggestion would be to:

  • only write the code to branch once (DRY).
  • call the code-that-determines-how-the-code-branches once and then don't branch again.
  • consider using the Strategy Pattern or the Command Pattern.

With an Object-oriented approach (per the patterns), the branching decision might be controlled by a SomethingStrategyManager class, which would give you an instance of ISomethingStrategy. Then you would run the ISomethingStrategy's doTheThing method.

In very generic pseudo-code:

interface IMyStrategy:
  method doTheThing(string name); //returns a BOOLEAN value
  ... //maybe this interface can have more methods?

class MyStrategyManager:
  method getStrategyForSomeArgs(int a, string c) //returns IMyStrategy
     if (a % 2 == 0) return new EvenNumberStrategy();
     else if (a.isPrimeNumber) return new PrimeNumberStrategy();
     else if (c == "CustomSomething") return new CustomStrategy();
     else return new DefaultStrategy();    

...

// Using the strategies:
IMyStrategy strategy = MyStrategyManager.getStrategyForSomeArgs(3, "foo");
BOOL success = strategy.doTheThing("john smith");
... 
0

You could create a class enclosing all your functions and then call them as object's methods

class SimpleFunctions {

  public function doThis() {
    // Do this
  }

  public function doThat() {
    // Do that
  }

  public function doThisInThat() {
    // Do this in That
    $this->doThis();
  }
}

$funcs = ['doThis', 'doThat', 'doThisInThat'];
$simpleFunctions = new SimpleFunctions();
foreach($funcs as $f) {
  if(!method_exists($simpleFunctions, $f))
    throw new \Exception(sprintf("Method %s not found in %s", $f, get_class($simpleFunctions));

  $simpleFunctions->$f();
}
1
  • Programmers is tour conceptual questions and answers are expected to explain things. Throwing code dumps instead of explanation is like copying code from IDE to whiteboard: it may look familiar and even sometimes be understandable, but it feels weird... just weird. Whiteboard doesn't have compiler
    – gnat
    Feb 24, 2014 at 9:13
0

I think, In your FOREACH loop, you may add

$method = '_' . <function name><key>;

if (method_exists($this, $method)) {
    $this->$method();
}
else {
    show_error('Could not load Method' . $method);
}

Algorithm/Logic: The variable '$method' will store the function name and available key which eventually can work as a method name. IF-condition simply checks the availability of the same method and opens it if method exists otherwise throws an error.

Look of a complete class will be:

public function your_function()
{
        foreach ($program as $k => $v) {
            //$this->call_function($v); // add 'call_function' in your loop
            $method = '_' . fn.$v; //<function name><key>

            if (method_exists($this, $method)) {
                $this->$method();
            }
            else {
                show_error('Could not load template ' . $method);
            }
        }
}   

public function _fnkey()
{
    echo "In Function 1";
}   

Hope this is useful!

0

I think a better way is to change the array to namespace. So you can use normal function names and normal calling from another functions.

When needed dynamic call - use reference.

namespace foo {
  function f1($a) { print "1\n"; };
  function f2($a) { print "2\n"; f1(3); };
}
namespace bar {
  $f = '\foo\f2';
  $f(2);
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.