Files
JobcardSystem/app/Domain/User/RoleRecord.php
T

124 lines
3.9 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace App\Domain\User;
use LogicException;
/** Framework-free normalization, validation, and safety rules for roles. */
final class RoleRecord
{
private const ADMINISTRATOR = 'administrator';
/** @var list<string> */
private const DISPLAY_FIELDS = ['id', 'name', 'description', 'created_at', 'permissions'];
/** @return array{name: string, description: string|null} */
public function normalize(array $record): array
{
$name = $record['name'] ?? null;
$description = $record['description'] ?? null;
return [
'name' => is_scalar($name) ? trim((string) $name) : '',
'description' => $this->normalizeDescription($description),
];
}
/** @return array{name: string, description: string|null, valid: bool, errors: array<string, string>} */
public function validate(array $record): array
{
$normalized = $this->normalize($record);
$errors = [];
if ($normalized['name'] === '') {
$errors['name'] = 'Role name is required.';
} elseif (mb_strlen($normalized['name']) > 80) {
$errors['name'] = 'Role name must be 80 characters or fewer.';
} elseif (preg_match('/[\x00-\x1F\x7F]/', $normalized['name']) === 1) {
$errors['name'] = 'Role name contains invalid control characters.';
}
if ($normalized['description'] !== null && mb_strlen($normalized['description']) > 255) {
$errors['description'] = 'Role description must be 255 characters or fewer.';
}
return [...$normalized, 'valid' => $errors === [], 'errors' => $errors];
}
public function isAdministrator(array $record): bool
{
return $this->canonicalName($record['name'] ?? null) === self::ADMINISTRATOR;
}
public function canRename(array $record, mixed $newName): bool
{
return !$this->isAdministrator($record) && $this->canonicalName($newName) !== self::ADMINISTRATOR;
}
public function canDelete(array $record): bool
{
return !$this->isAdministrator($record);
}
public function canChangePermissions(array $record): bool
{
return !$this->isAdministrator($record);
}
public function assertCanRename(array $record, mixed $newName): void
{
if (!$this->canRename($record, $newName)) {
throw new LogicException('The protected Administrator role cannot be renamed.');
}
}
public function assertCanDelete(array $record): void
{
if (!$this->canDelete($record)) {
throw new LogicException('The protected Administrator role cannot be deleted.');
}
}
public function assertCanChangePermissions(array $record): void
{
if (!$this->canChangePermissions($record)) {
throw new LogicException('The protected Administrator role permissions cannot be changed.');
}
}
/** @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] = $field === 'permissions' && is_array($record[$field])
? (new PermissionMatrix())->normalize($record[$field])
: $record[$field];
}
}
return $safe;
}
/** @return array<string, mixed> */
public function toDisplay(array $record): array
{
return $this->display($record);
}
private function normalizeDescription(mixed $value): ?string
{
if (!is_scalar($value)) {
return null;
}
$value = trim((string) $value);
return $value === '' ? null : $value;
}
private function canonicalName(mixed $value): string
{
return is_scalar($value) ? strtolower(trim((string) $value)) : '';
}
}