feat: introduce module system and extract address book as first module (MODULAR-MONOLITH-V1-001)
Add a module kernel (ModuleManifest, ModuleRegistry) that allows modules to contribute routes, UI slots, search providers, layout context, session lifecycle, and permissions. Modules are activated via config/modules.php or APP_ENABLED_MODULES env variable. Conflicts (duplicate routes, permissions, slot keys) cause a fail-fast with a clear error message. Extract the address book from hardcoded Core integration points into the first module (modules/addressbook/). The module provides: - Aside icon-bar tab + People panel via UI slot system - Global search resource via AddressBookSearchProvider - Layout context data via AddressBookLayoutProvider - Session lifecycle via AddressBookSessionProvider Core cleanup removes address-book hardcodings from SearchSqlResourceProvider, SearchUiMetaProvider, SearchItemMapperProvider, appBuildLayoutNavContext(), and the aside templates. Permissions (ADDRESS_BOOK_VIEW) and business logic (AddressBookService) remain in Core as they gate general user visibility. Includes 38 new tests (894 total), PHPStan level 5 clean, and architecture tests verifying zero hardcoded address-book references in search/templates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,18 +2,8 @@
|
||||
|
||||
namespace MintyPHP\Support\Search;
|
||||
|
||||
use MintyPHP\Service\User\UserAvatarService;
|
||||
|
||||
class SearchItemMapperProvider
|
||||
{
|
||||
/** @var (callable(): UserAvatarService)|null */
|
||||
private static $userAvatarServiceResolver = null;
|
||||
|
||||
public static function configure(callable $userAvatarServiceResolver): void
|
||||
{
|
||||
self::$userAvatarServiceResolver = $userAvatarServiceResolver;
|
||||
}
|
||||
|
||||
public static function mapPreviewItem(string $key, array $data, string $query): ?array
|
||||
{
|
||||
if ($key === 'users') {
|
||||
@@ -23,13 +13,6 @@ class SearchItemMapperProvider
|
||||
$label = $label === '' ? $email : ($label . ' — ' . $email);
|
||||
}
|
||||
$url = lurl('admin/users/edit/' . ($data['uuid'] ?? '')) . '?search=' . urlencode($query);
|
||||
} elseif ($key === 'address-book') {
|
||||
$label = trim(($data['first_name'] ?? '') . ' ' . ($data['last_name'] ?? ''));
|
||||
$email = trim((string) ($data['email'] ?? ''));
|
||||
if ($email !== '') {
|
||||
$label = $label === '' ? $email : ($label . ' — ' . $email);
|
||||
}
|
||||
$url = lurl('address-book/view/' . ($data['uuid'] ?? '')) . '?search=' . urlencode($query);
|
||||
} elseif ($key === 'permissions') {
|
||||
$label = (string) ($data['description'] ?? '');
|
||||
$url = lurl('admin/permissions/edit/' . ($data['id'] ?? '')) . '?search=' . urlencode($query);
|
||||
@@ -99,15 +82,6 @@ class SearchItemMapperProvider
|
||||
$title = trim(($data['first_name'] ?? '') . ' ' . ($data['last_name'] ?? ''));
|
||||
$description = (string) ($data['email'] ?? '');
|
||||
$url = lurl('admin/users/edit/' . ($data['uuid'] ?? ''));
|
||||
} elseif ($key === 'address-book') {
|
||||
$title = trim(($data['first_name'] ?? '') . ' ' . ($data['last_name'] ?? ''));
|
||||
$description = (string) ($data['email'] ?? '');
|
||||
$uuid = (string) ($data['uuid'] ?? '');
|
||||
$url = lurl('address-book/view/' . $uuid);
|
||||
$userAvatarService = self::userAvatarService();
|
||||
if ($uuid !== '' && $userAvatarService->hasAvatar($uuid)) {
|
||||
$image = lurl('admin/users/avatar-file') . '?uuid=' . urlencode($uuid) . '&size=64';
|
||||
}
|
||||
} elseif ($key === 'permissions') {
|
||||
$title = (string) ($data['key'] ?? '');
|
||||
$description = (string) ($data['description'] ?? '');
|
||||
@@ -170,18 +144,5 @@ class SearchItemMapperProvider
|
||||
];
|
||||
}
|
||||
|
||||
private static function userAvatarService(): UserAvatarService
|
||||
{
|
||||
if (!is_callable(self::$userAvatarServiceResolver)) {
|
||||
throw new \RuntimeException('SearchItemMapperProvider is not configured for dependency: ' . UserAvatarService::class);
|
||||
}
|
||||
|
||||
$service = (self::$userAvatarServiceResolver)();
|
||||
if (!$service instanceof UserAvatarService) {
|
||||
throw new \RuntimeException('SearchItemMapperProvider resolver returned invalid dependency: ' . UserAvatarService::class);
|
||||
}
|
||||
|
||||
return $service;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user