Nueva versiòn PHP 8.1!

Hemos activado la versiòn 8.1 de PHP para todos nuestros servicios de hosting.

A continuación, les presentaremos los detalles principales que destacan a esta nueva versiòn y los aspectos a tener en cuenta a la hora de optar por activarla para nuestros sitios webs.

PHP 8.1 es una continuación del progreso de PHP al proporcionar mejoras en el sistema de tipos. También se agregaron más funciones que fomentan la programación defensiva.

Algunos de los aspectos más destacados de PHP 8.1 incluyen soporte para Enums, Fibers, never return type, Intersection Types, readonly properties, y first-class callable syntax. También hay una serie de funciones que se han vuelto obsoletas, esto podría agregar algo de fricción al actualizar las aplicaciones PHP heredadas a PHP 8.1.

Enums

PHP 8.1 agrega soporte para enumeraciones. Una enumeración, o Enum para abreviar, es un tipo enumerado que tiene un número fijo de valores posibles.

Las enumeraciones están disponibles en una gran cantidad de lenguajes de programación y han sido una función solicitada con frecuencia para PHP durante muchos años.

Una analogía popular para un Enum son los palos en una baraja de cartas. Una baraja de naipes tiene cuatro palos y son fijos: tréboles, diamantes, corazones y picas.

En PHP, estos palos se pueden enumerar con un Enum:

enum Suit {
    case Clubs;
    case Diamonds;
    case Hearts;
    case Spades;
}

Con Suit Enum, ahora es posible aplicar tipos al aceptar o retornar un valor:

function pick_card(Suit $suit) {}

pick_card(Suit::Clubs); pick_card(Suit::Diamonds); pick_card(Suit::Hearts); pick_card(Suit::Spades);

En contraste con el uso interno de strings o números especiales (es decir, números mágicos) para almacenar y trabajar con parámetros; las enumeraciones hacen que el código de la aplicación sea más legible y evita una respuesta inesperada de la aplicación.

También puedes declarar una backed enum. Una backed enum contiene un valor para cada caso, donde el valor puede ser un string o número entero. Un ejemplo clásico es declarar métodos de solicitud HTTP o códigos de estado de respuesta:

namespace HTTP;

enum RequestMethod: string
{
    case DELETE  = 'DELETE';
    case GET     = 'GET';
    case HEAD    = 'HEAD';
    case OPTIONS = 'OPTIONS';
    case PATCH   = 'PATCH';
    case POST    = 'POST';
    case PUT     = 'PUT';
}

enum ResponseStatus: int
{
    case OK           = 200;
    case CLIENT_ERROR = 400;
    case SERVER_ERROR = 500;
}

Ten en cuenta que todos los valores deben ser del mismo tipo y cada valor debe ser único; el backed type está en la propia declaración de enumeración.

Las enumeraciones pueden definir métodos y también implementar interfaces, lo que significa que pueden tener comportamientos. Las principales diferencias son:

  • Las enumeraciones no pueden tener estado. Esto significa que no hay propiedades y que no puedes usar new para crear una instancia. En cambio, los métodos de instancia se aplican a casos de enumeración individuales. Como por ejemplo, en nuestra enumeración HTTP\ResponseStatus anterior, si definió un método ReasonPhrase() que devolvió un string (por ejemplo, la función pública ReasonPhrase(): string), lo invocaría usando un caso específico: echo ResponseStatus::400- >razónFrase().
  • Las enumeraciones no se pueden extender y no pueden heredar de otras clases. La visibilidad solo es útil para ocultar aspectos internos de cómo funciona la enumeración, ya que la herencia no es posible.

Las enumeraciones por si mismas implementan una de las siguientes interfaces internas:

//for standard enums:
interface UnitEnum
{
    /**
     * Returns an array with all cases for the enum implementation.
     */
    public static function cases(): array;
}

interface BackedEnum extends UnitEnum
{
    /**
     * Returns the enum case matching the given value, if found.
     * Otherwise, raises a ValueError.
     */
    public static function from(int|string $value): static;

    /**
     * Same as from(), but returns a null if no matching case is found.
     */
    public static function tryFrom(int|string $value): ?static;
}

No es posible definir explícitamente a los los métodos anteriores; el motor PHP los implementa internamente.

Fibers

Mientras que los desarrolladores han estado produciendo bibliotecas asíncronas y extensiones para PHP durante varios años, PHP 8.1 ahora ofrece soporte de motor de bajo nivel para la ejecución asíncrona a través de su nueva funcionalidad Fibers.

Fibers es una nueva característica de PHP 8.1 que brinda una concurrencia ligera y controlada hacia PHP.

