1. Go to this page and download the library: Download phpdot/error library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
phpdot / error example snippets
enum UserErrors: string implements ErrorCodeInterface
{
use ErrorCodeTrait;
case NOT_FOUND = '00010001';
case EMAIL_TAKEN = '00010002';
case INVALID_EMAIL = '00010003';
case WEAK_PASSWORD = '00010004';
public function getDetails(): array
{
return match ($this) {
self::NOT_FOUND => [
'message' => 'User not found',
'description' => 'errors.user.not_found',
'type' => ErrorType::NOT_FOUND,
'httpStatus' => HttpStatus::NOT_FOUND->value,
],
self::EMAIL_TAKEN => [
'message' => 'Email is already taken',
'description' => 'errors.user.email_taken',
'type' => ErrorType::CONFLICT,
'httpStatus' => HttpStatus::CONFLICT->value,
],
// ...
};
}
}
$errors = new ErrorBag(); // raw mode — descriptions stay as keys
// or
$errors = new ErrorBag($translator); // descriptions are translated at add() time
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors->add(UserErrors::INVALID_EMAIL, 'email');
}
if (strlen($password) < 8) {
$errors->add(UserErrors::WEAK_PASSWORD, 'password', ['min' => 8]);
}
if ($errors->hasErrors()) {
return $errors; // same structure for JSON, HTML, WebSocket, CLI
}
interface ErrorCodeInterface
{
public function getCode(): string;
public function getMessage(): string;
public function getDescription(): string;
public function getType(): ErrorType;
public function getHttpStatus(): int;
public function getDetails(): array;
}
trait ErrorCodeTrait
{
public function getCode(): string { return $this->value; }
public function getMessage(): string { return $this->getDetails()['message']; }
public function getDescription(): string { return $this->getDetails()['description']; }
public function getType(): ErrorType { return $this->getDetails()['type']; }
public function getHttpStatus(): int { return $this->getDetails()['httpStatus']; }
}
// User module
enum UserErrors: string implements ErrorCodeInterface
{
use ErrorCodeTrait;
case NOT_FOUND = '00010001';
case EMAIL_TAKEN = '00010002';
case INVALID_EMAIL = '00010003';
case WEAK_PASSWORD = '00010004';
case LOCKED = '00010005';
public function getDetails(): array
{
return match ($this) {
self::NOT_FOUND => [
'message' => 'User not found',
'description' => 'errors.user.not_found',
'type' => ErrorType::NOT_FOUND,
'httpStatus' => HttpStatus::NOT_FOUND->value,
],
self::EMAIL_TAKEN => [
'message' => 'Email is already taken',
'description' => 'errors.user.email_taken',
'type' => ErrorType::CONFLICT,
'httpStatus' => HttpStatus::CONFLICT->value,
],
self::INVALID_EMAIL => [
'message' => 'Invalid email address',
'description' => 'errors.user.invalid_email',
'type' => ErrorType::VALIDATION,
'httpStatus' => HttpStatus::UNPROCESSABLE_ENTITY->value,
],
self::WEAK_PASSWORD => [
'message' => 'Password must be at least 8 characters',
'description' => 'errors.user.weak_password',
'type' => ErrorType::VALIDATION,
'httpStatus' => HttpStatus::UNPROCESSABLE_ENTITY->value,
],
self::LOCKED => [
'message' => 'Account is locked',
'description' => 'errors.user.account_locked',
'type' => ErrorType::AUTHORIZATION,
'httpStatus' => HttpStatus::FORBIDDEN->value,
],
};
}
}
// Order module — separate file, separate team, no conflicts
enum OrderErrors: string implements ErrorCodeInterface
{
use ErrorCodeTrait;
case NOT_FOUND = '00020001';
case ALREADY_SHIPPED = '00020002';
case PAYMENT_FAILED = '00020003';
public function getDetails(): array { /* ... */ }
}
final readonly class ErrorEntry
{
public string $code; // '00010003'
public string $message; // 'Invalid email address' (English fallback)
public string $description; // 'errors.user.invalid_email' (i18n key)
public ErrorType $type; // ErrorType::VALIDATION
public int $httpStatus; // 422
public ?string $context; // 'email' (field, param, header, service, path)
public array $params; // ['min' => 8] (ICU interpolation params)
}
$entry->toArray(); // serializable array
$bag = new ErrorBag();
// From module enum
$bag->add(UserErrors::INVALID_EMAIL, 'email');
$bag->add(UserErrors::WEAK_PASSWORD, 'password', ['min' => 8]);
// Raw entry
$bag->addEntry(new ErrorEntry('CUSTOM', 'msg', 'desc', ErrorType::SERVER, 500));
// Chainable
$bag->add(UserErrors::INVALID_EMAIL, 'email')
->add(UserErrors::WEAK_PASSWORD, 'password');
use PHPdot\Error\ErrorBagFactory;
// No translator — produces raw bags
$factory = new ErrorBagFactory();
$bag = $factory->create();
$bag->add(UserErrors::EMAIL_TAKEN, 'email');
$bag->first()->description; // 'errors.user.email_taken' (raw key)
// With translator — every bag carries it
$factory = new ErrorBagFactory($translator);
$bag = $factory->create();
$bag->add(UserErrors::EMAIL_TAKEN, 'email', ['email' => '[email protected]']);
$bag->first()->description; // 'The email [email protected] is already registered.'
final class UserService
{
public function __construct(
private readonly ErrorBagFactory $bags,
) {}
public function register(string $email): User|ErrorBag
{
$bag = $this->bags->create();
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$bag->add(UserErrors::INVALID_EMAIL, 'email');
}
return $bag->hasErrors() ? $bag : $this->users->create($email);
}
}
enum ErrorType: string
{
case VALIDATION = 'validation'; // input is wrong
case AUTHENTICATION = 'authentication'; // who are you?
case AUTHORIZATION = 'authorization'; // you can't do this
case NOT_FOUND = 'not_found'; // doesn't exist
case CONFLICT = 'conflict'; // duplicate, version mismatch
case RATE_LIMIT = 'rate_limit'; // too many requests
case TIMEOUT = 'timeout'; // took too long
case UNAVAILABLE = 'unavailable'; // service down
case SERVER = 'server'; // unexpected internal error
}
enum HttpStatus: int
{
case OK = 200;
case CREATED = 201;
case NO_CONTENT = 204;
case BAD_REQUEST = 400;
case UNAUTHORIZED = 401;
case FORBIDDEN = 403;
case NOT_FOUND = 404;
case CONFLICT = 409;
case UNPROCESSABLE_ENTITY = 422;
case TOO_MANY_REQUESTS = 429;
case INTERNAL_SERVER_ERROR = 500;
case SERVICE_UNAVAILABLE = 503;
// ... 25 codes total
}
// In error enums
'httpStatus' => HttpStatus::NOT_FOUND->value, // 404
$errors->add(UserErrors::INVALID_EMAIL, 'email'); // form field
$errors->add(UserErrors::NOT_FOUND, 'user_id'); // route parameter
$errors->add(AuthErrors::INVALID_TOKEN, 'Authorization'); // HTTP header
$errors->add(PaymentErrors::GATEWAY_DOWN, 'stripe'); // service name
$errors->add(OrderErrors::INVALID_ADDRESS, 'address.city'); // nested path
$errors->add(SystemErrors::MAINTENANCE); // no context — global
$errors->forContext('email'); // errors for the email field
$errors->forContext('Authorization'); // errors for the auth header
$errors->add(ProductErrors::INSUFFICIENT_STOCK, 'quantity', [
'available' => 5,
'requested' => 10,
]);
// Translation files:
// en: 'errors.product.insufficient_stock' → 'Only {available} items in stock, you requested {requested}'
// ar: 'errors.product.insufficient_stock' → 'يتوفر {available} عناصر فقط، طلبت {requested}'
foreach ($bag->all() as $error) {
$translated = $i18n->trans($error->description, $error->params);
// Show next to the form field identified by $error->context
}
use PHPdot\Error\ErrorBag;
$bag = new ErrorBag($translator); // any class implementing MessageTranslatorInterface
$bag->add(UserErrors::EMAIL_TAKEN, 'email', ['email' => '[email protected]']);
$bag->first()->message; // 'Email is already taken' (always — enum's English string)
$bag->first()->description; // 'The email [email protected] is already registered.'
// (translated; was the raw key without translator)
$bag = new ErrorBag(); // no translator
$bag->add(UserErrors::EMAIL_TAKEN); // description stays as 'errors.user.email_taken'
$bag = new ErrorBag($translator); // translator wired
$bag->add(UserErrors::EMAIL_TAKEN); // description becomes the translated string