Refactorización de Traits a Clases en Laravel

Spread the love

¿Por qué quiero refactorizar un Trait?

La razón mas común para hacer esto, es porque el trait crece o se requiere funcionalidad que ya no puede ser manejada por el trait. También existe la posibilidad de que tomáramos una mala decisión y el trait no era lo que se necesitaba.

Para el ejemplo vamos a suponer un Trait que nos ayuda a relacionar imágenes subidas al servidor con el modelo User.

<?php


namespace App\Traits;


use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;

trait HasAvatar
{

    /**
     * @param Request $request
     * @return bool
     */
    public function avatarWasUploaded(Request $request): bool
    {
        return $request->hasFile('avatar') && $request->file('avatar')->isValid();
    }

    /**
     * @param Request $request
     */
    public function saveAvatarFrom(Request $request): void
    {
        if ($this->avatarWasUploaded($request)) {
            $this->saveAvatar($request->file('avatar'));
        }
    }

    /**
     * @param UploadedFile $file
     */
    public function saveAvatar(UploadedFile $file): void
    {
        $this->avatar = $file->storeAs(
            'avatars',
            $this->defaultName($file)
        );
        $this->save();
    }

    /**
     * @param UploadedFile $file
     * @return string
     */
    public function defaultName(UploadedFile $file): string
    {
        return sprintf('avatar-%s.%s', Str::uuid(), $file->extension());
    }

}

vamos a suponer que decides que no es buena idea tener un trait, ya que con el tiempo vas requerir realizar operaciones de optimización y se requiere que exista una forma para que el trait permita personalizar el nombre que se le asigna a los archivos entre otras cosas, así que es mejor tener todo en una clase.

Vídeo

Ejemplo de refactorización usando un Trait

Que vamos a hacer

Estrategia de refactorización

La idea es usar la técnica de refactorización Extract Class para mover los métodos saveAvatar y defaultName a una clase que llamaremos Image y se harán los cambios necesarios para que el Trait maneje la clase Image sin que cambien la forma en la que se usa el trait.

Antes de comenzar

Recuerda tener pruebas para el Trait y para la clase Image. Para la clase Image puedes escribir nuevos casos para los métodos que se van a mover o usar los casos existentes en el Trait y mover solo los que sean necesario y modificarlos para que puedan ser usadas para probar la clase Image..

Refactorización del Trait.

Crear clase Image

Creamos la clase a donde vamos a mover los métodos de HasAvatar.

namespace App\Services;

class Image
{

}

Crear una relación entre las clases

En HasAvatar vamos a crear una instancia de Image, nota que estoy haciendo esto mediante el helper app(), esto es muy util cuando necesitemos inyectar otras dependencias en el constructor de la clase Image.

    /**
     * @param Request $request
     */
    public function saveAvatarFrom(Request $request): void
    {
        if ($this->avatarWasUploaded($request)) {
            app(Image::class)->saveAvatar($request->file('avatar'), $this);
        }
    }

Mover los métodos y propiedades

Mueve todos los métodos y propiedades (Si aplica) del Trait a la clase Image, en nuestro caso solo moveremos los métodos saveAvatar() y defaultName().

namespace App\Services;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;

class Image
{

    /**
     * @param UploadedFile $file
     * @param $model
     */
    public function saveAvatar(UploadedFile $file, $model): void
    {
        $model->avatar = $file->storeAs(
            'avatars',
            $this->defaultName($file)
        );
        $model->save();
    }

    /**
     * @param UploadedFile $file
     * @return string
     */
    public function defaultName(UploadedFile $file): string
    {
        return sprintf('avatar-%s.%s', Str::uuid(), $file->extension());
    }
}

Después de que muevas los métodos, ajústalos para que trabajen de acuerdo a lo que necesita la clase Image y el Trait.

Nota que ahora la llamada al método saveAvatar acepta como parámetro al propio modelo para que pueda asignar el path del archivo a la propiedad avatar.

Para finalizar nuestro Trait queda de la siguiente forma:

<?php


namespace App\Traits;


use App\Services\Image;
use Illuminate\Http\Request;

trait HasAvatar
{

    /**
     * @param Request $request
     * @return bool
     */
    public function avatarWasUploaded(Request $request): bool
    {
        return $request->hasFile('avatar') && $request->file('avatar')->isValid();
    }

    /**
     * @param Request $request
     */
    public function saveAvatarFrom(Request $request): void
    {
        if ($this->avatarWasUploaded($request)) {
            app(Image::class)->saveAvatar($request->file('avatar'), $this);
        }
    }

}

Que sigue

A partir de aquí el siguiente paso seria refactorizar los nombre de los métodos en la clase Image. y comenzar a ver como puedes extender para proporcionar otras funcionalidades; como mejorar la forma en la que se asigna los nombres, y que puedas elegir en que propiedad se guarda el path del avatar entre otras cosas.

Deja tu comentario y recuerda compartir el articulo.