Roy
Roy

Reputation: 987

An alternative to an array of functions?

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?

Upvotes: 12

Views: 1216

Answers (6)

Jasper N. Brouwer
Jasper N. Brouwer

Reputation: 21817

I'm all in favor of what Mamuz describes in his answer, but I do want to give you a "quick fix":

$functions = [];

$functions['do this'] = function () {
    // does this
};

$functions['do call'] = function () use ($functions) {
    // calls 'do this'
    $functions['do this']();
}

$functions['do manipulation'] = function () use (&$functions) {
    // manipulates $functions
    $functions['do something else'] = function () {
        // does something else
    };
}

I think this is pretty self explanatory.

Upvotes: 3

Gustavo Rubio
Gustavo Rubio

Reputation: 10807

Maybe something like:

/**
 * Save an array with function friendly names and its associated function and params
 */
$functions = array(
    'Do This' => array(
        'fn_name' => 'fn1',
        'fn_args' => array('fn1', 'from fn1')),
    'Do That' => array(
        'fn_name' => 'fn2'
    )
);

/**
 * Call this per each value of the functions array, passing along that function data as parameter
 */
function execute_fn($fn_data) {
    $fn_name = $fn_data['fn_name'];

    if(isset($fn_data['fn_args'])) {
        call_user_func_array($fn_name, $fn_data['fn_args']);
    } else {
        call_user_func($fn_name);
    }


}

function fn1($val1, $val2) {
    echo "I'm being called from $val1 and my second argument value is $val2 \n";
}

function fn2() {
    echo "Doing something for fn2 \n";
    fn1('fn2', 'from fn2');
}

/**
 * Call this to execute the whole set of functions in your functions array
 */
foreach ($functions as $key => $value) {
    execute_fn($value);
}

That way you can define in $functions an array with function names and argument values if that particular function expects any.

Upvotes: 2

shadyyx
shadyyx

Reputation: 16055

You can do it in a cleaner way, that is, encapsulate all of these functions into a class. They can be static or dynamic, it doesn't matter much I guess in this case. I'll show both examples...

1. dynamic approach

class MyFunctions
{

    public function doThis()
    {
        /* implement do this here */
    }

    public function doThat()
    {
        /* implement do that here */
    }
    /* ... */
}

/* then somewhere else */
$program = ["doThis", "doThat", "doThis"];
$myFunctions = new MyFunctions;
foreach ($program as $method) {
    $myFunctions->$method();
}

2. static approach

class MyFunctions
{

    public static function doThis()
    {
        /* implement do this here */
    }

    public static function doThat()
    {
        /* implement do that here */
    }
    /* ... */
}

/* then somewhere else */
$program = ["doThis", "doThat", "doThis"];
foreach ($program as $method) {
    MyFunctions::$method();
}

IMHO this is cleaner than implementing those functions in an array of closures... And better than implementing a switch as You still have to call this in a loop - so why slowing down with another switch?

Upvotes: 7

Mamuz
Mamuz

Reputation: 1730

Closures are expensive for performance and memoryusage in php. Procedural coding provoked a big ball of mud, spaghetti code and other anti patterns. Switch structures are very hard to test and it's a violation of OCP.

You should prefer OOP in SOLID way to avoid redundancy, improving scalability and maintainablity. That is the best practice to provide a set of functions which are reuseable. You can also seperate your code in layers and modules to reduce complexity and improve interchangability.

In your case your classes can implement __invoke to call it as invokable and your keys could be the fullqualified namespaces for these classes, so you can call it like a function. From now on you can also use inheritence, polymorphism or design patterns like composite, decorator etc. to reuse other functions or to add functions.

Here a simple Example..

<?php
use Foo\Bar;

class This
{
    public function __invoke()
    {
        $this->execute();
    }

    public function execute()
    {
        /* ... */
    }
 }

class That extends This
{
    public function execute()
    {
        /* ... */
    }
 }

$namespaces = array("\Foo\Bar\This", "\Foo\Bar\That"); 
foreach ($namespaces as $fullQualifiedNamespace) {
    /** @var callable $fullQualifiedNamespace */
    $fullQualifiedNamespace(); 
}

This behavior is also reachable by implementing a specific interface to This and That. Within iteration you can check the interface to call the defined contract. Or you build a Process Class where you can add objects which implements this interface. The Process class can execute all attached objects (Chain of Responsibility).

I would prefer the Chain of Responsibility Pattern this is understandable by most developers and not so magic like PHP's __invoke interceptor. In a factory you can define your chain and you are able to define other dependencies to the chain or to the attached chain objects.

Upvotes: 20

enterx
enterx

Reputation: 879

What about the good old Command Pattern? ;)

<?php

interface Command{
    public function execute();
}

class DoThatCommand implements Command{
    public function execute()
    {
        echo "doing that...";
    }
}

class DoThisCommand implements Command{
    public function execute(){
        echo "doing this...";
        $doThatCommand = new DoThatCommand();
        $doThatCommand->execute();

    }
}

$program = [new DoThatCommand(), new DoThisCommand(), new DoThatCommand()];

foreach ($program as $command) {
    $command->execute();
    echo "\n";
}
?>

or in a more elegant dependecy injection way you can do it like:

<?php

interface Command{
    public function execute();
}

class DoThatCommand implements Command{
    public function execute()
    {
        echo "doing that... \n";
    }
}

class DoThisCommand implements Command{
    public function execute(){
        echo "doing this... \n";
    }
}

class DoTheThirdThingCommandThatDependsOnDoThisCommand implements Command{

    public $doThisCommand;

    public function __construct(DoThisCommand $doThisCommand)
    {
        $this->doThisCommand = $doThisCommand; 
    }

    public function execute()
    {
        echo "doing the ThirdThingCommand that depends on DoThisCommand... \n";
        $this->doThisCommand->execute();
    }
}

$command1 = new DoThatCommand(); 
$command2 = new DoThisCommand();
$command3 = new DoTheThirdThingCommandThatDependsOnDoThisCommand($command2);


$program = [$command1, $command2, $command3];
echo "<pre>";
foreach ($program as $command) {
    $command->execute();
}
?>

Upvotes: 2

Sharanya Dutta
Sharanya Dutta

Reputation: 4021

The giant function with the long switch statement is better because it allows you to call similar_functions("do this") as a part of similar_functions("do that"):

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

Upvotes: 1

Related Questions