|Collection|null $breadcrumbs * @return list */ public static function normalize(iterable|Collection|null $breadcrumbs): array { $items = collect($breadcrumbs instanceof Collection ? $breadcrumbs->all() : ($breadcrumbs ?? [])) ->map(fn (mixed $crumb): ?array => self::mapCrumb($crumb)) ->filter(fn (?array $crumb): bool => is_array($crumb) && $crumb['name'] !== '' && $crumb['url'] !== '') ->values(); $home = [ 'name' => 'Home', 'url' => self::absoluteUrl('/'), ]; $normalized = []; $seen = []; foreach ($items as $crumb) { $name = trim((string) ($crumb['name'] ?? '')); $url = self::absoluteUrl((string) ($crumb['url'] ?? '')); if ($name === '' || $url === '') { continue; } if (self::isHome($name, $url)) { $name = $home['name']; $url = $home['url']; } $key = strtolower($name) . '|' . rtrim(strtolower($url), '/'); if (isset($seen[$key])) { continue; } $seen[$key] = true; $normalized[] = ['name' => $name, 'url' => $url]; } if ($normalized === [] || ! self::isHome($normalized[0]['name'], $normalized[0]['url'])) { array_unshift($normalized, $home); } return array_values($normalized); } /** * @param mixed $crumb * @return array{name: string, url: string}|null */ private static function mapCrumb(mixed $crumb): ?array { if (is_array($crumb)) { return [ 'name' => trim((string) ($crumb['name'] ?? '')), 'url' => trim((string) ($crumb['url'] ?? '')), ]; } if (is_object($crumb)) { return [ 'name' => trim((string) ($crumb->name ?? '')), 'url' => trim((string) ($crumb->url ?? '')), ]; } return null; } private static function isHome(string $name, string $url): bool { $normalizedUrl = rtrim(strtolower($url), '/'); $homeUrl = rtrim(strtolower(self::absoluteUrl('/')), '/'); return strtolower($name) === 'home' || $normalizedUrl === $homeUrl; } private static function absoluteUrl(string $url): string { $trimmed = trim($url); if ($trimmed === '') { return ''; } if (preg_match('/^https?:\/\//i', $trimmed) === 1) { return $trimmed; } return url($trimmed); } }