Bitmask, How to abstract those callbacks in this implementation I've made an POC about a bitmask implementation in order to replace a huge if/else statement.

Those statements are about strings and I have almost 15 /20 string to test. Depends on values the if/else statement was about 1500 lines of code.

After refactoring I was able to have only 20 objects and a map with all my callbacks in a "handler" class.

The code is working well, but I'm sure it can be improved.


&lt;?php
declare(strict_types=1);

namespace App\Api\Application\Service;

abstract class AbstractValue implements BitwiseCallback
{
    protected $flag;
    public static function getFlag($value): BitwiseCallback
    {
        $class = get_called_class();
        $var = new $class();
        $var-&gt;bitValue = $value ? $var-&gt;flag : 0;
        return $var;
    }
    protected $bitValue;
    public function bitValue(): int
    {
        return $this-&gt;bitValue;
    }
}



&lt;?php
declare(strict_types=1);
namespace App\Api\Application\Service;

interface BitwiseCallback
{
    static function getFlag($value): BitwiseCallback;
    public function bitValue(): int;
}



&lt;?php
declare(strict_types=1);
namespace App\Api\Domain\ValueObject;

use App\Api\Application\Service\VarA;
use App\Api\Application\Service\VarB;
use App\Api\Application\Service\VarC;
use App\Api\Application\Service\VarX;

/**
 * Object Calisthenics or how to use lookup map and biwise map to refactor a huge if statement.
 * Class Test
 * @package App\Api\Domain\ValueObject
 */
class Test
{
    public function aMethod($values)
    {
        $a = VarA::getFlag($values[0]);
        $b = VarB::getFlag($values[1]);
        $c = VarC::getFlag($values[2]);

        $x = VarX::getFlag($values[5]);
// and so on

        $flags = $a-&gt;bitValue() | $b-&gt;bitValue() | $c-&gt;bitValue();
        $map = [
            1 =&gt; function() use ($a) { $a-&gt;doStuff(); },
            3 =&gt; function() use ($a, $b) { return $a-&gt;doStuff() . $b-&gt;doOtherStuff() . $a-&gt;toStuffWithB($b);},
            5 =&gt; function() use ($a, $c) { return $c-&gt;doStuff() . $b-&gt;doOtherStuff() . $c-&gt;toStuffWithB($b);},

            // and so on
            35 =&gt; function() use ($a, $b, $x) { return $x-&gt;sendEmailToAdmin($a, $b) ;}
            // and so on
        ];
        $result = $map[$flags];
        return $result();
    }
}


VarA / VarB / VarC/ and so on are like this

&lt;?php
declare(strict_types=1);
namespace App\Api\Application\Service;
class VarA extends AbstractValue
{
    protected $flag = 1;

    public function doStuff()
    {
        return "This is a correct answer ...";
    }

    public function toStuffWithB($b)
    {
        return ' and you earned a bonus point !';
    }
}


&lt;?php
declare(strict_types=1);
namespace App\Api\Application\Service;

use App\Api\Application\Service\VarA;
use App\Api\Application\Service\VarB;

class VarX extends AbstractValue
{
    protected $flag = 32;

    public function sendEmailToAdmin(VarA $a, VarB $b)
    {
// do stuff with varB and varA because VarX has been triggered
        return "We correctly aknowledge your answer, you'll be notify soon by our customer services";
    }
}


I can't show the real code with the proper names but the implementation remains the same. But' the whole thing is about a quiz with a custom baseline to show depends on answers, each stuff to do are very different depends on the bits (store data to a file / send notification to admin / send a mail about an answer given and so on).

So basically my class that handle the callback map is quite huge.

$map = [
            1 =&gt; function() use ($a) { $a-&gt;doStuff(); },
            3 =&gt; function() use ($a, $b) { return $a-&gt;doStuff() . $b-&gt;doOtherStuff() . $a-&gt;toStuffWithB($b);},
            5 =&gt; function() use ($c, $b) { return $c-&gt;doStuff() . $b-&gt;doOtherStuff() . $c-&gt;toStuffWithB($b);}
            // and so on
        ];


