Bienvenido

Hay mucha información obsoleta en la Web que lleva por mal camino a los nuevos usuarios de PHP, propagando malas prácticas y el código inseguro. PHP: La Manera Correcta es una referencia rápida y fácil de leer, referencia rápida para los estándares de codificación PHP más populares, enlaces a tutoriales autorizados y lo que los colaboradores consideran las mejores prácticas en la actualidad.

No existe una fórmula única para utilizar PHP. Este sitio web pretende introducir a los nuevos desarrolladores de PHP PHP a algunos temas que pueden no descubrir hasta que sea demasiado tarde, y tiene como objetivo dar a los profesionales experimentados algunas ideas frescas sobre los temas que han estado haciendo durante años sin reconsiderarlo nunca. Este sitio web tampoco le dirá qué herramientas utilizar, sino que sino que le sugerirá varias opciones y, cuando sea posible, explicará las diferencias de enfoque y uso.

Este es un documento vivo y seguirá actualizándose con más información útil y ejemplos a medida que estén disponibles. y ejemplos útiles a medida que estén disponibles.

Traducciones

PHP: La Manera Correcta está traducido a muchos idiomas:

Libro

La versión más reciente de PHP: La Manera Correcta también está disponible en formato PDF, EPUB y MOBI. Ir a Leanpub

¿Cómo contribuir?

¡Ayude a hacer de este sitio web el mejor recurso para los nuevos programadores de PHP! Contribuir en GitHub

Volver al inicio

Primeros pasos

Use la versión estable actual (8.3)

Si se está iniciando en PHP, comience con la versión estable actual de PHP 8.3. PHP 8.x añade muchas características nuevas con respecto a las versiones anteriores 7.x y 5.x. El motor se ha reescrito en gran medida, y PHP es ahora incluso más rápido que las versiones anteriores. PHP 8 es una actualización importante del lenguaje y contiene muchas nuevas funciones y optimizaciones.

Deberías intentar actualizar a la última versión estable rápidamente - PHP 7.4 ya ha llegado al final de su vida útil. Actualizar es fácil, ya que no hay muchas diferencias de compatibilidad con versiones anteriores. PHP 8.0, PHP 8.1, PHP 8.2, PHP 8.3. Si no está seguro de en qué versión se encuentra una función o característica, puede consultar la documentación de PHP en el sitio web php.net.

Servidor Web Integrado

Con PHP 5.4 o posteriores, puede empezar a aprender PHP sin necesidad de instalar y configurar un servidor web completo. Para iniciar el servidor, ejecute el siguiente comando desde su terminal en la raíz web de su proyecto:

> php -S localhost:8000

Instalación en macOS

macOS 12 (Monterey) y posteriores no vienen preempaquetados con PHP. Las versiones anteriores de macOS incluyen PHP pero no incluyen la última versión estable. Hay varias formas de instalar la última versión de PHP en macOS.

Instalar PHP vía Homebrew

Homebrew es un gestor de paquetes para macOS que te ayuda a instalar fácilmente PHP y varias extensiones. El repositorio central de Homebrew proporciona “fórmulas” para PHP 7.4, 8.0, 8.1, 8.2 y PHP 8.3. Instala la última versión con este comando:

brew install php@8.3

Puede cambiar entre las versiones de PHP de Homebrew modificando su variable PATH. Alternativamente, puede usar brew-php-switcher para cambiar de versión de PHP automáticamente.

También puede cambiar manualmente entre versiones de PHP desvinculando y vinculando la versión deseada:

brew unlink php
brew link --overwrite php@8.2
brew unlink php
brew link --overwrite php@8.3

Instalar PHP vía Macports

El proyecto MacPorts es una iniciativa de código abierto para diseñar un sistema fácil de usar paracompilar, instalar y actualizar software de código abierto basado en línea de comandos, X11 o Aqua en el sistema operativo macOS.

MacPorts soporta binarios pre-compilados, por lo que no necesitas recompilar cada dependencia desde los archivos tarball fuente, esto te salva la vida si no tienes ningún paquete instalado en tu sistema.

En la actualidad, puede instalar php54, php55, php56, php70, php71, php72, php73, php74, php80, php81, php82 o php83 utilizando el comando port install, por ejemplo:

    sudo port install php74
    sudo port install php83

And you can run select command to switch your active PHP:

    sudo port select --set php php83

Instalar PHP vía phpbrew

phpbrew es una herramienta para instalar y gestionar múltiples versiones de PHP. Esto puede ser realmente útil si dos aplicaciones/proyectos requieren diferentes versiones de PHP, y no está utilizando máquinas virtuales.

Instalar PHP vía el instalador de binarios Liip

Otra opción popular es php-osx.liip.ch que proporciona métodos de instalación de una línea para las versiones 5.3 a 7.3. No sobrescribe los binarios PHP instalados por Apple, sino que instala todo en una ubicación separada. (/usr/local/php5).

Compilar desde el código fuente

Otra opción que le da control sobre la versión de PHP que instala, es compilarlo usted mismo. En ese caso asegúrese de tener instalado Xcode o el sustituto de Apple “Command Line Tools for XCode” descargable desde el Centro de Desarrolladores de Apple.

Instaladores Todo en Uno

Las soluciones listadas arriba se encargan principalmente del propio PHP, y no suministran cosas como Apache, Nginx o un servidor SQL. Las soluciones “todo en uno” como MAMP y XAMPP instalarán estas otras partes del software por usted y las unirán todas, pero la facilidad de configuración tiene como contrapartida la falta de flexibilidad.

Instalación en Windows

Puede descargar los binarios en windows.php.net/download. Después de la extracción de PHP, se recomienda establecer el PATH a la raíz de su carpeta PHP (donde se encuentra php.exe) para que pueda ejecutar PHP desde cualquier lugar.

Para el aprendizaje y el desarrollo local, puede utilizar el construido en el servidor web con PHP 5.4 + por lo que no necesita preocuparse de configurarlo. Si desea una solución “todo-en-uno” que incluya un servidor web completo y MySQL también entonces herramientas como como XAMPP, EasyPHP, OpenServer y WAMP le ayudará a poner en marcha rápidamente un entorno de desarrollo Windows. Dicho esto, estas herramientas serán un poco diferentes de las de producción, así que ten cuidado con las diferencias de entorno si estás trabajando en Windows y desplegando en Linux.

Si necesita ejecutar su sistema de producción en Windows, IIS7 le proporcionará la mayor estabilidad y el mejor rendimiento. Puedes usar phpmanager (un plugin GUI para IIS7) para que la configuración y gestión de PHP sea más sencilla. IIS7 viene con FastCGI integrado y listo para usar, sólo necesitas configurar PHP como manejador. Para soporte y recursos adicionales existe un área dedicada en iis.net para PHP.

Por lo general, la ejecución de su aplicación en diferentes entornos en el desarrollo y la producción puede dar lugar a errores extraños que aparecen cuando usted va a producción. Si estás desarrollando en Windows y desplegando en Linux (o en cualquier otro sistema que no sea Windows) entonces deberías considerar el uso de una Máquina Virtual.

Chris Tankersley tiene una entrada de blog muy útil sobre qué herramientas utiliza para hacer desarrollo PHP usando Windows.

Instalación en Linux

La mayoría de las distribuciones GNU/Linux vienen con PHP disponible desde los repositorios oficiales, pero esos paquetes usualmente están un poco atrasados con respecto a la versión estable actual. Existen múltiples formas de obtener versiones más recientes de PHP en dichas distribuciones. En las distribuciones GNU/Linux basadas en Ubuntu y Debian, por ejemplo, las mejores alternativas para paquetes nativos las proporcionadas y mantenidas Ondřej Surý, a través de su Archivo Personal de Paquetes (PPA) en Ubuntu y DPA/bikeshed en Debian. Encontrarás las instrucciones para cada uno de ellos más abajo. Dicho esto, siempre puedes usar contenedores, compilar el código fuente PHP, etc.

Distribuciones basadas en Ubuntu

Para distribuciones Ubuntu, el PPA de Ondřej Surý proporciona versiones de PHP soportadas junto con muchas extensiones PECL. Para añadir este PPA a su sistema, realice los siguientes pasos en su terminal:

  1. En primer lugar, añada el PPA a las fuentes de software de su sistema mediante el comando

    sudo add-apt-repository ppa:ondrej/php
    
  2. Después de añadir el PPA, actualice la lista de paquetes de su sistema:

    sudo apt update
    

Esto asegurará que su sistema pueda acceder e instalar los últimos paquetes PHP disponibles en el PPA.

Distribuciones basadas en Debian

Para las distribuciones basadas en Debian, Ondřej Surý también proporciona un bikeshed (equivalente en Debian a un PPA). Para añadir el bikeshed a su sistema y actualizarlo, siga estos pasos:

  1. Asegúrese de que tiene acceso de root. Si no es así, es posible que tenga que utilizar sudo para ejecutar los siguientes comandos.

  2. Actualice la lista de paquetes de su sistema:

    sudo apt-get update
    
  3. Instale lsb-release, ca-certificates, y curl:

    sudo apt-get -y install lsb-release ca-certificates curl
    
  4. Descargue la clave de firma del repositorio:

    sudo curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg
    
  5. Añada el repositorio a las fuentes de software de su sistema:

    sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list'
    
  6. Por último, actualice de nuevo la lista de paquetes de su sistema:

    sudo apt-get update
    

Con estos pasos, su sistema será capaz de instalar los últimos paquetes PHP desde bikeshed.

Estructura Común de Directorios

Una pregunta común entre los que empiezan a escribir programas para la web es: “¿dónde pongo mis archivos?”. A lo largo de los años, la respuesta ha sido consistentemente “donde está el DocumentRoot”. Aunque esta respuesta no es del todo completa, es un buen punto de partida.

Por razones de seguridad, los archivos de configuración no deben ser accesibles por los visitantes de un sitio; por lo tanto, los scripts públicos se guardan en un directorio público y las configuraciones y datos privados se guardan fuera de ese directorio.

En cada equipo, CMS o framework en el que uno trabaja, cada uno de ellos utiliza una estructura de directorios estándar. Sin embargo, si uno está empezando un proyecto por si mismo, saber qué estructura de sistema de archivos utilizar puede ser intimidante.

Paul M. Jones ha realizado una fantástica investigación sobre las prácticas comunes de decenas de miles de proyectos de github en el ámbito de PHP. Ha compilado una estructura estándar de archivos y directorios, el Standard PHP Package Skeleton, basado en esta investigación. En esta estructura de directorios, DocumentRoot debe apuntar a public/, las pruebas unitarias deben estar en el directorio tests/, y las librerías de terceros, instaladas por composer, deben estar en el directorio vendor/. Para otros archivos y directorios, seguir el Standard PHP Package Skeleton tendrá más sentido para los contribuidores de un proyecto.

Volver al inicio

Guía de Estilos del Código

La comunidad PHP es grande y diversa, compuesta por innumerables bibliotecas, frameworks y componentes. Es habitual que los desarrolladores de PHP elijan varios de ellos y los combinen en un único proyecto. Es importante que el código PHP se adhiera (tanto como sea posible) a un estilo de código común para facilitar a los desarrolladores mezclar y combinar varias librerías para sus proyectos.

El Framework Interop Group ha propuesto y aprobado una serie de recomendaciones de estilo. No todas están relacionadas con el estilo del código, pero las que sí lo hacen son PSR-1, PSR-12, PSR-4 y PER Coding Style. Estas recomendaciones no son más que un conjunto de reglas que muchos proyectos como Drupal, Zend, Symfony, Laravel, CakePHP, phpBB, AWS SDK, FuelPHP, Lithium, etc. adoptan. Puedes utilizarlas para tus propios proyectos, o seguir utilizando tu estilo personal.

Lo ideal sería escribir código PHP que se adhiera a un estándar conocido. Esto podría ser cualquier combinación de PSRs, o uno de los estándares de codificación hechos por PEAR o Zend. Esto significa que otros desarrolladores pueden leer y trabajar fácilmente con su código, y las aplicaciones que implementan los componentes pueden tener consistencia incluso cuando se trabaja con mucho código de terceros.

Puedes usar PHP_CodeSniffer para comprobar el código contra cualquiera de estas recomendaciones, y plugins para editores de texto como Sublime Text para recibir feedback en tiempo real.

Puede corregir la estructura del código automáticamente utilizando una de las siguientes herramientas:

Y puedes ejecutar phpcs manualmente desde el shell:

phpcs -sw --standard=PSR1 file.php

Mostrará los errores y describirá cómo solucionarlos. También puede ser útil incluir el comando phpcs en un git pre-commit hook con el argumento CLI --filter=GitStaged. De este modo, el código que contenga errores contra la norma elegida no podrá entrar en el repositorio hasta que los errores hayan sido corregidos.

Si tiene PHP_CodeSniffer, puede arreglar los problemas de estructura del código reportados por esta herramienta automáticamente, con PHP Code Beautifier and Fixer.

phpcbf -w --standard=PSR1 file.php

Otra opción es usar el PHP Coding Standards Fixer. Mostrará qué tipo de errores tenía la estructura del código antes de corregirlos.

php-cs-fixer fix -v --rules=@PSR1 file.php

Se prefiere el inglés para todos los nombres de símbolos e infraestructura de códigos. Los comentarios pueden redactarse en cualquier idioma fácilmente legible por todas las partes actuales y futuras que puedan trabajar en el código base.

Por último, un buen recurso complementario para escribir código PHP limpio es Clean Code PHP.

Volver al inicio

Aspectos Destacados del Lenguaje

Paradigmas de Programación

PHP es un lenguaje flexible y dinámico que admite diversas técnicas de programación. A lo largo de los años ha evolucionado de forma espectacular, sobre todo con la incorporación de un sólido modelo orientado a objetos en PHP 5.0 (2004), funciones anónimas y namespaces en PHP 5.3 (2009) y traits en PHP 5.4 (2012).

