4.2kvistas

Hola de nuevo, y después de mucho tiempo alejado del blog les traigo un articulo que me enviaron TopTal para su publicación, de la mano de ILYA SANOSYAN .

Con PHP relativamente fácil construir un Sistema basado en web, lo cual es la razón de su popularidad. Pero es fácil de usar no obstante, php ha evolucionado en un lenguaje bastante sofisticado con muchas estructuras, matices y sutilezas que puedan engatusar a los desarrolladores, llevándolos a horas de depuración. Este articulo resalta 10 de los errores mas comunes que los desarrolladores php necesitan tener cuidado.

Error común # 1: Dejar referencias de array que cuelgan después de un bucle foreach

No estas seguro de cómo utilizar los bucles foreach en PHP? Utilizando las referencias en los bucles foreach puede ser útil si desea que opere en cada elemento del array que está interactuando. 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 se tiene cuidado, esto también puede tener algunas consecuencias o efectos secundarios indeseables. En concreto, en el ejemplo anterior, después de que se ejecuta el código, $value permanecerá a su alcance y mantendrá una referencia al último elemento de la array. Posteriormente las operaciones subsecuentes que implica a $value podrían, accidentalmente sin querer llegar a modificar el último elemento del array.

Lo principal para recordar es que foreach no crea un ámbito. Por lo tanto, $value en el ejemplo anterior es una referencia dentro del ámbito superior del script. En cada iteración foreach establece la referencia para apuntar al siguiente elemento del $array. Después de completar el bucle, por consiguiente, $value aún apunta al último elemento del $array y se mantiene a su alcance.

Aquí hay un ejemplo del tipo de errores confusos y evasivas que estos puede conducir a:

$array = [1, 2, 3];
echo implode(',', $array), "\n";

foreach ($array as &$value) {}    // por referencia
echo implode(',', $array), "\n";

foreach ($array as $value) {}     // por valor (es decir, copia)
echo implode(',', $array), "\n";

El código anterior tendria la siguiente resultado

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 bucle foreach, el $array no se altera, pero como se ha explicado anteriormente, $value es dejado como una referencia al último elemento en el $array (desde ahi el bucle foreach accedió al $value por referencia).

Como resultado, cuando vamos hacia del segundo bucle foreach, pasan “cosas extrañas”. Específicamente, desde que $value está siendo accedida por su valor (es decir, por copia ), foreach copia cada secuencia del elemento $array en $value en cada paso del bucle. Como resultado, esto es lo que pasa 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. Por lo tanto $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. Por lo tanto $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] aun es igual a 2. Por lo tanto $array contiene ahora [1, 2, 2].

Para que aun obtengas el beneficio de usar referencias en el bucle foreach sin tener que arriesgarte con estos tipo de problemas, llame a unset() a 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 tendra referencia a $arr[3]

Error común #2: El malentendido funcionamiento de isset()

A pesar de su nombre, isset() no sólo devuelve false si un item no existe, también devuelve false para valores null.

Este funcionamiento es más problemático de lo que podría parecer en un primer momento y es una fuente de problemas comunes.

Considere lo siguiente:

$data = fetchRecordFromStorage($storage, $identifier);
if (!isset($data['keyShouldBeSet']) {
    // haz algo aqui si 'keyShouldBeSet' no esta establecido
}

El autor de este código, probablemente quería comprobar si keyShouldBeSetse asigno algo en $data. Pero, como se mencionó, isset($data['keyShouldBeSet']) también devolverá false si $data['keyShouldBeSet'] estaba establecido, pero fue establecido en null. Por lo tanto la lógica anterior es defectuosa.

Aqui otro ejemplo:

if ($_POST['active']) {
    $postData = extractSomething($_POST);
}

// ...

if (!isset($postData)) {
    echo 'post no essta activo';
}

El código anterior asume que si $_POST['active'] regresa true, entonces postData será establecido, y por lo tanto isset($postData) retornara true. Así que por el contrario, el código anterior asume que la única forma en que isset($postData) retornara un false es SI $_POST['active'] retorna un false también.

No.

Como se ha explicado, isset($postData) también devolverá false si $postData se establece en null. Por lo tanto, es posible que isset($postData) retorne un false aun si $_POST['active'] retorna un true. Así que de nuevo, la lógica anterior es defectuosa.

Y por cierto, como un punto aparte, si la intención en el código anterior era chekear si $_POST['active'] retornara un TRUE, dependiendo deisset(), de todos modos fue una mala decision de codificación. En vez de eso, habría sido mejor, volver a revisar $_POST['active']; es decir.:

if ($_POST['active']) {
    $postData = extractSomething($_POST);
}

// ...

if ($_POST['active']) {
    echo 'post not active';
}

Para casos, sin embargo, donde es importante revisar si una variable fue realmente establecido (es decir, distinguir entre una variable que no se ha establecido y una variable que fue establecido en null), el método array_key_exists() es una mejor solución.

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

$data = fetchRecordFromStorage($storage, $identifier);
if (! array_key_exists('keyShouldBeSet', $data)) {
    // haz esto si 'keyShouldBeSet' no es establecido.
}

Ademas, combinando array_key_exists() con el get_defined_vars(), podemos comprobar de forma fiable si una variable dentro del ámbito actual se ha establecido o no:

if (array_key_exists('varShouldBeSet', get_defined_vars())) {
    // la variable $varShouldBeSet existe en el ambito actual
}

Error comun #3: Confusión sobre el retorno por referencia VS por valor

Considera este pequeño código:

class Config
{
    private $values = [];

    public function getValues() {
        return $this->values;
    }
}

$config = new Config();

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

Si tu ejecutas anterior, obtendrás lo siguiente:
PHP Notice: Undefined index: test in /path/to/my/script.php on line 21

Que esta mal?

El problema es que el código anterior confunde el retorno de los arrays con el retorno de los array por valor. A no ser que usted digas explicitamente al PHP que retorne un array por referencia (es decir usando &) PHP retornara por defecto el array “por valor”. Esto significa que una copia del array sera retornado y por lo tanto la la función llamada y el caller no tendrán el acceso a la misma instancia del array.

Asi que la llamada anterior a getValues() retorna una copia de los $values del array antes que una referencia a esta. Con esto en mente, vamos a revisar las dos lineas principales del ejemplo anterior.

// getValues() retorna una COPIA de los $values del array, asi que esto agrega un elemento 'test';
// a una COPIA de los $values del array, pero no los $values del array en si.
$config->getValues()['test'] = 'test';

// getValues() otra vez retorna OTRA COPIA de los $values del array, y ESTA copia no
// contiene un elemento 'test'  (por eso obtenemos el mensaje "undefined index").
echo $config->getValues()['test'];

Una posible solución seria salvar la primera copia de los $values del array retornados por getValues() y luego operar en esa copia subsecuentemente, por ejemplo:

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

Este código funcionara bien(es decir esto devolverá test sin generar un mensaje “undefined index”) pero dependiendo de lo que estas tratando de realizar. este enfoque podría o no podría ser adecuado. En los particular , el código anterior no modificara los $values originales del array. Así que si usted realmente quieres hacer tus modificaciones(tales como agregar un elemento test) para afectar el array original, necesitaría modificar la funcion getValues() para retornar una referencia a los $values del array en si. Esto sera hecho agregando un & antes del nombre de la función, de este modo indica que debería retornar una referencia, es decir:

class Config
{
    private $values = [];

    // retorna una REFERENCIA a los $values de los array actuales
    public function &getValues() {
        return $this->values;
    }
}

$config = new Config();

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

El resultado de esto es test, como se esperaba.
Para hacer las cosas mas confusas, considera este pequeño condigo:

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 adivinaste que el resultado seria el mismo error “undefined index” como en el ejemplo de array, estarías equivocado, de hecho este código funcionara bien. La razón es que, a diferencia que los array, PHP siempre pasa objetos por referencia (ArrayObject es un objeto SPL, simula totalmente el uso de los array, pero trabaja como un objeto)

Como demuestran estos ejemplos, en PHP no es siempre obvio si estas lidiando con una copia o una referencia. Por lo tanto es esencial entender que el funcionamiento por defecto (es decir: variables y array son pasados por valor; objetos son pasados por referencia) y también verificar cuidadosamente la Documentación de la API de la función que estas llamando para ver si devuelve un valor, una copia de un array, una referencia a un array o una referencia a un objeto.

Dicho todo esto, es importante tener en cuenta que la práctica de devolver una referencia a un array o un ArrayObject generalmente es algo que debe ser evitado, ya que proporciona al que llama la capacidad de modificar los datos privados de la instancia. Este “va en contra” de la encapsulación. En cambio, es mejor usar de estilo antiguo “getters” y “setters”, 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 da a la persona que llama la capacidad para establecer u obtener cualquier valor en la matriz sin proporcionar acceso público a los $values de array se auto-privada de otro modo.

Error Común #4: Realizar consultas en un bucle

No es raro encontrarse con algo como esto si su PHP no está funcionando:

$models = [];

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

Si bien puede haber absolutamente nada mal aquí, pero si usted sigue la lógica en el código, es posible que la llamada de aspecto inocente arriba para $valueRepository->findByValue() en última instancia se traduce en una consulta de algún tipo, como por ejemplo:

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

Como resultado, cada iteración del bucle de arriba resultaría en una consulta independiente a la base de datos. Por lo que si, por ejemplo, usted facilitó un conjunto de 1000 valores al bucle, generaría 1.000 consultas separadas al recurso! Si una secuencia de comandos se llama en varios sub procesos, podría potencialmente llevar el sistema a un punto muerto.

Por lo tanto es crucial reconocer cuando las consultas se realizan por su código y, siempre que sea posible, se reúnen los valores y luego ejecutar una consulta para obtener todos los resultados.

Un ejemplo de un lugar bastante común encontrar la consulta que se realiza de manera ineficiente (es decir, en un bucle) es cuando un formulario es asociado con una lista de valores (IDS, por ejemplo). Entonces, para recuperar los datos de registro completo para cada uno de los identificadores, el código del bucle recorre a través del array y hace una consulta SQL separada para cada ID. Esto a menudo es 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 realizar de manera más eficiente en una sola consulta SQL como sigue:

$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 para reconocer cuando se están haciendo consultas, ya sea directa o indirectamente, por su código. Siempre que sea posible, se reúnen los valores y luego ejecutar una consulta para obtener todos los resultados. Sin embargo, se debe tener cuidado allí también, lo que nos lleva a nuestro siguiente error común en PHP …

Error común # 5: Dolores de cabeza en el uso de la memoria y las ineficiencias

Recuperar muchos registros a la vez es definitivamente más eficiente que ejecutar una consulta individual para cada fila, este enfoque puede conducir potencialmente a una condición de “falta de memoria” en la libmysqlclient cuando se utiliza la extensión mysql de PHP.

Para demostrarlo, vamos a hacer unas pruebas con recursos limitados (512 MB de RAM), MySQL y PHP-CLI.

Empezamos con una tabla de base de datos como esta:

// conectamos con mysql
$connection = new mysqli('localhost', 'username', 'password', 'database');

// creamos una tabla con 400 columnas
$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);

// escribimos 2 millones de registros
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 vamos a ver el uso de los 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";  

Resultado:
Before: 224704
Limit 1: 224704
Limit 10000: 224704

Cool, Parece que la consulta se gestiona de forma segura internamente en términos de recursos.

Sin embargo, sólo para estar seguro, vamos a impulsar el límite una vez más y lo establecemos a 100.000. Cuando hacemos eso, obtenemos:
PHP Warning: mysqli::query(): (HY000/2013):
Lost connection to MySQL server during query in /root/test.php on line 11

¿Que pasó?
La cuestión aquí es la forma en que funciona el módulo MySQL de PHP. Esto en realidad ese un proxy para libmysqlclient, lo que hace el trabajo sucio. Cuando se selecciona una parte de los datos, eso va directamente a la memoria. Dado que esta memoria no está gestionado por el gestor de PHP, memory_get_peak_usage() no mostrará ningún aumento en el uso de los recursos, hasta el límite en nuestra consulta. Esto conduce a problemas como el que se ha demostrado anteriormente en el que estamos engañados que pensamos complacientemente que nuestra gestión de memoria está bien. Pero, en realidad, nuestra gestión de memoria fallo gravemente y podemos experimentar problemas como el que se muestra arriba.

Al menos puede evitar el headfake arriba (aunque no será en sí mejorar el uso de la memoria) en lugar de utilizar el módulo mysqlnd. mysqlnd se compila como una extensión nativa de PHP y usa un gestor de memoria de PHP.