this part is quite ugly (around 200 lines of code in my code) with a lot of context and $options.
As implied from the comments under OP, it means that VarA is completly unrelated to VarB, they do completly different things.

If that is true, then it is all entirely obscured way of saying that based on some integer you need to do some arbitrary stuff. Arbitrary except that in all cases it returns a string.

Then all you need is encapsulate each callback in an interface:

interface StringGenerator
{
  public function getTheString(): string;
}


Whether each implementation has the right dependencies that arise from bits that are set in the flags which drive which StringGenerator implementation is used is an implementation detail.

class GeneratorABX implements StringGenerator
{
   private $serviceA;
   private $serviceB;
   private $serviceX;

   public function __construct($serviceA, $serviceB, $serviceX)
   {
     $this-&gt;serviceA = $serviceA;
     $this-&gt;serviceB = $serviceB;
     $this-&gt;serviceX = $serviceX;
   }

   public function getTheString(): string
   {
      return $this-&gt;serviceA-&gt;doStuff() . $this-&gt;serviceB-&gt;doOtherStuff() . $this-&gt;serviceX-&gt;toStuffWithB($this-&gt;serviceB);
   }
}


Use a DI container to handle the instantiation of all the implementations of StringGenerator.

function setupContainer(): \Psr\Container\ContainerInterface
{
  $container = new ContainerBuilder();
  $container-&gt;addService('serviceA', ServiceA::class);
  $container-&gt;addService('serviceB', ServiceB::class);
  $container-&gt;addService('serviceX', ServiceX::class);
  //...
  $prefix = 'stringGenerator';
  $container-&gt;addService($prefix . '1', GeneratorA::class);
  $container-&gt;addService($prefix . '3', GeneratorAB::class);
  $container-&gt;addService($prefix . '5', GeneratorAC::class);
  $container-&gt;addService($prefix . '35', GeneratorABX::class);

  $container-&gt;addService('test', Test::class, ['prefix' =&gt; $prefix]);
  return $container-&gt;buildContainer();
}


To compute flags from array of bools you can use this:

private static function getFlags(bool ...$values): int
{
  $flags = 0;
  $flag = 1;
  foreach ($values as $value) {
    if ($value) {
      $flags |= $flag; // or $flags += $flag;
    }
    $flag = $flag &lt;&lt; 1; // or $flags *= 2;
  }
  return $flags;
}


And to get the right generator:

private function getGenerator($flags): StringGenerator
{
  return $this-&gt;container-&gt;get($this-&gt;prefix . $flags);
}


Final result:

class Test
{
  private $container;
  private $prefix;

  public function __construct(\Psr\Container\CotainerInterface $container, string $prefix)
  {
     $this-&gt;container = $container;
     $this-&gt;prefix = $prefix;
  }

  public function aMethod($values)
  {
     $flags = self::getFlags(...$values);

     return $this-&gt;getGenerator()-&gt;getTheString();
  }

  // and the private methods mentioned earlier
}


And call it like this:

$container = setupContainer();
$result = $container-&gt;get(Test::class)-&gt;aMethod($values);


EDIT: Altogether it is a bit analogous to a router. Router is first setup with a bunch of routes. Upon request, the router choses which controller is to be invoked based on the request and the set of routes. Then the appropriate controller is invoked and response is returned to the caller. The Test class is the router. The setup is done via DI container. The StringGenerators are the controllers. The flags are the request. And the resulting string is the response.

EDIT2: To avoid having a lot of StringGenerator implementations, we can instead have one method per each of the generators. Let me show a way how to do that:

class Test
{
  private $map = [
    1 =&gt; ServiceA::class,
    2 =&gt; ServiceB::class,
    32 =&gt; ServiceX::class,
    // ...
  ];

  private $container;

  public function __construct(\Psr\Container\ContainerInterface $container)
  {
    $this-&gt;container = $container;
  }

  public function aMethod(array $values): string
  {
    $services = $this-&gt;getServices(...$values);
    return $this-&gt;getTheString($services);
  }

