Certificación Zend PHP 5.3 (II) – Funciones y ámbito de variables

Continuamos con la serie de posts acerca de la certificación Zend PHP. En esta ocasión, vamos a comentar cosas interesantes sobre las funciones y el ámbito de las variables.

Como probablemente sabrás, PHP no es un lenguaje estrictamente orientado a objetos, y las funciones forman una parte muy importante del lenguaje. Existen multitud de funciones para tratamiento de strings, arrays, cálculos matemáticos, trabajo con streams y ficheros y aunque saberlas todas es prácticamente imposible, conocer un buen puñado nos puede ayudar en nuestro día a día. (Veremos más sobre muchas de ellas en próximos posts)

Además, también podemos crearnos nuestras propias funciones. Y es sobre este punto sobre lo que va a tratar este post.

Creación de funciones

Para definir una función usaremos la palabra function, seguida del nombre que le queremos dar a la función (Ver más aquí sobre nomenclatura).

A continuación, entre paréntesis, podemos definir parámetros de entrada (pasándolos por valor o referencia), y podemos asignar valores por defecto a los mismos.

Dentro de la función podemos realizar operaciones y al final de la misma, si queremos devolver algún valor o objeto usaremos la instrucción return seguida de la variable a devolver.
IMPORTANTE: Si no usamos return, la función devuelve el valor NULL.

<?php
    // Varios ejemplos
    // Esto no está permitido!
    function 1funcion() {}
 
    // 2 parámetros, el segundo opcional con valor por defecto
    function dosParametros1($param1, $param2 = 3) { }
 
    // 2 parámetros, el segundo opcional con valor por defecto array
    function dosParametros2($param1, $param2 = array(1 => 'aaa', 2 => 'bbb')) { }
 
    // Si no devuelven nada, recibimos NULL
    $a = dosParametros1(0); // $a tiene valor NULL
 
    // 2 parámetros, 1 por valor, otro por referencia
    function dosParametros3($param1, &$param2) {}
 
    // 2 parámetros, 1 por valor, otro por referencia y por defecto NULL
    // (Esto no se podía hacer en PHP4, pero sí en PHP5) 
    function dosParametros3($param1, &$param2 = NULL) {}
?>

Type-hinting en funciones

Una cosa que mucha gente desconoce es que PHP permite cierto tipo de type-hinting. No podemos fijar los tipos básicos, pero sí fijar parámetros del tipo array, o marcar el tipo de objeto.

Este type-hinting en el caso de los objetos, nos permite marcar que lo que pasamos sea:
– Un objeto exactamente de esa clase.
– Un objeto de una clase que extiende a la clase que marcamos.
– Un objeto que implementa ese interface.
(Veremos más sobre todo esto en próximos posts, pero podemos ver varios ejemplos a continuación)

<?php
    // Type hinting de array
    function recibeArray(array $stDatos) {}
    recibeArray(1);
    // Esto nos devuelve el siguiente error: 
    // Catchable fatal error: Argument 1 passed to recibeArray() must be an array, integer given
 
    // Type hinting de objetos
    function recibeObjeto(nomClase $stClase) {}
    class nomClase2 extends nomClase {}
    class nomClase {}
    class nomClase3 {}
 
    $a = new nomClase();
    $b = new nomClase2();
    $c = new nomClase3();
 
    recibeObjeto($a); // -> OK ya que $a es un objeto nomClase
    recibeObjeto($b); // -> OK ya que $b es un objeto nomClase2 que extiende de nomClase
    recibeObjeto($c); // -> Error: Catchable fatal error: Argument 1 passed to recibeObjeto() must be an instance of nomClase, instance of nomClase3 given
 
    function recibeObjetoInterface(nomInterface $stClase) {}
 
    interface nomInterface {}
    interface nomInterface2 {}
    class nomClase4 implements nomInterface {}
    class nomClase5 extends nomClase4 {}
    class nomClase6 implements nomInterface2 {}
 
    $d = new nomClase4();
    $e = new nomClase5();
    $f = new nomClase6();
 
     recibeObjetoInterface($d); // -> OK ya que $d es nomClase4 que implementa nomInterface
     recibeObjetoInterface($e); // -> OK ya que $d es nomClase5 que al extender nomClase4 también implementa nomInterface
     recibeObjetoInterface($f); // -> Error: Catchable fatal error: Argument 1 passed to recibeObjetoInterface() must implement interface nomInterface, instance of nomClase6 given