Programación orientada a Objetos

PHP tiene un conjunto muy completo de características de programación orientada a objetos, incluyendo soporte para clases, clases abstractas, interfaces, herencia, constructores, clonación, excepciones y más.

Programación Funcional

PHP soporta funciones de primera clase, lo que significa que una función puede ser asignada a una variable. Tanto las funciones definidas por el usuario como las funciones internas pueden referenciarse mediante una variable e invocarse dinámicamente. Las funciones pueden pasarse como argumentos a otras funciones (característica denominada Funciones de orden superior) y las funciones pueden devolver otras funciones.

La recursión, una característica que permite a una función llamarse a sí misma, está soportada por el lenguaje, pero la mayor parte del código PHP se centra en la iteración.

Las nuevas funciones anónimas (con soporte para closures) están presentes desde PHP 5.3 (2009).

PHP 5.4 añadió la capacidad de enlazar closures al ámbito de un objeto y también mejoró el soporte para callables de tal forma que pueden ser usadas indistintamente con funciones anónimas en casi todos los casos.

Meta Programación

PHP soporta varias formas de meta-programación a través de mecanismos como la API Reflection y los Métodos Mágicos. Hay muchos Métodos Mágicos disponibles tales como __get(), __set(), __clone(), __toString(), __invoke(), etc. que permiten a los desarrolladores conectarse al comportamiento de las clases. Los desarrolladores de Ruby a menudo dicen que PHP carece de method_missing, pero esto esta disponible como __call() y __callStatic().

Namespaces

Como se mencionó anteriormente, la comunidad PHP tiene muchos desarrolladores creando mucho código. Esto significa que el código PHP de una biblioteca puede usar el mismo nombre de clase que otra. Cuando ambas bibliotecas son usadas en el mismo espacio de nombres, estas colisionan y causan problemas.

Los espacios de nombres (Namespaces) resuelven este problema. Como se describe en el manual de referencia de PHP, los espacios de nombres pueden compararse con los directorios del sistema operativo que espacian o separan los archivos; dos archivos con el mismo nombre pueden coexistir en directorios separados. Del mismo modo, dos clases PHP con el mismo nombre pueden coexistir en espacios de nombres PHP separados. Así de simple.

Es importante que asigne un espacio de nombres a su código para que pueda ser utilizado por otros desarrolladores sin temor a colisionar con otras bibliotecas.

Una forma recomendada de utilizar los espacios de nombres es la descrita en PSR-4, cuyo objetivo es proporcionar una convención estándar de archivos, clases y espacios de nombres para permitir un código plug-and-play.

En octubre de 2014 el PHP-FIG dejó obsoleto el anterior estándar de autocarga: PSR-0. Tanto PSR-0 como PSR-4 siguen siendo perfectamente utilizables. Este último requiere PHP 5.3, por lo que muchos proyectos que solo usan PHP 5.2 implementan PSR-0.

Si va a utilizar un estándar de autocargador para una nueva aplicación o paquete, considere utilizar PSR-4.

Biblioteca Estándar de PHP

La Biblioteca Estándar de PHP (SPL) viene empaquetada con PHP y proporciona una colección de clases e interfaces. Se compone principalmente de clases de estructura de datos comúnmente necesarias (pila, cola, montón, etc.), e iteradores que pueden recorrer estas estructuras de datos o sus propias clases que implementan interfaces SPL.

Interfaz de Línea de Comandos (CLI)

PHP fue creado para escribir aplicaciones web, pero también es útil para scripting de programas de interfaz de línea de comandos (CLI). Los programas PHP de línea de comandos pueden ayudar a automatizar tareas comunes como pruebas, despliegue y administración de aplicaciones.

Los programas PHP CLI son poderosos porque puedes usar el código de tu aplicación directamente sin tener que crear y asegurar una web GUI para ella. ¡Solo asegúrate de no poner tus scripts PHP CLI en tu raíz web pública!

Intente ejecutar PHP desde su línea de comandos:

> php -i

La opción -i imprimirá su configuración PHP igual que la función phpinfo().

La opción -a proporciona un shell interactivo, similar al IRB de ruby o al shell interactivo de python. También existen de otras útiles opciones de línea de comandos, también.

Escribamos un simple programa CLI “Hola, $nombre”. Para probarlo, crea un archivo llamado hello.php, como se muestra a continuación.

<?php
if ($argc !== 2) {
    echo "Como usar: php hello.php <name>" . PHP_EOL;
    exit(1);
}
$name = $argv[1];
echo "Hola, $name" . PHP_EOL;

PHP establece dos variables especiales basadas en los argumentos con los que se ejecuta el script. $argc es una variable entera que contiene el conteo de argumentos y $argv es una variable array que contiene el valor de cada argumento. El primer argumento es siempre el nombre de su archivo de script PHP, en este caso hello.php.

La expresión exit() se utiliza con un número distinto de cero para hacer saber al shell que el comando ha fallado. Los códigos de salida más comunes se pueden encontrar aquí.

Para ejecutar nuestro script, antes visto, desde la línea de comandos:

> php hello.php
Como usar: php hello.php <name>
> php hello.php mundo
Hola, mundo

XDebug

Una de las herramientas más útiles en el desarrollo de software es un depurador adecuado. Te permite rastrear la ejecución de tu código y monitorizar el contenido de la pila. Xdebug, el depurador de PHP, puede ser utilizado por varios IDEs para proporcionar puntos de interrupción e inspección de pila. También puede permitir a herramientas como PHPUnit y KCacheGrind realizar análisis de cobertura de código y perfilado de código. y perfiles de código.

Si te encuentras en un aprieto, dispuesto a recurrir a var_dump()/print_r(), y sigues sin encontrar la solución - quizás necesites usar el depurador.

Instalar Xdebug puede ser complicado, pero una de sus características más importantes es la “Depuración Remota” - si desarrollar código localmente y luego probarlo dentro de una máquina virtual o en otro servidor, la depuración remota es la característica que usted querrá habilitar de inmediato.

Tradicionalmente, usted modificaría su Apache VHost o archivo .htaccess con estos valores:

php_value xdebug.remote_host 192.168.?.?
php_value xdebug.remote_port 9000

El “host remoto” y el “puerto remoto” corresponderán a tu ordenador local y al puerto en el que configures tu IDE para escuchar. Entonces es sólo cuestión de poner tu IDE en modo “escuchar conexiones”, y cargar la URL:

http://your-website.example.com/index.php?XDEBUG_SESSION_START=1

Su IDE interceptará ahora el estado actual mientras se ejecuta el script, permitiéndole establecer puntos de interrupción y explorar los valores en memoria.

Los depuradores gráficos hacen que sea muy fácil recorrer el código, inspeccionar variables y evaluar el código contra el tiempo de ejecución en vivo. Muchos IDEs tienen soporte integrado o basado en plugins para la depuración gráfica con Xdebug. MacGDBp es una GUI de Xdebug gratuita e independiente de código abierto para macOS.

Volver al inicio

Gestión de Dependencias

Hay un montón de librerías PHP, frameworks y componentes entre los que elegir. Su proyecto probablemente utilizará varios de ellos - estas son las dependencias del proyecto. Hasta hace poco, PHP no tenía una buena manera de gestionar estas dependencias del proyecto. Incluso si las gestionaba manualmente, aún tenía que preocuparse por los autoloaders. Esto ya no es un problema.

Actualmente existen dos grandes sistemas de gestión de paquetes para PHP - Composer y PEAR. Composer es actualmente el gestor de paquetes más popular para PHP, sin embargo durante mucho tiempo PEAR fue el principal gestor de paquetes en uso. Conocer la historia de PEAR es una buena idea, ya que aún puede encontrar referencias a él aunque nunca lo use.

Composer y Packagist

Composer es el gestor de dependencias recomendado para PHP. Enumera las dependencias de tu proyecto en un archivo composer.json y, con unos simples comandos, Composer descargará automáticamente las dependencias de tu proyecto y configurará la carga automática por ti. Composer es análogo a NPM en el mundo node.js, o Bundler en el mundo Ruby.

Existe una plétora de librerías PHP compatibles con Composer y listas para ser utilizadas en tu proyecto. Estos “paquetes” están listados en Packagist, el repositorio oficial de librerías PHP compatibles con Composer.

Cómo instalar Composer

La forma más segura de descargar Composer es siguiendo las instrucciones oficiales. Esto verificará que el instalador no está corrupto o manipulado. El instalador instala un binario composer.phar en su directorio de trabajo actual.

Recomendamos instalar Composer globalmente (por ejemplo, una única copia en /usr/local/bin). Para ello, ejecute este comando a continuación:

mv composer.phar /usr/local/bin/composer

Nota: Si lo anterior falla por los permisos, prefijar con sudo.

Para ejecutar un Composer instalado localmente se usaría php composer.phar, globalmente es simplemente composer.

Instalar en Windows

Para los usuarios de Windows la forma más fácil de empezar a funcionar es utilizar el instalador ComposerSetup, que realiza una instalación global y configura tu $PATH para que puedas llamar a composer desde cualquier directorio en tu línea de comandos.

Cómo Definir e Instalar Dependencias

Composer mantiene un registro de las dependencias de tu proyecto en un archivo llamado composer.json. Puedes gestionarlo manualmente si lo deseas, o utilizar el propio Composer. El comando composer require añade una dependencia del proyecto y si no tienes un archivo composer.json, se creará uno. Aquí tienes un ejemplo que añade Twig como dependencia de tu proyecto.

composer require twig/twig:^2.0

Como alternativa, el comando composer init le guiará a través de la creación de un archivo completo composer.json para su proyecto. De cualquier manera, una vez que haya creado su archivo composer.json puede decirle a Composer que descargue e instale sus dependencias en el directorio vendor/. Esto también se aplica a los proyectos que ha descargado que ya proporcionan un archivo composer.json:

composer install

A continuación, añada esta línea al archivo PHP principal de su aplicación; esto le dirá a PHP que utilice el autocargador (del Inglés “autoloader”) de Composer para las dependencias de su proyecto.

<?php
require 'vendor/autoload.php';

Ahora puedes utilizar las dependencias de tu proyecto y se cargarán automáticamente cuando las necesites.

Actualizar las dependencias

Composer crea un archivo llamado composer.lock que almacena la versión exacta de cada paquete que descargó cuando ejecutó por primera vez composer install. Si comparte su proyecto con otros, asegúrese de que el archivo composer.lock esté incluido, de modo que cuando ejecuten composer install obtengan las mismas versiones que usted. Para actualizar sus dependencias, ejecute composer update. No utilice composer update al desplegar, sólo composer install, de lo contrario puede terminar con diferentes versiones de paquetes en producción.

Ts muy útil cuando se definen los requisitos de versión de forma flexible. Por ejemplo, un requisito de versión de ~1.8 significa “cualquier cosa más nueva que 1.8.0, pero menos que 2.0.x-dev”. También puede utilizar el comodín * como en 1.8.*. Ahora el comando composer update de Composer actualizará todas tus dependencias a la versión más reciente que se ajuste a las restricciones que definas.

Notificaciones de Actualización

Para recibir notificaciones sobre el lanzamiento de nuevas versiones puede suscribirse a libraries.io, un servicio web que puede supervisar las dependencias y enviarle alertas sobre las actualizaciones.

Comprobar la seguridad de las dependencias

El Comprobador Local de Seguridad PHP (en Inglés “Local PHP Security Checker”) es una herramienta de línea de comandos, que examinará su archivo composer.lock y le dirá si necesita actualizar alguna de sus dependencias.

Gestión de dependencias globales con Composer

Composer también puede gestionar dependencias globales y sus binarios. El uso es sencillo, todo lo que necesitas hacer es anteponer a su comando con global. Si por ejemplo quieres instalar PHPUnit y tenerlo disponible globalmente, ejecutarías el siguiente comando:

composer global require phpunit/phpunit

Esto creará una carpeta ~/.composer donde residen tus dependencias globales. Para que los binarios de los paquetes instalados, añada la carpeta ~/.composer/vendor/bin a la variable $PATH.

PEAR

Un veterano gestor de paquetes que algunos desarrolladores PHP disfrutan es PEAR. Se comporta de manera similar a Composer, pero tiene algunas diferencias notables.

PEAR requiere que cada paquete tenga una estructura específica, lo que significa que el autor del paquete debe prepararlo para uso con PEAR. No es posible utilizar un proyecto que no haya sido preparado para funcionar con PEAR.

PEAR instala los paquetes globalmente, lo que significa que después de instalarlos una vez están disponibles para todos los proyectos en ese servidor. Esto puede ser bueno si muchos proyectos dependen del mismo paquete con la misma versión, pero puede llevar a problemas si surgen conflictos de versión entre dos proyectos.

Instalación de PEAR

Puede instalar PEAR descargando el instalador .phar y ejecutándolo. La documentación de PEAR tiene detalladas instrucciones de instalación para cada sistema operativo.

Si usas Linux, también puedes echar un vistazo al gestor de paquetes de tu distribución. Debian y Ubuntu, por ejemplo, tienen un paquete apt php-pear.

¿Cómo instalar un paquete?

Si el paquete aparece en la lista de paquetes de PEAR, puede instalarlo especificando el nombre oficial:

pear install foo

Si el paquete se aloja en otro canal, deberá “descubrir” primero el canal y especificarlo también al instalar. Consulta la documentación sobre el uso de canales para obtener más información sobre este tema.

Manejo de dependencias PEAR con Composer

Si ya está utilizando Composer y desea instalar algún código PEAR también, puede utilizar Composer para manejar sus dependencias PEAR. Los repositorios PEAR ya no son soportados directamente por Composer versión 2, por lo que debes añadir manualmente un repositorio para instalar paquetes PEAR:

