Skip to content

Instantly share code, notes, and snippets.

@olleharstedt
Last active February 14, 2022 18:19
Show Gist options
  • Save olleharstedt/5480aa4efc669dcdc99bd1ee909c143f to your computer and use it in GitHub Desktop.
Save olleharstedt/5480aa4efc669dcdc99bd1ee909c143f to your computer and use it in GitHub Desktop.

Pre-req: Functional core, imperative shell

Pre-req: Side-effects, purity, referential transparency

  • The functional core is more testable and more composable than the imperative shell or effectful code
  • Extending the functional core means making a bigger part of the software pure
  • Side-effects can in some cases easily be lifted out from a function, creating a more composable and testable unit
  • But sometimes, side-effects are tangled in business logic
  • Some side-effects can be delayed until end of request (e.g., updating user's name in database; exception at failure can still be thrown)
  • Example: Create dummy users
/**
 * Alternatives:
 *
 * 1) Naive (implicit depedencies, everything must be integrity tested)
 * 2) Injected implementation (factories)
 * 3) Queue implementation (this implementation; side-effects are delayed or "delegated" to the queue)
 */
function actionCreateDummyUsers(Request $request)
{
    $times  = $request->getParam('times', 5);
    $prefix = $request->getParam('prefix', 'randuser_');
    $email  = $request->getParam('email', 'no@email.com');

    $randomUsers = [];

    for (; $times > 0; $times--) {
        que(
            function (PasswordManagement $pwm, User $user) use ($prefix, $email, &$randomUsers) {
                $password         = $pwm->getRandomPassword();
                $name             = $user->getRandomUsername($prefix);
                $user->users_name = $name;
                $user->full_name  = $name;
                $user->email      = $email;
                $user->created    = date('Y-m-d H:i:s');
                $user->modified   = date('Y-m-d H:i:s');
                $user->password   = password_hash($password, PASSWORD_DEFAULT);
                if ($user->save()) {
                    $randomUsers[] = ['username' => $name, 'password' => $password];
                }
            }
        );
    }

    return [
        'success'     => true,
        'randomUsers' => &$randomUsers,
        'filename'    => $prefix
    ];
}

// Usage:
$result = actionCreateDummyUsers(new Request());
$queue = que(null);
$fn = $queue->pop();
$fn(new PasswordManagement(), new User());
var_export($result);
/*
array (
    'success' => true,
    'randomUsers' =>
    array (
        0 =>
        array (
            'username' => '1abc',
            'password' => '123',
        ),
    ),
    'filename' => 1,
)
*/

// Simple queue class
class Queue
{
    /** @var callable[] */
    private $fns = [];

    public function __construct(array $fns)
    {
        $this->fns = $fns;
    }

    // Run all callables
    // TODO: Autowire with reflection
    public function run()
    {
        array_walk($this->fns, fn ($fn) => $fn());
    }

    public function pop()
    {
        return array_pop($this->fns);
    }
}

// Simple que() function
function que(callable|null $fn)
{
    static $fns = [];
    if (is_callable($fn)) {
        $fns[] = $fn;
    } else {
        return new Queue($fns);
    }
}

// Dummy user, save() must be mocked in unit-test
class User
{
    public $id;

    public function getRandomUsername(string $prefix)
    {
        return $prefix . 'abc';
    }

    public function save()
    {
        return true;
    }
}

// Dummy request
class Request
{
    public function getParam($name, $default)
    {
        return 1;
    }
}

// Dummy class
class PasswordManagement
{
    public function getRandomPassword()
    {
        return '123';
    }
}
@olleharstedt
Copy link
Author

Counter args:

  • Harder to debug
  • Non-idiomatic
  • Switched mock DSL (PHPUnit mock builder) to effect DSL (effect builder)

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