134 lines
4.4 KiB
PHP
134 lines
4.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Domain\User;
|
|
|
|
final class UserRecord
|
|
{
|
|
/** @var list<string> */
|
|
private const DISPLAY_FIELDS = [
|
|
'id',
|
|
'name',
|
|
'email',
|
|
'role_id',
|
|
'role_name',
|
|
'is_active',
|
|
'last_login_at',
|
|
'created_at',
|
|
'updated_at',
|
|
];
|
|
|
|
private readonly PasswordPolicy $passwordPolicy;
|
|
|
|
public function __construct(?PasswordPolicy $passwordPolicy = null)
|
|
{
|
|
$this->passwordPolicy = $passwordPolicy ?? new PasswordPolicy();
|
|
}
|
|
|
|
/** @return array{name: string, email: string, role_id: int|string|null, is_active: bool|mixed} */
|
|
public function normalize(array $record): array
|
|
{
|
|
return [
|
|
'name' => trim(is_scalar($record['name'] ?? null) ? (string) $record['name'] : ''),
|
|
'email' => strtolower(trim(is_scalar($record['email'] ?? null) ? (string) $record['email'] : '')),
|
|
'role_id' => $this->normalizeRoleId($record['role_id'] ?? null),
|
|
'is_active' => $this->normalizeActive($record['is_active'] ?? $record['active'] ?? true),
|
|
];
|
|
}
|
|
|
|
/** @return array{name: string, email: string, role_id: int|string|null, is_active: bool|mixed, valid: bool, errors: array<string, string>} */
|
|
public function validate(array $record): array
|
|
{
|
|
$normalized = $this->normalize($record);
|
|
$errors = [];
|
|
|
|
if ($normalized['name'] === '') {
|
|
$errors['name'] = 'User name is required.';
|
|
} elseif (mb_strlen($normalized['name']) > 120) {
|
|
$errors['name'] = 'User name must be 120 characters or fewer.';
|
|
}
|
|
|
|
if ($normalized['email'] === '') {
|
|
$errors['email'] = 'Email address is required.';
|
|
} elseif (mb_strlen($normalized['email']) > 190) {
|
|
$errors['email'] = 'Email address must be 190 characters or fewer.';
|
|
} elseif (filter_var($normalized['email'], FILTER_VALIDATE_EMAIL) === false) {
|
|
$errors['email'] = 'Email address must be valid.';
|
|
}
|
|
|
|
if (!is_int($normalized['role_id']) || $normalized['role_id'] < 1) {
|
|
$errors['role_id'] = 'Role must be a positive integer.';
|
|
}
|
|
|
|
if (!is_bool($normalized['is_active'])) {
|
|
$errors['is_active'] = 'Active flag must be boolean.';
|
|
}
|
|
|
|
return [...$normalized, 'valid' => $errors === [], 'errors' => $errors];
|
|
}
|
|
|
|
/**
|
|
* Validates a new user and its initial password without returning the
|
|
* plaintext password in the result.
|
|
*
|
|
* @return array{name: string, email: string, role_id: int|string|null, is_active: bool|mixed, valid: bool, errors: array<string, string>}
|
|
*/
|
|
public function validateForCreate(array $record): array
|
|
{
|
|
$result = $this->validate($record);
|
|
$password = $this->passwordPolicy->validateInitial($record['password'] ?? null);
|
|
if (!$password['valid']) {
|
|
$result['errors']['password'] = 'Password requires: ' . implode(', ', $password['errors']) . '.';
|
|
$result['valid'] = false;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
public function display(array $record): array
|
|
{
|
|
$safe = [];
|
|
foreach (self::DISPLAY_FIELDS as $field) {
|
|
if (array_key_exists($field, $record)) {
|
|
$safe[$field] = $record[$field];
|
|
}
|
|
}
|
|
return $safe;
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
public function toDisplay(array $record): array
|
|
{
|
|
return $this->display($record);
|
|
}
|
|
|
|
private function normalizeRoleId(mixed $value): int|string|null
|
|
{
|
|
if (is_int($value)) {
|
|
return $value;
|
|
}
|
|
if (is_string($value) && preg_match('/^[0-9]+$/', trim($value)) === 1) {
|
|
return (int) trim($value);
|
|
}
|
|
return is_scalar($value) || $value === null ? $value : null;
|
|
}
|
|
|
|
private function normalizeActive(mixed $value): mixed
|
|
{
|
|
if (is_bool($value)) {
|
|
return $value;
|
|
}
|
|
if (is_int($value) && ($value === 0 || $value === 1)) {
|
|
return $value === 1;
|
|
}
|
|
if (is_string($value)) {
|
|
return match (strtolower(trim($value))) {
|
|
'1', 'true', 'yes', 'on' => true,
|
|
'0', 'false', 'no', 'off' => false,
|
|
default => $value,
|
|
};
|
|
}
|
|
return $value;
|
|
}
|
|
}
|