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.
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
Web Developer
Inicia la conversación
Hazte miembro de Antonio Jenaro para comenzar a comentar.
Regístrate ahora¿Ya estás registrado? Inicia sesión