Mejores prácticas para el desarrollo moderno de PHP

0
2705

1 configuración y configuración

1.1 Mantener actualizado

Llamemos a esto desde el principio: una cantidad deprimentemente pequeña de instalaciones de PHP en la actualidad son actuales o se mantienen actuales. Ya sea debido a las restricciones de alojamiento compartido, los valores predeterminados que nadie piensa cambiar, o no hay tiempo / presupuesto para las pruebas de actualización, los binarios humildes de PHP tienden a quedarse atrás. Entonces, una clara práctica recomendada que necesita más énfasis es usar siempre una versión actual de PHP (5.6.x a partir de este artículo). Además, también es importante programar actualizaciones periódicas tanto de PHP como de cualquier extensión o biblioteca de proveedores que esté utilizando. Las actualizaciones le brindan nuevas funciones de idioma, mayor velocidad, menor uso de memoria y actualizaciones de seguridad. Cuanto más frecuentemente se actualice, menos doloroso será el proceso.

1.2 Establecer valores predeterminados sensibles

PHP hace un trabajo decente de establecer buenos valores predeterminados fuera de la caja con sus archivos php.ini.development y php.ini.production , pero podemos hacerlo mejor. Por un lado, no establecen una fecha / zona horaria para nosotros.Eso tiene sentido desde una perspectiva de distribución, pero sin una, PHP lanzará un error E_WARNING cada vez que llamemos una función relacionada con la fecha / hora. Aquí hay algunas configuraciones recomendadas:

  • date.timezone – elige de la lista de zonas horarias admitidas
  • session.save_path: si estamos usando archivos para sesiones y no otro controlador de guardado, configúrelo en algo fuera de / tmp . Dejar esto como / tmp puede ser arriesgado en un entorno de alojamiento compartido, ya que / tmp suele tener los permisos abiertos. Incluso con el conjunto de bitios pegajosos, cualquier persona con acceso para enumerar los contenidos de este directorio puede aprender todos sus ID de sesión activos.
  • session.cookie_secure: no se lo crea, active esta opción si está proporcionando su código PHP a través de HTTPS.
  • session.cookie_httponly: configúrelo para evitar que las cookies de sesión de PHP sean accesibles a través de JavaScript
  • Más … use una herramienta como iniscan para probar su configuración en busca de vulnerabilidades comunes

1.3 Extensiones

También es una buena idea desactivar (o al menos no habilitar) las extensiones que no utilizará, como los controladores de base de datos. Para ver qué está habilitado, ejecute el phpinfo()comando o vaya a una línea de comandos y ejecute esto.

$ php -i

La información es la misma, pero phpinfo () tiene formato HTML agregado. Sin embargo, la versión CLI es más fácil de canalizar a grep para encontrar información específica. Ex.

$ php -i | grep error_log

Sin embargo, hay una advertencia sobre este método: es posible que se apliquen diferentes configuraciones de PHP a la versión web y la versión CLI.

2 usar compositor

Esto puede ser una sorpresa, pero una de las mejores prácticas para escribir PHP moderno es escribir menos. Si bien es cierto que una de las mejores maneras de mejorar la programación es hacerlo, hay una gran cantidad de problemas que ya se han resuelto en el espacio de PHP, como enrutamiento, bibliotecas básicas de validación de entrada, conversión de unidades, abstracción de bases de datos. capas, etc … Solo ve a Packagist y navega alrededor. Probablemente encontrará que partes importantes del problema que está tratando de resolver ya se han escrito y probado.

Si bien es tentador escribir todo el código usted mismo (y no hay nada de malo en escribir su propio marco o biblioteca como experiencia de aprendizaje), debe luchar contra esos sentimientos de No inventado aquí y ahorrarse mucho tiempo y dolor de cabeza. Sigue la doctrina de PIE en su lugar: orgullosamente inventada en otro lugar.Además, si elige escribir lo que quiera, no lo libere a menos que haga algo significativamente diferente o mejor que las ofertas existentes.

Composer es un gestor de paquetes para PHP, similar a pip en Python, gema en Ruby y npm en Node. Le permite definir un archivo JSON que enumera las dependencias de su código e intentará resolver esos requisitos para usted mediante la descarga e instalación de los paquetes de códigos necesarios.

2.1 Instalando Composer

Asumimos que este es un proyecto local, así que instalemos una instancia de Composer solo para el proyecto actual. Navegue a su directorio de proyecto y ejecute esto:

$ curl -sS https://getcomposer.org/installer | php

Tenga en cuenta que canalizar cualquier descarga directamente a un intérprete de script (sh, ruby, php, etc.) es un riesgo para la seguridad, así que lea el código de instalación y asegúrese de estar cómodo con él antes de ejecutar cualquier comando como este.

Para mayor comodidad (si prefiere escribir composer installsobre php composer.phar install, puede usar este comando para instalar una única copia del compositor globalmente:

$ mv composer.phar /usr/local/bin/composer
$ chmod +x composer

Es posible que tenga que ejecutarlos en sudofunción de sus permisos de archivo.

2.2 Usando Composer

Composer tiene dos categorías principales de dependencias que puede gestionar: “require” y “require-dev”. Las dependencias listadas como “requeridas” se instalan en todas partes, pero las dependencias “require-dev” solo se instalan cuando se solicitan específicamente. Por lo general, estas son herramientas para cuando el código está en desarrollo activo, como PHP_CodeSniffer . La siguiente línea muestra un ejemplo de cómo instalar Guzzle , una popular biblioteca HTTP.

$ php composer.phar require guzzle/guzzle

Para instalar una herramienta solo para propósitos de desarrollo, agregue la --devbandera:

$ php composer.phar require --dev 'sebastian/phpcpd'

Esto instala el Detector de copiar y pegar de PHP , otra herramienta de calidad de código como una dependencia de solo desarrollo.

2.3 Instalar vs actualizar

La primera vez composer installque lo ejecutemos, instalaremos las bibliotecas y las dependencias que necesitemos, según el archivo composer.json . Cuando se hace eso, el compositor crea un archivo de bloqueo, predeciblemente llamadocomposer.lock . Este archivo contiene una lista de las dependencias compuestas por nosotros y sus versiones exactas, con hashes. Luego, en cualquier momento futuro que ejecutemos composer install, se buscará en el archivo de bloqueo e instalará las versiones exactas.

composer updateEs un poco de una bestia diferente. Ignorará el archivocomposer.lock (si está presente) e intentará encontrar las versiones más actualizadas de cada una de las dependencias que aún satisfacen las restricciones encomposer.json . A continuación, escribe un nuevo archivo composer.lock cuando está terminado.

2.4 Autocarga

Tanto la instalación del compositor como la actualización del compositor generarán un autocargador que le indicará a PHP dónde encontrar todos los archivos necesarios para usar las bibliotecas que acabamos de instalar. Para usarlo, simplemente agregue esta línea (generalmente a un archivo de rutina de carga que se ejecuta en cada solicitud):

require 'vendor/autoload.php';

3 Seguir los buenos principios de diseño.

3.1 SOLID

SOLID es un mnemotécnico para recordarnos cinco principios clave en un buen diseño de software orientado a objetos.

3.1.1 S – Principio de responsabilidad única

Esto establece que las clases solo deben tener una responsabilidad, o dicho de otra manera, solo deben tener una única razón para cambiar. Esto encaja muy bien con la filosofía de Unix de muchas herramientas pequeñas, haciendo una cosa bien. Las clases que solo hacen una cosa son mucho más fáciles de probar y depurar, y es menos probable que te sorprendan. No desea una llamada de método a una clase Validator que actualice los registros de la base de datos. Aquí hay un ejemplo de una infracción de SRP, las características que normalmente verías en una aplicación basada en el patrón ActiveRecord .

class Person extends Model
{
    public $name;
    public $birthDate;
    protected $preferences;

    public function getPreferences() {}

    public function save() {}
}

Así que este es un modelo de entidad bastante básico . Sin embargo, una de estas cosas no pertenece aquí. La única responsabilidad de un modelo de entidad debe ser el comportamiento relacionado con la entidad que representa, no debe ser responsable de persistir a sí mismo.

class Person extends Model
{
    public $name;
    public $birthDate;
    protected $preferences;

    public function getPreferences() {}
}

class DataStore
{
    public function save(Model $model) {}
}

Este es mejor. El modelo de Persona ha vuelto a hacer solo una cosa, y el comportamiento de guardado se ha movido a un objeto de persistencia en su lugar.Tenga en cuenta también que sólo escribo insinuado en Modelo, no Persona.Volveremos a eso cuando lleguemos a las partes L y D de SOLID.

3.1.2 O – Principio Abierto Abierto

Hay una prueba impresionante para esto que resume bastante bien de qué se trata este principio: piense en una característica para implementar, probablemente la más reciente en la que trabajó o en la que está trabajando. ¿Puede implementar esa característica SOLAMENTE en su base de código existente agregando nuevas clases y no cambiando las clases existentes en su sistema? Su configuración y el código de cableado reciben un poco de aprobación, pero en la mayoría de los sistemas esto es sorprendentemente difícil. Tienes que confiar mucho en el envío polimórfico y la mayoría de las bases de código simplemente no están configuradas para eso. Si estás interesado en eso, hay una buena charla de Google en YouTube sobre elpolimorfismo y la escritura de código sin Ifs que indaga en ello. Como beneficio adicional, la charla es dada por Miško Hevery , a quien muchos conocen como el creador de AngularJs .

3.1.3 L – Principio de sustitución de Liskov

Este principio lleva el nombre de Barbara Liskov y se imprime a continuación:

“Los objetos en un programa deben ser reemplazables con instancias de sus subtipos sin alterar la corrección de ese programa”.

Todo eso suena bien, pero se ilustra más claramente con un ejemplo.

abstract class Shape 
{
    public function getHeight();

    public function setHeight($height);

    public function getLength();

    public function setLength($length);
}

Esto va a representar nuestra forma básica de cuatro lados. Nada de lujos aquí.

class Square extends Shape
{
    protected $size;

    public function getHeight() {
        return $this->size;
    }

    public function setHeight($height) {
        $this->size = $height;
    }

    public function getLength() {
        return $this->size;
    }

    public function setLength($length) {
        $this->size = $length;
    }
}

Aquí está nuestra primera forma, la Plaza. Forma bastante sencilla, ¿verdad? Puede suponer que hay un constructor donde establecemos las dimensiones, pero como se ve aquí desde esta implementación, la longitud y la altura siempre serán las mismas.Los cuadrados son así.

class Rectangle extends Shape
{
    protected $height;
    protected $length;

    public function getHeight() {
        return $this->height;
    }

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

    public function getLength() {
        return $this->length;
    }

    public function setLength($length) {
        $this->length = $length;
    }
}

Así que aquí tenemos una forma diferente. Todavía tiene las mismas firmas de métodos, sigue siendo una forma de cuatro lados, pero ¿y si empezamos a tratar de usarlas en lugar de otras? De repente, si cambiamos la altura de nuestra Forma, ya no podemos asumir que la longitud de nuestra forma coincidirá. Hemos violado el contrato que teníamos con el usuario cuando le dimos nuestra forma cuadrada.

Este es un ejemplo de libro de texto de una violación del LSP y necesitamos este tipo de principio para hacer el mejor uso de un sistema de tipos. Incluso el tipeo de patono nos dirá si el comportamiento subyacente es diferente, y dado que no podemos saber que sin verlo se rompa, es mejor asegurarse de que no sea diferente en primer lugar.

3.1.3 I – Principio de segregación de interfaz

Este principio dice que favorece muchas interfaces pequeñas y de grano fino frente a una grande. Las interfaces deben basarse en el comportamiento en lugar de “es una de estas clases”. Piense en las interfaces que vienen con PHP. Transversal, Contable, Serializable, cosas así. Anuncian las capacidades que posee el objeto, no de lo que hereda. Así que mantén tus interfaces pequeñas. No quieres que una interfaz tenga 30 métodos, 3 es un objetivo mucho mejor.

3.1.4 D – Principio de inversión de dependencia

Probablemente haya escuchado sobre esto en otros lugares que hablaron sobre lainyección de dependencia , pero la inversión de dependencia y la inyección de dependencia no son exactamente lo mismo. La inversión de dependencia es en realidad una forma de decir que debe depender de las abstracciones de su sistema y no de sus detalles. Ahora, ¿qué significa eso para usted en el día a día?

No use directamente mysqli_query () en todo su código, use algo como DataStore-> query () en su lugar.

El núcleo de este principio es en realidad acerca de las abstracciones. Se trata más de decir “usar un adaptador de base de datos” en lugar de depender de llamadas directas a cosas como mysqli_query. Si está usando mysqli_query directamente en la mitad de sus clases, entonces está enlazando todo directamente a su base de datos.No hay nada a favor o en contra de MySQL aquí, pero si está usando mysqli_query, ese tipo de detalle de bajo nivel debe ocultarse en un solo lugar y luego esa funcionalidad debe exponerse a través de un contenedor genérico.

Ahora sé que este es un tipo de ejemplo trillado si lo piensa, porque la cantidad de veces que realmente cambiará completamente su motor de base de datos después de que su producto esté en producción es muy, muy bajo. Lo escogí porque pensé que la gente estaría familiarizada con la idea a partir de su propio código. Además, incluso si tiene una base de datos con la que sabe que se está quedando, ese objeto contenedor abstracto le permite corregir errores, cambiar el comportamiento o implementar características que desearía que tuviera la base de datos elegida.También hace posible la prueba unitaria donde las llamadas de bajo nivel no lo harían.

4 objetos de calistenia

Esto no es una inmersión completa en estos principios, pero los dos primeros son fáciles de recordar, ofrecen un buen valor y pueden aplicarse inmediatamente a casi cualquier base de código.

4.1 No más de un nivel de sangría por método.

Esta es una forma útil de pensar acerca de los métodos de descomposición en trozos más pequeños, dejándole con un código más claro y más autodocumentado. Cuantos más niveles de sangrado tenga, más hará el método y más estado tendrá para realizar un seguimiento mental mientras trabaja con él.

De inmediato, sé que la gente se opondrá a esto, pero esto es solo una pauta / heurística, no una regla dura y rápida. No espero que nadie aplique las reglas de PHP_CodeSniffer para esto (aunque la gente lo tenga ).

Veamos una muestra rápida de cómo podría verse esto:

public function transformToCsv($data)
{
    $csvLines = array();
    $csvLines[] = implode(',', array_keys($data[0]));
    foreach ($data as $row) {
        if (!$row) {
            continue;
        }
        $csvLines[] = implode(',', $row);
    }

    return $csvLines;
}

Si bien este no es un código terrible (es técnicamente correcto, verificable, etc.), podemos hacer mucho más para aclararlo. ¿Cómo reduciríamos los niveles de anidación aquí?

Sabemos que necesitamos simplificar enormemente el contenido del bucle foreach (o eliminarlo por completo), así que comencemos allí.

if (!$row) {
    continue;
}

Este primer bit es fácil. Todo lo que está haciendo es ignorar filas vacías. Podemos abreviar todo este proceso utilizando una función PHP incorporada antes de que lleguemos al bucle.

$data = array_filter($data);
foreach ($data as $row) {
    $csvLines[] = implode(',', $row);
}

Ahora tenemos nuestro único nivel de anidación. Pero viendo esto, todo lo que estamos haciendo es aplicar una función a cada elemento en una matriz. Ni siquiera necesitamos el bucle foreach para hacer eso.

$data = array_filter($data);
$csvLines =+ array_map('implode', $data, array_fill(0, count($data), ',');

Ahora no tenemos ningún anidamiento, y es probable que el código sea más rápido, ya que estamos haciendo todo el bucle con funciones C nativas en lugar de PHP. Sinimplodeembargo, tenemos que involucrarnos en un poco de artimañas para pasar la coma , por lo que podría argumentar que detenerse en el paso anterior es mucho más comprensible.

4.2 Intenta no usar else

Esto realmente trata con dos ideas principales. El primero es múltiples declaraciones de retorno de un método. Si tiene suficiente información, tome una decisión sobre el resultado del método, tome la decisión y regrese. La segunda es una idea conocida como cláusulas de guardia . Estas son básicamente verificaciones de validación combinadas con devoluciones anticipadas, generalmente cerca de la parte superior de un método. Déjame mostrarte lo que quiero decir.

public function addThreeInts($first, $second, $third) {
    if (is_int($first)) {
        if (is_int($second)) {
            if (is_int($third)) {
                $sum = $first + $second + $third;
            } else {
                return null;
            }
        } else {
            return null;
        }
    } else {
        return null;
    }

    return $sum;
}

Así que esto es bastante sencillo de nuevo, agrega 3 ints juntos y devuelve el resultado, o nullsi alguno de los parámetros no es un número entero. Ignorando el hecho de que podríamos combinar todas esas comprobaciones en una sola línea con los operadores AND, creo que puede ver cómo la estructura anidada si / else hace que el código sea más difícil de seguir. Ahora mira este ejemplo en su lugar.

public function addThreeInts($first, $second, $third) {
    if (!is_int($first)) {
        return null;
    }

    if (!is_int($second)) {
        return null;
    }

    if (!is_int($third)) {
        return null;
    }

    return $first + $second + $third;
}

Para mí este ejemplo es mucho más fácil de seguir. Aquí estamos usando cláusulas de protección para verificar nuestras afirmaciones iniciales sobre los parámetros que estamos pasando y salir inmediatamente del método si no se pasan. Tampoco tenemos la variable intermedia para rastrear la suma a lo largo del método. En este caso, hemos verificado que ya estamos en el camino feliz y podemos hacer lo que vinimos a hacer aquí. Una vez más, podríamos hacer todos esos controles en uno,ifpero el principio debería ser claro.

5 unidades de prueba

La prueba de unidad es la práctica de escribir pruebas pequeñas que verifican el comportamiento en su código. Casi siempre están escritos en el mismo idioma que el código (en este caso PHP) y están diseñados para ser lo suficientemente rápidos para ejecutarse en cualquier momento. Son extremadamente valiosos como una herramienta para mejorar su código. Además de los beneficios obvios de asegurarse de que su código está haciendo lo que cree que es, las pruebas unitarias también pueden proporcionar comentarios de diseño muy útiles. Si una pieza de código es difícil de probar, a menudo presenta problemas de diseño. También le brindan una red de seguridad contra las regresiones, y eso le permite refactorizar mucho más a menudo y hacer que su código evolucione hacia un diseño más limpio.

5.1 herramientas

Existen varias herramientas de prueba de unidades en PHP, pero la más común esPHPUnit . Puede instalarlo descargando un archivo PHAR directamente , o instálelo con el editor . Ya que estamos usando compositor para todo lo demás, mostraremos ese método. Además, dado que no es probable que PHPUnit se implemente en producción, podemos instalarlo como una dependencia de desarrollo con el siguiente comando:

composer require --dev phpunit/phpunit

5.2 Las pruebas son una especificación

La función más importante de las pruebas unitarias en su código es proporcionar una especificación ejecutable de lo que se supone que debe hacer el código. Incluso si el código de prueba es incorrecto, o si el código tiene errores, el conocimiento de lo que se supone que debe hacer el sistema no tiene precio.

5.3 Escribe tus pruebas primero

Si ha tenido la oportunidad de ver un conjunto de pruebas escritas antes del código y una vez que el código se terminó, son sorprendentemente diferentes. Las pruebas “posteriores” están mucho más relacionadas con los detalles de la implementación de la clase y se aseguran de que tengan una buena cobertura de líneas, mientras que las pruebas “anteriores” tienen más que ver con verificar el comportamiento externo deseado. De todos modos, eso es lo que realmente nos importa con las pruebas de unidad: asegurarse de que la clase muestre el comportamiento correcto. Las pruebas enfocadas en la implementación en realidad hacen que la refactorización sea más difícil porque se rompen si cambian los aspectos internos de las clases, y usted solo se está costando la información que oculta los beneficios de la POO

5.4 ¿Qué hace una buena prueba unitaria?

Las buenas pruebas unitarias comparten muchas de las siguientes características:

  • Rápido – debe ejecutarse en milisegundos.
  • Sin acceso a la red: debe poder apagar / desconectar la conexión inalámbrica y todas las pruebas aún pasan.
  • Acceso limitado al sistema de archivos: esto se agrega a la velocidad y flexibilidad si se implementa código en otros entornos.
  • Sin acceso a la base de datos: evita costosas actividades de configuración y desmontaje.
  • Pruebe solo una cosa a la vez: una prueba de unidad debe tener solo una razón para fallar.
  • Bien nombrado – ver 5.2 arriba.
  • Principalmente objetos falsos: los únicos objetos “reales” en las pruebas unitarias deben ser los objetos que estamos probando y los objetos de valor simple. El resto debe ser algún tipo de prueba doble.

Hay razones para ir en contra de algunos de estos, pero como pautas generales le servirán bien.

5.5 cuando la prueba es dolorosa

Las pruebas unitarias te obligan a sentir el dolor del mal diseño desde el principio – Michael Feathers

Cuando estás escribiendo pruebas de unidad, te estás obligando a usar la clase para lograr cosas. Si escribe pruebas al final, o peor aún, simplemente coloque el código en el muro para el control de calidad o para quien escriba las pruebas, no obtendrá ningún comentario sobre cómo se comporta realmente la clase. Si estamos escribiendo exámenes, y la clase es un verdadero dolor de usar, lo averiguaremos mientras lo escribimos, que es el momento más barato para solucionarlo.

Si una clase es difícil de probar, es un defecto de diseño. Sin embargo, diferentes defectos se manifiestan de diferentes maneras. Si tiene que hacer un montón de burlas, su clase probablemente tenga demasiadas dependencias, o sus métodos están haciendo demasiado. Cuanta más configuración tenga que hacer para cada prueba, más probable será que sus métodos estén haciendo demasiado. Si tiene que escribir escenarios de prueba realmente complicados para ejercer un comportamiento, los métodos de la clase probablemente están haciendo demasiado.Si tienes que cavar dentro de un montón de métodos privados y un estado para probar cosas, tal vez haya otra clase tratando de salir. Las pruebas unitarias son muy buenas para exponer “clases de iceberg” donde el 80% de lo que hace la clase está oculto en código protegido o privado. Solía ​​ser un gran fanático de hacer lo más posible protegido,

LEAVE A REPLY

Please enter your comment!
Please enter your name here