Hello guys, today we dive into how can we stop using complicated if else and write them in a better way. This is a design pattern but I’ll prefer to explain this design pattern using PHP.
Let’s create a system that has these features:
- Email verification
- Fill up detail of the profile as required after registration
- Authorization
- Dashboard
What is the problem with that system?
After logging in we should check users if verified their email and filled up their profile. If our users have done these steps, we should decide to redirect it to the dashboard of the authorization.
Let’s create a bad code example:
public function login(LoginRequest $request)
{
if(Auth::attempt(["email"=>$request->email,"password"=>$request->password])){
$request->session()->regenerate();
if(auth()->user()->email_verified_at){
if(auth()->user()->address && auth()->user()->phone && auth()->user()->profile_pic){}else{
return redirect("/profile/update");
}
}else{
return redirect("/email-verify")->withErrors(trans("auth.please_confirm_your_email_address"));
}
return redirect()->intended("/admin")->with("success",trans("auth.success",["name"=>auth()->user()->name]));
}
return redirect()->back()->withErrors(trans("auth.failed"));
}
As you see there is more than one if else. Even we can put there more if else for the define which user will be redirected to which page relevant to their roles but I wanted to keep it simple to ensure that you understand what is the problem.
What is the solution for this?
The solution is “chain of responsibility” design pattern.
What is the Chain of Responsibility design pattern?
Basically, splitting “if else” into classes that is just responsible for doing one check. Afterward, if the request passed the control, the chain continues and sends the client request to another part of the chain.
interface Handler
{
public function setNext(Handler $handler): Handler; public function handle()
}abstract class AbstractHandler implements Handler
{
/**
* @var Handler
*/
private $nextHandler;public function setNext(Handler $handler): Handler
{
$this->nextHandler = $handler;
return $handler;
}public function handle()
{
if ($this->nextHandler) {
return $this->nextHandler->handle($request);
}return null;
}
}
Until here, we defined an interface to pass a handler class to the setNext function.
Basically that AbstractHandler’s setNext function helps us to define the chain after that chain. The “handle” function helps us to make the chain works. When we call the “handle” function, it checks if there is nextHandler, if it is, it runs it.
Now let’s adapt it to our code
class ProfileFilUpHandler extends AbstractHandler
{
public function handle()
{
if (auth()->user()->address & auth()->user()->phone && auth()->user()->profile_pic) {
return parent::handle();
} else {
return redirect("/profile/update");
}
}
}class EmailVerifyHandler extends AbstractHandler
{
public function handle()
{
if (auth()->user()->email_verified_at) {
return parent::handle();
} else {
return redirect("/email-verify")->withErrors(trans("auth.please_confirm_your_email_address"));
}
}
}class LoginHandler extends AbstractHandler
{
public static function handle(EmailVerifyHandler $EmailVerifyHandler, ProfileFilUpHandler $ProfileFilUpHandler){
$EmailVerifyHandler->setNext($ProfileFilUpHandler);
$EmailVerifyHandler->handle();
}}
We defined the last handler to run all these chains.
public function login(LoginRequest $request)
{
if(Auth::attempt(["email"=>$request->email,"password"=>$request->password])){
$request->session()->regenerate();
LoginHandler::handle();
return redirect()->intended("/admin")->with("success",trans("auth.success",["name"=>auth()->user()->name]));
}
return redirect()->back()->withErrors(trans("auth.failed"));
}
A new version of our code and that’s all.
Thanks for reading till there, I hope I could help you guys :)
Sources: