PHP: PHP References and useful auxiliary functions

PHP: Referencias en PHP y funciones útiles auxiliares

Jul 14 2010

One of the PHP interesting features are reference handling. As it is known, in PHP trying to access to a variable or a array key that doesn't exists produces a notice and generally you have to check continuisly with isset and this code is redundant. PHP allows to create references to variables that doesn't exists so you can create them later. It is like creating a potential variable slot and assign it if you decide so. Also allows to check the existence of a referenced variable using that reference. It is pretty typical to want to obtain the value of a variable if it exists, or a default value in the case it doesn't exists. For example: ```php $page = isset($_GET['page']) ? $_GET['page'] : 1; ``` In PHP 5.3 there was added a ternary operator shortcut. It looked pretty great, since it would have allowed to do this: `$page = $_GET['page'] ?: 1` But results that they missed that opportunity. That shortcut implicitly casts to bool, if the result is false, it returns the result of the right part of the expression. This doesn't avoid the notice problem when trying to access to an existent key in the array. Furthermore there is a small difference against isset. If the varaible exists but has a value evaluated as false (like an empty string or 0 value), you would still get the right value. And generally that's not what you want. So I see pretty useless this new PHP 5.3 addition. Neverteless PHP and its references allow to create auxiliar functions that allow to solve these problems easily, avoiding to repeat the variable twice: ```php $page = isset_default($_GET['page'], 1); ``` It is possible without notices because in the latest PHP versions you specify the reference parameters in the own function declaration. So: ```php function isset_default(&$var, $default) { return isset($var) ? $var : $default; } ``` This would be like a pretty useful macro. It is also pretty common to check the existence of segeral keys in an array. ```php if (isset($_GET['a'], $_GET['b'], $_GET['c'])) { } ``` -> ```php if (isset_array($_GET, array('a', 'b', 'c'))) { } ``` Here the reference is used to avoid PHP duplicating the array, that could be potentially big. ```php function isset_array(array &$array, array $keys) { foreach ($keys as $key) if (!isset($array[$key])) return false; return true; } ``` Another pretty common pattern that you can solve with reference function *macros* is to make a change to the value later. ```php function method() { if ($this->alreadyPerformed) return; $this->alreadyPerformed = true; // ... } ``` -> ```php function method() { if (post_assign($this->alreadyPerformed, true)) return; //... } function post_assign(&$var, $value) { $last_value = $var; $var = $value; return $last_value; } ``` In this specific case, you could do a small trick using integers to avoid having the variable referenced twice. And then using the post-increment unary operator: ```php function method() { if ($this->alreadyPerformed++) return; // ... } ``` There are a lot of useful ways of using PHP references. For example to have a pretty lightweight temporary inmemoty cache: ```php static function cached_method($key) { static $cache = array(); $result = &$cache[$key]; if (!isset($result)) { // ... } return $result; } ``` Using references and PHP 5.3: ```php function &cache_result(&$var, $func) { if (!isset($var)) $var = $func(); return $var; } static function cached_method($key) { static $cache = array(); return cache_result($cache[$key], function() use ($key) { // ... }); } ``` Other functions that I find useful despite not having references: For example if we want to obtain an array from other without the keys starting with _: ```php $array = array_filter_keys($_GET, function($key) { return substr($key, 0, 1) != '_'; } ); function array_filter_keys(&$array, $callback) { $result = array(); foreach ($array as $key => $value) { if ($callback($key, $value)) $result[$key] = $value; } return $result; } ``` In some sitions we want to have a look to an array visually that might contain html. Using xdebug and `var_dump` function is colorized. But xdebug is not always available, and its representation is not always the most convenient. Here there is a function that allows to show a print_r compatible with html. Making use of the tag pre and escaping html: ```php function print_r_pre($var) { echo '
';  
 echo htmlspecialchars(print_r($var, true));  
 echo '