?>

Ámbito (scope) de las variables

En PHP existen 3 tipos de ámbitos:
– Global (Fuera de las funciones y objetos)
– Función (En el interior de una función)
– Objeto (Relacionado con los objetos, la visibilidad de las propiedades y la herencia)

En condiciones normales, no podemos ver variables fuera de su ámbito, pero podemos hacer cosas para recuperarlas.

<?php
      // Esto es el scope global, $a existe ahí pero no en las funciones
      $a = 20;
      function testVariables($int1, &$int2) {
           // Dentro de la función existe un nuevo scope donde lo que existe fuera, aquí no está
           echo $a; // -> Notice: Variable no definida
           // Ahora la variable existe en este scope, aunque no es la misma $a del scope global
           $a = 10;
           // Sin embargo, para traernos variables de otro scope a la función
           global $a; // Y con esto $a tiene el valor de 20 aquí dentro
           $a++;
           $int1++;
           $int2++;
      }
      $val1 = 30; $val2 = 10;
      testVariables($val1, $val2);
      echo $a; // 21 ya que en la función es global -> POCO RECOMENDABLE USAR GLOBAL
      echo $val1; // 30 ya que se pasa por valor y aunque la modifiquemos en la función, no cambia
      echo $val2; // 11 ya que se pasa por referencia
?>

¿Por valor o por referencia?

Cuando daba clase, mucha gente se liaba con esto. Y hay mucha gente que trabaja en esto que no lo tiene claro. Parte de la confusión viene de los tiempos de PHP4, donde los objetos se pasaban por valor, pero esto ya hace mucho que no es así.

En PHP 5, si no hacemos nada (como poner un & por delante) TODO va por valor EXCEPTO los objetos que van por referencia. Sí, los arrays también van por valor. Y alguno podría pensar, “yo sé mucho” y siempre paso los arrays por referencia que seguro que va más rápido. Y esto en PHP es incorrecto, de hecho es peor pasarlos por referencia, ya que en ese caso PHP hace una copia del array original y en realidad consumimos más memoria y no ganamos velocidad.

Un objeto nunca podremos pasarlo por valor. Si queremos conservar el estado original de un objeto deberemos hacer una copia con el constructor de lenguaje clone, que se puede “tunear” con el método mágico __clone de los objetos.

Si una función recibe un parámetro por valor, en realidad está recibiendo una copia del mismo. En ese caso, dentro de la función podemos hacer lo que queramos con ese valor que la variable exterior no recibirá esos cambios.

Sin embargo, al pasar un parámetro por referencia, estamos pasando una dirección de memoria. En ese caso, los cambios realizados a la variable dentro de la función afectarán a la misma fuera de la función (como se ha visto en el anterior ejemplo).

Funciones y propiedades curiosas sobre las funciones y sus parámetros

En la definición de una función, al marcar parámetros, estamos definiendo el número MÍNIMO de parámetros que debe recibir la función. Sin embargo, podemos pasarle muchos más valores no definidos.

Y alguno dirá, ¿y cómo los podemos recoger?. Pues con las siguientes funciones:

func_num_args: Nos da el número de parámetros recibidos.
func_get_args: Devuelve un array con los parámetros pasados en orden.
func_get_arg(0): Nos devuelve el primer parámetro pasado.

<?php
    // Definimos un solo parámetro en la función
    function testParams($iCount) {
        $iParams = func_num_args(); // $iParams = 3
        $arParams = func_get_args(); // $arParams = array(0 => 5, 1 => 'abc', 2 => array(1 => 'test'))
        $sSecondParam = func_get_arg(1); // $sSecondParam = 'abc' 
 
         // Vamos a ser fancy y usaremos list
        list($iValor1, $sValor2, $arValor3) = func_get_args();
        // Tras esto, $iValor1 tiene el 5, $sValor2 el texto 'abc' y $arValor3 el array
    }
    // Pero le pasamos 3!
    testParams(5, 'abc', array(1 => 'test'));
?>

