Hazme sentir como en casa con avatares
Una de las mejores formas de personalizar una aplicación es permitir que los usuarios suban avatares o fotos de perfil. Esto dará una sensación más humana a la interfaz.
Veamos cómo lo he implementado en una aplicación Laravel para permitir que los usuarios suban fotos de perfil y añadan el avatar en la interfaz.
Añadimos un nuevo método updateAvatar
que acepta el avatar que el usuario ha subido desde un input de archivo, y almacenamos el path
del archivo de manera pública con el prefijo avatars
.
use Illuminate\Http\UploadedFile;
class User extends Authenticatable
{
// ...
public function updateAvatar(UploadedFile $avatar, $storagePath = 'avatars')
{
$this->forceFill([
'avatar_path' => $avatar->storePublicly(
$storagePath, ['disk' => 'public']
),
])->save();
}
}
Con esto, almacenamos el avatar en el disco public
, pero una mejor práctica es extraer el disco para avatares y crear un nuevo método que obtenga el disco desde la configuración de la aplicación.
class User extends Authenticatable
{
protected function avatarDisk()
{
return config('app.avatar_disk', 'public');
}
}
Así podemos cambiar el disco sin modificar el modelo User
.
class User extends Authenticatable
{
// ...
public function updateAvatar(UploadedFile $avatar, $storagePath = 'avatars')
{
$this->forceFill([
'avatar_path' => $avatar->storePublicly(
$storagePath, ['disk' => $this->avatarDisk()]
),
])->save();
}
}
Es posible que el usuario ya haya subido una foto de perfil anteriormente, por lo que debemos verificar si ya tenemos un path
del avatar y eliminarlo cuando se suba una nueva foto.
class User extends Authenticatable
{
// ...
public function updateAvatar(UploadedFile $avatar)
{
tap($this->avatar_path, function ($previous) use ($avatar, $storagePath) {
$this->forceFill([
'avatar_path' => $avatar->storePublicly(
$storagePath, ['disk' => $this->avatarDisk()]
),
])->save();
if ($previous) {
Storage::disk($this->avatarDisk())->delete($previous);
}
});
}
}
También necesitamos una API para eliminar el avatar. Crearemos un nuevo método deleteAvatar
que verificará si tenemos un avatar_path
y lo eliminará del disco antes de establecer el path
del avatar a nulo para el usuario.
class User extends Authenticatable
{
// ...
public function deleteAvatar()
{
if (is_null($this->avatar_path)) {
return;
}
Storage::disk($this->avatarDisk())->delete($this->avatar_path);
$this->forceFill([
'avatar_path' => null,
])->save();
}
}
Ya tenemos el avatar y el path
almacenado, pero ahora necesitamos obtener la URL del avatar para mostrarla públicamente en la interfaz. Esto se hará usando un accesor.
use Illuminate\Database\Eloquent\Casts\Attribute;
class User extends Authenticatable
{
// ...
protected function avatarUrl(): Attribute
{
return Attribute::get(function (): string {
return $this->avatar_path
? Storage::disk($this->avatarDisk())->url($this->avatar_path)
: null;
});
}
}
Si existe el path
, obtenemos la URL; si no, devolvemos nulo. Sin embargo, una mejor alternativa sería mostrar un avatar por defecto si no se ha subido uno. Utilizaremos el servicio UI Avatars para generar un avatar por defecto basado en el nombre del usuario, obteniendo las iniciales de su nombre completo.
use Illuminate\Database\Eloquent\Casts\Attribute;
class User extends Authenticatable
{
// ...
protected function defaultAvatarUrl()
{
$name = trim(collect(explode(' ', $this->name))->map(function ($segment) {
return mb_substr($segment, 0, 1);
})->join(' '));
return 'https://ui-avatars.com/api/?name='.urlencode($name).'&color=7F9CF5&background=EBF4FF';
}
}
Ahora actualizamos el método avatarUrl
para incluir el avatar por defecto.
use Illuminate\Database\Eloquent\Casts\Attribute;
class User extends Authenticatable
{
// ...
protected function avatarUrl(): Attribute
{
return Attribute::get(function (): string {
return $this->avatar_path
? Storage::disk($this->avatarDisk())->url($this->avatar_path)
: $this->defaultAvatarUrl();
});
}
}
Con esto, concluimos nuestra API para fotos de perfil. Opcionalmente, podríamos crear un trait
llamado HasAvatar
para utilizarlo en otros modelos, como por ejemplo en un modelo Team
.