Skip to content

Instantly share code, notes, and snippets.

@Ocramius
Last active January 22, 2024 00:09
Show Gist options
  • Save Ocramius/b3a5c4b5610efc132411 to your computer and use it in GitHub Desktop.
Save Ocramius/b3a5c4b5610efc132411 to your computer and use it in GitHub Desktop.
`__invoke` vs `function` vs `Closure`
<?php
final class Login
{
private $sessions;
public function __construct(SessionRepository $sessions)
{
$this->sessions = $sessions;
}
public function __invoke(User $user)
{
$session = new Session($user);
$this->sessions->add($session);
return $session;
}
}
// usage (PHP 7) - see http://3v4l.org/kPnLM:
$session = (new Login($sessionRepository))($user);

__invoke VS function VS Closure

This gist is about:

Mainly:

  • function cannot (should not) be used when side-effects occur
  • function does not work with dependency injection
  • function is not testable in isolation if it isn't a pure function without dependencies

In order to fix the problems exposed above, and avoid global or static scope pollution, two approaches are possible:

Few notes:

  • a class that only implements __invoke and __construct is basically a function combined with a functor

  • a class can be easily tested in isolation

  • a Closure with dependencies cannot be tested in isolation

  • in order to make a Closure testable in isolation, it must be combined with a higher order function

  • there is no runtime difference between using a Closure and any object with an __invoke method defined in its class

  • in PHP, $a = function () use (...) {} simply means "call the constructor of class Closure with these parameters and this body for __invoke", so you end up with an object that implements __invoke anyway.

Therefore, object + __invoke is much simpler and flexible than using a Closure, whereas a function is not applicable in any context that requires the usage of DI.

<?php
// higher order function / factory - you name it
$loginFactory = function (SessionRepository $sessions) {
return function (User $user) use ($sessions) {
// argh! (note that a static registry/locator isn't any better)
global $sessions;
$session = new Session($user);
$sessions->add($session);
return $session;
};
};
// usage (PHP 7) - see http://3v4l.org/kPnLM:
$session = $loginFactory(sessionRepository)($user);
<?php
function login(User $user)
{
// argh! (note that a static registry/locator isn't any better)
global $sessions;
$session = new Session($user);
$sessions->add($session);
return $session;
}
// usage:
$session = login($user);
@oviniciusfeitosa
Copy link

Thank you for sharing knowledge and congratulations on the gist

A small adjustment that can be made in the example closure.php would be:

$session = $loginFactory(sessionRepository) ($user);

for

$session = $loginFactory($sessionRepository) ($user);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment