Código PHP con errores: los 10 errores más comunes que cometen los desarrolladores de PHP

Publicado: 2022-03-11

PHP hace que sea relativamente fácil construir un sistema basado en web, lo cual es en gran parte la razón de su popularidad. Pero a pesar de su facilidad de uso, PHP se ha convertido en un lenguaje bastante sofisticado con muchos marcos, matices y sutilezas que pueden molestar a los desarrolladores, lo que lleva a horas de depuración. Este artículo destaca diez de los errores más comunes que los desarrolladores de PHP deben tener en cuenta.

Error común n.º 1: dejar referencias de matrices colgantes después de bucles foreach

¿No está seguro de cómo usar bucles foreach en PHP? El uso de referencias en bucles foreach puede ser útil si desea operar en cada elemento de la matriz sobre la que está iterando. Por ejemplo:

 $arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)

El problema es que, si no tienes cuidado, esto también puede tener algunos efectos secundarios y consecuencias indeseables. Específicamente, en el ejemplo anterior, después de ejecutar el código, $value permanecerá en el alcance y mantendrá una referencia al último elemento de la matriz. Por lo tanto, las operaciones posteriores que involucren $value podrían terminar modificando involuntariamente el último elemento de la matriz.

Lo más importante que debe recordar es que foreach no crea un alcance. Por lo tanto, $value en el ejemplo anterior es una referencia dentro del alcance superior del script. En cada iteración, foreach establece la referencia para que apunte al siguiente elemento de $array . Después de que se completa el ciclo, por lo tanto, $value aún apunta al último elemento de $array y permanece dentro del alcance.

Aquí hay un ejemplo del tipo de errores evasivos y confusos que esto puede provocar:

 $array = [1, 2, 3]; echo implode(',', $array), "\n"; foreach ($array as &$value) {} // by reference echo implode(',', $array), "\n"; foreach ($array as $value) {} // by value (ie, copy) echo implode(',', $array), "\n";

El código anterior generará lo siguiente:

 1,2,3 1,2,3 1,2,2

No, eso no es un error tipográfico. El último valor en la última línea es de hecho un 2, no un 3.

¿Por qué?

Después de pasar por el primer ciclo foreach , $array permanece sin cambios pero, como se explicó anteriormente, $value se deja como una referencia colgante al último elemento en $array (ya que ese ciclo foreach accedió a $value por referencia ).

Como resultado, cuando pasamos por el segundo bucle foreach , parece que suceden "cosas raras". Específicamente, dado que ahora se accede a $value por valor (es decir, por copia ), foreach copia cada elemento secuencial $array en $value en cada paso del ciclo. Como resultado, esto es lo que sucede durante cada paso del segundo bucle foreach :

  • Paso 1: copia $array[0] (es decir, "1") en $value (que es una referencia a $array[2] ), por lo que $array[2] ahora es igual a 1. Entonces $array ahora contiene [1, 2, 1].
  • Paso 2: copia $array[1] (es decir, "2") en $value (que es una referencia a $array[2] ), por lo que $array[2] ahora es igual a 2. Entonces $array ahora contiene [1, 2, 2].
  • Paso 3: copia $array[2] (que ahora es igual a "2") en $value (que es una referencia a $array[2] ), por lo que $array[2] sigue siendo igual a 2. Entonces $array ahora contiene [1 , 2, 2].

Para obtener el beneficio de usar referencias en bucles foreach sin correr el riesgo de este tipo de problemas, llame a unset() en la variable, inmediatamente después del bucle foreach , para eliminar la referencia; p.ej:

 $arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value no longer references $arr[3]

Error común #2: No entender el comportamiento de isset()

A pesar de su nombre, isset() no solo devuelve false si un elemento no existe, sino que también devuelve false para valores null .

Este comportamiento es más problemático de lo que podría parecer al principio y es una fuente común de problemas.

Considera lo siguiente:

 $data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }

El autor de este código presumiblemente quería verificar si keyShouldBeSet estaba configurado en $data . Pero, como se discutió, isset($data['keyShouldBeSet']) también devolverá false si se configuró $data['keyShouldBeSet'] , pero se configuró en null . Así que la lógica anterior es defectuosa.

Aquí hay otro ejemplo:

 if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }

El código anterior asume que si $_POST['active'] devuelve true , necesariamente se establecerá postData y, por lo tanto, isset($postData) devolverá true . Por el contrario, el código anterior asume que la única forma en que isset($postData) devolverá false es si $_POST['active'] también devuelve false .

No.

Como se explicó, isset($postData) también devolverá false si $postData se estableció en null . Por lo tanto, es posible que isset($postData) devuelva false incluso si $_POST['active'] devuelve true . Entonces, nuevamente, la lógica anterior es defectuosa.

Y, por cierto, como punto adicional, si la intención en el código anterior realmente era verificar nuevamente si $_POST['active'] devolvía verdadero, confiar en isset() para esto fue una mala decisión de codificación en cualquier caso. En cambio, hubiera sido mejor simplemente volver a $_POST['active'] ; es decir:

 if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }

Sin embargo, para los casos en los que es importante verificar si una variable realmente se configuró (es decir, para distinguir entre una variable que no se configuró y una variable que se configuró como null ), el método array_key_exists() es mucho más robusto. solución.

