Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditionally chain a method?

Tags:

oop

php

How do we conditionally chain methods in PHP? For example, this works fine:

$a->foo()->bar->baz->qux();

However, depending on a condition, I'd like to chain some methods but not others. Basically, shorten the following code:

if ($cond === true) {
    $a->foo()->baz();
} else {
    $a->foo()->bar();
}

Ideally something like the following would work:

$a->foo()
    ->bar()
    ($cond === true) ? ->baz() : ->qux()
    ->more();

Additionally, how would we conditionally chain a method (or not) depending on a condition? For example:

$a->foo()
    ->bar()
    if($cond === true) ->baz()
    ->more();
like image 833
LookingToLearn Avatar asked Nov 29 '16 02:11

LookingToLearn


4 Answers

The Self-Explanatory Mock-Snippet below (which you may Quick-Test Here) shows how you could do that

<?php   

    class Test{
        protected $prop1;
        protected $prop2;
        protected $prop3;
        protected $prop4;

        public function __construct() {
        }

        public function setProp1($prop1) {
            $this->prop1 = $prop1;
            return $this;
        }

        public function setProp2($prop2) {
            $this->prop2 = $prop2;
            return $this;
        }

        public function setProp3($prop3) {
            $this->prop3 = $prop3;
            return $this;
        }

        public function setProp4($prop4) {
            $this->prop3 = $prop4;
            return $this;
        }
    }

    $a      = 2;
    $b      = 7;
    $cond   = ($a > $b);
    $cond2  = ($b > 50);
    $test   = new Test;

    $test->setProp1(2)->{($cond  === true)  ? 'setProp4' : 'setProp3'}(11);
    $test->setProp3(3)->{($cond2 === false) ? 'setProp2' : 'setProp4'}(6);

    var_dump($test);
    //YIELDS::
    object(Test)[1]
      protected 'prop1' => int 2
      protected 'prop2' => int 6
      protected 'prop3' => int 3
      protected 'prop4' => null
like image 54
Poiz Avatar answered Nov 18 '22 01:11

Poiz


What you're looking for is variable methods (see example #2). They allow you to do something like this:

class a {
    function foo() { echo '1'; return $this; }
    function bar() { echo '2'; return $this; }
    function baz() { echo '3'; return $this; }
}
$a = new a();

$cond = true;
$a->foo()->{($cond === true) ? 'baz' : 'bar'}();
// Prints 13
$cond = false;
$a->foo()->{($cond === true) ? 'baz' : 'bar'}();
// Prints 12

Here's a way that lets you set up requirements for each of the function calls. Note that this is just as hard to maintain as the previous solution, if not harder. You'll probably want to use some sort of configuration and the ReflectionClass's getMethods function, too.

class a {
    function foo() { echo '1'; return $this; }
    function bar() { echo '2'; return $this; }
    function baz() { echo '3'; return $this; }
}

function evaluateFunctionRequirements($object, $functionRequirements, $condition) {
  foreach ($functionRequirements as $function=>$requirements) {
    foreach ($requirements as $requiredVariableName=>$requiredValue) {
      if (${$requiredVariableName} !== $requiredValue) {
        continue 2;
      }
    }
    $object->{$function}();
  }
}

$a = new a();
$functionRequirements = array('foo'=>array(), 'bar'=>array(), 'baz'=>array('condition'=>true));
$condition = true;
evaluateFunctionRequirements($a, $functionRequirements, $condition);
// Prints 123
$condition = false;
evaluateFunctionRequirements($a, $functionRequirements, $condition);
// Prints 12

Notes: This has the added even harder to maintain of requiring the functions in order for the $functionRequirements array. Additionally, this rudimentary example has only one possible condition var passed, update to another setup for getting more $requiredVariableName vars with func_get_args. You'll also want to verify that the methods passed in via $functionRequirements are is_callable() safe.

like image 35
Kevin Stich Avatar answered Nov 18 '22 01:11

Kevin Stich


Try this by assigning the chaining to variable

$a = $a->foo();

if ($cond === true) {
   $a = $a->baz();
} else {
   $a = $a->bar();
}

$a->more();
like image 25
cyberfly Avatar answered Nov 18 '22 03:11

cyberfly


Another way to solve this is to create a method when (or name it whatever makes sense to you):

    public function when($condition, $callback)
    {
        if ($condition) {
            return $callback($this) ?: $this;
        }
        return $this;
    }

Of course, you can extend it to accept additional arguments if you need to pass them to your methods foo, bar, etc...

And the usage with chaining would be:

$a->when($cond === true, function ($a) {
    return $a->foo();
})->when($cond !== true, function ($a) {
    return $a->bar();
}
)->baz(); // a regular chaining method without condition
like image 1
user345602 Avatar answered Nov 18 '22 01:11

user345602