Login to bookmark this video
Buy Access to Course
08.

Triggering Chain of Responsibility

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Open up GameApplication. We're going to set up the chain inside its constructor, and we'll start by instantiating all of the handlers. Write $casinoHandler = new CasinoHandler(), $levelHandler = new LevelHandler(), and finally $onFireHandler = new OnFireHandler().

221 lines | src/GameApplication.php
// ... lines 1 - 8
use App\ChainHandler\CasinoHandler;
use App\ChainHandler\LevelHandler;
use App\ChainHandler\OnFireHandler;
// ... lines 12 - 16
class GameApplication
{
// ... lines 19 - 26
public function __construct(private readonly CharacterBuilder $characterBuilder)
{
// ... lines 29 - 30
$casinoHandler = new CasinoHandler();
$levelHandler = new LevelHandler();
$onFireHandler = new OnFireHandler();
// ... lines 34 - 38
}
// ... lines 40 - 219
}

Here's where we can decide the order or sequence of the chain. If your application doesn't need to execute the handlers in any particular order, great! You can set this up however you want! But in our case, we know that the CasinoHandler can impact the player if we roll a 7, so that needs to be called first. The other two handlers can go in any order, so we'll start with the CasinoHandler.

Write $casinoHandler->setNext($levelHandler), followed by the LevelHandler - $levelHandler->setNext($onFireHandler) - and we can leave the OnFireHandler alone. It'll be the last one in the chain. The last thing we need to do is to set the CasinoHandler in a property of this class, so write $this->xpBonusHandler = $casinoHandler, and hold "Option" + "Enter" to add the property. Oh! And change its type to XpBonusHandlerInterface. Perfect!

221 lines | src/GameApplication.php
// ... lines 1 - 11
use App\ChainHandler\XpBonusHandlerInterface;
// ... lines 13 - 16
class GameApplication
{
// ... lines 19 - 24
private XpBonusHandlerInterface $xpBonusHandler;
// ... line 26
public function __construct(private readonly CharacterBuilder $characterBuilder)
{
// ... lines 29 - 34
$casinoHandler->setNext($levelHandler);
$levelHandler->setNext($onFireHandler);
$this->xpBonusHandler = $casinoHandler;
}
// ... lines 40 - 219
}

Okay, this chain needs to be triggered after a battle finishes, and there's a convenient method we can use to do that. Find the endBattle() method, and right before notifying the observers, trigger the chain by writing $xpBonus = $this->xpBonusHandler->handle(), where the first argument is the $winner and the second is $fightResultSet->of($winner). Finally, we'll add the extra XP to the $winner with $winner->addXp($xpBonus).

224 lines | src/GameApplication.php
// ... lines 1 - 16
class GameApplication
{
// ... lines 19 - 100
private function endBattle(FightResultSet $fightResultSet, Character $winner, Character $loser): void
{
// ... lines 103 - 109
$xpBonus = $this->xpBonusHandler->handle($winner, $fightResultSet->of($winner));
$winner->addXp($xpBonus);
// ... lines 112 - 116
}
// ... lines 118 - 222
}

Cool! Let's give this a try!

Spin over to your terminal and run:

php bin/console app:game:play

I'll be a fighter and attack until I hopefully win, and... we won! But, hmm... how do we know which handler kicked in? Well, that's a downside of this design pattern. It's hard to debug, since any handler can be called. To get around this, we can print a message in each handler so it's obvious.

Debugging Handlers

To do this quickly, you can copy the code from below this video, paste, and... sweet! Let's see if that worked!

33 lines | src/ChainHandler/LevelHandler.php
// ... lines 1 - 6
use App\GameApplication;
// ... line 8
class LevelHandler implements XpBonusHandlerInterface
{
// ... lines 11 - 12
public function handle(Character $player, FightResult $fightResult): int
{
if ($player->getLevel() === 1) {
GameApplication::$printer->info('You earned extra XP thanks to the Level handler!');
// ... lines 17 - 18
}
// ... lines 20 - 25
}
// ... lines 27 - 31
}

33 lines | src/ChainHandler/OnFireHandler.php
// ... lines 1 - 6
use App\GameApplication;
// ... line 8
class OnFireHandler implements XpBonusHandlerInterface
{
// ... lines 11 - 12
public function handle(Character $player, FightResult $fightResult): int
{
if ($fightResult->getWinStreak() >= 3) {
GameApplication::$printer->info('You earned extra XP thanks to the OnFire handler!');
// ... lines 17 - 18
}
// ... lines 20 - 25
}
// ... lines 27 - 31
}

43 lines | src/ChainHandler/CasinoHandler.php
// ... lines 1 - 7
use App\GameApplication;
// ... line 9
class CasinoHandler implements XpBonusHandlerInterface
// ... lines 11 - 13
public function handle(Character $player, FightResult $fightResult): int
{
// ... lines 16 - 24
if ($dice1 === $dice2) {
GameApplication::$printer->info('You earned extra XP thanks to the Casino handler!');
// ... lines 27 - 28
}
// ... lines 30 - 35
}
// ... lines 37 - 41
}

Back at your terminal, run

php bin/console app:game:play

Again, play until the battle finishes, and... look at this! There's our message! The LevelHandler kicked in and rewarded us with extra XP. Awesome!

Okay, this is cool, but I think we can do better. We can leverage Symfony's dependency injection to initialize the chain. As a bonus, we'll refactor the handlers to stop checking to see if $next is set by applying the Null Object pattern. Let's do this!