61 lines
1.8 KiB
PHP
61 lines
1.8 KiB
PHP
<?php
|
|||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace App\Domain\User;
|
||
|
|
|
||
|
|
/** Canonicalizes permission names and exposes only safe permission data. */
|
||
|
|
final class PermissionMatrix
|
||
|
|
{
|
||
|
|
/** @var list<string> */
|
||
|
|
private const DISPLAY_FIELDS = ['id', 'name', 'description', 'permissions'];
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Trim permission names, canonicalize them, discard malformed entries, and
|
||
|
|
* preserve first-seen order while removing duplicates.
|
||
|
|
*
|
||
|
|
* @param array<mixed> $permissions
|
||
|
|
* @return list<string>
|
||
|
|
*/
|
||
|
|
public function normalize(array $permissions): array
|
||
|
|
{
|
||
|
|
$normalized = [];
|
||
|
|
$seen = [];
|
||
|
|
|
||
|
|
foreach ($permissions as $permission) {
|
||
|
|
if (!is_scalar($permission)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$permission = strtolower(trim((string) $permission));
|
||
|
|
if ($permission === '' || preg_match('/[\x00-\x20\x7F]/', $permission) === 1) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (!isset($seen[$permission])) {
|
||
|
|
$seen[$permission] = true;
|
||
|
|
$normalized[] = $permission;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $normalized;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @param array<string, mixed> $record @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])
|
||
|
|
? $this->normalize($record[$field])
|
||
|
|
: $record[$field];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $safe;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @param array<string, mixed> $record @return array<string, mixed> */
|
||
|
|
public function toDisplay(array $record): array
|
||
|
|
{
|
||
|
|
return $this->display($record);
|
||
|
|
}
|
||
|
|
}
|