phunkie

Validation

Validation is a data type that represents computations that might fail. Unlike Either, Validation can accumulate errors using a Semigroup instance.

What is Validation?

Validation has two variants:

abstract class Validation implements Applicative, Monad, Kind, Foldable {
    abstract public function getOrElse($default);
    abstract public function map(callable $f): Kind;
}

Creating Validations

Basic Creation

use Phunkie\Validation\Success;
use Phunkie\Validation\Failure;

$success = Success(42);
$failure = Failure("Invalid input");

// Using Nel constructors
$successNel = SuccessNel(1, 2, 3);        // Success(Nel(1, 2, 3))
$failureNel = FailureNel("e1", "e2");     // Failure(Nel("e1", "e2"))

Using Either

$validation = Either("Invalid email")(
    fn() => filter_var($email, FILTER_VALIDATE_EMAIL)
);

Using Attempt

$validation = Attempt(function() {
    if (!file_exists("config.php")) {
        throw new \Exception("Config file not found");
    }
    return require "config.php";
});

Failure Creation

// Single error
$failure = Failure("Invalid input");

// Multiple errors using Nel constructor
$failure = FailureNel("Error 1", "Error 2");

// Combining failures automatically creates Nel
$f1 = Failure("Error 1");
$f2 = Failure("Error 2");
$result = $f1->combine($f2);
// Failure(Nel("Error 1", "Error 2"))

// Combining with existing Nel
$f1 = FailureNel("Error 1", "Error 2");
$f2 = Failure("Error 3");
$result = $f1->combine($f2);
// Failure(Nel("Error 1", "Error 2", "Error 3"))

Core Operations

map

Transform successful values:

$success = Success(42)->map(fn($x) => $x * 2); // Success(84)
$failure = Failure("error")->map(fn($x) => $x * 2); // Failure("error")

flatMap

Chain validations:

$validateAge = fn($age) => 
    $age >= 18 ? Success($age) : Failure("Must be 18 or older");

$success = Success(20)->flatMap($validateAge); // Success(20)
$failure = Success(16)->flatMap($validateAge); // Failure("Must be 18 or older")

getOrElse

Provide default values:

$success = Success(42)->getOrElse(0); // 42
$failure = Failure("error")->getOrElse(0); // 0

combine

Accumulate errors or combine successes:

$v1 = Success("Hello");
$v2 = Success("World");

// Combine successes
$result = $v1->combine($v2);
// Success(Nel("Hello", "World"))

// Combine failures
$f1 = Failure("Error 1");
$f2 = Failure("Error 2");
$result = $f1->combine($f2);
// Failure(Nel("Error 1", "Error 2"))

Common Use Cases

1. Form Validation

$validateName = fn($name) => 
    strlen($name) >= 2 
        ? Success($name) 
        : Failure("Name too short");

$validateEmail = fn($email) => 
    filter_var($email, FILTER_VALIDATE_EMAIL)
        ? Success($email)
        : Failure("Invalid email");

$validateAge = fn($age) => 
    $age >= 18
        ? Success($age)
        : Failure("Must be 18 or older");

// Combine validations
$form = Success(['name' => 'John', 'email' => 'john@example.com', 'age' => 25]);
$result = $form
    ->map(fn($data) => $validateName($data['name']))
    ->flatMap(fn($_) => $validateEmail($data['email']))
    ->flatMap(fn($_) => $validateAge($data['age']));

2. Error Accumulation

$validations = ImmList(
    Success(1),
    Failure("Error 1"),
    Failure("Error 2")
);

apply(...$validations); // Failure("Error 1Error 2")

3. Exception Handling

$result = Attempt(function() {
    $config = require "config.php";
    $db = new PDO($config['dsn']);
    return $db->query("SELECT * FROM users");
});

Best Practices

  1. Use Validation when you need to accumulate errors
  2. Combine multiple validations using combine or apply
  3. Provide meaningful error messages
  4. Use Attempt for exception handling
  5. Consider using Nel for non-empty error lists

Implementation Notes