{
    "repositories": [
        {
            "type": "package",
            "package": {
                "name": "pear2/pear2-http-request",
                "version": "2.5.1",
                "dist": {
                    "url": "https://github.com/pear2/HTTP_Request/archive/refs/heads/master.zip",
                    "type": "zip"
                }
            }
        }
    ],
    "require": {
        "pear2/pear2-http-request": "*"
    },
    "autoload": {
        "psr-4": {"PEAR2\\HTTP\\": "vendor/pear2/pear2-http-request/src/HTTP/"}
    }
}

La primera sección "repositorios" se utilizará para que Composer sepa que debe “inicializar” (o “descubrir” en terminología PEAR) el repositorio pear. Entonces la sección require prefijará el nombre del paquete así:

pear-channel/package

El prefijo “pear” está codificado para evitar conflictos, ya que un canal pear puede ser el mismo que el nombre de proveedor de otro paquete, por ejemplo, entonces el nombre corto del canal (o URL completa) puede ser usado para referenciar en qué canal está el paquete.

Cuando se instala este código estará disponible en su directorio de proveedores y automáticamente disponible a través del autocargador de Composer:

vendor/pear2/pear2-http-request/pear2/HTTP/Request.php

Para utilizar este paquete PEAR simplemente haga referencia a él de la siguiente manera:

<?php
require __DIR__ . '/vendor/autoload.php';

use PEAR2\HTTP\Request;

$request = new Request();

Volver al inicio

Buenas Prácticas

Conceptos Básicos

PHP is a vast language that allows coders of all levels the ability to produce code not only quickly, but efficiently. However, while advancing through the language, we often forget the basics that we first learnt (or overlooked) in favor of short cuts and/or bad habits. To help combat this common issue, this section is aimed at reminding coders of the basic coding practices within PHP.

Fecha y Hora

PHP has a class named DateTime to help you when reading, writing, comparing or calculating with date and time. There are many date and time related functions in PHP besides DateTime, but it provides nice object-oriented interface to most common uses. DateTime can handle time zones, but that is outside the scope of this short introduction.

To start working with DateTime, convert raw date and time string to an object with createFromFormat() factory method or do new DateTime to get the current date and time. Use format() method to convert DateTime back to a string for output.

<?php
$raw = '22. 11. 1968';
$start = DateTime::createFromFormat('d. m. Y', $raw);

echo 'Start date: ' . $start->format('Y-m-d') . PHP_EOL;

Calculating with DateTime is possible with the DateInterval class. DateTime has methods like add() and sub() that take a DateInterval as an argument. Do not write code that expects the same number of seconds in every day. Both daylight saving and time zone alterations will break that assumption. Use date intervals instead. To calculate date difference use the diff() method. It will return new DateInterval, which is super easy to display.

<?php
// create a copy of $start and add one month and 6 days
$end = clone $start;
$end->add(new DateInterval('P1M6D'));

$diff = $end->diff($start);
echo 'Difference: ' . $diff->format('%m month, %d days (total: %a days)') . PHP_EOL;
// Difference: 1 month, 6 days (total: 37 days)

You can use standard comparisons on DateTime objects:

<?php
if ($start < $end) {
    echo "Start is before the end!" . PHP_EOL;}

One last example to demonstrate the DatePeriod class. It is used to iterate over recurring events. It can take two DateTime objects, start and end, and the interval for which it will return all events in between.

<?php
// output all thursdays between $start and $end
$periodInterval = DateInterval::createFromDateString('first thursday');
$periodIterator = new DatePeriod($start, $periodInterval, $end, DatePeriod::EXCLUDE_START_DATE);
foreach ($periodIterator as $date) {
    // output each date in the period
    echo $date->format('Y-m-d') . ' ';
}

A popular PHP API extension is Carbon. It inherits everything in the DateTime class, so involves minimal code alterations, but extra features include Localization support, further ways to add, subtract and format a DateTime object, plus a means to test your code by simulating a date and time of your choosing.

Patrones de Diseño

When you are building your application it is helpful to use common patterns in your code and common patterns for the overall structure of your project. Using common patterns is helpful because it makes it much easier to manage your code and lets other developers quickly understand how everything fits together.

If you use a framework then most of the higher level code and project structure will be based on that framework, so a lot of the pattern decisions are made for you. But it is still up to you to pick out the best patterns to follow in the code you build on top of the framework. If, on the other hand, you are not using a framework to build your application then you have to find the patterns that best suit the type and size of application that you’re building.

You can learn more about PHP design patterns and see working examples at:

Trabajar con UTF-8

This section was originally written by Alex Cabal over at PHP Best Practices and has been used as the basis for our own UTF-8 advice.

There’s no one-liner. Be careful, detailed, and consistent.

Right now PHP does not support Unicode at a low level. There are ways to ensure that UTF-8 strings are processed OK, but it’s not easy, and it requires digging in to almost all levels of the web app, from HTML to SQL to PHP. We’ll aim for a brief, practical summary.

UTF-8 at the PHP level

The basic string operations, like concatenating two strings and assigning strings to variables, don’t need anything special for UTF-8. However, most string functions, like strpos() and strlen(), do need special consideration. These functions often have an mb_* counterpart: for example, mb_strpos() and mb_strlen(). These mb_* strings are made available to you via the Multibyte String Extension, and are specifically designed to operate on Unicode strings.

You must use the mb_* functions whenever you operate on a Unicode string. For example, if you use substr() on a UTF-8 string, there’s a good chance the result will include some garbled half-characters. The correct function to use would be the multibyte counterpart, mb_substr().

The hard part is remembering to use the mb_* functions at all times. If you forget even just once, your Unicode string has a chance of being garbled during further processing.

Not all string functions have an mb_* counterpart. If there isn’t one for what you want to do, then you might be out of luck.

You should use the mb_internal_encoding() function at the top of every PHP script you write (or at the top of your global include script), and the mb_http_output() function right after it if your script is outputting to a browser. Explicitly defining the encoding of your strings in every script will save you a lot of headaches down the road.

Additionally, many PHP functions that operate on strings have an optional parameter letting you specify the character encoding. You should always explicitly indicate UTF-8 when given the option. For example, htmlentities() has an option for character encoding, and you should always specify UTF-8 if dealing with such strings. Note that as of PHP 5.4.0, UTF-8 is the default encoding for htmlentities() and htmlspecialchars().

Finally, If you are building a distributed application and cannot be certain that the mbstring extension will be enabled, then consider using the symfony/polyfill-mbstring Composer package. This will use mbstring if it is available, and fall back to non UTF-8 functions if not.

UTF-8 at the Database level

If your PHP script accesses MySQL, there’s a chance your strings could be stored as non-UTF-8 strings in the database even if you follow all of the precautions above.

To make sure your strings go from PHP to MySQL as UTF-8, make sure your database and tables are all set to the utf8mb4 character set and collation, and that you use the utf8mb4 character set in the PDO connection string. See example code below. This is critically important.

Note that you must use the utf8mb4 character set for complete UTF-8 support, not the utf8 character set! See Further Reading for why.

UTF-8 at the browser level

Use the mb_http_output() function to ensure that your PHP script outputs UTF-8 strings to your browser.

The browser will then need to be told by the HTTP response that this page should be considered as UTF-8. Today, it is common to set the character set in the HTTP response header like this:

<?php
header('Content-Type: text/html; charset=UTF-8')

The historic approach to doing that was to include the charset <meta> tag in your page’s <head> tag.

<?php
// Tell PHP that we're using UTF-8 strings until the end of the script
mb_internal_encoding('UTF-8');
$utf_set = ini_set('default_charset', 'utf-8');
if (!$utf_set) {
    throw new Exception('could not set default_charset to utf-8, please ensure it\'s set on your system!');
}

// Tell PHP that we'll be outputting UTF-8 to the browser
mb_http_output('UTF-8');

// Our UTF-8 test string
$string = 'Êl síla erin lû e-govaned vîn.';

// Transform the string in some way with a multibyte function
// Note how we cut the string at a non-Ascii character for demonstration purposes
$string = mb_substr($string, 0, 15);

// Connect to a database to store the transformed string
// See the PDO example in this document for more information
// Note the `charset=utf8mb4` in the Data Source Name (DSN)
$link = new PDO(
    'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
    'your-username',
    'your-password',
    array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_PERSISTENT => false
    )
);

// Store our transformed string as UTF-8 in our database
// Your DB and tables are in the utf8mb4 character set and collation, right?
$handle = $link->prepare('insert into ElvishSentences (Id, Body, Priority) values (default, :body, :priority)');
$handle->bindParam(':body', $string, PDO::PARAM_STR);
$priority = 45;
$handle->bindParam(':priority', $priority, PDO::PARAM_INT); // explicitly tell pdo to expect an int
$handle->execute();

// Retrieve the string we just stored to prove it was stored correctly
$handle = $link->prepare('select * from ElvishSentences where Id = :id');
$id = 7;
$handle->bindParam(':id', $id, PDO::PARAM_INT);
$handle->execute();

// Store the result into an object that we'll output later in our HTML
// This object won't kill your memory because it fetches the data Just-In-Time to
$result = $handle->fetchAll(\PDO::FETCH_OBJ);

// An example wrapper to allow you to escape data to html
function escape_to_html($dirty){
    echo htmlspecialchars($dirty, ENT_QUOTES, 'UTF-8');
}

header('Content-Type: text/html; charset=UTF-8'); // Unnecessary if your default_charset is set to utf-8 already
?><!doctype html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>UTF-8 test page</title>
    </head>
    <body>
        <?php
        foreach($result as $row){
            escape_to_html($row->Body);  // This should correctly output our transformed UTF-8 string to the browser
        }
        ?>
    </body>
</html>

Further reading

Internacionalización (i18n) y Localización (l10n)

Disclaimer for newcomers: i18n and l10n are numeronyms, a kind of abbreviation where numbers are used to shorten words - in our case, internationalization becomes i18n and localization, l10n.

First of all, we need to define those two similar concepts and other related things:

Common ways to implement

The easiest way to internationalize PHP software is by using array files and using those strings in templates, such as <h1><?=$TRANS['title_about_page']?></h1>. This way is, however, hardly recommended for serious projects, as it poses some maintenance issues along the road - some might appear in the very beginning, such as pluralization. So, please, don’t try this if your project will contain more than a couple of pages.

The most classic way and often taken as reference for i18n and l10n is a Unix tool called gettext. It dates back to 1995 and is still a complete implementation for translating software. It is easy enough to get running, while still sporting powerful supporting tools. It is about Gettext we will be talking here. Also, to help you not get messy over the command-line, we will be presenting a great GUI application that can be used to easily update your l10n source.

Other tools

There are common libraries used that support Gettext and other implementations of i18n. Some of them may seem easier to install or sport additional features or i18n file formats. In this document, we focus on the tools provided with the PHP core, but here we list others for completion:

Other frameworks also include i18n modules, but those are not available outside of their codebases:

If you decide to go for one of the libraries that provide no extractors, you may want to use the gettext formats, so you can use the original gettext toolchain (including Poedit) as described in the rest of the chapter.

Gettext

Installation

You might need to install Gettext and the related PHP library by using your package manager, like apt-get or yum. After installed, enable it by adding extension=gettext.so (Linux/Unix) or extension=php_gettext.dll (Windows) to your php.ini.

Here we will also be using Poedit to create translation files. You will probably find it in your system’s package manager; it is available for Unix, macOS, and Windows, and can be downloaded for free on their website as well.

Structure

Types of files

There are three files you usually deal with while working with gettext. The main ones are PO (Portable Object) and MO (Machine Object) files, the first being a list of readable “translated objects” and the second, the corresponding binary to be interpreted by gettext when doing localization. There’s also a POT (Template) file, which simply contains all existing keys from your source files, and can be used as a guide to generate and update all PO files. Those template files are not mandatory: depending on the tool you are using to do l10n, you can go just fine with only PO/MO files. You will always have one pair of PO/MO files per language and region, but only one POT per domain.

Domains

There are some cases, in big projects, where you might need to separate translations when the same words convey different meaning given a context. In those cases, you split them into different domains. They are, basically, named groups of POT/PO/MO files, where the filename is the said translation domain. Small and medium-sized projects usually, for simplicity, use only one domain; its name is arbitrary, but we will be using “main” for our code samples. In Symfony projects, for example, domains are used to separate the translation for validation messages.

Locale code

A locale is simply a code that identifies one version of a language. It is defined following the ISO 639-1 and ISO 3166-1 alpha-2 specs: two lower-case letters for the language, optionally followed by an underline and two upper-case letters identifying the country or regional code. For rare languages, three letters are used.

For some speakers, the country part may seem redundant. In fact, some languages have dialects in different countries, such as Austrian German (de_AT) or Brazilian Portuguese (pt_BR). The second part is used to distinguish between those dialects - when it is not present, it is taken as a “generic” or “hybrid” version of the language.

Directory structure

To use Gettext, we will need to adhere to a specific structure of folders. First, you will need to select an arbitrary root for your l10n files in your source repository. Inside it, you will have a folder for each needed locale, and a fixed LC_MESSAGES folder that will contain all your PO/MO pairs. Example:

<project root>
 ├─ src/
 ├─ templates/
 └─ locales/
    ├─ forum.pot
    ├─ site.pot
    ├─ de/
    │  └─ LC_MESSAGES/
    │     ├─ forum.mo
    │     ├─ forum.po
    │     ├─ site.mo
    │     └─ site.po
    ├─ es_ES/
    │  └─ LC_MESSAGES/
    │     └─ ...
    ├─ fr/
    │  └─ ...
    ├─ pt_BR/
    │  └─ ...
    └─ pt_PT/
       └─ ...

Plural forms

As we said in the introduction, different languages might sport different plural rules. However, gettext saves us from this trouble once again. When creating a new .po file, you will have to declare the plural rules for that language, and translated pieces that are plural-sensitive will have a different form for each of those rules. When calling Gettext in code, you will have to specify the number related to the sentence, and it will work out the correct form to use - even using string substitution if needed.

Plural rules include the number of plurals available and a boolean test with n that would define in which rule the given number falls (starting the count with 0). For example:

Now that you understood the basis of how plural rules works - and if you didn’t, please look at a deeper explanation on the LingoHub tutorial -, you might want to copy the ones you need from a list instead of writing them by hand.

When calling out Gettext to do localization on sentences with counters, you will have to provide it the related number as well. Gettext will work out what rule should be in effect and use the correct localized version. You will need to include in the .po file a different sentence for each plural rule defined.

Sample implementation

After all that theory, let’s get a little practical. Here’s an excerpt of a .po file - don’t mind with its format, but with the overall content instead; you will learn how to edit it easily later:

msgid ""
msgstr ""
"Language: pt_BR\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

msgid "We are now translating some strings"
msgstr "Nós estamos traduzindo algumas strings agora"

msgid "Hello %1$s! Your last visit was on %2$s"
msgstr "Olá %1$s! Sua última visita foi em %2$s"

msgid "Only one unread message"
msgid_plural "%d unread messages"
msgstr[0] "Só uma mensagem não lida"
msgstr[1] "%d mensagens não lidas"

The first section works like a header, having the msgid and msgstr especially empty. It describes the file encoding, plural forms and other things that are less relevant. The second section translates a simple string from English to Brazilian Portuguese, and the third does the same, but leveraging string replacement from sprintf so the translation may contain the user name and visit date. The last section is a sample of pluralization forms, displaying the singular and plural version as msgid in English and their corresponding translations as msgstr 0 and 1 (following the number given by the plural rule). There, string replacement is used as well so the number can be seen directly in the sentence, by using %d. The plural forms always have two msgid (singular and plural), so it is advised not to use a complex language as the source of translation.

Discussion on l10n keys

As you might have noticed, we are using as source ID the actual sentence in English. That msgid is the same used throughout all your .po files, meaning other languages will have the same format and the same msgid fields but translated msgstr lines.

Talking about translation keys, there are two main “schools” here:

  1. msgid as a real sentence. The main advantages are:
    • if there are pieces of the software untranslated in any given language, the key displayed will still maintain some meaning. Example: if you happen to translate by heart from English to Spanish but need help to translate to French, you might publish the new page with missing French sentences, and parts of the website would be displayed in English instead;
    • it is much easier for the translator to understand what’s going on and do a proper translation based on the msgid;
    • it gives you “free” l10n for one language - the source one;
    • The only disadvantage: if you need to change the actual text, you would need to replace the same msgid across several language files.
  2. msgid as a unique, structured key. It would describe the sentence role in the application in a structured way, including the template or part where the string is located instead of its content.
    • it is a great way to have the code organized, separating the text content from the template logic.
    • however, that could bring problems to the translator that would miss the context. A source language file would be needed as a basis for other translations. Example: the developer would ideally have an en.po file, that translators would read to understand what to write in fr.po for instance.
    • missing translations would display meaningless keys on screen (top_menu.welcome instead of Hello there, User! on the said untranslated French page). That is good it as would force translation to be complete before publishing - however, bad as translation issues would be remarkably awful in the interface. Some libraries, though, include an option to specify a given language as “fallback”, having a similar behavior as the other approach.

The Gettext manual favors the first approach as, in general, it is easier for translators and users in case of trouble. That is how we will be working here as well. However, the Symfony documentation favors keyword-based translation, to allow for independent changes of all translations without affecting templates as well.

Everyday usage

In a typical application, you would use some Gettext functions while writing static text in your pages. Those sentences would then appear in .po files, get translated, compiled into .mo files and then, used by Gettext when rendering the actual interface. Given that, let’s tie together what we have discussed so far in a step-by-step example:

1. A sample template file, including some different gettext calls

<?php include 'i18n_setup.php' ?>
<div id="header">
    <h1><?=sprintf(gettext('Welcome, %s!'), $name)?></h1>
    <!-- code indented this way only for legibility -->
    <?php if ($unread): ?>
        <h2><?=sprintf(
            ngettext('Only one unread message',
                     '%d unread messages',
                     $unread),
            $unread)?>
        </h2>
    <?php endif ?>
</div>

<h1><?=gettext('Introduction')?></h1>
<p><?=gettext('We\'re now translating some strings')?></p>

2. A sample setup file (i18n_setup.php as used above), selecting the correct locale and configuring Gettext

<?php
/**
 * Verifies if the given $locale is supported in the project
 * @param string $locale
 * @return bool
 */
function valid($locale) {
   return in_array($locale, ['en_US', 'en', 'pt_BR', 'pt', 'es_ES', 'es']);
}

//setting the source/default locale, for informational purposes
$lang = 'en_US';

if (isset($_GET['lang']) && valid($_GET['lang'])) {
    // the locale can be changed through the query-string
    $lang = $_GET['lang'];    //you should sanitize this!
    setcookie('lang', $lang); //it's stored in a cookie so it can be reused
} elseif (isset($_COOKIE['lang']) && valid($_COOKIE['lang'])) {
    // if the cookie is present instead, let's just keep it
    $lang = $_COOKIE['lang']; //you should sanitize this!
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
    // default: look for the languages the browser says the user accepts
    $langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
    array_walk($langs, function (&$lang) { $lang = strtr(strtok($lang, ';'), ['-' => '_']); });
    foreach ($langs as $browser_lang) {
        if (valid($browser_lang)) {
            $lang = $browser_lang;
            break;
        }
    }
}

// here we define the global system locale given the found language
putenv("LANG=$lang");

// this might be useful for date functions (LC_TIME) or money formatting (LC_MONETARY), for instance
setlocale(LC_ALL, $lang);

// this will make Gettext look for ../locales/<lang>/LC_MESSAGES/main.mo
bindtextdomain('main', '../locales');

// indicates in what encoding the file should be read
bind_textdomain_codeset('main', 'UTF-8');

// if your application has additional domains, as cited before, you should bind them here as well
bindtextdomain('forum', '../locales');
bind_textdomain_codeset('forum', 'UTF-8');

// here we indicate the default domain the gettext() calls will respond to
textdomain('main');

// this would look for the string in forum.mo instead of main.mo
// echo dgettext('forum', 'Welcome back!');
?>

3. Preparing translation for the first run

One of the great advantages Gettext has over custom framework i18n packages is its extensive and powerful file format. “Oh man, that’s quite hard to understand and edit by hand, a simple array would be easier!” Make no mistake, applications like Poedit are here to help - a lot. You can get the program from their website, it’s free and available for all platforms. It’s a pretty easy tool to get used to, and a very powerful one at the same time - using all features Gettext has available. This guide is based on PoEdit 1.8.

In the first run, you should select “File > New…” from the menu. You’ll be asked straight ahead for the language: here you can select/filter the language you want to translate to, or use that format we mentioned before, such as en_US or pt_BR.

Now, save the file - using that directory structure we mentioned as well. Then you should click “Extract from sources”, and here you’ll configure various settings for the extraction and translation tasks. You’ll be able to find all those later through “Catalog > Properties”:

After setting those points it will run a scan through your source files to find all the localization calls. After every scan PoEdit will display a summary of what was found and what was removed from the source files. New entries will fed empty into the translation table, and you’ll start typing in the localized versions of those strings. Save it and a .mo file will be (re)compiled into the same folder and ta-dah: your project is internationalized.

4. Translating strings

As you may have noticed before, there are two main types of localized strings: simple ones and those with plural forms. The first ones have simply two boxes: source and localized string. The source string cannot be modified as Gettext/Poedit do not include the powers to alter your source files - you should change the source itself and rescan the files. Tip: you may right-click a translation line and it will hint you with the source files and lines where that string is being used. On the other hand, plural form strings include two boxes to show the two source strings, and tabs so you can configure the different final forms.

Whenever you change your sources and need to update the translations, just hit Refresh and Poedit will rescan the code, removing non-existent entries, merging the ones that changed and adding new ones. It may also try to guess some translations, based on other ones you did. Those guesses and the changed entries will receive a “Fuzzy” marker, indicating it needs review, appearing golden in the list. It is also useful if you have a translation team and someone tries to write something they are not sure about: just mark Fuzzy, and someone else will review later.

Finally, it is advised to leave “View > Untranslated entries first” marked, as it will help you a lot to not forget any entry. From that menu, you can also open parts of the UI that allow you to leave contextual information for translators if needed.

Tips & Tricks

Possible caching issues

If you are running PHP as a module on Apache (mod_php), you might face issues with the .mo file being cached. It happens the first time it is read, and then, to update it, you might need to restart the server. On Nginx and PHP5 it usually takes only a couple of page refreshes to refresh the translation cache, and on PHP7 it is rarely needed.

Additional helper functions

As preferred by many people, it is easier to use _() instead of gettext(). Many custom i18n libraries from frameworks use something similar to t() as well, to make translated code shorter. However, that is the only function that sports a shortcut. You might want to add in your project some others, such as __() or _n() for ngettext(), or maybe a fancy _r() that would join gettext() and sprintf() calls. Other libraries, such as php-gettext’s Gettext also provide helper functions like these.

In those cases, you’ll need to instruct the Gettext utility on how to extract the strings from those new functions. Don’t be afraid; it is very easy. It is just a field in the .po file, or a Settings screen on Poedit. In the editor, that option is inside “Catalog > Properties > Source keywords”. Remember: Gettext already knows the default functions for many languages, so don’t be afraid if that list seems empty. You need to include there the specifications of those new functions, following a specific format:

After including those new rules in the .po file, a new scan will bring in your new strings just as easy as before.

References

Volver al inicio

Inyección de Dependencias

De Wikipedia:

Inyección de dependencias (Dependency Injection, DI) es un patrón de diseño en el que se suministran objetos a una clase en lugar de ser la propia clase la que cree dichos objetos. Esos objetos cumplen contratos que necesitan nuestras clases para poder funcionar (de ahí el concepto de dependencia).

Esta cita hace que el concepto suene mucho más complejo de lo que es en realidad. La inyección de dependencias consiste en proporcionar a un componente sus dependencias a través de la inyección en constructor, llamadas a métodos o la configuración de propiedades. Es así de simple.

Concepto Básico

Podemos demostrar el concepto con un simple pero a la vez inofencivo ejemplo.

Tenemos una clase Database que requiere un adaptador para comunicarse con la base de datos. Instanciamos el adaptador en el constructor y creamos una dependencia rígida. Esto dificulta las pruebas y significa que la clase Database esta fuertemente acoplada al adaptador.

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct()
    {
        $this->adapter = new MySqlAdapter;
    }
}

class MysqlAdapter {}

Este código se puede refactorizar para usar Inyección de Dependencia y de esta manera desacoplamos la dependencia. Aquí, inyectamos la dependencia en un constructor haciendo uso de la promoción de propiedades en el constructor y de esta manera estará disponible como una propiedad de la clase:

<?php
namespace Database;

class Database
{
    public function __construct(protected MySqlAdapter $adapter)
    {
    }
}

class MysqlAdapter {}

Ahora le estamos dando a la clase Database su dependencia en lugar de crearla directamente. Incluso podríamos crear un método que acepte un argumento de la dependencia y configurarla de esa manera, o si la propiedad $adapter fuera public podríamos configurarla directamente.

Problema Complejo

Si alguna vez has leído sobre Inyección de Dependencias es probable que hayas visto los términos “Inversión de control” o “Principio de Inversión de Dependencias”. Estos son los problemas complejos que resuelve la Inyección de Dependencias.

Inversión de Control

Inversión de Control, como su nombre lo indica, es “invertir el control” de un sistema al mantener el control organizacional completamente separado de nuestros objetos. En términos de Inyección de Dependencias, esto significa desacoplar nuestras dependencias al controlarlas e instanciarlas en otras partes del sistema.

Durante años, los frameworks PHP han estado logrando la Inversión de Control, sin embargo, surge la pregunta, ¿qué parte del control estamos invirtiendo y hacia dónde? Por ejemplo, los frameworks MVC generalmente proveen un superobjeto o controlador base que otros controladores deben extender para obtener acceso a sus dependencias. Esto es Inversión de Control, sin embargo, en lugar de desacoplar las dependencias, este método simplemente las mueve.

La Inyección de Dependencia nos permite resolver este problema de una forma mas elegante al inyectar solamente las dependencias que necesitamos, cuando la necesitamos sin la necesidad de tener dependencias en duro.

S.O.L.I.D.

Principio de Responsabilidad Única

El Principio de Responsabilidad Única (en inglés Single Responsibility Principle) se refiere a los actores y a la arquitectura de alto nivel. Establece que “Una clase debería tener solo una razón para cambiar”. Esto significa que cada clase debería solo tener responsabilidad sobre una única parte de la funcionalidad proporcionada por el software. El mayor beneficio de este enfoque es que permite una mejor reutilización del código. Al diseñar nuestras clases para que hagan solo una cosa, podemos utilizar (o reutilizarlas) en cualquier otro programa sin cambiarlas.

Principio Abierto/Cerrado

El Principio abierto/cerrado (Open/Closed Principle) se refiere al diseño de clases y la extensión de funcionalidades. Establece que “Las entidades de software (clases, módulos, funciones, etc.) deben estar abiertas a la extensión, pero cerradas a la modificación”. Esto significa que debemos diseñar nuestros módulos, clases y funciones de manera que cuando se necesite una nueva funcionalidad, no deberíamos modificar nuestro código existente sino que escribamos código nuevo que será utilizado por el código existente. En términos prácticos, esto significa que deberíamos escribir clases que implementen y respeten las interfaces, luego hacer sugerencias de tipos hacía esas interfaces en lugar de clases específicas.

