All Projects → TiendaNube → Php Programming Best Practices

TiendaNube / Php Programming Best Practices

Referencia para los desarrolladores de Tiendanube y para la comunidad de PHP.

Projects that are alternatives of or similar to Php Programming Best Practices

Architecture
.NET 6, ASP.NET Core 6, Entity Framework Core 6, C# 10, Angular 13, Clean Code, SOLID, DDD.
Stars: ✭ 2,285 (+1555.8%)
Mutual labels:  solid, best-practices, clean-architecture, clean-code
Clean Code Dotnet
🛁 Clean Code concepts and tools adapted for .NET
Stars: ✭ 4,425 (+3106.52%)
Mutual labels:  solid, best-practices, clean-architecture, clean-code
Android Modular Architecture
📚 Sample Android Components Architecture on a modular word focused on the scalability, testability and maintainability written in Kotlin, following best practices using Jetpack.
Stars: ✭ 2,048 (+1384.06%)
Mutual labels:  solid, best-practices, clean-architecture, clean-code
Movies Kotlin Kata
Katas for practice Kotlin( Coroutines, dataclasses, delegate properties...) Clean Architecture and best practices in Android(DI, Dagger, MVP, Espresso) implemented by Jorge Sánchez (Xurxodev)
Stars: ✭ 240 (+73.91%)
Mutual labels:  best-practices, clean-architecture, clean-code
Clean Code Java
Clean Code concepts adapted for Java. Based on @ryanmcdermott repository.
Stars: ✭ 155 (+12.32%)
Mutual labels:  best-practices, clean-architecture, clean-code
Clean Code Javascript Tr
JavaScript için Uyarlanmış Temiz Kod Kavramları
Stars: ✭ 232 (+68.12%)
Mutual labels:  best-practices, clean-architecture, clean-code
Run Aspnetcore
A starter kit for your next ASP.NET Core web application. Boilerplate for ASP.NET Core reference application, demonstrating a layered application architecture with applying Clean Architecture and DDD best practices. Download 100+ page eBook PDF from here ->
Stars: ✭ 227 (+64.49%)
Mutual labels:  best-practices, clean-architecture, clean-code
Write Readable Javascript Code
📖 All about writing maintainable JavaScript
Stars: ✭ 244 (+76.81%)
Mutual labels:  best-practices, clean-architecture, clean-code
Coroutines Flows Modularised
Clean Architecture Modular Project: MVVM + Coroutines+ Flows + Dagger2 + LiveData + UnitTests + UITests + MockWebServer
Stars: ✭ 166 (+20.29%)
Mutual labels:  solid, clean-architecture, clean-code
clean architecture typescript example
This repository provides an implementation (or at least an attempt) of Uncle Bob's Clean Architecture with Typescript.
Stars: ✭ 78 (-43.48%)
Mutual labels:  solid, clean-code, clean-architecture
clean-code-javascript
🛁 Clean Code-Konzepte adaptiert für JavaScript.
Stars: ✭ 37 (-73.19%)
Mutual labels:  best-practices, clean-code, clean-architecture
Event Sourcing Castanha
An Event Sourcing service template with DDD, TDD and SOLID. It has High Cohesion and Loose Coupling, it's a good start for your next Microservice application.
Stars: ✭ 68 (-50.72%)
Mutual labels:  solid, clean-architecture, clean-code
Clean Ts Api
API em NodeJs usando Typescript, TDD, Clean Architecture, Design Patterns e SOLID principles
Stars: ✭ 619 (+348.55%)
Mutual labels:  solid, clean-architecture, clean-code
Dotnet New Caju
Learn Clean Architecture with .NET Core 3.0 🔥
Stars: ✭ 228 (+65.22%)
Mutual labels:  solid, clean-architecture, clean-code
Clean Code Typescript
Clean Code concepts adapted for TypeScript
Stars: ✭ 5,075 (+3577.54%)
Mutual labels:  solid, best-practices, clean-code
Clean Code Javascript
🛁 Clean Code concepts adapted for JavaScript
Stars: ✭ 62,912 (+45488.41%)
Mutual labels:  best-practices, clean-architecture, clean-code
Aspnet Core Clean Arch
It is a clean architecture project template which is based on hexagonal-architecture principles built with .Net core.
Stars: ✭ 60 (-56.52%)
Mutual labels:  clean-architecture, clean-code
Go Bank Transfer
Simple API for banking routines using a Clean Architecture in Golang. 💳 💰 💸
Stars: ✭ 123 (-10.87%)
Mutual labels:  clean-architecture, clean-code
Android Mvp Architecture
MVP + Kotlin + Retrofit2 + Dagger2 + Coroutines + Anko + Kotlin-Android-Extensions + RX-java + Mockk + Espresso + Junit5
Stars: ✭ 82 (-40.58%)
Mutual labels:  clean-architecture, clean-code
Flutter pokedex
Pokedex app built with Flutter (with lots of animations) using Clean Architecture
Stars: ✭ 972 (+604.35%)
Mutual labels:  clean-architecture, clean-code

