Principios SOLID en Laravel

  • Publicado el 15 agosto, 2024
  • Palabras: 413

En el desarrollo de software, SOLID (Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion) es un acrónimo mnemónico introducido por Robert C. Martin​ a comienzos de la década del 2000​ que representa cinco principios básicos de la programación orientada a objetos y el diseño.

Principios SOLID en Laravel

Vamos a repasar estos principio aplicados a Laravel:

 

#Single-Responsibility Principle

 

Cada clase debería tener solo una razón para cambiar

 

Este es un mal ejemplo moderno de cómo mezclar los datos y la capa de representación en una clase.

 

class UserResource extends JsonResource
{
	public function toArray($request)
	{
		$mostPopularPosts = $user->posts()
			->where('like_count', '>', 50)
			->where('share_count', '>', 25)
			->orderByDesc('visits')
			->take(10)
			->get();
		
		return [
			'id' => $this->id,
			'full_name' => $this->full_name, 
			'most_popular_posts' => $mostPopularPosts,
		];
	}
}

 

Y uno bueno

 

class UserResource extends JsonResource
{
	public function toArray($request)
	{
		return [
			'id' => $this->id,
			'full_name' => $this->full_name, 
			'most_popular_posts' => $this->when(
					$request->most_popular_posts,
					GetMostPopularPosts::execute($user), 
			),
		];
	}
}

class GetMostPopularPosts 
{
	/**
	* @return Collection<Post> 
	*/
	public static function execute(User $user): Collection {
		return $user->posts()
			->where('like_count', '>', 50)
			->where('share_count', '>', 25)
			->orderByDesc('visits')
			->take(10)
			->get();
	} 
}

 

Ahora tenemos dos clases bien definidas:

  • UserResource es responsable únicamente de la representación y tiene un motivo para cambiar.
  • GetMostPopularPosts es responsable únicamente de la consulta y tiene un motivo para cambiar.

 

#Open-Closed Principle

 

Una clase debe estar abierta a la extensión pero cerrada a la modificación.

 

trait Likeable 
{
	public function like(): void 
	{
		//
	}
	
	public function dislike(): void 
	{
		//
	}
}

class Post extends Model 
{
	use Likeable; 
}

class Comment extends Model 
{
	use Likeable; 
}

 

Esto es bastante típico, ¿verdad? Pero piense en lo que sucedió aquí.

¡Simplemente agregamos nueva funcionalidad a varias clases sin cambiarlas! Extendimos nuestras clases en lugar de modificarlas

 

#Liskov Substitution Principle


Cada clase base puede ser reemplazada por sus subclases

 

abstract class EmailProvider 
{
	abstract public function addSubscriber(User $user): array;
}
	
class MailChimp extends EmailProvider 
{
	public function addSubscriber(User $user): array 
	{ 
		// Using MailChimp API
	}
}

class ConvertKit extends EmailProvider 
{
	public function addSubscriber(User $user): array 
	{
		// Using ConvertKit API
	}
}

 

Tenemos un EmailProvider abstracto y, por algún motivo, utilizamos MailChimp y ConvertKit. Estas clases deberían comportarse exactamente de la misma manera, pase lo que pase.

 

class AuthController 
{
	public function register( RegisterRequest $request, MailChimp $emailProvider){}
}

class AuthController 
{
	public function register( RegisterRequest $request, ConvertKit $emailProvider){}
}

 

#Interface Segregation Principle

 

Deberías tener muchas interfaces pequeñas en lugar de unas pocas enormes

 

class TextInput extends Field implements CanHaveNumericState, ContractsCanBeLengthConstrained, ContractsHasAffixActions
{
    use ConcernsCanBeAutocapitalized;
    use ConcernsCanBeAutocompleted;
    use ConcernsCanBeLengthConstrained;
    use ConcernsCanBeReadOnly;
    use ConcernsHasAffixes;
    use ConcernsHasDatalistOptions;
    use ConcernsHasExtraInputAttributes;
    use ConcernsHasInputMode;
    use ConcernsHasPlaceholder;	
}

 

Cada uno de esos rasgos tiene una interfaz bastante pequeña y bien definida y agrega una pequeña parte de funcionalidad a la clase.

 

#Dependency Inversion Principle

 

Depende de abstracciones, no de concreciones.

 

interface MarketDataProvider 
{
	public function getPrice(string $ticker): float; 
}

class IexCloud implements MarketDataProvider 
{
	public function getPrice(string $ticker): float 
	{
		// Using IEX API
	} 
}

class Finnhub implements MarketDataProvider 
{
	public function getPrice(string $ticker): float 
	{
		// Using Finnhub API
	} 
}

class CompanyController 
{
	public function show(Company $company, MarketDataProvider $marketDataProvider)
	{
		$price = $marketDataProvider->getPrice();
		return view('company.show', compact('company', 'price')); 
	}
}

 

Por lo tanto, cada clase debería depender del MarketDataProvider abstracto, no de la implementación concreta.

Antonio Jenaro

Antonio Jenaro

Web Developer

Archivado en:

Inicia la conversación

Hazte miembro de Antonio Jenaro para comenzar a comentar.

Regístrate ahora

¿Ya estás registrado? Inicia sesión