El mayor beneficio de este enfoque es que podemos extender nuestro código muy fácilmente con soporte para algo nuevo sin tener que modificar el código existente, lo que significa que podemos reducir el tiempo de Control de Calidad (QA) y el riesgo de un impacto negativo a la aplicación se reduce considerablemente. Podemos desplegar nuevo código más rápido y con más confianza.

Principio de Sustitución de Liskov

El Principio de Sustitución de Liskov (Liskov Substitution Principle) se refiere a la subtipificación y la herencia. Establece que “Las clases hijas nunca deben romper las definiciones de tipo de la clase padre”. O, en palabras de Robert C. Martin, “Los subtipos deben ser sustituibles por sus tipos base”.

Por ejemplo, si tenemos una interfaz FileInterface que define un método embed(), y contamos con las clases Audio y Video las cuales implementan esta interfaz FileInterface, podemos esperar que el uso del método embed() siempre haga lo que pretendemos. Si más adelante creamos una clase PDF o una clase Gist que también implementen la interfaz FileInterface, ya sabremos y entenderemos lo que hará el método embed(). El mayor beneficio de este enfoque es que tenemos la capacidad de construir programas flexibles y fácilmente configurables, ya que cuando cambiamos un objeto de un tipo (por ejemplo, FileInterface) por otro, no necesitamos modificar más nada en nuestro programa.

Principio de Segregación de Interfaces

El Principio de Segregación de Interfaces (Interface Segregation Principle, ISP por sus siglas en inglés) trata sobre la comunicación entre la lógica de negocio y los clientes. Establece que “Ningún cliente debería verse obligado a depender de métodos que no utiliza”. Esto quiere decir que en lugar de tener una única interfaz monolítica que todas las clases conformantes deben implementar, deberíamos en su lugar proporcionar un conjunto de interfaces más pequeñas y específicas por concepto que una clase conformante implemente una o más de ellas.

Por ejemplo, una clase Car o Bus estaría interesada en un método steeringWheel(), pero una clase Motorcycle o Tricycle no lo estaría. Por otro lado, una clase Motorcycle o Tricycle se interesaría en un método handlebars(), mientras que una clase Car o Bus no. No es necesario que todos estos tipos de vehículos implementen soporte tanto para steeringWheel() como para handlebars(), por lo que deberíamos descomponer la interfaz original.

Principio de Inversión de Dependencias

El Principio de Inversión de Dependencias (Dependency Inversion Principle) trata sobre eliminar los vínculos rígidos entre clases discretas para que se pueda aprovechar nueva funcionalidad al pasar una clase diferente. Establece que se debe “depender de abstracciones. No depender de implementaciones”. En términos simples, esto significa que nuestras dependencias deberían ser interfaces/contratos o clases abstractas en lugar de implementaciones concretas. Podemos fácilmente refactorizar el ejemplo anterior para seguir este principio.

<?php
namespace Database;

class Database
{
    public function __construct(protected AdapterInterface $adapter)
    {
    }
}

interface AdapterInterface {}

class MysqlAdapter implements AdapterInterface {}

Hay varios beneficios al hacer que la clase Database ahora dependa de una interfaz en lugar de una implementación concreta.

Consideremos que estamos trabajando en un equipo y el adaptador está siendo desarrollado por un colega. En nuestro primer ejemplo, tendríamos que esperar a que dicho colega termine el adaptador antes de poder simularlo adecuadamente para nuestras pruebas unitarias. Ahora que la dependencia es una interfaz/contrato, podemos simular esa interfaz sin problemas, sabiendo que nuestro colega construirá el adaptador basado en ese contrato.

Un beneficio incluso aún mayor de este enfoque es que nuestro código ahora es mucho más escalable. Si dentro de un año decidimos que queremos migrar a un tipo diferente de base de datos, podemos escribir un adaptador que implemente la interfaz original e inyectar eso en su lugar, no se requeriría más refactorización ya que podemos asegurar que el adaptador sigue el contrato establecido por la interfaz.

Contenedores

Lo primero que deberías entender sobre los Contenedores de Inyección de Dependencias es que no son lo mismo que la Inyección de Dependencias. Un contenedor es una herramienta de conveniencia que nos ayuda a implementar la Inyección de Dependencias; sin embargo, pueden ser y a menudo se usan de forma incorrecta para implementar un anti-patrón, Localización de Servicios. Inyectar un contenedor de DI como Localizador de Servicios en tus clases puede crear una dependencia más fuerte en el contenedor que la dependencia que estás reemplazando. Esto además hace que tu código sea mucho menos transparente y en última instancia más difícil de probar.

La mayoría de los frameworks modernos tienen su propio Contenedor de Inyección de Dependencias que te permite conectar tus dependencias a través de la configuración. Lo que esto significa en la práctica es que puedes escribir código de aplicación que sea tan limpio y desacoplado como el framework en el que se basa.

Lectura Adicional

Volver al inicio

Bases de Datos

Muchas veces su código PHP utilizará una base de datos para persistir información. Tiene algunas opciones para conectarse e interactuar con su base de datos. La opción recomendada hasta PHP 5.1.0 era usar controladores nativos como mysqli, pgsql, mssql, etc.

Los drivers nativos son geniales si sólo estás usando una base de datos en tu aplicación, pero si, por ejemplo, estás usando MySQL y un poco de MSSQL, o necesitas conectarte a una base de datos Oracle, entonces no podrás usar los mismos drivers. Tendrás que aprender una API completamente nueva para cada base de datos — y eso puede llegar a ser una tontería.

Extensión MySQL

La extensión mysql para PHP es increíblemente antigua y ha sido reemplazada por otras dos extensiones:

No sólo el desarrollo de mysql se detuvo hace mucho tiempo, sino que además ha sido oficialmente eliminado en PHP 7.0.

Para ahorrarse la búsqueda en su php.ini para ver qué módulo está utilizando, una opción es buscar mysql_* en el editor de su elección. Si aparecen funciones como mysql_connect() y mysql_query(), entonces está usando mysql.

Incluso si aún no está utilizando PHP 7.x o posterior, no considerar esta actualización lo antes posible le acarreará mayores dificultades cuando se produzca la actualización de PHP. La mejor opción es reemplazar el uso de mysql por mysqli o PDO en tus aplicaciones dentro de tus propios calendarios de desarrollo para que no te veas apurado más adelante.

Si está actualizando de mysql a mysqli, tenga cuidado con las guías de actualización perezosas que sugieren que simplemente puede encontrar y reemplazar mysql_* con mysqli_*. No sólo es una simplificación excesiva, sino que pierde las ventajas que mysqli proporciona, como la vinculación de parámetros, que también se ofrece en PDO.

Extensión PDO

PDO es una librería de abstracción de conexión a bases de datos — incorporada en PHP desde 5.1.0 — que proporciona una interfaz común común para hablar con muchas bases de datos diferentes. Por ejemplo, puede utilizar código básicamente idéntico para interactuar con MySQL o SQLite:

<?php
// PDO + MySQL
$pdo = new PDO('mysql:host=example.com;dbname=database', 'user', 'password');
$statement = $pdo->query("SELECT some_field FROM some_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);

// PDO + SQLite
$pdo = new PDO('sqlite:/path/db/foo.sqlite');
$statement = $pdo->query("SELECT some_field FROM some_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);

PDO no traducirá sus consultas SQL o emulará las características que faltan; es puramente para la conexión a múltiples tipos de base de datos con la misma API.

Y lo que es más importante, PDO le permite inyectar de forma segura entradas ajenas (por ejemplo, IDs) en sus consultas SQL sin preocuparse por ataques de inyección SQL a bases de datos. Esto es posible utilizando sentencias PDO y parámetros vinculados.

Supongamos que un script PHP recibe un ID numérico como parámetro de consulta. Este ID debe ser usado para obtener un registro de usuario de una base de datos. Esta es la forma incorrecta de hacer esto:

<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$pdo->query("SELECT name FROM users WHERE id = " . $_GET['id']); // <-- NO!

Este código es terrible. Estás insertando un parámetro de consulta sin procesar en una consulta SQL. Esto hará que te hackeen en un santiamén, usando una práctica llamada Inyección SQL. Imagínese si un hacker pasa un parámetro id inventivo llamando a una URL como http://domain.com/?id=1%3BDELETE+FROM+users. Esto establecerá la variable $_GET['id'] a 1;DELETE FROM users lo que borrará todos los usuarios. En su lugar, debería desinfectar/sanitizar la entrada de ID utilizando parámetros vinculados a PDO.

<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$stmt = $pdo->prepare('SELECT name FROM users WHERE id = :id');
$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT); // <-- filtre primero los datos (ver [Filtrado de Datos](#filtrado_de_datos)), especialmente importante para INSERT, UPDATE, etc.
$stmt->bindParam(':id', $id, PDO::PARAM_INT); // <-- SQL saneado automáticamente por PDO
$stmt->execute();

Este código es correcto. Utiliza un parámetro vinculado en una sentencia PDO. Esto escapa el ID de entrada externo antes de que sea introducido en la base de datos previniendo potenciales ataques de inyección SQL.

Para escrituras, como INSERT o UPDATE, es especialmente crítico filtrar sus datos primero y sanearlos para otras cosas (eliminación de etiquetas HTML, JavaScript, etc). PDO sólo lo desinfectará para SQL, no para su aplicación.

También hay que tener en cuenta que las conexiones a bases de datos consumen recursos y no era raro que se agotaran los recursos si las conexiones no se cerraban implícitamente, aunque esto era más común en otros lenguajes. Usando PDO puedes cerrar implícitamente la conexión destruyendo el objeto asegurándote de que todas las referencias restantes a él son borradas, es decir, establecidas a NULL. Si no hace esto explícitamente, PHP cerrará automáticamente la conexión cuando su script termine - a menos que esté usando conexiones persistentes.

Interacción con Bases de Datos

Cuando los desarrolladores comienzan a aprender PHP, a menudo terminan mezclando su interacción con la base de datos con su lógica de presentación, utilizando código que podría parecerse a esto:

<ul>
<?php
foreach ($db->query('SELECT * FROM table') as $row) {
    echo "<li>".$row['field1']." - ".$row['field1']."</li>";
}
?>
</ul>

Esta es una mala práctica por todo tipo de razones, principalmente que es difícil de depurar, difícil de probar, difícil de leer y que va a dar salida a un montón de campos si no pones un límite.

Aunque hay muchas otras soluciones para hacer esto - dependiendo de si prefieres POO o programación funcional - debe haber algún elemento de separación.

Considere el paso más básico:

<?php
function getAllFoos($db) {
    return $db->query('SELECT * FROM table');
}

$results = getAllFoos($db);
foreach ($results as $row) {
    echo "<li>".$row['field1']." - ".$row['field1']."</li>"; // BAD!!
}

Es un buen comienzo. Pon esos dos elementos en dos archivos diferentes y tendrás una separación limpia.

Crea una clase en la que colocar ese método y tendrás un “Modelo”. Crea un simple archivo .php para colocar la lógica de presentación y tendrás una “Vista”, que es muy parecido a MVC - una arquitectura OOP común para la mayoría de los frameworks.

foo.php

<?php
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8mb4', 'username', 'password');

// Ponga su modelo a disposición
include 'models/FooModel.php';

// Crear una instancia
$fooModel = new FooModel($db);
// Obtener la lista de Foos
$fooList = $fooModel->getAllFoos();

// Mostrar la vista
include 'views/foo-list.php';

models/FooModel.php

<?php
class FooModel
{
    public function __construct(protected PDO $db)
    {
    }

    public function getAllFoos() {
        return $this->db->query('SELECT * FROM table');
    }
}

views/foo-list.php

<?php foreach ($fooList as $row): ?>
    <li><?= $row['field1'] ?> - <?= $row['field1'] ?></li>
<?php endforeach ?>

Esto es esencialmente lo mismo que hacen la mayoría de los frameworks modernos, aunque un poco más manual. Puede que no necesites hacer todo eso cada vez, pero mezclar demasiada lógica de presentación e interacción con la base de datos puede ser un verdadero problema si alguna vez quieres hacer test unitarios a tu aplicación.

Capas de Abstracción

Muchos frameworks proporcionan su propia capa de abstracción que puede o no sentarse encima de PDO. Estos a menudo emulan características de un sistema de base de datos que falta en otro envolviendo sus consultas en métodos PHP, dándole abstracción de base de datos real en lugar de sólo la abstracción de conexión que PDO proporciona. Esto, por supuesto, añadirá un poco de sobrecarga, pero si usted está construyendo una aplicación portable que necesita trabajar con MySQL, PostgreSQL y SQLite entonces un poco de sobrecarga valdrá la pena por el bien de la limpieza del código.

Algunas capas de abstracción se han construido utilizando los estándares de espacio de nombres PSR-0 o PSR-4, por lo que pueden instalarse en cualquier aplicación que se desee:

Volver al inicio

Plantillas

Las Plantillas proporcionan una manera conveniente de separar la lógica de tu controlador y dominio de tu lógica de presentación. Las plantillas suelen contener el código HTML de tu aplicación, pero también se pueden utilizar otros formatos, como XML. A menudo se hace referencia a las plantillas como “vistas”, las cuales forman parte del segundo componente del patrón de arquitectura de software modelo-vista-controlador (MVC).

Beneficios

El principal beneficio de utilizar plantillas es la clara separación que crean entre la lógica de presentación y el resto de tu aplicación. Las plantillas tienen la única responsabilidad de mostrar contenido formateado. No son responsables de la búsqueda de datos, la persistencia u otras tareas más complejas. Esto lleva a un código más limpio y más legible, lo cual es especialmente útil en un entorno de equipo donde los desarrolladores trabajan en el código del lado del servidor (controladores, modelos) y los diseñadores trabajan en el código del lado del cliente (maquetado).

Las plantillas también mejoran la organización del código de presentación. Por lo general, las plantillas se colocan en una carpeta de “vistas” (“views”), cada una definida en un solo archivo. Este enfoque fomenta la reutilización del código, donde bloques más grandes de código se dividen en piezas más pequeñas y reutilizables, a menudo llamadas parciales (partials). Por ejemplo, el encabezado y el pie de página de tu sitio pueden definirse como plantillas, las cuales se incluyen antes y después de cada plantilla de página.

Finalmente, dependiendo de la biblioteca que uses, las plantillas pueden ofrecer más seguridad al escapar automáticamente el contenido generado por los usuarios. Algunas bibliotecas incluso ofrecen un sistema de “sand-boxing”, donde los diseñadores de plantillas solo tienen acceso a variables y funciones en una lista blanca.

Plantillas en PHP simple

Las plantillas en PHP simple son simplemente plantillas que utilizan código PHP nativo. Son una opción natural ya que PHP es, de hecho, un lenguaje de plantillas. Esto significa que puedes combinar código PHP con otro código, como HTML. Esto es beneficioso para los desarrolladores de PHP ya que no hay nueva sintaxis que aprender, conocen las funciones disponibles y sus editores de código ya tienen resaltado de sintaxis y autocompletado incorporado. Además, las plantillas en PHP simple tienden a ser muy rápidas, ya que no se requiere una etapa de compilación.

Todos los frameworks modernos de PHP emplean algún tipo de sistema de plantillas, la mayoría de los cuales utilizan PHP simple por defecto. Fuera de los frameworks, bibliotecas como Plates o Aura.View facilitan el trabajo con plantillas en PHP simple al ofrecer funcionalidades modernas de plantillas, como herencia, layouts y extensiones.

Ejemplo simple de una plantilla en PHP simple

Usando la biblioteca Plates.

<?php // user_profile.php ?>

<?php $this->insert('header', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->escape($name)?></p>

<?php $this->insert('footer') ?>

Ejemplo de plantilla en PHP simple usando herencia

Haciendo uso de la biblioteca Plates.

<?php // template.php ?>

<html>
<head>
    <title><?=$title?></title>
</head>
<body>

<main>
    <?=$this->section('content')?>
</main>

</body>
</html>
<?php // user_profile.php ?>

<?php $this->layout('template', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->escape($name)?></p>

Plantillas Compiladas

Aunque PHP ha evolucionado hasta convertirse en un lenguaje maduro, orientado a objetos, no ha mejorado mucho como lenguaje de plantillas. Las plantillas compiladas, como Twig, Brainy o Smarty*, llenan este vacío al ofrecer una nueva sintaxis diseñada específicamente para la creación de plantillas. Desde el escape automático hasta la herencia y estructuras de control simplificadas, las plantillas compiladas están diseñadas para ser más fáciles de escribir, más legibles y más seguras de usar. Las plantillas compiladas incluso pueden compartirse entre diferentes lenguajes, siendo Mustache un buen ejemplo de esto. Dado que estas plantillas deben ser compiladas, hay una ligera pérdida de rendimiento, sin embargo, esto es muy mínimo cuando se utiliza un sistema de caché apropiado.

*Aunque Smarty ofrece escape automático, NO está habilitado por defecto.

Ejemplo de una plantilla compilada

Haciendo uso de Twig.

{% include 'header.html' with {'title': 'User Profile'} %}

<h1>User Profile</h1>
<p>Hello, {{ name }}</p>

{% include 'footer.html' %}

Ejemplo de plantillas compiladas usando herencia

Usando Twig.

// template.html

<html>
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>

<main>
    {% block content %}{% endblock %}
</main>

</body>
</html>
// user_profile.html

{% extends "template.html" %}

{% block title %}User Profile{% endblock %}
{% block content %}
    <h1>User Profile</h1>
    <p>Hello, {{ name }}</p>
{% endblock %}

Lectura Adicional

Artículos & Tutoriales

Bibliotecas

Volver al inicio

Errores y Excepciones

Errores

En muchos lenguajes de programación “dependientes de excepciones”, cada vez que algo sale mal, se lanzará una excepción. Esta es una forma viable de manejar errores, pero PHP es un lenguaje “ligero en excepciones”. Aunque tiene excepciones y cada vez más partes del núcleo las usan cuando se trabaja con objetos, la mayoría de PHP intentará seguir procesando sin importar lo que suceda, a menos que ocurra un error fatal.

Por ejemplo:

$ php -a
php > echo $foo;
Notice: Undefined variable: foo in php shell code on line 1

Esto es solo un aviso, y PHP continuará felizmente. Esto puede ser confuso para aquellos que vienen de lenguajes “dependientes de excepciones”, porque en Python, por ejemplo, hacer referencia a una variable no definida lanzará una excepción:

$ python
>>> print foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined

La única diferencia real es que Python reaccionará a cualquier pequeño problema, de manera que los desarrolladores puedan estar muy seguros de que cualquier posible problema o caso límite sea detectado, mientras que PHP seguirá procesando a menos que suceda algo extremo, en cuyo caso lanzará un error y lo reportará.

Gravedad de los Errores

PHP tiene varios niveles de gravedad de errores. Los tres tipos más comunes de mensajes son errores, avisos y advertencias. Estos tienen diferentes niveles de severidad: E_ERROR, E_NOTICE, y E_WARNING. Los errores son fallos fatales en tiempo de ejecución y usualmente son causados por fallos en tu código que deben ser corregidos, ya que harán que PHP deje de ejecutarse. Los avisos son mensajes de carácter informativo causados por código que puede o no causar problemas durante la ejecución del script, pero la ejecución no se detiene. Las advertencias son errores no fatales, y la ejecución del script no se detendrá.

Otro tipo de mensaje de error reportado en tiempo de compilación son los mensajes E_STRICT. Estos mensajes se utilizan para sugerir cambios en tu código para ayudar a garantizar la mejor interoperabilidad y compatibilidad con futuras versiones de PHP.

Cambiar el Comportamiento de Reporte de Errores en PHP

El reporte de errores se puede cambiar utilizando configuraciones de PHP y/o llamadas a funciones de PHP. Usando la función incorporada error_reporting(), puedes establecer el nivel de errores durante la ejecución del script pasando una de las constantes predefinidas de nivel de error. Por ejemplo, si solo quieres ver Errores y Advertencias, pero no Avisos (Notices), puedes configurarlo así:

<?php
error_reporting(E_ERROR | E_WARNING);

También puedes controlar si los errores se muestran en pantalla (útil en desarrollo) o si se ocultan y registran (útil en producción). Para más información, revisa la sección de Reporte de Errores.

Supresión de Errores en Línea

También puedes decirle a PHP que suprima errores específicos usando el Operador de Control de Errores @. Pones este operador al inicio de una expresión, y cualquier error que sea resultado directo de la expresión se silenciará.

<?php
echo @$foo['bar'];

Esto imprimirá $foo['bar'] si existe, pero simplemente devolverá nulo y no mostrará nada si la variable $foo o la clave 'bar' no existen. Sin el operador de control de errores, esta expresión podría crear un error PHP Notice: Undefined variable: foo o PHP Notice: Undefined index: bar.

Esto podría parecer una buena idea, pero hay algunas desventajas indeseables. PHP maneja expresiones con un @ de una manera menos eficiente que expresiones sin el @. La optimización prematura puede ser la raíz de todas las discusiones de programación, pero si el rendimiento es particularmente importante para tu aplicación/biblioteca, es importante entender las implicaciones de rendimiento del operador de control de errores.

En segundo lugar, el operador de control de errores suprime completamente el error. El error no se muestra y no se envía al registro de errores. Además, los sistemas PHP estándar/producción no tienen forma de desactivar el operador de control de errores. Aunque puede que tengas razón al considerar que el error que estás viendo es inofensivo, un error diferente y menos inofensivo será igual de silencioso.

Si hay una forma de evitar el uso del operador de supresión de errores, deberías considerarla. Por ejemplo, nuestro código anterior podría reescribirse de la siguiente manera:

<?php
// Null Coalescing Operator
echo $foo['bar'] ?? '';

Un caso donde podría tener sentido suprimir un error es cuando fopen() falla al encontrar un archivo. Podrías verificar la existencia del archivo antes de intentar cargarlo, pero si el archivo se elimina después de la verificación y antes de la llamada a fopen() (lo cual puede parecer imposible, pero puede ocurrir), entonces fopen() devolverá falso y lanzará un error. Esto es algo que PHP debería resolver, pero es un caso donde la supresión de errores podría parecer la única solución válida.

Mencionamos antes que no hay forma en un sistema PHP estándar de desactivar el operador de control de errores. Sin embargo, Xdebug tiene una configuración xdebug.scream en el archivo ini que deshabilitará el operador de control de errores. Puedes configurarlo en tu archivo php.ini de la siguiente manera:

xdebug.scream = On

También puedes establecer este valor en tiempo de ejecución con la función ini_set

<?php
ini_set('xdebug.scream', '1')

Esto es muy útil cuando estás depurando código y sospechas que un error informativo está siendo suprimido. Usa scream con cuidado y como una herramienta temporal de depuración. Hay mucho código de bibliotecas PHP que puede no funcionar con el operador de control de errores deshabilitado.

ErrorException

PHP es perfectamente capaz de ser un lenguaje de programación “dependiente de excepciones” y solo requiere unas pocas líneas de código para hacer el cambio. Básicamente, puedes lanzar tus “errores” como “excepciones” usando la clase ErrorException, que extiende la clase Exception.

Esta es una práctica común implementada por un gran número de frameworks modernos como Symfony y Laravel. En modo de depuración (o modo dev), ambos frameworks mostrarán una stack trace limpia y agradable.

También hay algunos paquetes disponibles para un mejor manejo y reporte de errores y excepciones, como Whoops!, que viene con la instalación predeterminada de Laravel y puede usarse en cualquier framework.

Al lanzar errores como excepciones durante el desarrollo, puedes manejarlos mejor que con el resultado habitual, y si ves una excepción durante el desarrollo, puedes envolverla en una declaración de captura con instrucciones específicas sobre cómo manejar la situación. Cada excepción que captures hace que tu aplicación sea un poco más robusta.

Más información sobre esto y detalles sobre cómo usar ErrorException con el manejo de errores se pueden encontrar en la Clase ErrorException.

Excepciones

Las excepciones son una parte estándar de la mayoría de los lenguajes de programación populares, pero a menudo son pasadas por alto por los programadores de PHP. Lenguajes como Ruby son extremadamente dependientes de excepciones, así que cada vez que algo sale mal, como una falla en una solicitud HTTP, un error en una consulta a la base de datos, o incluso si no se puede encontrar un recurso de imagen, Ruby (o las gems utilizadas) lanzará una excepción a la pantalla, lo que significa que instantáneamente sabrás que hay un error.

PHP, por su parte, es bastante indulgente con esto, y una llamada a file_get_contents() generalmente solo te devolverá un FALSE y un warning. Muchos frameworks antiguos de PHP, como CodeIgniter, simplemente devolverán un falso, registrarán un mensaje en sus registros y tal vez te permitan usar un método como $this->upload->get_error() para ver qué salió mal. El problema aquí es que tienes que ir a buscar un error y consultar la documentación para ver cuál es el método de error para esta clase, en lugar de que se te haga extremadamente obvio.

Otro problema es cuando las clases lanzan automáticamente un error en la pantalla y terminan el proceso. Cuando haces esto, detienes a otro desarrollador de poder manejar dinámicamente ese error. Las excepciones deben lanzarse para hacer que un desarrollador sea consciente de un error; luego puede elegir cómo manejarlo. Por ejemplo:

<?php
$email = new Fuel\Email;
$email->subject('My Subject');
$email->body('How the heck are you?');
$email->to('guy@example.com', 'Some Guy');

try
{
    $email->send();
}
catch(Fuel\Email\ValidationFailedException $e)
{
    // La validación falló
}
catch(Fuel\Email\SendingFailedException $e)
{
    // El controlador no pudo enviar el correo electrónico
}
finally
{
    // Ejecutado independientemente de si se ha lanzado una excepción, y antes de que se reanude la ejecución normal
}

Excepciones SPL

La clase genérica Exception proporciona muy poco contexto de depuración para el desarrollador; sin embargo, para remediar esto, es posible crear un tipo de Exception especializado al sub-clasificar la clase genérica Exception:

<?php
class ValidationException extends Exception {}

Esto significa que puedes agregar múltiples bloques de captura y manejar diferentes excepciones de manera diferente. Esto puede llevar a la creación de un gran número de Excepciones personalizadas, algunas de las cuales podrían haberse evitado utilizando las excepciones SPL proporcionadas en la extensión SPL.

Si, por ejemplo, utilizas el método mágico __call() y se solicita un método no válido, en lugar de lanzar una Excepción estándar que es vaga, o crear una Excepción personalizada solo para eso, podrías simplemente throw new BadMethodCallException;.

Volver al inicio

Seguridad

The best resource I’ve found on PHP security is The 2018 Guide to Building Secure PHP Software by Paragon Initiative.

Seguridad de Aplicaciones Web

It is very important for every PHP developer to learn the basics of web application security, which can be broken down into a handful of broad topics:

  1. Code-data separation.
    • When data is executed as code, you get SQL Injection, Cross-Site Scripting, Local/Remote File Inclusion, etc.
    • When code is printed as data, you get information leaks (source code disclosure or, in the case of C programs, enough information to bypass ASLR).
  2. Application logic.
    • Missing authentication or authorization controls.
    • Input validation.
  3. Operating environment.
    • PHP versions.
    • Third party libraries.
    • The operating system.
  4. Cryptography weaknesses.

There are bad people ready and willing to exploit your web application. It is important that you take necessary precautions to harden your web application’s security. Luckily, the fine folks at The Open Web Application Security Project (OWASP) have compiled a comprehensive list of known security issues and methods to protect yourself against them. This is a must read for the security-conscious developer. Survive The Deep End: PHP Security by Padraic Brady is also another good web application security guide for PHP.

Hashing de Contraseñas

Eventually everyone builds a PHP application that relies on user login. Usernames and passwords are stored in a database and later used to authenticate users upon login.

It is important that you properly hash passwords before storing them. Hashing and encrypting are two very different things that often get confused.

Hashing is an irreversible, one-way function. This produces a fixed-length string that cannot be feasibly reversed. This means you can compare a hash against another to determine if they both came from the same source string, but you cannot determine the original string. If passwords are not hashed and your database is accessed by an unauthorized third-party, all user accounts are now compromised.

Unlike hashing, encryption is reversible (provided you have the key). Encryption is useful in other areas, but is a poor strategy for securely storing passwords.

Passwords should also be individually salted by adding a random string to each password before hashing. This prevents dictionary attacks and the use of “rainbow tables” (a reverse list of cryptographic hashes for common passwords.)

Hashing and salting are vital as often users use the same password for multiple services and password quality can be poor.

Additionally, you should use a specialized password hashing algorithm rather than fast, general-purpose cryptographic hash function (e.g. SHA256). The short list of acceptable password hashing algorithms (as of June 2018) to use are:

Fortunately, nowadays PHP makes this easy.

Hashing passwords with password_hash

In PHP 5.5 password_hash() was introduced. At this time it is using BCrypt, the strongest algorithm currently supported by PHP. It will be updated in the future to support more algorithms as needed though. The password_compat library was created to provide forward compatibility for PHP >= 5.3.7.

Below we hash a string, and then check the hash against a new string. Because our two source strings are different (‘secret-password’ vs. ‘bad-password’) this login will fail.

<?php
require 'password.php';

$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);

if (password_verify('bad-password', $passwordHash)) {
    // Correct Password
} else {
    // Wrong password
}

password_hash() takes care of password salting for you. The salt is stored, along with the algorithm and “cost”, as part of the hash. password_verify() extracts this to determine how to check the password, so you don’t need a separate database field to store your salts.

Filtrado de Datos

Never ever (ever) trust foreign input introduced to your PHP code. Always sanitize and validate foreign input before using it in code. The filter_var() and filter_input() functions can sanitize text and validate text formats (e.g. email addresses).

Foreign input can be anything: $_GET and $_POST form input data, some values in the $_SERVER superglobal, and the HTTP request body via fopen('php://input', 'r'). Remember, foreign input is not limited to form data submitted by the user. Uploaded and downloaded files, session values, cookie data, and data from third-party web services are foreign input, too.

While foreign data can be stored, combined, and accessed later, it is still foreign input. Every time you process, output, concatenate, or include data in your code, ask yourself if the data is filtered properly and can it be trusted.

Data may be filtered differently based on its purpose. For example, when unfiltered foreign input is passed into HTML page output, it can execute HTML and JavaScript on your site! This is known as Cross-Site Scripting (XSS) and can be a very dangerous attack. One way to avoid XSS is to sanitize all user-generated data before outputting it to your page by removing HTML tags with the strip_tags() function or escaping characters with special meaning into their respective HTML entities with the htmlentities() or htmlspecialchars() functions.

Another example is passing options to be executed on the command line. This can be extremely dangerous (and is usually a bad idea), but you can use the built-in escapeshellarg() function to sanitize the executed command’s arguments.

One last example is accepting foreign input to determine a file to load from the filesystem. This can be exploited by changing the filename to a file path. You need to remove "/", "../", null bytes, or other characters from the file path so it can’t load hidden, non-public, or sensitive files.

Sanitization

Sanitization removes (or escapes) illegal or unsafe characters from foreign input.

For example, you should sanitize foreign input before including the input in HTML or inserting it into a raw SQL query. When you use bound parameters with PDO, it will sanitize the input for you.

Sometimes it is required to allow some safe HTML tags in the input when including it in the HTML page. This is very hard to do and many avoid it by using other more restricted formatting like Markdown or BBCode, although whitelisting libraries like HTML Purifier exist for this reason.

See Sanitization Filters

Unserialization

It is dangerous to unserialize() data from users or other untrusted sources. Doing so can allow malicious users to instantiate objects (with user-defined properties) whose destructors will be executed, even if the objects themselves aren’t used. You should therefore avoid unserializing untrusted data.

Use a safe, standard data interchange format such as JSON (via json_decode and json_encode) if you need to pass serialized data to the user.

Validation

Validation ensures that foreign input is what you expect. For example, you may want to validate an email address, a phone number, or age when processing a registration submission.

See Validation Filters

Archivos de Configuración

When creating configuration files for your applications, best practices recommend that one of the following methods be followed:

Registro de Globales

NOTE: As of PHP 5.4.0 the register_globals setting has been removed and can no longer be used. This is only included as a warning for anyone in the process of upgrading a legacy application.

When enabled, the register_globals configuration setting makes several types of variables (including ones from $_POST, $_GET and $_REQUEST) available in the global scope of your application. This can easily lead to security issues as your application cannot effectively tell where the data is coming from.

For example: $_GET['foo'] would be available via $foo, which can override variables that have been declared.

If you are using PHP < 5.4.0 make sure that register_globals is off.

Reporte de Errores

Error logging can be useful in finding the problem spots in your application, but it can also expose information about the structure of your application to the outside world. To effectively protect your application from issues that could be caused by the output of these messages, you need to configure your server differently in development versus production (live).

Development

To show every possible error during development, configure the following settings in your php.ini:

display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On

Passing in the value -1 will show every possible error, even when new levels and constants are added in future PHP versions. The E_ALL constant also behaves this way as of PHP 5.4. - php.net

The E_STRICT error level constant was introduced in 5.3.0 and is not part of E_ALL, however it became part of E_ALL in 5.4.0. What does this mean? In terms of reporting every possible error in version 5.3 it means you must use either -1 or E_ALL | E_STRICT.

Reporting every possible error by PHP version

Production

To hide errors on your production environment, configure your php.ini as:

display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On

With these settings in production, errors will still be logged to the error logs for the web server, but will not be shown to the user. For more information on these settings, see the PHP manual:

Volver al inicio

Pruebas

Writing automated tests for your PHP code is considered a best practice and can lead to well-built applications. Automated tests are a great tool for making sure your application does not break when you are making changes or adding new functionality and should not be ignored.

There are several different types of testing tools (or frameworks) available for PHP, which use different approaches - all of which are trying to avoid manual testing and the need for large Quality Assurance teams, just to make sure recent changes didn’t break existing functionality.

Desarrollo Guiado por Pruebas

From Wikipedia:

Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: first the developer writes a failing automated test case that defines a desired improvement or new function, then produces code to pass that test and finally refactors the new code to acceptable standards. Kent Beck, who is credited with having developed or ‘rediscovered’ the technique, stated in 2003 that TDD encourages simple designs and inspires confidence.

There are several different types of testing that you can do for your application:

Test Unitarios

Unit Testing is a programming approach to ensure functions, classes and methods are working as expected, from the point you build them all the way through the development cycle. By checking values going in and out of various functions and methods, you can make sure the internal logic is working correctly. By using Dependency Injection and building “mock” classes and stubs you can verify that dependencies are correctly used for even better test coverage.

When you create a class or function you should create a unit test for each behavior it must have. At a very basic level you should make sure it errors if you send it bad arguments and make sure it works if you send it valid arguments. This will help ensure that when you make changes to this class or function later on in the development cycle that the old functionality continues to work as expected. The only alternative to this would be var_dump() in a test.php, which is no way to build an application - large or small.

The other use for unit tests is contributing to open source. If you can write a test that shows broken functionality (i.e. fails), then fix it, and show the test passing, patches are much more likely to be accepted. If you run a project which accepts pull requests then you should suggest this as a requirement.

PHPUnit is the de-facto testing framework for writing unit tests for PHP applications, but there are several alternatives:

Integration Testing

From Wikipedia:

Integration testing (sometimes called Integration and Testing, abbreviated “I&T”) is the phase in software testing in which individual software modules are combined and tested as a group. It occurs after unit testing and before validation testing. Integration testing takes as its input modules that have been unit tested, groups them in larger aggregates, applies tests defined in an integration test plan to those aggregates, and delivers as its output the integrated system ready for system testing.

Many of the same tools that can be used for unit testing can be used for integration testing as many of the same principles are used.

Functional Testing

Sometimes also known as acceptance testing, functional testing consists of using tools to create automated tests that actually use your application instead of just verifying that individual units of code are behaving correctly and that individual units can speak to each other correctly. These tools typically work using real data and simulating actual users of the application.

Functional Testing Tools

Desarrollo Guiado por Comportamiento

There are two different types of Behavior-Driven Development (BDD): SpecBDD and StoryBDD. SpecBDD focuses on technical behavior of code, while StoryBDD focuses on business or feature behaviors or interactions. PHP has frameworks for both types of BDD.

With StoryBDD, you write human-readable stories that describe the behavior of your application. These stories can then be run as actual tests against your application. The framework used in PHP applications for StoryBDD is Behat, which is inspired by Ruby’s Cucumber project and implements the Gherkin DSL for describing feature behavior.

With SpecBDD, you write specifications that describe how your actual code should behave. Instead of testing a function or method, you are describing how that function or method should behave. PHP offers the PHPSpec framework for this purpose. This framework is inspired by the RSpec project for Ruby.

Herramientas de Prueba Complementarias

Besides individual testing and behavior driven frameworks, there are also a number of generic frameworks and helper libraries useful for any preferred approach taken.

Volver al inicio

Servidores y Despliegue

PHP applications can be deployed and run on production web servers in a number of ways.

Plataforma como Servicio (PaaS)

PaaS provides the system and network architecture necessary to run PHP applications on the web. This means little to no configuration for launching PHP applications or PHP frameworks.

Recently PaaS has become a popular method for deploying, hosting, and scaling PHP applications of all sizes. You can find a list of PHP PaaS “Platform as a Service” providers in our resources section.

Servidores Virtuales o Dedicados

If you are comfortable with systems administration, or are interested in learning it, virtual or dedicated servers give you complete control of your application’s production environment.

nginx and PHP-FPM

PHP, via PHP’s built-in FastCGI Process Manager (FPM), pairs really nicely with nginx, which is a lightweight, high-performance web server. It uses less memory than Apache and can better handle more concurrent requests. This is especially important on virtual servers that don’t have much memory to spare.

Apache and PHP

PHP and Apache have a long history together. Apache is wildly configurable and has many available modules to extend functionality. It is a popular choice for shared servers and an easy setup for PHP frameworks and open source apps like WordPress. Unfortunately, Apache uses more resources than nginx by default and cannot handle as many visitors at the same time.

Apache has several possible configurations for running PHP. The most common and easiest to setup is the prefork MPM with mod_php. While it isn’t the most memory efficient, it is the simplest to get working and to use. This is probably the best choice if you don’t want to dig too deeply into the server administration aspects. Note that if you use mod_php you MUST use the prefork MPM.

Alternatively, if you want to squeeze more performance and stability out of Apache then you can take advantage of the same FPM system as nginx and run the worker MPM or event MPM with mod_fastcgi or mod_fcgid. This configuration will be significantly more memory efficient and much faster but it is more work to set up.

If you are running Apache 2.4 or later, you can use mod_proxy_fcgi to get great performance that is easy to setup.

Servidores Compartidos

PHP has shared servers to thank for its popularity. It is hard to find a host without PHP installed, but be sure it’s the latest version. Shared servers allow you and other developers to deploy websites to a single machine. The upside to this is that it has become a cheap commodity. The downside is that you never know what kind of a ruckus your neighboring tenants are going to create; loading down the server or opening up security holes are the main concerns. If your project’s budget can afford to avoid shared servers, you should.

Make sure your shared servers are offering the latest versions of PHP.

Construir y Desplegar su Aplicación

If you find yourself doing manual database schema changes or running your tests manually before updating your files (manually), think twice! With every additional manual task needed to deploy a new version of your app, the chances for potentially fatal mistakes increase. Whether you’re dealing with a simple update, a comprehensive build process or even a continuous integration strategy, build automation is your friend.

Among the tasks you might want to automate are:

Deployment Tools

Deployment tools can be described as a collection of scripts that handle common tasks of software deployment. The deployment tool is not a part of your software, it acts on your software from ‘outside’.

There are many open source tools available to help you with build automation and deployment, some are written in PHP others aren’t. This shouldn’t hold you back from using them, if they’re better suited for the specific job. Here are a few examples:

Phing can control your packaging, deployment or testing process from within a XML build file. Phing (which is based on Apache Ant) provides a rich set of tasks usually needed to install or update a web application and can be extended with additional custom tasks, written in PHP. It’s a solid and robust tool and has been around for a long time, however the tool could be perceived as a bit old fashioned because of the way it deals with configuration (XML files).

Capistrano is a system for intermediate-to-advanced programmers to execute commands in a structured, repeatable way on one or more remote machines. It is pre-configured for deploying Ruby on Rails applications, however you can successfully deploy PHP systems with it. Successful use of Capistrano depends on a working knowledge of Ruby and Rake.

Ansistrano is a couple of Ansible roles to easily manage the deployment process (deploy and rollback) for scripting applications such as PHP, Python and Ruby. It’s an Ansible port for Capistrano. It’s been used by quite a lot of PHP companies already.

Deployer is a deployment tool written in PHP. It’s simple and functional. Features include running tasks in parallel, atomic deployment and keeping consistency between servers. Recipes of common tasks for Symfony, Laravel, Zend Framework and Yii are available. Younes Rafie’s article Easy Deployment of PHP Applications with Deployer is a great tutorial for deploying your application with the tool.

Magallanes is another tool written in PHP with simple configuration done in YAML files. It has support for multiple servers and environments, atomic deployment, and has some built in tasks that you can leverage for common tools and frameworks.

Further reading:

Server Provisioning

Managing and configuring servers can be a daunting task when faced with many servers. There are tools for dealing with this so you can automate your infrastructure to make sure you have the right servers and that they’re configured properly. They often integrate with the larger cloud hosting providers (Amazon Web Services, Heroku, DigitalOcean, etc) for managing instances, which makes scaling an application a lot easier.

Ansible is a tool that manages your infrastructure through YAML files. It’s simple to get started with and can manage complex and large scale applications. There is an API for managing cloud instances and it can manage them through a dynamic inventory using certain tools.

Puppet is a tool that has its own language and file types for managing servers and configurations. It can be used in a master/client setup or it can be used in a “master-less” mode. In the master/client mode the clients will poll the central master(s) for new configuration on set intervals and update themselves if necessary. In the master-less mode you can push changes to your nodes.

Chef is a powerful Ruby based system integration framework that you can build your whole server environment or virtual boxes with. It integrates well with Amazon Web Services through their service called OpsWorks.

Further reading:

Continuous Integration

Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily — leading to multiple integrations per day. Many teams find that this approach leads to significantly reduced integration problems and allows a team to develop cohesive software more rapidly.

– Martin Fowler

There are different ways to implement continuous integration for PHP. Travis CI has done a great job of making continuous integration a reality even for small projects. Travis CI is a hosted continuous integration service. It can be integrated with GitHub and offers support for many languages including PHP. GitHub has continuous integration workflows with GitHub Actions.

Further reading:

Volver al inicio

Virtualización

Running your application on different environments in development and production can lead to strange bugs popping up when you go live. It’s also tricky to keep different development environments up to date with the same version for all libraries used when working with a team of developers.

If you are developing on Windows and deploying to Linux (or anything non-Windows) or are developing in a team, you should consider using a virtual machine. This sounds tricky, but besides the widely known virtualization environments like VMware or VirtualBox, there are additional tools that may help you setting up a virtual environment in a few easy steps.

Vagrant

Vagrant helps you build your virtual boxes on top of the known virtual environments and will configure these environments based on a single configuration file. These boxes can be set up manually, or you can use “provisioning” software such as Puppet or Chef to do this for you. Provisioning the base box is a great way to ensure that multiple boxes are set up in an identical fashion and removes the need for you to maintain complicated “set up” command lists. You can also “destroy” your base box and recreate it without many manual steps, making it easy to create a “fresh” installation.

Vagrant creates folders for sharing your code between your host and your virtual machine, which means that you can create and edit your files on your host machine and then run the code inside your virtual machine.

Docker

Docker - a lightweight alternative to a full virtual machine - is so called because it’s all about “containers”. A container is a building block which, in the simplest case, does one specific job, e.g. running a web server. An “image” is the package you use to build the container - Docker has a repository full of them.

A typical LAMP application might have three containers: a web server, a PHP-FPM process and MySQL. As with shared folders in Vagrant, you can leave your application files where they are and tell Docker where to find them.

You can generate containers from the command line (see example below) or, for ease of maintenance, build a docker-compose.yml file for your project specifying which to create and how they communicate with one another.

Docker may help if you’re developing multiple websites and want the separation that comes from installing each on its own virtual machine, but don’t have the necessary disk space or the time to keep everything up to date. It’s efficient: the installation and downloads are quicker, you only need to store one copy of each image however often it’s used, containers need less RAM and share the same OS kernel, so you can have more servers running simultaneously, and it takes a matter of seconds to stop and start them, no need to wait for a full server boot.

Example: Running your PHP Applications in Docker

After installing docker on your machine, you can start a web server with one command. The following will download a fully functional Apache installation with the latest PHP version, map /path/to/your/php/files to the document root, which you can view at http://localhost:8080:

docker run -d --name my-php-webserver -p 8080:80 -v /path/to/your/php/files:/var/www/html/ php:apache

This will initialize and launch your container. -d makes it run in the background. To stop and start it, simply run docker stop my-php-webserver and docker start my-php-webserver (the other parameters are not needed again).

Learn more about Docker

The command above shows a quick way to run a basic server. There’s much more you can do (and thousands of pre-built images in the Docker Hub). Take time to learn the terminology and read the Docker User Guide to get the most from it, and don’t run random code you’ve downloaded without checking it’s safe – unofficial images may not have the latest security patches. If in doubt, stick to the official repositiories.

The PHPDocker.io site will auto-generate all the files you need for a fully-featured LAMP/LEMP stack, including your choice of PHP version and extensions.

Volver al inicio

Almacenamiento en Caché

PHP is pretty quick by itself, but bottlenecks can arise when you make remote connections, load files, etc. Thankfully, there are various tools available to speed up certain parts of your application, or reduce the number of times these various time-consuming tasks need to run.

Caché Opcode

When a PHP file is executed, it must first be compiled into opcodes (machine language instructions for the CPU). If the source code is unchanged, the opcodes will be the same, so this compilation step becomes a waste of CPU resources.

An opcode cache prevents redundant compilation by storing opcodes in memory and reusing them on successive calls. It will typically check signature or modification time of the file first, in case there have been any changes.

It’s likely an opcode cache will make a significant speed improvement to your application. Since PHP 5.5 there is one built in - Zend OPcache. Depending on your PHP package/distribution, it’s usually turned on by default - check opcache.enable and the output of phpinfo() to make sure. For earlier versions there’s a PECL extension.

Read more about opcode caches:

Caché de Objetos

There are times when it can be beneficial to cache individual objects in your code, such as with data that is expensive to get or database calls where the result is unlikely to change. You can use object caching software to hold these pieces of data in memory for extremely fast access later on. If you save these items to a data store after you retrieve them, then pull them directly from the cache for following requests, you can gain a significant improvement in performance as well as reduce the load on your database servers.

Many of the popular bytecode caching solutions let you cache custom data as well, so there’s even more reason to take advantage of them. APCu and WinCache both provide APIs to save data from your PHP code to their memory cache.

The most commonly used memory object caching systems are APCu and memcached. APCu is an excellent choice for object caching, it includes a simple API for adding your own data to its memory cache and is very easy to setup and use. The one real limitation of APCu is that it is tied to the server it’s installed on. Memcached on the other hand is installed as a separate service and can be accessed across the network, meaning that you can store objects in a hyper-fast data store in a central location and many different systems can pull from it.

Note that whether the cache is shared across PHP processes depends on how PHP is used. When running PHP via PHP-FPM, the cache is shared across all processes of all pools. When running PHP as a (Fast-)CGI application inside your webserver, the cache is not shared, i.e every PHP process will have its own APCu data. When running PHP as a command line, the cache is not shared and will only exist for the duration of the command. So you have to be mindful of your situation and goals. And you might want to consider using memcached instead, as it’s not tied to the PHP processes.

In a networked configuration APCu will usually outperform memcached in terms of access speed, but memcached will be able to scale up faster and further. If you do not expect to have multiple servers running your application, or do not need the extra features that memcached offers then APCu is probably your best choice for object caching.

Example logic using APCu:

<?php
// check if there is data saved as 'expensive_data' in cache
$data = apcu_fetch('expensive_data');
if ($data === false) {
    // data is not in cache; save result of expensive call for later use
    apcu_add('expensive_data', $data = get_expensive_data());
}

print_r($data);

Note that prior to PHP 5.5, there was the APC extension which provided both an object cache and a bytecode cache. The new APCu is a project to bring APC’s object cache to PHP 5.5+, since PHP now has a built-in bytecode cache (OPcache).

Volver al inicio

Documentar tu Código

PHPDoc

PHPDoc is an informal standard for commenting PHP code. There are a lot of different tags available. The full list of tags and examples can be found at the PHPDoc manual.

Below is an example of how you might document a class with a few methods;

<?php
/**
 * @author A Name <a.name@example.com>
 * @link https://www.phpdoc.org/docs/latest/index.html
 */
class DateTimeHelper
{
    /**
     * @param mixed $anything Anything that we can convert to a \DateTime object
     *
     * @throws \InvalidArgumentException
     *
     * @return \DateTime
     */
    public function dateTimeFromAnything($anything)
    {
        $type = gettype($anything);

        switch ($type) {
            // Some code that tries to return a \DateTime object
        }

        throw new \InvalidArgumentException(
            "Failed Converting param of type '{$type}' to DateTime object"
        );
    }

    /**
     * @param mixed $date Anything that we can convert to a \DateTime object
     *
     * @return void
     */
    public function printISO8601Date($date)
    {
        echo $this->dateTimeFromAnything($date)->format('c');
    }

    /**
     * @param mixed $date Anything that we can convert to a \DateTime object
     */
    public function printRFC2822Date($date)
    {
        echo $this->dateTimeFromAnything($date)->format('r');
    }
}

The documentation for the class as a whole has the @author tag and a @link tag. The @author tag is used to document the author of the code and can be repeated for documenting several authors. The @link tag is used to link to a website indicating a relationship between the website and the code.

Inside the class, the first method has a @param tag documenting the type, name and description of the parameter being passed to the method. Additionally it has the @return and @throws tags for documenting the return type, and any exceptions that could be thrown respectively.

The second and third methods are very similar and have a single @param tag as did the first method. The important difference between the second and third methods’ doc block is the inclusion/exclusion of the @return tag. @return void explicitly informs us that there is no return; historically omitting the @return void statement also results in the same (no return) action.

Volver al inicio

Recursos

Desde la Fuente

Personas a Seguir

It’s difficult to find interesting and knowledgeable PHP community members when you are first starting out. You can find an abbreviated list of PHP community members to get you started at:

Proveedores PaaS PHP

Frameworks

Rather than re-invent the wheel, many PHP developers use frameworks to build out web applications. Frameworks abstract away many of the low-level concerns and provide helpful, easy-to-use interfaces to complete common tasks.

You do not need to use a framework for every project. Sometimes plain PHP is the right way to go, but if you do need a framework then there are three main types available:

Micro-frameworks are essentially a wrapper to route a HTTP request to a callback, controller, method, etc as quickly as possible, and sometimes come with a few extra libraries to assist development such as basic database wrappers and the like. They are prominently used to build remote HTTP services.

Many frameworks add a considerable number of features on top of what is available in a micro-framework; these are called Full-Stack Frameworks. These often come bundled with ORMs, Authentication packages, etc.

Component-based frameworks are collections of specialized and single-purpose libraries. Disparate component-based frameworks can be used together to make a micro- or full-stack framework.

Componentes

As mentioned above “Components” are another approach to the common goal of creating, distributing and implementing shared code. Various component repositories exist, the main two of which are:

Both of these repositories have command line tools associated with them to help the installation and upgrade processes, and have been explained in more detail in the Dependency Management section.

There are also component-based frameworks and component-vendors that offer no framework at all. These projects provide another source of packages which ideally have little to no dependencies on other packages, or specific frameworks.

For example, you can use the FuelPHP Validation package, without needing to use the FuelPHP framework itself.

Laravel’s Illuminate components will become better decoupled from the Laravel framework. For now, only the components best decoupled from the Laravel framework are listed above.

Otros Recursos Útiles

Cheatsheets

More best practices

News around the PHP and web development communities

You can subscribe to weekly newsletters to keep yourself informed on new libraries, latest news, events and general announcements, as well as additional resources being published every now and then:

There are also Weeklies on other platforms you might be interested in; here’s a list of some.

PHP universe

Tutoriales en Vídeo

YouTube Channels

Libros

There are many PHP books; sadly some are now quite old and no longer accurate. In particular, avoid books on “PHP 6”, a version that will now never exist. The next major release of PHP after 5.6 was “PHP 7”, partly because of this.

This section aims to be a living document for recommended books on PHP development in general. If you would like your book to be added, send a PR and it will be reviewed for relevancy.

Free Books

Volver al inicio

Comunidad

The PHP community is as diverse as it is large, and its members are ready and willing to support new PHP programmers. Consider joining your local PHP user group (PUG) or attending larger PHP conferences to learn more about the best practices shown here. You can hang out on IRC in the #phpc channel on irc.freenode.com and follow the @phpc on Twitter or Mastodon. Get out there, meet new developers, learn new topics, and above all, make new friends! Other community resources include StackOverflow.

Read the Official PHP Events Calendar

Grupos de Usuarios PHP

If you live in a larger city, odds are there’s a PHP user group nearby. You can easily find your local PUG at PHP.ug. Alternate sources might be Meetup.com or a search for php user group near me using your favorite search engine (i.e. Google). If you live in a smaller town, there may not be a local PUG; if that’s the case, start one!

Special mention should be made of two global user groups: NomadPHP and PHPWomen. NomadPHP offers twice monthly online user group meetings with presentations by some of the top speakers in the PHP community. PHPWomen is a non-exclusive user group originally targeted towards the women in the PHP world. Membership is open to everyone who supports a more diverse community. PHPWomen provide a network for support, mentorship and education, and generally promote the creating of a “female friendly” and professional atmosphere.

Read about User Groups on the PHP Wiki

Conferencias sobre PHP

The PHP community also hosts larger regional and national conferences in many countries around the world. Well-known members of the PHP community usually speak at these larger events, so it’s a great opportunity to learn directly from industry leaders.

Find a PHP Conference

ElePHPants

ElePHPant is that beautiful mascot of the PHP project with an elephant in its design. It was originally designed for the PHP project in 1998 by Vincent Pontier - spiritual father of thousands of elePHPants around the world - and ten years later adorable plush elephant toys came to birth as well. Now elePHPants are present at many PHP conferences and with many PHP developers at their computers for fun and inspiration.

Interview with Vincent Pontier