Por lo tanto, si se corre la prueba anterior utilizando mysqlnd en lugar de mysql, obtenemos una imagen mucho más realista de nuestro uso de la memoria:
Before: 232048
Limit 1: 324952
Limit 10000: 32572912

Y es aún peor. Según la documentación de PHP, MySQL utiliza el doble de tantos recursos como mysqlnd para almacenar datos, por lo que usando el script original de MySQL este utiliza aun más memoria de la que se muestra aquí (aproximadamente el doble).

Para evitar estos problemas, considere limitar el tamaño de sus consultas y utilizando un bucle con un pequeño número de iteraciones o repeticiones; 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");
}

Si tenemos en cuenta tanto este error PHP y el error #4 anterior, nos damos cuenta de que hay un equilibrio saludable que el código necesita idealmente para lograr entre, por un lado, que tiene sus consultas ser demasiado granular y repetitivo, frente a tener cada uno de sus consultas individuales sean demasiado grandes. Como ocurre con la mayoría de cosas en la vida, se necesita equilibrio; temperaturas extremas no es bueno y puede causar problemas con PHP y no funciona correctamente.

Error  común #6: Ignorando problemas Unicode/UTF-8

En cierto sentido, esto es realmente más de un problema en el mismo PHP que algo que se encontraría con PHP durante la depuración, pero nunca ha sido tratado adecuadamente. el núcleo de PHP 6 debía hacerse compatibles con Unicode, pero que quedó en suspenso cuando el desarrollo de PHP 6 se suspendió de nuevo en 2010.

Pero eso el desarrollador de ninguna manera podra pasar correctamente UTF-8 y evitar la suposición errónea de que todas las cadenas tienen que ser necesariamente “simple y llano ASCII”. El código que falla al manejar adecuadamente las cadenas NO-ASCII es notorio para la introducción de heisenbug retorcidos en su código. Incluso simples llamadas a strlen($_POST['name']) podrían causar problemas si alguien con un apellido como “Schrödinger” trató de inscribirse en el sistema.

He aquí una pequeña lista para evitar este tipo de problemas en su código:

  • Si usted no sabe mucho acerca de Unicode y UTF-8, al menos debe aprender los conceptos básicos. Hay un gran articulo sobre eso aquí.
  • Asegúrese de utilizar siempre las funciones mb_* en lugar de las antiguas funciones de cadena (asegúrese de que la extensión “multibyte” se incluye en su instalación de PHP).
  • Asegúrese de que su base de datos y tablas están configurados para utilizar Unicode (muchas distribuciones de MySQL todavía usan Latin1 por defecto).
  • Recuerde que json_encode() convierte los símbolos que no son ASCII (por ejemplo, “Schrödinger” se convierte en “Schröder\u00f6dinger”), pero serialize() no lo hace.
  • Asegúrese de que sus archivos de código PHP también están codificación UTF-8 para evitar colisiones al concatenar cadenas con las constantes de cadena no modificables o configurados.
  • Un recurso particularmente importante en este sentido es el UTF-8 Primer para PHP y MySQL.

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

