Introduction
Functional programming is old. But it did not become popular, and probably for a reason. It is sometimes quite hard to understand and use it. But it has a lot of advantages. One of them is the ability to avoid null-checking and exceptions.
In this article, we will look at the Maybe monad and how to use it in Symfony.
What is the Maybe monad?
Let's start with the definition of monad itself. A monad is a structure that represents computations defined as sequences of steps. It is a generalization of the concept of a function that takes an argument and returns a result. A monad in functional programming is not something new. It has been around since the 1960s.
The Maybe monad is a monad that encapsulates an optional value. A value of type Maybe a
contains either a value of type a
(represented as Just a
) or nothing at all (represented as Nothing
). Using the Maybe monad, we can avoid null values and exceptions.
How to use the Maybe monad in Symfony?
Let's create a Monad class that will implement the Maybe monad.
// src/Utils/Maybe.php
<?php
namespace App\Utils;
/**
* @template T
*/
class Maybe
{
/**
* @var T|null
*/
private $value;
/**
* @param T|null $value
*/
private function __construct($value)
{
$this->value = $value;
}
/**
* @param T|null $value
* @return Maybe<T>
*/
public static function just($value): Maybe
{
return new self($value);
}
/**
* @return Maybe<T>
*/
public static function nothing(): Maybe
{
return new self(null);
}
/**
* @template U
* @param callable(T):U $fn
* @return Maybe<U>
*/
public function map(callable $fn): Maybe
{
if ($this->value === null) {
return self::nothing();
}
return self::just($fn($this->value));
}
/**
* @param T $defaultValue
* @return T
*/
public function getOrElse($defaultValue)
{
return $this->value ?? $defaultValue;
}
}
The Maybe
class has two static methods: just
and nothing
. The just
method creates a Maybe
object with a value.
The nothing
method creates a Maybe
object without a value. The map
method takes a function as an argument and applies it to the value inside the Maybe
object. If the value inside the Maybe
object is null
, the map
method returns nothing
. The getOrElse
method returns the value inside the Maybe
object or a default value if the value inside the Maybe
object is null
.
Let's check how to use it in the Symfony application.
// src/Controller/DefaultController.php
<?php
namespace App\Controller;
use App\Entity\User;
use App\Service\UserSrvice;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends AbstractController
{
#[Route('', name: 'default')]
public function getUserData(Request $request, UserSrvice $userSrvice): JsonResponse
{
$email = $request->get('email');
$maybeUser = $userSrvice->getUserByEmail($email);
$userData = $maybeUser
->map(fn(User $user) => [
'name' => $user->getName(),
'email' => $user->getEmail(),
])
->getOrElse([
'name' => 'Unknown',
'email' => 'Unavailable',
])
;
return $this->json($userData);
}
}
// src/Service/UserSrvice.php
<?php
declare(strict_types=1);
namespace App\Service;
use App\Repository\UserRepository;
use App\Utils\Maybe;
class UserSrvice
{
public function __construct(private readonly UserRepository $userRepository)
{
}
public function getUserByEmail(string $email): Maybe
{
return Maybe::just($this->userRepository->getUserByEmail($email));
}
}
In the DefaultController
class, we get the email from the request. Then we get the user by email using the UserSrvice
class.
The UserSrvice
class returns a Maybe
object. We use the map
method to get the user data. If the user is not found, the map
method returns nothing
.
Then we use the getOrElse
method to get the user data or a default value if the user is not found.
Conclusion
In this article, we looked at the Maybe monad and how to use it in Symfony. We created a Maybe
class that implements the Maybe monad. We used the Maybe
class in the DefaultController
class to avoid null-checking and exceptions. Using this approach, we can avoid null-checking and exceptions in our Symfony application, and the code will be more readable.
The full code is available on GitHub.