  private function getServices(bool ...$values): array
  {
    $services = [];
    $flag = 1;
    foreach ($values as $value) {
      if ($value) {
        $services[$flag] = $this-&gt;container-&gt;get($this-&gt;map[$flag]);
      }
      $flag = $flag &lt;&lt; 1; // or $flags *= 2;
    }
    return $services;
  }

  private function getTheString(array $services): string
  {
    $flags = \array_sum(\array_keys($services));
    $method = 'generate' . $flags;
    if (!method_exists($this, $method)) {
      throw new Exception('invalid combination of flags ' . $flags);
    }
    return $this-&gt;$method(...array_values($services));
  }

  private function generate1(ServiceA $a): string {}
  private function generate2(ServiceB $b): string {}
  private function generate35(ServiceA $a, ServiceB $b, ServiceX $x): string {}
}

function setupContainer(): \Psr\Container\ContainerInterface
{
  $container = new ContainerBuilder();
  $container-&gt;addService(ServiceA::class);
  $container-&gt;addService(ServiceB::class);
  $container-&gt;addService(ServiceX::class);
  //...
  return $container-&gt;buildContainer();
}

$container = setupContainer();
$test = new Test($container);
$result = $test-&gt;aMethod($values);


Or the same in a more general, more SOLID, although more verbose version:

interface ServiceProviderInterface
{
  public function getServices(bool ...$values): array;
}

class Services implements ServiceProviderInterface
{
  private $map = [
    1 =&gt; ServiceA::class,
    2 =&gt; ServiceB::class,
    32 =&gt; ServiceX::class,
    // ...
  ];

  private $container;

  public function __construct(\Psr\Container\ContainerInterface $container)
  {
    $this-&gt;container = $container;
  }

  public function getServices(bool ...$values): array
  {
    $services = [];
    $flag = 1;
    foreach ($values as $value) {
      if ($value) {
        $services[$flag] = $this-&gt;container-&gt;get($this-&gt;map[$flag]);
      }
      $flag = $flag &lt;&lt; 1; // or $flags *= 2;
    }
    return $services;
  }
}

interface StringGeneratorInterface
{
  public function getTheString(array $services): string;
}

class Methods
{
  public function generate1(ServiceA $a): string {}
  public function generate2(ServiceB $b): string {}
  public function generate35(ServiceA $a, ServiceB $b, ServiceX $x): string {}
}

class StringGenerator implements StringGeneratorInterface
{
  private $methods;

  public function __construct(object $methods)
  {
    $this-&gt;method = $methods;
  }

  public function getTheString(array $services): string
  {
    $flags = \array_sum(\array_keys($services));
    $method = 'generate' . $flags; // could be a method name inflector responsibility
    if (!method_exists($this-&gt;methods, $method)) {
      throw new Exception('invalid combination of flags ' . $flags);
    }
    return $this-&gt;methods-&gt;$method(...array_values($services));
  }
}

class Test
{
  private $services;
  private $generator;

  public function __construct(ServiceProviderInterface $services, StringGeneratorInterface $generator)
  {
     $this-&gt;services = $services;
     $this-&gt;generator = $generator;
  }

  public function aMethod(array $values): string
  {
    $services = $this-&gt;services-&gt;getServices(...$values);
    return $this-&gt;generator-&gt;getTheString($services);
  }
}

function setupContainer(): \Psr\Container\ContainerInterface
{
  $container = new ContainerBuilder();
  $container-&gt;addService(ServiceA::class);
  $container-&gt;addService(ServiceB::class);
  $container-&gt;addService(ServiceX::class);
  //...
  $container-&gt;addService(Services::class);
  $container-&gt;addService(Methods::class);
  $container-&gt;addService(StringGenerator::class, ['methods' =&gt; Methods::class]);
  $container-&gt;addService(Test::class);
  return $container-&gt;buildContainer();
}

$container = setupContainer();
$result = $container-&gt;get(Test::class)-&gt;aMethod($values);
```