Mejores prácticas en PHP utilizadas en Tiendanube.

Este documento es una referencia para los desarrolladores de Tiendanube y para la comunidad de PHP en general. Tiene como objetivo ayudar y guiar a los mismos a escribir código limpio y de alta calidad.

Tabla de Contenidos

Introducción

Humorous image of software quality estimation as a count of how many expletives you shout when reading code

En esta guía recopilamos varios principios de la Ingeniería de Software introducidos en diferentes libros y experiencias propias que creemos que son buenas prácticas.

No todos los principios deben seguirse estrictamente y puede que no estés de acuerdo con todos ellos, si es así, eres bienvenido a enviar tu Pull Request.

Hay que tener en cuenta que todos de los ejemplos en este artículo funcionan en versiones superiores de PHP 7.0.

Inspirado en clean-code-dotnet.

¿Por qué seguir buenas prácticas?

Como desarrolladores, a veces nos sentimos tentados a escribir código de cierta manera que sea conveniente para terminar rápidamente sin tener en cuenta las buenas prácticas. Posteriormente esto dificulta las revisiones y las pruebas de código. Podríamos decir que estamos codificando y, al hacerlo, hacemos más difícil para otros decodificar nuestro trabajo. Sin embargo, nuestro objetivo como desarrollador debe ser que nuestro código sea reutilizable, legible y mantenible. Y eso algunas veces requiere un esfuerzo extra.

Clean Code

Naming

Evitar usar nombres complicados Un buen nombre permite que los desarrolladores entiendan fácilmente el código. El nombre debe reflejar lo que hace y dar contexto.

Mal:

$ymdstr = $moment->format('y-m-d');

Bien:

$currentDate = $moment->format('y-m-d');

⬆ Volver

Usar notación Camel Case

Usar la notación CamelCase para variables, clases, métodos y funciones.

Mal:

$customerphone = '1134971828';

public function calculate_product_price(int $productid, int $quantity)
{
    // some logic
}

Bien:

$customerPhone = '1134971828';

public function calculateProductPrice(int $productId, int $quantity)
{
    // some logic
}

⬆ Volver

Variables

Evitar valores mágicos

Valores mágicos se refiere a los valores constantes que se especifican directamente dentro del código. Con frecuencia, dichos valores terminan duplicados dentro del sistema y se convierten en una fuente común de errores cuando se realizan cambios.

Mal:

class Payment 
{
    public function isPending()
    {
        if ($this->status === "pending") 
        { 
            // lógica aquí 
        }
    }
}

Bien:

class Payment 
{
    const STATUS_PENDING = 'pending';

    public function isPending()
    {
        if ($this->status === self::STATUS_PENDING) 
        { 
            // lógica aquí 
        }
    }
}

