*/ public const PROTECTED_ATTRIBUTES = [ 'password', 'remember_token', 'two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at', 'role', 'roles', 'token', 'refresh_token', 'secret', 'k1', 'email_verified_at', 'nostr', ]; /** * Kanonische Map Modelklasse → Tabellenname. Einmal pro Prozess ermittelt. * * @var array, string>|null */ private static ?array $models = null; /** * Normalisierter Name (Kurzname ODER Tabelle) → Modelklasse. Lazy aus $models gebaut. * * @var array>|null */ private static ?array $index = null; /** * Liste aller bearbeitbaren Models. * * @return array, table: string}> */ public static function list(): array { $models = []; foreach (self::models() as $class => $table) { $models[] = [ 'key' => Str::kebab(class_basename($class)), 'class' => $class, 'table' => $table, ]; } usort($models, fn (array $a, array $b): int => $a['key'] <=> $b['key']); return $models; } /** * Löst einen Model-Namen (Kurzname, FQCN, kebab-case oder Tabellenname) zur Klasse auf. * * @return class-string|null */ public static function resolve(?string $name): ?string { if (! is_string($name) || trim($name) === '') { return null; } return self::index()[self::normalize(Str::of($name)->afterLast('\\')->value())] ?? null; } /** * Verfügbare Model-Keys (kebab-case) als Auswahlhilfe. * * @return array */ public static function keys(): array { return array_map(fn (array $m): string => $m['key'], self::list()); } /** * Ermittelt einmalig alle ladbaren, konkreten Models unter App\Models samt Tabellenname. * * @return array, string> */ private static function models(): array { if (self::$models !== null) { return self::$models; } $models = []; foreach (glob(app_path('Models').'/*.php') ?: [] as $file) { $class = 'App\\Models\\'.basename($file, '.php'); try { // Manche Model-Stubs erben von nicht (mehr) installierten Vendor-Klassen // (z. B. Jetstream) und lassen sich nicht laden – diese überspringen wir. if (! class_exists($class) || ! is_subclass_of($class, Model::class)) { continue; } if ((new ReflectionClass($class))->isAbstract()) { continue; } $models[$class] = (new $class)->getTable(); } catch (\Throwable) { continue; } } return self::$models = $models; } /** * Baut den Auflösungs-Index: Kurzname UND Tabellenname (jeweils normalisiert) → Klasse. * * @return array> */ private static function index(): array { if (self::$index !== null) { return self::$index; } $index = []; foreach (self::models() as $class => $table) { $index[self::normalize(class_basename($class))] = $class; $index[self::normalize($table)] = $class; } return self::$index = $index; } private static function normalize(string $value): string { return Str::of($value)->lower()->replace(['-', '_', ' '], '')->value(); } }