A pesar de su nombre, el array $_POST no siempre contienen sus datos POST y se puede encontrar fácilmente vacía. Para entender esto, vamos a echar un vistazo a un ejemplo. Supongamos que hacemos una solicitud del 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, aqui se indico el contentType:'application/json'. Enviamos los datos como JSON, que es muy popular para las API. Esto es predeterminado, por ejemplo, para publicar en el servicio $http de AngularJS)

En el lado del servidor de nuestro ejemplo, simplemente volcamos el array $_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 sólo analiza una carga útil del POST de forma automática cuando se tiene un tipo de contenido application/x-www-form-urlencoded o multipart/form-data. Las razones de esto son históricos-estos dos tipos de contenido son esencialmente los únicos que hace años se utilizan cuando PHP implementó $_POST. Así que con cualquier otro tipo de contenido (incluso los que son muy populares hoy en día, como application/json), PHP no carga automáticamente los paquetes POST.

Desde que $_POST es superglobal, si sobrescribimos una vez (preferiblemente a principios de nuestra secuencia de comandos), el valor modificado (es decir, incluyendo el paquete POST) será entonces referenciable a lo largo de nuestro código. Esto es importante ya que $_POST es comúnmente utilizado frameworks de PHP y casi todos los scripts personalizados para extraer y transformar datos de la solicitud.

Así, por ejemplo,al procesar un paquete POST con un tipo de contenido de application/json, tenemos que analizar manualmente el contenido de la solicitud (es decir, decodificar los datos JSON) y anular la variable $_POST, como sigue:

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

A continuación, cuando volcamos el array $_POST, vemos que incluye correctamente el paquete POST; por ejemplo:

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

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

Mira este ejemplo de codigo y trata de adivinar lo que imprimirá:

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

Si respondiste ‘a’ hasta ‘z’, te sorprendera saber que estabas equivocado
Si, imprime ‘a’ hasta ‘z’, pero tambien imprime ‘aa’ hasta ‘yz’.veamos porque
En PHP no hay tipo de datos char ; solo string esta disponible. Con eso en mente, incrementando el string z en PHP produce aa:

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

Sin embargo, para confundir mas las cosas, aa es lexicográficamente menos que z:

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

Es por eso que el ejemplo de codigo presentado anteriormente imprime las letras a hasta z, pero luego imprime aa hasta yz. Eso es para cuando buscas za, lo cual es el primer valor que se encuentra que es “más grande que” z:

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

Siendo el caso, aqui hay una manera apropiada de asegurar a través del valor ‘a’ hasta ‘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 # 9: Ignorando los estandares 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 discubriremos aquí.

Ignorar los estándares de codificación puede causar un montón de problemas en un proyecto. En el mejor de los casos, resulta en un código que es inconsistente (ya que cada desarrollador está “haciendo lo suyo”). Pero en el peor de los casos, produce código PHP que no funciona o puede ser difícil (a veces casi imposible) para navegar, por lo que es extremadamente difícil de depurar, mejorar y mantener. Y eso significa una reducción de la productividad para su equipo, incluyendo un montón de esfuerzos desperdiciados (o al menos innecesarios).

Afortunadamente para los desarrolladores PHP, existe las Recomendación de Estándar de PHP (PSR), compuesta por los siguientes cinco estándares:

PSR-0: Estandar de Auto carga.
PSR-1: Estándar de Codificación Básica
PSR-2: Guía de estilo de codificación
PSR-3: Guía de estilo de codificación
PSR-4: Carga Automatica

PSR fue creado originalmente sobre la base de los aportes de los mantenedores de las plataformas más reconocidas en el mercado. Zend, Drupal, Symfony, Joomla y otros contribuyeron a estos estándares, y ahora los están siguiendo. Incluso PEAR, que intentó ser un estándar durante años antes de eso, participa en el PSR ahora.

