3. FUNCTIONAL PHP
WHAT IS FUNCTIONAL PROGRAMMING?
1. Functions as primary citizens:
- save them as variables
- pass them as arguments
- return them from another
function
2. Lambda calculus:
- closures (PHP >= 5.3)
- generators (PHP >= 5.5)
4. FUNCTIONAL PHP
WHAT IS FUNCTIONAL PROGRAMMING?
$functionAsVariable = function (callable $functionAsArgument): callable {
return function () use ($functionAsArgument) {
return $functionAsArgument();
};
};
$foo = 'bar';
return $functionAsVariable(function () use ($foo): string {
return $foo;
})();
5. FUNCTIONAL PHP
YOU HAVE PROBABLY USED IT ALREADY
const functionAsVariable = arg => functionAsArgument => functionAsArgument(arg);
let foo = 'bar';
return functionAsVariable(foo)( (arg) => arg );
Modern JS (ES6):
6. FUNCTIONAL PHP
YOU HAVE PROBABLY USED IT ALREADY
$.ajax({
url: "demo_test.txt",
success: function (result) {
$("#div1").html(result);
},
error: function (xhr) {
alert("An error occured: " + xhr.status + " " + xhr.statusText);
}
});
Old JS (jQuery):
7. FUNCTIONAL PHP
YOU CAN USE IT ALREADY
use ReactPromisePromise;
(new Promise(file_get_contents('foo.txt')))
->then(function (string $result): string {
echo 'well done! '.$result;
})
->otherwise(function (Throwable $error) {
throw $error;
});
Adequate code in PHP:
8. FUNCTIONAL PHP
PROS (PHP)
▸ LESS ERRORS
YOU HAVE TO HANDLE EVERY CASE
Fatal error: Call to a member function on null
▸ METHOD CHAINING
▸ CONCURRENT (ASYNC) CODE
▸ IMMUTABILITY
▸ ES / DDD MADE EASY
9. FUNCTIONAL PHP
CONS (PHP)
▸ LESS READABLE CODE
(PHP SYNTAX DOESN’T FIT BEST)
▸ NOT OBVIOUS FOR PHP DEVS
(while JS Devs use it everyday)
▸ PERFORMANCE
(but do we really care about it?)
10. TOOLS
FUNCTIONAL PHP
PhpSlang is a PHP library aiming to fill the gaps
between PHP and classical functional languages.
PHPSLANG
REACTPHP
ReactPHP is a low-level library for event-driven programming in PHP.
phpslang.io
reactphp.org
11. TOOLS
FUNCTIONAL PHP
Asynchronous & Fault-tolerant PHP Framework
for Distributed Applications.
KRAKEN FRAMEWORK
IMMUTABLE.PHP
Immutable collections, with filter, map, join, sort, slice, and other methods.
Well-suited for functional programming and memory-intensive applications.
Runs especially fast in PHP7.
https://github.com/jkoudys/immutable.php
kraken-php.com
12. HOW?
FUNCTIONAL PHP
1. Forget about null checks
2. Forget about throwing Exceptions
3. Forget about variables
4. Forget about loops
5. Use async calls wherever you can
6. Avoid mutating the State
20. OPTION
FUNCTIONAL PHP
Best practices:
1. Return Option whenever function might return null
2. Use map(Closure $expression) when $expression returns unpacked value
map(function(): mixed): Option
3. Use flatMap(Closure $expression) when $expression returns value packed
in Option
flatMap(function(): Option): Option
4. Don’t forget to call getOrCall() / getOrElse() at end! - only at this point all
defined expressions are evaluated
21. MAP() VS FLATMAP()
use PhpSlangOptionOption;
public function getUser(string $email): Option<User>
{
return Option::of($this->userRepository->findByEmail($email));
}
public function getUserNameOption(string $email): Option<string>
{
return $this->getUser($email)
->map(function (User $user): Option {
return Option::of($user->getName());
})
->getOrElse(new Some($email));
}
public function getUserNameFlat(string $email): string
{
return $this->getUser($email)
->flatMap(function (User $user): Option {
return Option::of($user->getName());
})
->getOrElse($email);
}
FUNCTIONAL PHP
23. HOW?
public function getUserName(string $email): ?string
{
try {
return $this->findUser($email)->getName();
} catch (InvalidArgumentException $e) {
return null;
}
}
private function findUser(string $email): User
{
$user = $this->userRepository->findBy(['email' => $email]);
if (!$user instanceof User) {
throw new InvalidArgumentException('User not found.');
}
return $user;
}
FUNCTIONAL PHP
2. Forget about throwing Exceptions
24. HOW?
public function getUserName(string $email): ?string
{
try {
return $this->findUser($email)->getName();
} catch (InvalidArgumentException $e) {
return null;
}
}
private function findUser(string $email): User
{
$user = $this->userRepository->findBy(['email' => $email]);
if (!$user instanceof User) {
throw new InvalidArgumentException('User not found.');
}
return $user;
}
FUNCTIONAL PHP 2. Forget about throwing Exceptions
25. HOW?
public function getUserName(string $email): Option
{
return $this->findUser($email)
->right(function (User $user): Some {
return new Some($user->getName());
})
->left(function (): None {
return new None();
})
->get();
}
private function findUser(string $email): Either
{
return Option::of($this->userRepository->findBy(['email' => $email]))
->map(function (User $user) {
return new Right($user);
})
->getOrElse(function () {
return new Left('User not found.');
});
}
FUNCTIONAL PHP 2. Forget about throwing Exceptions: Use Either instead
26. EITHER
FUNCTIONAL PHP
abstract class Either
{
abstract public function left(Closure $expression): Either;
abstract public function right(Closure $expression): Either;
abstract public function flatLeft(Closure $expression): Either;
abstract public function flatRight(Closure $expression): Either;
public function get(): mixed {};
}
class Right extends Either {}
class Left extends Either {}
27. EITHER
FUNCTIONAL PHP
Best practices:
1. Use Right of expected value, Left for unexpected/error
2. Use left(Closure $expression) / right(Closure $expression) when $expression returns unpacked value
right(function(): mixed): Either
3. Use flatLeft(Closure $expression) / flatRight(Closure $expression) when $expression returns value
packed in either Right or Left
flatRight(function(): Either): Either
4. flatRight() for chaining, right() on success, left() on error
5. Don’t forget to call get() at end! - only at this point all defined expressions are evaluated
34. COLLECTIONS
FUNCTIONAL PHP
Libraries which provide them:
1. Doctrine collections (incomplete functionality)
2. PhpSlang
3. Immutable.php
Collections provide unified API for PHP’s array_* functions and sometimes they are even faster than
native PHP.
Methods:
1. filter() (array_filter)
2. map() (array_map)
3. reduce() / fold() (array_reduce)
4. sort() (usort)
35. HOW?
FUNCTIONAL PHP
1. Forget about null checks
2. Forget about throwing Exceptions
3. Forget about variables
4. Forget about loops
5. Use async calls wherever you can
36. HOW?
use function ClueReactBlockawaitAll;
/* use try-catch because ReactPHP's awaitAll uses Exceptions to escape loop
and PhpSlang's Future monad is not ready yet */
try {
$argumentsArray = awaitAll([
$this->getStatus($valuesArray[‘status']),
$this->getIdea($valuesArray['idea']),
$this->getUserFromToken(),
$valuesArray['reason'] ?? null
], LoopFactory::create());
return new Right($argumentsArray);
} catch (NotFoundHttpException $e) {
return new Left($e);
}
FUNCTIONAL PHP
5. Use async calls wherever you can
37. HOW?
FUNCTIONAL PHP
1. Forget about null checks
2. Forget about loops
3. Forget about throwing Exceptions
4. Forget about variables
5. Use async calls wherever you can
6. Avoid mutating the State
38. $today = new DateTime();
$yesterday = $today->modify('-1 day');
echo "Yesterday was {$yesterday->format('l')}
and today is {$today->format('l')}";
FUNCTIONAL PHP
HOW? 6. Avoid mutating the State
39. $today = new DateTime();
$yesterday = $today->modify('-1 day');
echo "Yesterday was {$yesterday->format('l')}
and today is {$today->format('l')}";
// prints "Yesterday was Thursday and today is Thursday"
FUNCTIONAL PHP
HOW? 6. Avoid mutating the State
40. $today = new DateTimeImmutable();
$yesterday = $today->modify('-1 day');
echo "Yesterday was {$yesterday->format('l')}
and today is {$today->format(‘l')}";
// prints "Yesterday was Thursday and today is Friday"
FUNCTIONAL PHP
HOW? 6. Avoid mutating the State
Use Immutable Classes
43. HOW IS IT CALLED IN OTHER LANGUAGES?
FUNCTIONAL PHP
JS (ELM) PHP (PHPSLANG) SCALA (JAVASLANG)
Maybe (Just, Nothing) Option (Some, None) Option (Some, None)
Either (Right, Left) Either (Right, Left) Either (Right, Left)
Promise Promise (Future?) Future
async, await await, awaitAll Await.result