'; } ```
Uno de los aspectos interesantes de PHP es la gestión de referencias. Como es bien sabido en PHP, intentar acceder a una variable o una clave en un array que no existe produce un notice y generalmente hay que estar tratando continuamente con isset y con bastantes código redundante. El caso es que PHP permite crear una referencia a una variable inexistente para crearla a posteriori. Es como crear un slot de la variable en potencia y asignarlo si así se decide. También permite comprobar la existencia de la variable referenciada usando la variable de referencia. Es bastante típico en PHP querer obtener el valor de una variable si existe, o un valor por defecto en el caso de que no exista. Ejemplo: ```php $page = isset($_GET['page']) ? $_GET['page'] : 1; ``` En PHP 5.3 se añadió un shortcut del operador ternario. A priori parecía bastante interesante, porque teóricamente hubiese podido permitir hacer esto: $page = $_GET['page'] ?: 1; Pero resulta que la cagaron estrepitosamente. Resulta que lo que hace el shortcutdel ternario es evaluar la parte de la izquierda y si al castear implícitamente a bool, el resultado es false, se devuelve el resultado de la expresión de la derecha. Esto para empezar no evita el tema de los notices al intentar acceder a una clave inexistente en el array. Además hay un pequeño matiz que difiere con el isset. Y es que si la variable existe pero tiene un valor evaluado como false (como una cadena vacía o el valor 0), ya se estaría devolviendo lo de la derecha. Y generalmente no es lo deseado. Así que veo completamente inútil esta adición al PHP 5.3. Sin embargo PHP y sus referencias permiten crear funciones auxiliares que permiten resolver estos problemas con facilidad y evitando repetir la variable dos veces: ```php $page = isset_default($_GET['page'], 1); ``` Ésto es posible sin notices porque en las últimas versiones de PHP especificas los valores que se pasan por referencia en la propia descripción de la función. Así pues: ```php function isset_default(&$var, $default) { return isset($var) ? $var : $default; } ``` Esto vendría a ser como una macro bastante útil. También es bastante común comprobar la existencia de varias claves en un array. ```php if (isset($_GET['a'], $_GET['b'], $_GET['c'])) { } ``` -> ```php if (isset_array($_GET, array('a', 'b', 'c'))) { } ``` Aquí la referencia evita que PHP duplique un array potencialmente grande. ```php function isset_array(array &$array, array $keys) { foreach ($keys as $key) if (!isset($array[$key])) return false; return true; } ``` Otro patrón común que se puede resolver con macros de referencias es hacer un cambio a posteriori de una variable. ```php function method() { if ($this->alreadyPerformed) return; $this->alreadyPerformed = true; // ... } ``` -> ```php function method() { if (post_assign($this->alreadyPerformed, true)) return; //... } function post_assign(&$var, $value) { $last_value = $var; $var = $value; return $last_value; } ``` En este caso concreto también se podría usar el pequeño truco de usar enteros para evitar tener la variable dos veces referenciada. Y usar el operador unario del postincremento: ```php function method() { if ($this->alreadyPerformed++) return; // ... } ``` Hay muchas formas útiles de usar las referencias en PHP. Por ejemplo para tener una caché temporal en memoria muy ligera: ```php static function cached_method($key) { static $cache = array(); $result = &$cache[$key]; if (!isset($result)) { // ... } return $result; } ``` Con referencias y PHP 5.3: ```php function &cache_result(&$var, $func) { if (!isset($var)) $var = $func(); return $var; } static function cached_method($key) { static $cache = array(); return cache_result($cache[$key], function() use ($key) { // ... }); } ``` Otras funciones que considero útiles aunque no tengan que ver directamente con referencias: Por ejemplo si queremos obtener una array a partir de otro sin las claves que empiezan por _: ```php $array = array_filter_keys($_GET, function($key) { return substr($key, 0, 1) != '_'; } ); function array_filter_keys(&$array, $callback) { $result = array(); foreach ($array as $key => $value) { if ($callback($key, $value)) $result[$key] = $value; } return $result; } ``` En determinadas ocasiones queremos ver un array visualmente que contiene html. Con xdebug la functión var_dump puede estar coloreada. Pero ni siempre está disponible el xdebug, ni tampoco es siempre lo más cómodo visualmente. Aquí hay una función que permite mostrar un print_r para html. Haciendo uso del tag pre y del escapado de html. ```php function print_r_pre($var) { echo '
';  
 echo htmlspecialchars(print_r($var, true));  
 echo '
'; } ```