Home > database >  PHP Nesting functions OOP
PHP Nesting functions OOP

Time:09-04

I am using a library and it has following process to attaching many operations to one event

$action = (new EventBuilder($target))->addOperation($Operation1)->addOperation($Operation2)->addOperation($Operation3)->compile();

I am not sure how to dynamically add operations depending on what I need done.

Something like this

$action = (new EventBuilder($target));

while (some event)
{
$action = $action->addOperation($OperationX);
}

$action->compile();

I need to be able to dynamically add operations in while loop and when all have been added run it.

Thank you in advance

CodePudding user response:

Your proposed solution will work. The EventBuilder provides what is known as a Fluent Interface, which means that there are methods that return an instance of the builder itself, allowing you to chain calls to addOperation as many times as you want, then call the compile method to yield a result. However you are free to ignore the return value of addOperation as long as you have a variable containing an instance of the builder that you can eventually call compile on.

Take a walk with me...

// Some boilerplate classes to work with
class Target
{
    private ?string $name;
    
    public function __construct(string $name)
    {
        $this->name = $name;
    }
    
    public function getName(): string
    {
        return $this->name;
    }
}

class Operation
{
    private ?string $verb;
    
    public function __construct(string $verb)
    {
        $this->verb = $verb;
    }
    
    public function getVerb(): string
    {
        return $this->verb;
    }
}

class Action
{
    private ?Target $target;
    private array   $operations = [];
    
    public function __construct(Target $target, array $operations)
    {
        $this->target     = $target;
        $this->operations = $operations;
    }
    
    /**
     * Do the things
     * @return array
     */
    public function run(): array
    {
        $output = [];
        foreach ($this->operations as $currOperation)
        {
            $output[] =  $currOperation->getVerb() . ' the ' . $this->target->getName();
        }
        
        return $output;
    }
}

Here is a basic explanation of what your EventBuilder is doing under the covers:


class EventBuilder
{
    private ?Target $target;
    private array   $operations = [];
    
    public function __construct(Target $target)
    {
        $this->target = $target;
    }
    
    /**
     * @param Operation $operation
     * @return $this
     */
    public function addOperation(Operation $operation): EventBuilder
    {
        $this->operations[] = $operation;
        
        // Fluent interface - return a reference to the instance
        return $this;
    }
    
    public function compile(): Action
    {
        return new Action($this->target, $this->operations);
    }
}

Let's try both techniques and prove they will produce the same result:

// Mock some operations
$myOperations = [
    new Operation('Repair'),
    new Operation('Clean'),
    new Operation('Drive')
];

// Create a target
$target = new Target('Car');

/*
 * Since the EventBuilder implements a fluent interface (returns an instance of itself from addOperation),
 * we can chain the method calls together and just put a call to compile() at the end, which will return
 * an Action instance
 */
$fluentAction = (new EventBuilder($target))
                    ->addOperation($myOperations[0])
                    ->addOperation($myOperations[1])
                    ->addOperation($myOperations[2])
                    ->compile();

// Run the action
$fluentResult = $fluentAction->run();

// Traditional approach, create an instance and call the addOperation method as needed
$builder = new EventBuilder($target);

// Pass our mocked operations
while (($currAction = array_shift($myOperations)))
{
    /*
     * We can ignore the result from addOperation here, just keep calling the method
     * on the builder variable
     */
    $builder->addOperation($currAction);
}

/*
 * After we've added all of our operations, we can call compile on the builder instance to
 * generate our Action.
 */
$traditionalAction = $builder->compile();

// Run the action
$traditionalResult = $traditionalAction->run();

// Verify that the results from both techniques are identical
assert($fluentResult == $traditionalResult, 'Results from both techniques should be identical');

// Enjoy the fruits of our labor
echo json_encode($traditionalResult, JSON_PRETTY_PRINT).PHP_EOL;

Output:

[
    "Repair the Car",
    "Clean the Car",
    "Drive the Car"
]

CodePudding user response:

Rob Ruchte thank you for detailed explanation, one thing I did not include was that each operation itself had ->build() call and I needed to move that to each $builder for it to work.

  • Related