En cierto sentido, casi no importa cuál sea su estándar de codificación, siempre y cuando usted esté de acuerdo en un estándar y se adhieren a él, pero seguir el PSR es generalmente una buena idea a menos que tenga alguna razón convincente en su proyecto para hacer lo contrario . Más y más equipos y proyectos están de acuerdo con el PSR. 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 asegurar que los nuevos desarrolladores estén familiarizados y cómodos con su estándar de codificación cuando se unan a su equipo.

Error común #10: El Mal usado empty()

Algunos desarrolladores PHP gustan usar empty() para chekear booleanos, para casi todo. Hay casos, sin embargo, donde esto puede conducir a la confusión.

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

// PHP 5.0 o posterior:
$array = [];
var_dump(empty($array));        // resultado bool(true) 
$array = new ArrayObject();
var_dump(empty($array));        // resultado bool(false)
// ¿Por qué no ambos producen el misma resultado?

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

// Antes de PHP 5.0:
$array = [];
var_dump(empty($array));        // resultado bool(false) 
$array = new ArrayObject();
var_dump(empty($array));        // resultado bool(false)

Este enfoque es desafortunadamente muy popular. Por ejemplo, esta es la forma en que Zend\Db\TableGateway de Zend Framework 2 devuelve datos al llamar a current() en el resultado de TableGateway::select() como sugiere el documento. El Desarrollador puede fácilmente convertirse en víctima de este error con estos datos.

Para evitar estos problemas, la mejor propuesta para comprobar estructuras de matriz vacías es utilizar count():

// Tenga en cuenta que este trabajo en todas las versiones de PHP (pre y post 5.0):
$array = [];
var_dump(count($array));        // resultado int(0)
$array = new ArrayObject();
var_dump(count($array));        // resultado int(0)

Y por cierto, puesto que PHP pone 0 a false, count() también se puede usar dentro de una condición if() condiciones para comprobar arrays vacíos. También vale la pena señalar que, en PHP, count() es una complejidad constante (operación O(1)) en arrays, lo que hace 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 magica __get(). Vamos a definir dos clases y tener una propiedad de prueba en ambos.

Primero vamos a definir una clase Regular que incluye a test como una propiedad normal:

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

A continuación, definamos una clase Magic que utiliza el operador amgico __get() para acceder a su propiedad test

 
class Magic
{
	private $values = ['test' => 'value'];

	public function __get($key)
	{
		if (isset($this->values[$key])) {
			return $this->values[$key];
		}
	}
}

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

$regular = new Regular();
var_dump($regular->test);    // resultado string(4)='value'
$magic = new Magic();
var_dump($magic->test);      // resultado 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));    // resultado bool(false)
var_dump(empty($magic->test));      // resultado bool(true)

Ugh. Así que si nos basamos en empty(), podemos ser engañados para creer que la propiedad test de $magic está vacía, mientras que en realidad se establece en 'value'

Desafortunadamente, si una clase usa la función mágica __get() para recuperar el valor de una propiedad, no hay manera infalible de comprobar si ese valor de propiedad está vacío o no. Fuera del alcance de la clase, realmente sólo puede comprobar si se devuelve un valor nulo, y eso no significa necesariamente que la clave correspondiente no está establecida, ya que de hecho podría haber sido establecida en null.

En contraste, si intentamos hacer referencia a una propiedad inexistente de una instancia de la 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

Así que el punto principal aquí es que el método empty() debe ser usado con cuidado, ya que puede prestarse a resultados confusos, o incluso potencialmente engañosos, si uno no es cuidadoso.

La facilidad de uso de PHP puede darles a los desarrolladores una falsa sensación de comodidad, dejándolos vulnerables a la depuración prolongada de PHP debido a algunos de los matices e idiosincrasias del lenguaje. Esto puede resultar que PHP no funcione y con los problemas como los descritos aquí.

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

Articulo original: https://www.toptal.com/php/10-most-common-mistakes-php-programmers-make
Traducido por: Miguel Nureña con una grandisiona colaboracion de Sidny Mendieta.

Shares