En esencia, una fiber (fibra) es un bloque de código que mantiene su propio stack (variables y estado), que pueden ser iniciadas, suspendidas o terminadas de forma cooperativa por el código principal y la fibra.

Esta característica es similar a los Generators agregados en PHP 5.4. Sin embargo, con los generators, una vez que un bloque de código (una función o método) produce un valor, tenía formas limitadas de reanudar la ejecución de ese bloque; en la mayoría de los casos, se veia obligado a operar dentro de un loop para reanudar o terminar el bloque. Sin embargo, con Fibers, puede reanudar la ejecución en cualquier momento, lo que le permite pasar Fiber como un mensaje entre diferentes métodos.

Las fibras son similares a los threads (hilos) de un programa de computadora. Los threads son programados por el sistema operativo y este no garantiza cuándo ni en qué punto se pausan y reanudan los subprocesos. Las fibras son creadas, iniciadas, suspendidas y terminadas por el programa en si mismo y permite un control preciso de la ejecución del programa principal y la ejecución de la fibra.

Fibers permite que el bloque de código dentro de el suspenda al bloque mismo y retorne cualquier dato al programa principal. El programa principal permite reanudar Fiber desde el punto en que ha sido suspendido.

Es importante que la ejecución concurrente no signifique una ejecución simultánea. La fibra por sí sola no permite la ejecución simultánea de multiples fibras o del thread principal y una fibra. Sin embargo, es un camino para que los marcos PHP simultáneos administren de manera efectiva su stack de ejecución y permitan la ejecución simultánea.

Propiedades Read Only:

PHP 8.1 brinda soporte para propiedades de clase de solo lectura. Una propiedad de clase declarada de solo lectura solo se puede inicializar una vez y no se permite efectuar más cambios en la propiedad.

Las propiedades de clase de solo lectura se declaran con la palabra clave contextual de solo lectura en una propiedad con tipo.

class User {
    public readonly int $uid;

    public function __construct(int $uid) {
        $this->uid = $uid;
    }
}

Con la propiedad de solo lectura, sobrescribir la propiedad o escribir en ella desde fuera de la clase da como resultado una excepción.

$user = new User(42);
$user->uid = 16; // Not allowed.

Mejoras en el Type System:

PHP 8.1 continúa brindando sintaxis y funciones para escribir aplicaciones más defensivas y tipificadas. Esto puede significar que PHP 8.1 agregará nuevos avisos de obsolescencia en cuanto a las aplicaciones heredadas que contengan firmas que no coincidan con las clases o, en algunos casos, la ausencia de tipos al extender las clases de PHP integradas.

Intersection Types:

PHP 8.1 es compatible con los intersection types, que te permite declarar un tipo para un parámetro, propiedad o tipos de retorno y hace que los valores que pertenecen a todos los tipos de clase/interfaz cumplan con los declarados. Esto es lo opuesto a Union Types, que permite cualquiera de los tipos declarados.

La implementación de intersection types en PHP 8.1 se llama tipos de intersección «puros» porque no esta permitido combinar Union Types e Intersection Types en una misma declaración.

Los tipos de intersección se declaran combinando los nombres de clase/interfaz con un signo &.

Para ejemplo de caso de uso, considera las interfaces Iterator y Countable integradas de PHP. La interfaz Iterator hace posible iterar el objeto de clase usando foreach. Sin embargo, a menos que la misma clase implemente la interfaz Countable, no es posible llamar a la función count() en dichos objetos.

Con un tipo de intersección, ahora es posible verificar el tipo de los objetos de clase para implementar las interfaces Iterator y Countable.

function count_and_iterate(Iterator&\Countable $value) {
    foreach($value as $val) {}
    count($value);
}

En el snippet anterior, el parámetro $value debe ser un objeto de la clase que implemente las interfaces Iterator y Countable. Pasar cualquier otro valor provoca un error de tipo.

never return type

never es un nuevo tipo de retorno agregado en PHP 8.1.

Una función/método que se declara con el tipo de retorno never indica que nunca retornara un valor, y siempre lanza una excepción o termina con una llamada de die/exit.

El tipo de retorno never es similar al tipo de retorno void existente, pero el tipo never garantiza que el programa terminará o se lanzará. En otras palabras, una función/método declarado nunca debe llamar a return en absoluto, ni siquiera con la forma return;.

function redirect(string $url): never {
    header('Location: ' . $url);
    exit();
}

Final class constants:

PHP admite constantes desde la version 5.0 y ha agregado operadores de visibilidad para estos a partir de la version 5.3. En contrapartida, una interfaz o extensión de clase puede anular una constante, redefiniendo efectivamente su valor. Esto significa que si deseas depender de un valor específico de una constante, siempre debe hacerse referencia a esta a través de la interfaz o clase específica que definió originalmente el valor, en lugar de usar la notación $this:: o self:: .

Para resolver este problema, PHP 8.1 introduce la capacidad de marcar un constant final. Esto tiene la misma semántica que declarar una propiedad o un método final; extender o implementar clases no permite redefinirlo.

La palabra clave final solo se permite para constantes públicas y protegidas, ya que las constantes privadas ya están establecidas como internas para la implementación dada.

array_is_list:

PHP usa arrays tanto para listas como para mapas. Una lista es una matriz de valores donde todas las claves son números enteros y donde las claves son secuenciales, comenzando desde 0. Un mapa es una matriz de valores donde las claves pueden ser números enteros o cadenas; si las claves son números enteros, entonces no son secuenciales y/o parten de un valor distinto de 0. Por lo general, los mapas funcionan como tablas de búsqueda y son más similares a las propiedades de los objetos.

Debido a que las matrices de PHP pueden ser cualquiera de las dos, los desarrolladores a menudo necesitan variar el comportamiento según el tipo de matriz que hayan recibido. Como ejemplo, muchas bibliotecas aceptan una serie de opciones para configurar el comportamiento de objetos o aplicaciones; estos casi siempre son mapas, y nunca listas. Como tal, a menudo es conveniente determinar qué tipo de matriz tiene antes de intentar de interactuar, para generar un error de forma temprana.

Afortunadamente, en PHP 8.1, ahora tenemos una función interna dedicada para esto: array_is_list(). La función retorna True tanto para matrices vacías como para matrices que representan listas; todas las demás matrices hacen que la función retorne False.

Desempaquetado de arrays con claves de cadena

PHP 7.4 introdujo la idea de usar el operador de propagación (…) para desempaquetar arrays. Antes de la 7.4, si deseabas fusionar varias arrays, debías usar array_merge():

$matriz1 = ['perro', 'gato'];
$matriz2 = ['pájaro', 'pez'];
$all = array_merge($array1, $array2, ['lagarto', 'granja de hormigas']);

Usar el operador de propagación significa que podemos definir una matriz y desempaquetar otras matrices en ella; lo siguiente es equivalente a lo anterior:

$all = [...$matriz1, ...$matriz2, ...['lagarto', 'granja de hormigas']];

Esto solo funciona para matrices indexadas. Con PHP 8.1, esto también funciona con mapas (arrays que definen claves de cadena).

Algo a tener en cuenta: Al usar el desempaquetado, una clave que aparece en una matriz posterior sobrescribirá los valores de las matrices anteriores.

Mejoras en cURL:

PHP 8.1 agrega dos nuevas características para la extensión cURL: la capacidad de usar DNS-Over-HTTPS (DoH), así como la capacidad de cargar contenidos de string arbitrarias como archivo (en lugar de requerir un identificador de archivo o nombre de archivo).

DNS-Over-HTTPS se ha vuelto cada vez más común como medida de privacidad para evitar intromisiones de red que buscan ver qué dominios se están buscando. Normalmente, las solicitudes de DNS se realizan sin encriptación, lo que significa que un rastreador de tráfico de red puede tener una idea de a qué dominios intenta acceder un servidor y, si es un mal actor, responder con una dirección diferente que dirige el tráfico a un servidor malicioso. DNS-Over-HTTPS cifra dichas solicitudes; siempre confía en el proveedor de DoH, puedes estar seguro tanto de la privacidad como de recibir sus solicitudes donde pertenecen.

Para usar esta función, debes configurar el parámetro CURLOPT_DOH_URL en el identificador cURL:

$ch = curl_init();
curl_setopt($ch, CURLOPT_DOH_URL, 'https://mozilla.cloudflare-dns.com/dns-query');

Una vez configurado, cURL utilizará el proveedor DoH proporcionado para todas las búsquedas de DNS que realice.

Cuando se trata de proporcionar un archivo de carga a cURL, las versiones anteriores de PHP ofrecían la clase CURLFile, que aceptaba un nombre de archivo local, su MIME type y el nombre de archivo que desea proporcionar en los metadatos durante la carga. Esto causo que la carga de un archivo fuera relativamente trivial, pero ignoraron un caso extremo: si el contenido que deseaba cargar estaba en la memoria (por ejemplo, un CSV en memoria creado a partir de una consulta de base de datos), tenía que escribir el archivo en un archivo temporal (sin tener en cuenta la limpieza posterior), o saltar a través de hoops para crear data:// URIs con valores codificados en Base64.