Si queremos hacer un código compatible con diferentes versiones de PHP, podemos usar la funcion function_exists para que en el caso de que una función no exista (por ser una versión anterior de PHP) podamos implementarla.

Podemos hacer un ejemplo de esto con filter_var y su validación de mails (que sí, ya sé que no va muy fina, pero no tengo mucha imaginación para el ejemplo 🙂 )

<?php
   function isValidMail($sMail) {
      // Introducida en PHP 5.2 
      if (function_exists('filter_var')) {
          if(filter_var($address, FILTER_VALIDATE_EMAIL) === FALSE) {
             return false;
          } else {
            return true;
          }
     // Podemos hacerlo a la vieja usanza, con una buena regexp 
     } else {
        // Expresion regular galáctica, cogida de la conocida phpMailer  
        return preg_match('/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!\.)){0,61}[a-zA-Z0-9_-]?\.)+[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!$)){0,61}[a-zA-Z0-9_]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/', $mail);
     } 
   }
?>

En unos días publicaré varias preguntas parecidas a lo que os podéis encontrar en un test real y las respuestas con un bonito botón de spoiler para que podáis pensarlo un poco.

¡Vaya post más largo me ha quedado! En fin, espero que os haya gustado, hayáis aprendido alguna cosa y… ¡Espero vuestros comentarios!

You may also like...

5 Responses

  1. Yo sigo todo loco con las referencias…
    Este post me suele dar dolor de cabeza 1 vez al mes
    http://es2.php.net/manual/en/language.references.pass.php#92348

    Still puzzled with references?

    See this code:

    If you expect the output to be 3, you are wrong. $a remains unchanged and the output is 1. The reference was NOT assigned to $a.

    Now let’s try this one:

    If you thought that now the output would be 3, you are wrong again. It’s 2.

    However this:

    Actually outputs 3.

    But how about Objects? Objects, you might think, are always passed by reference. So this should work!

    Well, let’s check it out:
    i = 1;
    $b = new StdClass;
    $b->i = 2;
    a($a, $b);
    $b->i = 3;
    print $a->i;
    ?>
    Outputs 1! So obviously you are wrong again.

    What’s the reason? Simple! The first (and the last) code example above translates to this fragment:

  2. Vaya! No se ha copiado bien el código (seguridad buena! jaja), a ver sin los tags php

    // primer ejemplo
    function a(&$a, &$b) { $a =& $b; }
    $a = 1;
    $b = 2;
    a($a, $b);
    $b = 3;
    print $a;

    // segundo ejemplo
    function a(&$a, &$b) { $a = $b; }
    $a = 1;
    $b = 2;
    a($a, $b);
    $b = 3;
    print $a;

    // tercer ejemplo (el único un poco lógico :mrgreen:)
    $a = 1;
    $b = 2;
    $a =& $b;
    $b = 3;
    print $a;

    // el ultimo
    function a($a, $b) { $a = $b; }
    $a = new StdClass;
    $a->i = 1;
    $b = new StdClass;
    $b->i = 2;
    a($a, $b);
    $b->i = 3;
    print $a->i;

  3. Ricard Clau says:

    Vaya tela el primero y el último! jejejeje

    El segundo me cuadra ya que haces que a sea lo que tiene b en ese momento, o sea 2, pero si cambias b no tiene que cambiar a ya que no está asignada la referencia.

    El último da 3 en PHP5.3, o sea lo esperado, no sé si en PHP 5.1 daba otra cosa jejeje!

    Y el primero me tiene loco! La explicación es que equivale a

    $a = 1;
    $b = 2;
    $a1 =& $a;
    $b1 =& $b;
    $a1 =& $b1;
    $b = 3;
    print $a;

    Pero no me cuadra! ¿A alguien le cuadra?

    • Francisco says:

      En php 5.3 el resultado de la 4 es 1
      de hecho debe comportarse igual que el 1er ejemplo, al asignar por referencia otra variable a un párametro asignado por referencia se está perdiendo la referencia anterior. y creando una nueva a $b

  4. Alexis Carrés says:

    Pues en el primero diría que tiene que ver con el ámbito de la variable $a que es global con valor 1.
    Así pues después de la llamada a la función a() la variable $a sigue teniendo el valor de 1 no?¿

Leave a Reply

Your email address will not be published. Required fields are marked *