PHP, un año más tarde

Nov 21, 2015

Hace muchos años que conozco PHP, pero trabajar con el a nivel profesional, hace solo un año. Durante este último, he tenido que lidiar con una serie de problemas que presenta el lenguaje y que creo que sería interesante compartir con aquellos que se dedican diariamente al desarrollo profesional en PHP.

Los problemas se pueden caracterizar en varios tipos:

  • Problemas de tipos: PHP es un lenguaje de tipado débil, lo que hace que haya que tomar decisiones a la hora de convertir tipos, y que, en muchos casos no son las más intuitivas.
  • Flexibilidad en la sintaxis: Existen ciertas formas de sintaxis que, aunque parecen intuitivas para el programador, en PHP no se pueden llevar a cabo.
  • Paradigma: Si eres programador de PHP desde hace años, habrás podido observar como a lo largo del tiempo el lenguaje ha ido variando el paradigma de programación principal, desde programación estructurada, programación orientada a objetos o programación funcional, llegando a un punto de no saber en qué paradigma se está programando. Múltiples veces dentro de un objecto se tienen que usar funciones de un paradigma estructurado porque no tienen interfaz como objecto.
  • Extensiones: La instalación de nuevas librerías que permiten extender el core del lenguaje provocan muchos problemas, incluso, para instalar alguna librería concreta, me he visto obligado a recompilar PECL, el instalador de paquetes. Es decir, para instalar un nuevo paquete necesito instalar de nuevo el instalador de paquetes.
  • Errores en general: El core está lleno de errores de funciones que no hacen lo que dicen hacer, o lo hacen de forma que en ciertos casos no es útil. Como ejemplo, las funciones de generación de números aleatorios, que, aunque sí los generan, no tienen la suficiente entropía para ser criptográficamente seguros.

Sin más, vamos a ver ejemplos de errores que explican el por qué de los apartados anteriores. Cabe destacar que me he limitado a reproducir un solo caso de cada error, hay muchas más funciones que tienen errores similares.

Bucle foreach

<?php

$numbers = array(
'one' => 1,
'two' => 2,
'three' => 3
);

foreach($numbers as &$number);
foreach($numbers as $number);

print_r($numbers); //Array ( [one] => 1 [two] => 2 [three] => 2 )
?>

La referencia del la variable &$number en un foreach permanece después de salir del bucle.

El problema se soluciona liberando la variable después del primer bucle. Será corregido en PHP 7.

<?php

$numbers = array(
'one' => 1,
'two' => 2,
'three' => 3
);

foreach($numbers as &$number);
unset($number);
foreach($numbers as $number);

print_r($numbers);

?>

Además, también presenta problemas a la hora de iterar un array ordenado por índice, ya que los arrays en PHP, en realidad, son hashmaps.

<?php

$arr[2] = 2;
$arr[1] = 1;
$arr[0] = 0;
foreach ($arr as $elem) { echo "$elem "; } // 2 1 0

?>

Objetos json vacíos en json_encode

<?php

$numbers = array(
  'one' => 1,
  'two' => 2,
  'three' => 3
);

echo json_encode($numbers); //{"one":1,"two":2,"three":3}
echo json_encode(array()); // []

?>

Cuando se serializa un array a json utilizado la función json_encode, es imposible obtener la salida objecto vacío "{}". Siempre devuelve un array json, un objecto json o un array json vacío.

Booleanos

<?php

var_dump('false' == true); //bool(true)
var_dump('false' == false); //bool(false)
var_dump('false' == 0); //bool(true)
var_dump(false == 0); //bool(true)

?>

Este es uno de los punto más problemáticos del lenguaje. Si aplicamos transitividad en las líneas 4, 5, y 6 llegamos a una contradicción.

Premisa 1: 'false' != false Premisa 2: 'false' == 0 Luego podemos concluír que 0 != false cuando no es así según la línea 6.

Como consecuencia de estas incongruencias de tipos, nacen errores propagados a funciones como por ejemplo.

<?php

strpos('abc', 'a'); // return 0

?>

Pero 0... ¿quiere decir que está en la posición 0 o es false? Para solucionar esto se utiliza el operador === que hace distinción entre tipos.

Conversiones de tipos

Como he mencionado anteriormente, cuando se hacen conversiones de tipos en un lenguaje donde el tipado es débil la toma de decisiones por parte de los desarrolladores del lenguaje cobra vida. Estas son alguna de las decisiones que ha tomado los programadores de PHP.

<?php

echo intval(42);                      // 42
echo intval(4.2);                     // 4
echo intval('42');                    // 42
echo intval('+42');                   // 42
echo intval('-42');                   // -42
echo intval(042);                     // 34
echo intval('042');                   // 42
echo intval(1e10);                    // 1410065408
echo intval('1e10');                  // 1
echo intval(0x1A);                    // 26
echo intval(42000000);                // 42000000
echo intval(420000000000000000000);   // 0
echo intval('420000000000000000000'); // 2147483647
echo intval(42, 8);                   // 42
echo intval('42', 8);                 // 34
echo intval(array());                 // 0
echo intval(array('foo', 'bar'));     // 1

?>

Como se puede observar, han decidido que si se inserta un numero 042 se interprete como octal dando el resultado 34. Algo que puede resultar bastante problemático.

También han decidido que si un string empieza por un número '1e10' se tenga encuenta el primer número y se ignore el resto de caracteres. Esto se puede ver más claro en el siguiente ejemplo:

<?php

echo '3 cerditos' + 5; // 8

?>

La inteligencia artificial del intérprete de PHP

Se nota que desarrolladores del lenguaje han querido dotar a PHP de cierta inteligencia artificial y que muestre los mínimos errores posibles independientemente si el código sigue la sintaxis correcta o no. Por tanto, existen casos como el siguiente, donde el interprete toma decisiones por si solo.

<?php

$numbers = array(
  'one' => 1,
  'two' => 2,
  'three' => 3
);

echo $numbers[three];
//Salida con error_reporting(~E_NOTICE);
//3

//Salida con error_reporting(E_NOTICE);
//Notice: Use of undefined constant three - assumed 'three' in [...] on line 7
//3

?>

Incongruencias de la sintaxis

Hay determinadas instrucciones que, aunque tienen un comportamiento diferente, son sintácticamente iguales.

$now = new DateTime();

echo (new DateTime())->format('Y-m-d'); // Funciona
echo (clone $now)->format('Y-m-d'); //Error
echo (clone (new DateTime()))->format('Y-m-d'); // Error
echo call_user_func(array(clone $now, 'format'), 'Y-m-d'); //Funciona

En este caso, no tiene sentido que si new permite llamar al método directamente, clone no lo permita, ya que, en ambos casos, de devuelve una referencia a un objeto.

Conclusión

El lenguaje PHP ha tenido unos comienzos bastante caóticos, pero desde PHP 5.3 en adelante, creo que ha sido un punto de inflexión y el comienzo de una transición hacia algo más coherente. Se ha volcado en la programación orientada a objetos, lo que hace que el el tipado débil comienze a desaparacer permitiendo cada vez más zonas donde restringir tipos: parámetros de funciones, y en PHP 7 también los return.

Todavía quedan unos años para ver un lenguaje a la altura de la madurez de Java. Esperemos que llegue a tiempo y siga el ritmo de la evolución tecnológica.