PHP 8.1 proporciona una nueva clase, CURLStringFile, para este propósito. Acepta los mismos argumentos, con un único cambio: el primer argumento es el contenido textual completo que desea cargar:

$uploadFile = new CURLStringFile($someCsvText, 'text/csv', 'data.csv');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, ['file' => $uploadFile]);

Nuevos algoritmos de hashing:

PHP 8.1 agrega soporte para los algoritmos hash xxHash y MurMurHash3. Son algunos de los algoritmos de hashing más rápidos y pueden procesar datos fácilmente casi a la velocidad de un medio de almacenamiento. PHP es el primer lenguaje de programación que incluye estos dos algoritmos hash en su biblioteca estándar.

  • Compatibilidad con el algoritmo hash MurmurHash3.
  • Compatibilidad con algoritmos hash xxHash.
  • Benchmarks del algoritmo hash.

Deprecations:

Si bien PHP intenta seguir el control de versiones semántico, cada versión menor generalmente presenta obsolescencia a la funcionalidad existente y, a menudo, interrupciones de compatibilidad con versiones anteriores. Los desarrolladores intentan evitar esto último, pero ocasionalmente la única forma de solucionar un problema es introducir un break; esto se hace para facilitar el mantenimiento a largo plazo o evitar problemas evitables del usuario.

Las deprecations se proporcionan para señalar a los usuarios que funcionalidad probablemente cambiará o se eliminará en una próxima versión principal, ya sea la próxima versión principal inmediata o una posterior. Si excluyes E_DEPRECATION de tu log de errores (que es la sugerencia para los sistemas de producción), generalmente no los verás. Sin embargo, es bueno que revises periódicamente tu código para ver si estás usando alguna característica obsoleta, para que puedas prepararte para el futuro.

A continuación mostraremos una serie de cambios y obsolescencias que puedes encontrar al adoptar la nueva versión. Esta lista no es completa; hubo más de 2 docenas de cambios en la version 8.1.

Migración de resource:

PHP 8.1 continúa con sus esfuerzos para migrar los tipos de resources heredados a clases estándar.

En todas las versiones anteriores de PHP, los resources eran un tipo de «recurso» especial que podías verificar usando is_resource(); para diferenciar entre tipos de recursos, debías usar get_resource_type().

Esta práctica dificultó especialmente a los autores de librerias, ya que tendrían que realizar una serie de comprobaciones para validar que efectivamente tenían un recurso del tipo esperado (por ejemplo, una conexión de Postgres, un identificador cURL, etc.).

A partir de PHP 8.0, el lenguaje comenzó a convertir estos resources en clases de marcadores inmutables. Esta práctica permite escribir sugerencias para tipos de recursos específicos:

public function transform(GdImage $image): void
{
}

Tenga en cuenta, sin embargo, que estos nuevos resource types son un cambio radical en el lenguaje. Si anteriormente usabas is_resource() o get_resource_type() para validar tus recursos, estas funciones no funcionaran adecuadamente y deberás actualizar tu código para realizar una verificación de instancia, agregar una sugerencia de tipo o variar la verificación en función de la versión de PHP en uso. De lo contrario, se pueden usar exactamente de la misma manera en que se usaron los recursos originales.

Los siguientes resource types se introdujeron en PHP 8.1:

  • Los recursos de file_info ahora son instancias de finfo.
  • Los recursos de imap ahora son instancias de IMAP\Connection.
  • Los recursos ftp ahora son instancias FTP\Connection.
  • Los recursos de fuentes GD ahora son instancias de GdFont.
  • Los recursos LDAP ahora son instancias LDAP\Connection, LDAP\Result o LDAP\ResultEntry.
  • Los recursos de PostgreSQL ahora son una instancia de PgSql\Connection, PgSql\Result o PgSql\Lob.
  • Los recursos de PSpell ahora son una instancia de PSpell\Dictionary o PSpell\Config.

Return types en interfaces y clases internas:

En los últimos diez años, PHP como lenguaje se ha inclinado hacia un uso más explícito y tipificado. En PHP 8.0, el lenguaje comenzó a agregar sugerencias de tipo a los argumentos de funciones y métodos, generando errores fatales para firmas incompatibles en implementaciones y clases extendidas.

PHP 8.1 continúa con esto al agregar tipos de retorno explícitos a todas las interfaces y clases internas, y ahora genera un aviso de obsolescencia cuando las implementaciones o extensiones proveen firmas incompatibles. Además, este aviso de obsolescencia se convierte en un error fatal cuando se habilita la declaración (strict_types=1).