De esta manera solo tenemos que cambiar en un único lugar centralizado.`

⬆ Volver

Usar variables explicativas

No fuerce al lector del código a traducir lo que significa la variable. Explícito es mejor que implícito.

Mal:

$address = 'Avenida Rivadavia 1678, 1406';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{4})$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches[1], $matches[2]);

No tan mal:

$address = 'Avenida Rivadavia 1678, 1406';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{4})$/';
preg_match($cityZipCodeRegex, $address, $matches);

[, $street, $zipCode] = $matches;
saveCityZipCode($street, $zipCode);

Bien: Al utilizar subpatrones es más fácil entender la expresión regular.

$address = 'Avenida Rivadavia 1678, 1406';
$cityZipCodeRegex = '/^[^,]+,\s*(?<street>.+?)\s*(?<zipCode>\d{4})$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches['street'], $matches['zipCode']);

⬆ Volver

Evitar anidación profunda.

Debemos minimizar al máximo los niveles de indentación. Muchas declaraciones if-else anidadas pueden hacer tu código difícil de seguir.

Mal

function isShopOpen($day): bool
{
    if ($day) {
        if (is_string($day)) {
            $day = strtolower($day);
            if ($day === 'friday') {
                return true;
            } elseif ($day === 'saturday') {
                return true;
            } elseif ($day === 'sunday') {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    } else {
        return false;
    }
}

Bien

function isShopOpen(string $day): bool
{
    if (empty($day)) {
        return false;
    }

    $openingDays = [
        'friday', 'saturday', 'sunday'
    ];

    return in_array(strtolower($day), $openingDays, true);
}

⬆ Volver

Evitar utilizar sentencia else.

La sentencia else puede dificultar la lectura del código si la sentencia if contiene demasiado código.

Mal

if ($product->hasStock()) {
    // mucho código...
} else {
    return false;
}

Bien

Para evitar utilizar la sentencia else podemos utilizar Cláusulas de Guarda. Las cláusulas de guarda es una técnica sencilla que consiste en negar la condición de la consulta y eliminar la sentencia else.

if (!$product->hasStock()) {
    return false;
}

// código...

⬆ Volver

No agregar contexto innecesario

Si el nombre de tu clase/objeto te dice algo, no lo repitas en el nombre del atributo.

Mal:

class Product
{
    private $productPrice;
    private $productStock;
    private $productDescription;

    //...
}

Bien:

class Product
{
    private $price;
    private $stock;
    private $description;

    //...
}

⬆ Volver

Métodos y Funciones

Los siguiente consejos aplican tanto a métodos como a funciones.

Declaración de tipos en parámetros y retornos

Desde PHP 7 podemos utilizar la declaración de tipos para los argumentos de entrada y salida de nuestras funciones y métodos.

Mal:

function sum($val1, $val2)
{
    return $val1 + $val2;
}

Bien:

function sum(int $val1, int $val2): int
{
    return $val1 + $val2;
}

⬆ Volver

Argumentos de la función (idealmente no más de 2)

El número ideal de argumentos es cero, después uno (monádico) y después dos (diádico). Siempre debes evitar desarrollar funciones o métodos con tres o más argumentos (poliádico). Más de dos argumentos significa que el método esta haciendo demasiadas cosas. Para los casos en donde se necesiten más de dos argumentos, es mejor utilizar un objeto como argumento.

Mal:

function createProduct(string $title, string $description, float $price, int $stock): void
{
    // ...
}

Bien:

class ProductConfig
{
    private string $title;
    private string $description;
    private float $price;
    private int $stock;

    public function __construct(string $title, string $description, float $price, int $stock) 
    {
        $this->title = $title;
        $this->description = $description;
        $this->price = $price;
        $this->stock = $stock;
    }
}

$config = new ProductConfig('foo', 'bar', 10.0, 100);
createProduct($config);

function createProduct(ProductConfig $config): void
{
    // ...
}

⬆ Volver

Las funciones deben hacer una cosa

Cuando las funciones hacen más de una acción, se vuelven difíciles de razonar y probar. Cuando puedes aislar una función en una sola acción, ellas pueden ser refactorizadas con facilidad y el código será mucho más fácil de leer y reutilizar.

Mal:

function emailClients(array $clients): void
{
    foreach ($clients as $client) {
        $clientRecord = $db->find($client);
        if ($clientRecord->isActive()) {
            email($client);
        }
    }
}

Bien:

function emailClients(array $clients): void
{
    $activeClients = activeClients($clients);
    array_walk($activeClients, 'email');
}

function activeClients(array $clients): array
{
    return array_filter($clients, 'isClientActive');
}

function isClientActive(int $client): bool
{
    $clientRecord = $db->find($client);

    return $clientRecord->isActive();
}

⬆ Volver

Tamaño reducido

La primera regla de las funciones es que deben ser de tamaño reducido. La segunda regla es que deben ser todavía más reducidos. La experiencia cuenta que las funciones con más de 20 líneas de código aproximadamente, esconden más de una acción.

⬆ Volver

Los nombres de las funciones y métodos deben indicar lo que hacen

Mal:

class Email
{
    //...

    public function handle(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}

$message = new Email(...);
// ¿Qué es esto? ¿Un "manejador" para los mensajes? 🤔
$message->handle();

Bien:

class Email 
{
    //...

    public function send(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}

$message = new Email(...);
// Limpio y obvio
$message->send();

⬆ Volver

Las funciones deben tener sólo un nivel de abstracción

Cuando existe más de un nivel de abstracción usualmente es porque la función está haciendo demasiado. Dividirlas en funciones más pequeñas lleva a la reutilización de las mismas y facilita las pruebas.

Mal:

function parseBetterJSAlternative(string $code): void
{
    $regexes = [
        // ...
    ];

    $statements = explode(' ', $code);
    $tokens = [];
    foreach ($regexes as $regex) {
        foreach ($statements as $statement) {
            // ...
        }
    }

    $ast = [];
    foreach ($tokens as $token) {
        // lex...
    }

    foreach ($ast as $node) {
        // convertir...
    }
}

También mal:

Hemos separado algunas de las funcionalidades, pero la función parseBetterJSAlternative() todavía es muy compleja e imposible de probar.

function tokenize(string $code): array
{
    $regexes = [
        // ...
    ];

    $statements = explode(' ', $code);
    $tokens = [];
    foreach ($regexes as $regex) {
        foreach ($statements as $statement) {
            $tokens[] = /* ... */;
        }
    }

    return $tokens;
}

function lexer(array $tokens): array
{
    $ast = [];
    foreach ($tokens as $token) {
        $ast[] = /* ... */;
    }

    return $ast;
}

function parseBetterJSAlternative(string $code): void
{
    $tokens = tokenize($code);
    $ast = lexer($tokens);
    foreach ($ast as $node) {
        // convertir...
    }
}

Bien:

Lo mejor es sacar las dependencias de la función parseBetterJSAlternative().

class Tokenizer
{
    public function tokenize(string $code): array
    {
        $regexes = [
            // ...
        ];

        $statements = explode(' ', $code);
        $tokens = [];
        foreach ($regexes as $regex) {
            foreach ($statements as $statement) {
                $tokens[] = /* ... */;
            }
        }

        return $tokens;
    }
}

class Lexer
{
    public function lexify(array $tokens): array
    {
        $ast = [];
        foreach ($tokens as $token) {
            $ast[] = /* ... */;
        }

        return $ast;
    }
}

class BetterJSAlternative
{
    private $tokenizer;
    private $lexer;

    public function __construct(Tokenizer $tokenizer, Lexer $lexer)
    {
        $this->tokenizer = $tokenizer;
        $this->lexer = $lexer;
    }

    public function parse(string $code): void
    {
        $tokens = $this->tokenizer->tokenize($code);
        $ast = $this->lexer->lexify($tokens);
        foreach ($ast as $node) {
            // convertir...
        }
    }
}

⬆ Volver

No usar flags como parámetros de funciones

Las banderas le dicen al usuario que la función hace más de una cosa. Se debe dividir las funciones si siguen diferentes caminos basados en un valor booleano.

Mal:

function createFile(string $name, bool $temp = false): void
{
    if ($temp) {
        touch('./temp/'.$name);
    } else {
        touch($name);
    }
}

Bien:

function createFile(string $name): void
{
    touch($name);
}

function createTempFile(string $name): void
{
    touch('./temp/'.$name);
}

⬆ Volver

Evitar efectos secundarios

El punto principal es evitar compartir estados entre objetos sin alguna estructura, usar tipos de datos mutables que puedan ser escritos por cualquiera, y no centralizar donde el efectos ocurren.

Mal:

function createUser(string $name, string $email, string $password)
{
    $user = new User($name, $email, $password);
    $user->create();
    $message = new Email($email);
    $message->send();
}

Bien:

function createUser(string $name, string $email, string $password)
{
    $user = new User($name, $email, $password);
    $user->create();
}

function sendEmail($email)
{
    $message = new Email($email);
    $message->send();
}

⬆ Volver

Evitar retornar null

Al retornar null podríamos estar obligando al cliente de la función o método a realizar una validación adicional. Basta con que falte la comprobación de null para que la aplicación pierda el control.

Si siente la tentación de devolver null, pruebe con lanzar una excepción, devolver una estructura vacía o un objeto vacío (Null Object Pattern).

Mal:

function getItems()
{
    //...

    return null;
}

$items = getItems();
if ($items !== null) {
    foreach ($items as $item) {
        // ...
    }
}

Bien:

function getItems()
{
    //...

    return [];
}

$items = getItems();
foreach ($items as $item) {
    // ...
}

⬆ Volver

Condicionales

Evitar condicionales

Al tener declaraciones if dentro de nuestras funciones, estamos diciendo que nuestra función puede hacer más de una cosa.

Una de varias soluciones es utilizar polimorfismo para evitar condicionales.

Mal:

class Airplane
{
    public function getCruisingAltitude(): int
    {
        switch ($this->type) {
            case '777':
                return $this->getMaxAltitude() - $this->getPassengerCount();
            case 'Air Force One':
                return $this->getMaxAltitude();
            case 'Cessna':
                return $this->getMaxAltitude() - $this->getFuelExpenditure();
        }
    }
}

Bien:

interface Airplane
{
    public function getCruisingAltitude(): int;
}

class Boeing777 implements Airplane
{
    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude() - $this->getPassengerCount();
    }
}

class AirForceOne implements Airplane
{
    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude();
    }
}

class Cessna implements Airplane
{
    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude() - $this->getFuelExpenditure();
    }
}

⬆ Volver

Evitar condicionales negativos

Mal:

if (!haveNotPromotions($product))
{
    // ...
}

Bien:

if (havePromotions($product))
{
    // ...
}

⬆ Volver

Evitar verificación de tipo

Hacer verificaciones de tipos da una falsa "seguridad de tipado" además de afectar la legibilidad del código.

Mal:

function travelToTexas($vehicle): void
{
    if ($vehicle instanceof Bicycle) {
        $vehicle->pedalTo(new Location('texas'));
    } elseif ($vehicle instanceof Car) {
        $vehicle->driveTo(new Location('texas'));
    }
}

Bien:

function travelToTexas(Traveler $vehicle): void
{
    $vehicle->travelTo(new Location('texas'));
}

⬆ Volver

Evitar revisión de tipos primitivos

El punto anterior también aplica a los tipos primitivos.

Mal:

function combine($val1, $val2): int
{
    if (!is_numeric($val1) || !is_numeric($val2)) {
        throw new \Exception('Must be of type Number');
    }

    return $val1 + $val2;
}

Bien:

function combine(int $val1, int $val2): int
{
    return $val1 + $val2;
}

⬆ Volver

Encapsular condicionales

Encapsula condiciones dentro de métodos o funciones brinda un unico punto de modificación, mayor cohesión y promueve la reutilización.

Mal:

if ($payment->status === 'pending') {
    // ...
}

Bien:

if ($payment->isPending()) {
    // ...
}

⬆ Volver

Clases y Objetos

Clases finales por defecto

Al momento de pensar y crear una clase debemos partir de la premisa que son clases finales. De esta forma promovemos la idea de que deben funcionar como una unidad del sistema. Permitiendo mayor entendimiento, mantenimiento y reutilización de la clase.

La visibilidad de los objetos se debe ir abriendo según evolucione el código o en casos justificados.

No tan bien:

class Employee
{
    // código...
}

Bien:

final class Employee
{
    // código...
}

⬆ Volver

Propiedades privadas por defecto

La visibilidad de las propiedades debe ser private por defecto. Ya que el objeto no debe revelar nada de si mismo, excepto lo que sea necesario para que otras partes del sistema interactúen con él.

Mal:

final class Product
{
    public float $price = 1000.00;
}

Bien:

final class Product
{
    private float $price = 1000.00;

⬆ Volver

Constructor sobre setters públicos

Siempre debemos esconder los detalles de implementación de los objetos. Para eso, debemos construir los objetos por medio de su constructor y no por setters públicos.

Mal:

final class Product
{
    private string $description;
    private float $price;

    public function setDescription(string $value): void
    {
        $this->description = $value;
    }

    public function setPrice(float $value): void
    {
        $this->price = $value;
    }
}

$product = new Product();
$product->setDescription('Foo');
$product->setPrice(1000.00);

Bien:

final class Product
{
    private string $description;
    private float $price;

    public function __construct(string $description, float $price)
    {
        $this->description = $description;
        $this->price = $price;
    }
}

$product = new Product('Foo', 1000.00);

O también:

Note que los setters son private. Esta forma es útil cuando deseas agregar lógica al momento de establecer una propiedad.

final class Product
{
    private string $description;
    private float $price;

    public function __construct(string $description, float $price)
    {
        $this->setDescription($description);
        $this->setPrice($price);
    }
    
    private function setDescription(string $value): void
    {
        $this->description = $value;
    }

    private function setPrice(float $value): void
    {
        $this->price = $value;
    }   
}

$product = new Product('Foo', 1000.00);

⬆ Volver

Preferir la composición sobre la herencia

Composición sobre herencia es un principio de diseño que permite una mayor flexibilidad. Es más natural construir clases de dominio a partir de varios componentes que tratar de encontrar puntos en común entre ellos y crear un árbol genealógico, como ocurre con la herencia. La composición también proporciona un dominio del negocio más estable a largo plazo, ya que es menos propenso a las peculiaridades que puedan llegar a tener las clases hijas de hijas de hijas...

Mal:

class Vehicle
{    
    public function move()
    {
        echo "Move the car";
    }    
}

class Car extends Vehicle
{
    public function accelarate()
    {    
        $this->move();    
    }
}

$car = new Car();
$car->accelarate();

En este ejemplo existe un acoplamiento muy estrecho entre las clases Vehicle y Car. Si algo cambia en la clase Vehicle, específicamente en el método move(), la clase Car puede romperse fácilmente ya que la superclase Vehicle no tiene idea de para qué la utilizan las clases hijas.

Bien:

interface MoveVehicle
{
    public function move();
}

final class Vehicle implements MoveVehicle
{    
    public function move()
    {
        echo "Move the car";
    }    
}

final class Car
{
    private MoveVehicle $vehicle;

    public function __construct(MoveVehicle $vehicle)
    {
        $this->vehicle = $vehicle;
    }

    public function accelarate()
    {    
        $this->vehicle->move();    
    }
}

$vehicle = new Vehicle();
$car = new Car($vehicle);
$car->accelarate();

Ahora nuestra clase Car se compone de cualquier clase que implemente la interfaz. Esto brinda una gran flexibilidad y rompe con el acoplamiento estrecho que teníamos en la herencia.

⬆ Volver

Principios de Diseño de Software

¿Qué es el Diseño de Software?

Los principios de diseño son un conjunto de diseños y buenas prácticas que se emplean en OOD y OOP (diseño y programación orientada a objetos).

Robert C. Martin dice que la mayoría de nosotros usamos lenguajes orientados a objetos sin saber por qué, y sin saber cómo obtener el beneficio máximo de ellos. Por este motivo es que surgen estos patrones de diseño.

Aplicar estos principios nos ayudaran, entre otras cosas, a crear un código que sea más legible, simple, reusable, escalable y fácil de mantener. Es decir, nos ayudaran a evitar los problemas con los que nos solemos encontrar a medida que nuestro código va creciendo.

Algunos de ellos son:

SOLID

¿Qué es SOLID?

SOLID es un acrónimo donde cada letra representa un principio del diseño orientado a objetos. Los cinco principios están muy relacionados entre si y nos brindan distintas técnicas para crear código de alta calidad.

Principio de responsabilidad única (SRP)

El Principio de Responsabilidad Única (SRP) es la S del Principio SOLID y nos dice que una clase debe tener un único motivo por el cual debe ser modificada.

Mal:

class Page 
{
    private string $title;
    
    public function title(): string 
    {
        return $this->title;
    }
    
    public function formatJson() 
    {
        return json_encode($this->title());
    }
}

Si necesitamos agregar otro tipo de formato debemos modificar la clase para agregar otro método. Eso está bien para una clase tan simple como esta, pero si la clase tuviera más propiedades, el formato sería más complejo de cambiar.

Bien:

class Page 
{
    private string $title;
 
    public function title(): string
    {
        return $this->title;    
    }
}
 
class JsonPageFormatter 
{
    public function format(Page $page)
    {
        return  json_encode($page->title());
    }
}

Hacer esto significa que si quisiéramos crear un formato XML, simplemente podríamos agregar una clase llamada XmlPageFormatter y escribir un código simple para generar XML. Ahora solo tenemos una razón para cambiar la clase Page.

⬆ Volver

Principio abierto/cerrado (OCP)

Las entidades de software (clases, módulos, funciones, etc) deben ser abiertas para ser extendidas, pero cerradas para modificarlas. Este principio establece que puedes permitir a tus colegas desarrolladores agregar nuevas funcionalidades pero sin cambiar el código existente.

Parece complicado pero gracias a las interfaces y abstracciones se convierte en algo muy simple.

Mal:

class Customer
{
    public function pay(float $total, CreditCardPayment $paymentMethod)
    {
        $paymentMethod->execute($total);
    }
}

class CreditCardPayment
{
    public function execute(float $total)
    {
        // lógica para pagar con tarjeta de crédito...
    }
}

$customer = new Customer();
$customer->pay(1000.00, new CreditCardPayment());

De esta forma, el cliente solamente puede pagar con tarjeta de crédito. Si quisiéramos agregar un nuevo método de pago deberíamos modificar el método pay().

Bien:

interface PaymentMethod
{
    public function execute(float $total);
}

class Customer
{
    public function pay(float $total, PaymentMethod $paymentMethod)
    {
        $paymentMethod->execute($total);
    }
}

class CreditCardPayment implements PaymentMethod
{
    public function execute(float $total)
    {
        // lógica para pagar con tarjeta de crédito...
    }
}

class CashPayment implements PaymentMethod
{
    public function execute(float $total)
    {
        // lógica para pagar con dinero...
    }
}

$customer = new Customer();
$customer->pay(1000.00, new CreditCardPayment());
// o
$customer->pay(1000.00, new CashPayment());

Gracias a la interfaz PaymentMethod, el método pay() quedo cerrado para modificaciones pero abierto a nuevas formas de pago. ⬆ Volver

Principio de sustitución de Liskov (LSP)

El principio de Liskov establece que, dada una clase padre y una clase hija, pueden intercambiarse sin obtener resultados incorrectos.

Mal:

class Rectangle
{
    // ...

    public function setWidth(int $width): void
    {
        $this->width = $width;
    }

    public function setHeight(int $height): void
    {
        $this->height = $height;
    }

    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}

class Square extends Rectangle
{
    //... 

    public function setWidth(int $width): void
    {
        $this->width = $this->height = $width;
    }

    public function setHeight(int $height): void
    {
        $this->width = $this->height = $height;
    }
}

Debido a que Square es diferente a Rectangle, se necesita anular parte del código para permitir que un cuadrado exista correctamente.

Anular mucho código en clases para adaptarse a situaciones específicas puede provocar problemas de mantenimiento.

Bien:

interface Quadrilateral {
  public function setHeight(int $height);
 
  public function setWidth(int $width);
 
  public function getArea();
}
 
class Rectangle implements Quadrilateral
{
    // ...
}
 
class Square implements Quadrilateral
{
    // ...
}

La conclusión aquí es que si se anulando gran parte del código, entonces tal vez la arquitectura no sea la correcta y se debería pensar en el principio de sustitución de Liskov.

⬆ Volver

Principio de la segregación de la interfaz (ISP)

El principio de segregación de la interfaz dice que un cliente solo debe conocer los métodos que van a utilizar y no aquellos que no utilizará.

Mal:

interface Worker 
{
  public function takeBreak();
  public function code();
  public function callToClient();
  public function attendMeetings();
  public function getPaid();
}

class Manager implements Worker 
{
    // ...

    public function code() 
    {
        return false;
    }
}

class Developer implements Worker 
{
    // ...

    public function callToClient() 
    {
        echo "I'll ask my manager.";
    }
}

En el ejemplo anterior, la interfaz Worker abarca demasiadas funcionalidades obligando a implementar el método callToClient() en la clase Developer o devolver falso en el método code() para Manager ya que los managers no desarrollan.

Bien:

Al aplicar ISP las interfaces quedan divididas en varias especificas.

interface Worker 
{
    public function takeBreak();
    public function getPaid();
}
 
interface Coder 
{
    public function code();
}
 
interface ClientFacer 
{
    public function callToClient();
    public function attendMeetings();
}

class Developer implements Worker, Coder 
{
    // ...
}
 
class Manager implements Worker, ClientFacer 
{
    // ...
}

⬆ Volver

Principio de la inversión de dependencias (DIP)

El principio de la inversión de dependencias consiste en que las clases no deben depender de clases concretas, sino que deben depender de abstracciones.

Mal:

class MySqlConnection 
{
    public function connect() {}
}
 
class PageLoader 
{
    private MySqlConnection $dbConnection;

    public function __construct(MySqlConnection $dbConnection) 
    {
        $this->dbConnection = $dbConnection;
    }
}

Bien:

interface DbConnectionInterface 
{
    public function connect();
} 
 
class MySqlConnection implements DbConnectionInterface 
{
    public function connect() {}
}
 
class PageLoader 
{
    private DbConnectionInterface $dbConnection;

    public function __construct(DbConnectionInterface $dbConnection) 
    {
        $this->dbConnection = $dbConnection;
    }
}

Otro ejemplo de este principio lo vimos en la sección de [Composición sobre Herencia]

⬆ Volver

Principio DRY

Principio DRY: Don’t Repeat Yourself

Según este principio toda pieza de información nunca debería ser duplicada debido a que la duplicación incrementa la dificultad en los cambios y evolución posterior, puede perjudicar la claridad y crear un espacio para posibles inconsistencias.

A menudo se tiene código duplicado porque existen dos o más cosas ligeramente diferentes que comparten mucho en común, pero sus diferencias fuerzan a tener dos o más funciones separadas haciendo mas de lo mismo. Remover código duplicado significa crear una abstracción que puedan manejar diferentes conjuntos de cosas en una función/método/clase.

Mal:

function showDeveloperList(array $developers): void
{
    foreach ($developers as $developer) {
        $expectedSalary = $developer->calculateExpectedSalary();
        $experience = $developer->getExperience();
        $githubLink = $developer->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}

function showManagerList(array $managers): void
{
    foreach ($managers as $manager) {
        $expectedSalary = $manager->calculateExpectedSalary();
        $experience = $manager->getExperience();
        $githubLink = $manager->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}

Bien:

function showList(array $employees): void
{
    foreach ($employees as $employee) {
        $expectedSalary = $employee->calculateExpectedSalary();
        $experience = $employee->getExperience();
        $githubLink = $employee->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}

Mejor:

Mejor aún si se eliminan variables temporales.

function showList(array $employees): void
{
    foreach ($employees as $employee) {
        render([
            $employee->calculateExpectedSalary(),
            $employee->getExperience(),
            $employee->getGithubLink()
        ]);
    }
}

⬆ Volver

Principio Tell don't ask

¿Qué es el principio Tell don't ask?

El principio Tell Don’t Ask dice que no se debe preguntar a un objeto sobre su estado para luego realizar una acción. Por el contrario, debe realizar la acción directamente.

Mal:

$order = new Order();
if ($order->hasFreeShipping()) {
    $order->shippingCost = 0;
}

Bien:

$order = new Order();
$order->updateShippingPrice();

class Order
{
    public function updateShippingPrice()
    {
        if ($this->hasFreeShipping()) {
            $this->shippingCost = 0;
        }

        // ...
    }
}

⬆ Volver

Testing

WIP

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].