Por ejemplo, podríamos reescribir el primero de los dos ejemplos anteriores de la siguiente manera:

 $data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) { // do this if 'keyShouldBeSet' isn't set }

Además, al combinar array_key_exists() con get_defined_vars() , podemos verificar de manera confiable si una variable dentro del alcance actual se ha establecido o no:

 if (array_key_exists('varShouldBeSet', get_defined_vars())) { // variable $varShouldBeSet exists in current scope }

Error común n.º 3: Confusión sobre la devolución por referencia frente a por valor

Considere este fragmento de código:

 class Config { private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

Si ejecuta el código anterior, obtendrá lo siguiente:

 PHP Notice: Undefined index: test in /path/to/my/script.php on line 21

¿Qué ocurre?

El problema es que el código anterior confunde la devolución de matrices por referencia con la devolución de matrices por valor. A menos que le diga explícitamente a PHP que devuelva una matriz por referencia (es decir, mediante el uso de & ), PHP de forma predeterminada devolverá la matriz "por valor". Esto significa que se devolverá una copia de la matriz y, por lo tanto, la función llamada y la persona que llama no accederán a la misma instancia de la matriz.

Entonces, la llamada anterior a getValues() devuelve una copia de la matriz $values ​​en lugar de una referencia a ella. Con eso en mente, revisemos las dos líneas clave del ejemplo anterior:

 // getValues() returns a COPY of the $values array, so this adds a 'test' element // to a COPY of the $values array, but not to the $values array itself. $config->getValues()['test'] = 'test'; // getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't // contain a 'test' element (which is why we get the "undefined index" message). echo $config->getValues()['test'];

Una posible solución sería guardar la primera copia de la matriz $values ​​devuelta por getValues() y luego operar en esa copia posteriormente; p.ej:

 $vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];

Ese código funcionará bien (es decir, generará una test sin generar ningún mensaje de "índice indefinido"), pero dependiendo de lo que intente lograr, este enfoque puede o no ser adecuado. En particular, el código anterior no modificará la matriz $values ​​original. Entonces, si desea que sus modificaciones (como agregar un elemento de 'prueba') afecten la matriz original, en su lugar, deberá modificar la función getValues() para devolver una referencia a la matriz $values ​​en sí. Esto se hace agregando un & antes del nombre de la función, lo que indica que debe devolver una referencia; es decir:

 class Config { private $values = []; // return a REFERENCE to the actual $values array public function &getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

El resultado de esto será test , como se esperaba.

Pero para hacer las cosas más confusas, considere en su lugar el siguiente fragmento de código:

 class Config { private $values; // using ArrayObject rather than array public function __construct() { $this->values = new ArrayObject(); } public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

Si supuso que esto daría como resultado el mismo error de "índice indefinido" que nuestro ejemplo de array anterior, estaba equivocado. De hecho, este código funcionará bien. La razón es que, a diferencia de los arreglos, PHP siempre pasa los objetos por referencia . ( ArrayObject es un objeto SPL, que imita completamente el uso de arreglos, pero funciona como un objeto).

Como demuestran estos ejemplos, no siempre es del todo obvio en PHP si se trata de una copia o una referencia. Por lo tanto, es esencial comprender estos comportamientos predeterminados (es decir, las variables y las matrices se pasan por valor; los objetos se pasan por referencia) y también revisar cuidadosamente la documentación de la API para la función que está llamando para ver si está devolviendo un valor, un copia de una matriz, una referencia a una matriz o una referencia a un objeto.

Dicho todo esto, es importante tener en cuenta que la práctica de devolver una referencia a una matriz o un ArrayObject generalmente es algo que debe evitarse, ya que brinda a la persona que llama la capacidad de modificar los datos privados de la instancia. Esto "vuela en la cara" de la encapsulación. En su lugar, es mejor usar "captadores" y "establecedores" de estilo antiguo, por ejemplo:

 class Config { private $values = []; public function setValue($key, $value) { $this->values[$key] = $value; } public function getValue($key) { return $this->values[$key]; } } $config = new Config(); $config->setValue('testKey', 'testValue'); echo $config->getValue('testKey'); // echos 'testValue'

Este enfoque le da a la persona que llama la capacidad de establecer u obtener cualquier valor en la matriz sin proporcionar acceso público a la propia matriz $values , que de otro modo sería privada.

Error común n.º 4: realizar consultas en bucle

No es raro encontrar algo como esto si su PHP no funciona:

 $models = []; foreach ($inputValues as $inputValue) { $models[] = $valueRepository->findByValue($inputValue); }

Si bien puede que no haya absolutamente nada de malo aquí, pero si sigue la lógica del código, puede encontrar que la llamada de aspecto inocente anterior a $valueRepository->findByValue() finalmente da como resultado una consulta de algún tipo, como:

 $result = $connection->query("SELECT `x`,`y` FROM `values` WHERE `value`=" . $inputValue);

Como resultado, cada iteración del ciclo anterior daría como resultado una consulta separada a la base de datos. Entonces, si, por ejemplo, proporcionó una matriz de 1,000 valores al ciclo, ¡generaría 1,000 consultas separadas al recurso! Si se llama a una secuencia de comandos de este tipo en varios subprocesos, es posible que el sistema se detenga por completo.

Por lo tanto, es crucial reconocer cuándo el código realiza consultas y, siempre que sea posible, recopilar los valores y luego ejecutar una consulta para obtener todos los resultados.

Un ejemplo de un lugar bastante común para encontrar consultas que se realizan de manera ineficiente (es decir, en un bucle) es cuando se publica un formulario con una lista de valores (ID, por ejemplo). Luego, para recuperar los datos de registro completos para cada una de las ID, el código recorrerá la matriz y realizará una consulta SQL separada para cada ID. Esto a menudo se verá algo como esto:

 $data = []; foreach ($ids as $id) { $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = " . $id); $data[] = $result->fetch_row(); }

Pero lo mismo se puede lograr de manera mucho más eficiente en una sola consulta SQL de la siguiente manera:

 $data = []; if (count($ids)) { $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids)); while ($row = $result->fetch_row()) { $data[] = $row; } }

Por lo tanto, es crucial reconocer cuándo se realizan consultas, ya sea directa o indirectamente, por su código. Siempre que sea posible, recopile los valores y luego ejecute una consulta para obtener todos los resultados. Sin embargo, también se debe tener precaución allí, lo que nos lleva a nuestro siguiente error común de PHP...

Error común n.º 5: falsificaciones e ineficiencias en el uso de la memoria

Si bien obtener muchos registros a la vez es definitivamente más eficiente que ejecutar una sola consulta para obtener cada fila, este enfoque puede conducir potencialmente a una condición de "memoria insuficiente" en libmysqlclient cuando se usa la extensión mysql de PHP.

Para demostrarlo, echemos un vistazo a un cuadro de prueba con recursos limitados (512 MB de RAM), MySQL y php-cli .

Arrancaremos una tabla de base de datos como esta:

 // connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); // create table of 400 columns $query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT'; for ($col = 0; $col < 400; $col++) { $query .= ", `col$col` CHAR(10) NOT NULL"; } $query .= ');'; $connection->query($query); // write 2 million rows for ($row = 0; $row < 2000000; $row++) { $query = "INSERT INTO `test` VALUES ($row"; for ($col = 0; $col < 400; $col++) { $query .= ', ' . mt_rand(1000000000, 9999999999); } $query .= ')'; $connection->query($query); }

Bien, ahora verifiquemos el uso de recursos:

 // connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); echo "Before: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1'); echo "Limit 1: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000'); echo "Limit 10000: " . memory_get_peak_usage() . "\n";

Producción:

 Before: 224704 Limit 1: 224704 Limit 10000: 224704

Frio. Parece que la consulta se administra internamente de manera segura en términos de recursos.

Sin embargo, solo para estar seguros, aumentemos el límite una vez más y fijémoslo en 100,000. UH oh. Cuando hacemos eso, obtenemos:

 PHP Warning: mysqli::query(): (HY000/2013): Lost connection to MySQL server during query in /root/test.php on line 11

¿Qué sucedió?

El problema aquí es la forma en que funciona el módulo mysql de PHP. Realmente es solo un proxy para libmysqlclient , que hace el trabajo sucio. Cuando se selecciona una parte de los datos, va directamente a la memoria. Dado que esta memoria no es administrada por el administrador de PHP, memory_get_peak_usage() no mostrará ningún aumento en la utilización de recursos a medida que aumentamos el límite en nuestra consulta. Esto conduce a problemas como el que se muestra arriba, en el que nos engañan para que nos complazcamos pensando que nuestra gestión de la memoria está bien. Pero en realidad, nuestra gestión de la memoria tiene fallas graves y podemos experimentar problemas como el que se muestra arriba.

Al menos puede evitar el headfake anterior (aunque no mejorará en sí mismo la utilización de la memoria) utilizando en su lugar el módulo mysqlnd . mysqlnd se compila como una extensión nativa de PHP y utiliza el administrador de memoria de PHP.

Por lo tanto, si ejecutamos la prueba anterior usando mysqlnd en lugar de mysql , obtenemos una imagen mucho más realista de la utilización de nuestra memoria:

 Before: 232048 Limit 1: 324952 Limit 10000: 32572912

Y es incluso peor que eso, por cierto. De acuerdo con la documentación de PHP, mysql usa el doble de recursos que mysqlnd para almacenar datos, por lo que el script original que usaba mysql realmente usaba incluso más memoria que la que se muestra aquí (aproximadamente el doble).

Para evitar este tipo de problemas, considere limitar el tamaño de sus consultas y usar un ciclo con una pequeña cantidad de iteraciones; p.ej:

 $totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i <= ceil($totalNumberToFetch / $portionSize); $i++) { $limitFrom = $portionSize * $i; $res = $connection->query( "SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize"); }

Cuando consideramos este error de PHP y el error n.° 4 anterior, nos damos cuenta de que existe un equilibrio saludable que su código idealmente debe lograr entre, por un lado, que sus consultas sean demasiado granulares y repetitivas, y que cada una de sus las consultas individuales sean demasiado grandes. Como ocurre con la mayoría de las cosas en la vida, se necesita equilibrio; cualquiera de los extremos no es bueno y puede causar problemas con PHP que no funciona correctamente.

Error común n.º 6: ignorar los problemas de Unicode/UTF-8

En cierto sentido, esto es realmente más un problema en PHP que algo con lo que te encontrarías al depurar PHP, pero nunca se ha abordado adecuadamente. El núcleo de PHP 6 debía ser compatible con Unicode, pero eso se suspendió cuando se suspendió el desarrollo de PHP 6 en 2010.

Pero eso de ninguna manera absuelve al desarrollador de manejar correctamente UTF-8 y evitar la suposición errónea de que todas las cadenas serán necesariamente "ASCII normal y corriente". El código que no maneja adecuadamente las cadenas que no son ASCII es notorio por introducir errores heisenbug retorcidos en su código. Incluso las simples strlen($_POST['name']) podrían causar problemas si alguien con un apellido como "Schrodinger" intentara registrarse en su sistema.

Aquí hay una pequeña lista de verificación para evitar tales problemas en su código:

  • Si no sabe mucho sobre Unicode y UTF-8, al menos debería aprender los conceptos básicos. Hay una gran cartilla aquí.
  • Asegúrese de usar siempre las funciones mb_* en lugar de las antiguas funciones de cadena (asegúrese de que la extensión "multibyte" esté incluida en su compilación de PHP).
  • Asegúrese de que su base de datos y tablas estén configuradas para usar Unicode (muchas compilaciones de MySQL todavía usan latin1 de forma predeterminada).
  • Recuerde que json_encode() convierte símbolos que no son ASCII (p. ej., "Schrodinger" se convierte en "Schr\u00f6dinger") pero serialize() no lo hace.
  • Asegúrese de que sus archivos de código PHP también estén codificados en UTF-8 para evitar colisiones al concatenar cadenas con constantes de cadena codificadas o configuradas.

Un recurso particularmente valioso en este sentido es la publicación UTF-8 Primer for PHP and MySQL de Francisco Claria en este blog.

Error común #7: Asumir que $_POST siempre contendrá sus datos POST

A pesar de su nombre, la matriz $_POST no siempre contendrá sus datos POST y se puede encontrar fácilmente vacía. Para entender esto, echemos un vistazo a un ejemplo. Supongamos que hacemos una solicitud de servidor con una llamada jQuery.ajax() de la siguiente manera:

 // js $.ajax({ url: 'http://my.site/some/path', method: 'post', data: JSON.stringify({a: 'a', b: 'b'}), contentType: 'application/json' });

(Por cierto, tenga en cuenta el tipo de contentType: 'application/json' aquí. Enviamos datos como JSON, que es bastante popular para las API. Es el valor predeterminado, por ejemplo, para publicar en el servicio $http AngularJS).

En el lado del servidor de nuestro ejemplo, simplemente volcamos la matriz $_POST :

 // php var_dump($_POST);

Sorprendentemente, el resultado será:

 array(0) { }

¿Por qué? ¿Qué pasó con nuestra cadena JSON {a: 'a', b: 'b'} ?

La respuesta es que PHP solo analiza automáticamente una carga POST cuando tiene un tipo de contenido application/x-www-form-urlencoded o multipart/form-data . Las razones de esto son históricas: estos dos tipos de contenido eran esencialmente los únicos que se usaban hace años cuando se implementó $_POST de PHP. Entonces, con cualquier otro tipo de contenido (incluso aquellos que son bastante populares hoy en día, como application/json ), PHP no carga automáticamente la carga útil de POST.

Dado que $_POST es un superglobal, si lo anulamos una vez (preferiblemente al principio de nuestro script), el valor modificado (es decir, incluida la carga útil de POST) será referenciable en todo nuestro código. Esto es importante ya que $_POST es comúnmente utilizado por los marcos PHP y casi todos los scripts personalizados para extraer y transformar datos de solicitud.

Entonces, por ejemplo, al procesar una carga útil POST con un tipo de contenido de application/json , debemos analizar manualmente el contenido de la solicitud (es decir, decodificar los datos JSON) y anular la variable $_POST , de la siguiente manera:

 // php $_POST = json_decode(file_get_contents('php://input'), true);

Luego, cuando volcamos la matriz $_POST , vemos que incluye correctamente la carga útil POST; p.ej:

 array(2) { ["a"]=> string(1) "a" ["b"]=> string(1) "b" }

Error común #8: Pensar que PHP admite un tipo de datos de caracteres

Mire este fragmento de código de muestra e intente adivinar qué imprimirá:

 for ($c = 'a'; $c <= 'z'; $c++) { echo $c . "\n"; }

Si respondió de la 'a' a la 'z', se sorprenderá al saber que estaba equivocado.

Sí, imprimirá de 'a' a 'z', pero luego también imprimirá de 'aa' a 'yz'. Veamos por qué.

En PHP no hay tipo de datos char ; solo la string está disponible. Con eso en mente, incrementar la string z en PHP produce aa :

 php> $c = 'z'; echo ++$c . "\n"; aa

Sin embargo, para confundir aún más las cosas, aa es lexicográficamente menor que z :

 php> var_export((boolean)('aa' < 'z')) . "\n"; true

Es por eso que el código de muestra presentado anteriormente imprime las letras de la a a la z , pero luego también imprime de aa a yz . Se detiene cuando llega a za , que es el primer valor que encuentra que es "mayor que" z :

 php> var_export((boolean)('za' < 'z')) . "\n"; false

Siendo ese el caso, aquí hay una forma de recorrer correctamente los valores 'a' a 'z' en PHP:

 for ($i = ord('a'); $i <= ord('z'); $i++) { echo chr($i) . "\n"; }

O alternativamente:

 $letters = range('a', 'z'); for ($i = 0; $i < count($letters); $i++) { echo $letters[$i] . "\n"; }

Error común n.º 9: ignorar los estándares de codificación

Aunque ignorar los estándares de codificación no conduce directamente a la necesidad de depurar el código PHP, es probablemente una de las cosas más importantes que se discutirán aquí.

Ignorar los estándares de codificación puede causar una gran cantidad de problemas en un proyecto. En el mejor de los casos, da como resultado un código que es inconsistente (ya que cada desarrollador está "haciendo lo suyo"). Pero en el peor de los casos, produce un código PHP que no funciona o puede ser difícil (a veces casi imposible) de navegar, lo que lo hace extremadamente difícil de depurar, mejorar y mantener. Y eso significa una productividad reducida para su equipo, incluido mucho esfuerzo desperdiciado (o al menos innecesario).

Afortunadamente para los desarrolladores de PHP, existe la Recomendación de estándares de PHP (PSR), que consta de los siguientes cinco estándares:

  • PSR-0: estándar de carga automática
  • PSR-1: Estándar de codificación básica
  • PSR-2: Guía de estilo de codificación
  • PSR-3: Interfaz de registrador
  • PSR-4: cargador automático

PSR se creó originalmente en base a los aportes de los mantenedores de las plataformas más reconocidas del mercado. Zend, Drupal, Symfony, Joomla y otros contribuyeron a estos estándares y ahora los siguen. Incluso PEAR, que intentó ser un estándar durante años, ahora participa en PSR.

En cierto sentido, casi no importa cuál sea su estándar de codificación, siempre que esté de acuerdo con un estándar y se ciña a él, pero seguir el PSR generalmente es una buena idea a menos que tenga alguna razón convincente en su proyecto para hacer lo contrario. . Cada vez más equipos y proyectos se ajustan al PSR. Tt es definitivamente reconocido en este punto como "el" estándar por la mayoría de los desarrolladores de PHP, por lo que su uso ayudará a garantizar que los nuevos desarrolladores estén familiarizados y se sientan cómodos con su estándar de codificación cuando se unan a su equipo.

Error común # 10: mal uso de empty()

A algunos desarrolladores de PHP les gusta usar empty() para verificaciones booleanas para casi todo. Sin embargo, hay casos en los que esto puede generar confusión.

Primero, volvamos a las matrices y las instancias de ArrayObject (que imitan las matrices). Dada su similitud, es fácil suponer que las matrices y las instancias de ArrayObject se comportarán de manera idéntica. Esto demuestra, sin embargo, ser una suposición peligrosa. Por ejemplo, en PHP 5.0:

 // PHP 5.0 or later: $array = []; var_dump(empty($array)); // outputs bool(true) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false) // why don't these both produce the same output?

Y para empeorar las cosas, los resultados habrían sido diferentes antes de PHP 5.0:

 // Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)

Desafortunadamente, este enfoque es bastante popular. Por ejemplo, esta es la forma en que Zend\Db\TableGateway de Zend Framework 2 devuelve datos cuando se llama a current() en TableGateway::select() como sugiere el documento. El desarrollador puede convertirse fácilmente en víctima de este error con dichos datos.

Para evitar estos problemas, el mejor enfoque para verificar estructuras de matrices vacías es usar count() :

 // Note that this work in ALL versions of PHP (both pre and post 5.0): $array = []; var_dump(count($array)); // outputs int(0) $array = new ArrayObject(); var_dump(count($array)); // outputs int(0)

Y, por cierto, dado que PHP convierte 0 en false , count() también se puede usar dentro de las condiciones if () para verificar matrices vacías. También vale la pena señalar que, en PHP, count() es una complejidad constante (operación O(1) ) en matrices, lo que deja aún más claro que es la elección correcta.

Otro ejemplo cuando empty() puede ser peligroso es cuando se combina con la función de clase mágica __get() . Definamos dos clases y tengamos una propiedad de test en ambas.

Primero definamos una clase Regular que incluya test como una propiedad normal:

 class Regular { public $test = 'value'; }

Luego, definamos una clase Magic que use el operador mágico __get() para acceder a su propiedad de test :

 class Magic { private $values = ['test' => 'value']; public function __get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } } }

Bien, ahora veamos qué sucede cuando intentamos acceder a la propiedad de test de cada una de estas clases:

 $regular = new Regular(); var_dump($regular->test); // outputs string(4) "value" $magic = new Magic(); var_dump($magic->test); // outputs string(4) "value"

Bien hasta ahora.

Pero ahora veamos qué sucede cuando llamamos a empty() en cada uno de estos:

 var_dump(empty($regular->test)); // outputs bool(false) var_dump(empty($magic->test)); // outputs bool(true)

Puaj. Entonces, si confiamos en empty() , podemos engañarnos al creer que la propiedad de test de $magic está vacía, mientras que en realidad está establecida en 'value' .

Desafortunadamente, si una clase usa la función mágica __get() para recuperar el valor de una propiedad, no hay una forma infalible de verificar si el valor de esa propiedad está vacío o no. Fuera del alcance de la clase, realmente solo puede verificar si se devolverá un valor null , y eso no significa necesariamente que la clave correspondiente no esté configurada, ya que en realidad podría haberse configurado en null .

Por el contrario, si intentamos hacer referencia a una propiedad inexistente de una instancia de clase Regular , obtendremos un aviso similar al siguiente:

 Notice: Undefined property: Regular::$nonExistantTest in /path/to/test.php on line 10 Call Stack: 0.0012 234704 1. {main}() /path/to/test.php:0

Entonces, el punto principal aquí es que el método empty() debe usarse con cuidado, ya que puede prestarse a resultados confusos, o incluso potencialmente engañosos, si no se tiene cuidado.

Envolver

La facilidad de uso de PHP puede llevar a los desarrolladores a una falsa sensación de comodidad, haciéndolos vulnerables a una larga depuración de PHP debido a algunos de los matices e idiosincrasias del lenguaje. Esto puede provocar que PHP no funcione y que surjan problemas como los que se describen aquí.

El lenguaje PHP ha evolucionado significativamente a lo largo de sus 20 años de historia. Familiarizarse con sus sutilezas es un esfuerzo que vale la pena, ya que ayudará a garantizar que el software que produzca sea más escalable, robusto y mantenible.