Si tu código necesita admitir varias versiones de PHP, o si no puedes cambiar los tipos de retorno para que se ajusten a las firmas originales, puedes agregar el siguiente atributo #[\ReturnTypeWillChange] a cada método infractor:

/**
 * This is a method that has an incompatible return type.
 *
 * @return null|int
 */
#[\ReturnTypeWillChange]
public function count()
{
}

Nota: los nombres de los atributos se resuelven a partir del namespace actual. Debe calificar globalmente el nombre (como se hizo anteriormente) o importar el nombre del atributo a su archivo (por ejemplo, usando ReturnTypeWillChange;).

Cambios en error modes en mysqli:

En las versiones anteriores de PHP, la extensión mysqli silenciaría los errores de forma predeterminada. Puede habilitar el informe de errores (generar una excepción o desencadenar un error) configurando el modo de manejo de errores a través de la función mysqli_report() antes de realizar una conexión; de lo contrario, deberá verificar manualmente los estados de retorno de varias funciones y/o métodos.

Con PHP 8.1, el modo de error de mysqli está como predeterminado en MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT, lo que hace que genere excepciones. Si previamente has verficado y manejado manualmente las condiciones de error, deberás llamar a mysqli_report (MYSQLI_REPORT_OFF) antes de realizar una conexión.

Algoritmo de firma predeterminado de Phar:

Las versiones anteriores de PHP usaban el algoritmo SHA1 para firmar un archivo PHAR. PHP 8.1 agrega dos nuevos algoritmos de firma, SHA256 y SHA512, y ahora el valor predeterminado es SHA256. En la mayoría de los casos, esto no debería afectar a los usuarios, a menos que vuelvan a empaquetar un archivo PHAR previamente empaquetado. En tales casos, utilice Phar::getSignature() para determinar la firma utilizada originalmente y proporcione ese valor a Phar::setSignatureAlgorithm() antes de empaquetar el PHAR.

Pasaje de parámetros nulos a no anulables:

No esta permitido pasar un valor nulo a una función que no acepta valores NULL o un parámetro de método. Sin embargo, solo se ha aplicado para funciones y métodos definidos por el usuario, y no para funciones internas de PHP. A partir de PHP 8.1, pasar un valor nulo a una función o método PHP interno que no admite valores NULL genera una advertencia de obsolescencia o, si se declaró declare(strict_types=1), genera un TypeError.

Este es un error increíblemente sutil de detectar. Anteriormente PHP forzaba valores nulos cuando podía; como cuando algo marcado como string se convertiría en un string vacío, cuando un int se forzaba a 0, y así sucesivamente. Al no generarse advertencias, ni siquiera con los strict types habilitados habrían sido detectados. Asegúrate de tener un marco de prueba sólido y completo para detectarlos, especialmente si habilitas strict types.

funciones mhash

Antes de PHP 5.3, el lenguaje incluía la extensión mhash, que proveía una serie de funciones para proporcionar hashes criptográficos de valores. A partir de PHP 5.3, el lenguaje introdujo una nueva extensión «hash» que brindo una mejor funcionalidad y proporcionó una forma compatible para adoptar nuevos algoritmos hash. Desde la version PHP 7.4, la extensión hash ha sido parte de la distribución principal, lo que significa que ya no es posible compilar sin ella.

Con PHP 8.1, la extensión «mhash» ahora está formalmente obsoleta, su eliminación esta programada para efectuarse en PHP 9.0. Si aún confías en mhash, es hora de comenzar a migrar a la extensión hash.

Conversión implícita de float a int

PHP ha permitido la coerción implícita de valores flotantes a valores int. Cuando se proporciona un valor flotante donde se requiere un número entero, el lenguaje lo convierte realizando una operación de floor(). Sin embargo, esto puede conducir a la pérdida de datos, lo que puede generar errores difíciles de rastrear.

A partir de PHP 8.1, estas operaciones generan un aviso de obsolescencia o en el caso en que los strict types estén habilitados, arrojara un TypeError.

Uno de esos casos es el uso de claves de matriz flotante. Se espera que las claves de matriz sean cadena de caracteres o números enteros. Los números flotantes estaban permitidos anteriormente, pero se cambiaron silenciosamente a números enteros, esto provocaría colisiones y una posible sobrescritura.

Si actualmente no estás utilizando strict types, entonces ahora estas al tanto de los cambios que debes realizar para asegurarte de que tu aplicación sea compatible con versiones posteriores. Si estás utilizando strict types, esto implicara una interrupción inmediata de la compatibilidad con versiones anteriores. Asegúrate de que tu aplicación sea testeada exhaustivamente antes de actualizar para que pueda detectar estos problemas.

Deja una respuesta