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.
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.
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.
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.