*/ private const COLORS = [ '#F97316', '#22C55E', '#06B6D4', '#EC4899', '#8B5CF6', '#EAB308', '#EF4444', '#14B8A6', ]; public function generate(User $user): User { $oldPath = $user->avatar_path; $color = $this->colorFor($user); $path = sprintf('avatars/generated/%d-%s.svg', $user->id, (string) Str::uuid()); $stored = Storage::disk('public')->put( $path, $this->svg($this->initials($user->name), $color), ); if (! $stored) { throw new RuntimeException('Generated avatar could not be stored.'); } $user->forceFill([ 'avatar_path' => $path, 'avatar_color' => $color, 'avatar_is_generated' => true, ])->saveQuietly(); $this->deletePath($oldPath); return $user->refresh(); } public function upload(User $user, UploadedFile $avatar): User { $oldPath = $user->avatar_path; $path = $avatar->store('avatars/uploads', 'public'); if (! is_string($path)) { throw new RuntimeException('Uploaded avatar could not be stored.'); } $user->forceFill([ 'avatar_path' => $path, 'avatar_color' => $this->colorFor($user), 'avatar_is_generated' => false, ])->saveQuietly(); $this->deletePath($oldPath); return $user->refresh(); } public function delete(User $user): void { $this->deletePath($user->avatar_path); } private function colorFor(User $user): string { if (is_string($user->avatar_color) && in_array($user->avatar_color, self::COLORS, true)) { return $user->avatar_color; } return self::COLORS[random_int(0, count(self::COLORS) - 1)]; } private function deletePath(?string $path): void { if (blank($path)) { return; } Storage::disk('public')->delete($path); } private function initials(string $name): string { $parts = preg_split('/\s+/u', trim($name)) ?: []; if ($parts === []) { return '?'; } $first = mb_substr($parts[0], 0, 1); $last = count($parts) > 1 ? mb_substr($parts[count($parts) - 1], 0, 1) : ''; return mb_strtoupper($first.$last); } private function svg(string $initials, string $color): string { $escapedInitials = htmlspecialchars($initials, ENT_QUOTES | ENT_XML1, 'UTF-8'); return << {$escapedInitials} SVG; } }