Funciones definidas por el usuario y el uso de librerias

Una función es un conjunto de líneas de código agrupadas bajo un nombre que la identifica, que realizan una tarea específica, que pueden recibir valores con los que operar y que pueden devolver un valor de retorno.

05-Ene-2017

Una buena técnica de programación para el desarrollo de nuestros proyectos, consiste en agrupar líneas de código creando bloques que nos permitan reducir la cantidad de instrucciones repetitivas para obtener porciones de código más reducidas y legibles.

En consecuencia, podemos decir que una función es un conjunto de líneas de código agrupadas bajo un nombre que la identifica, que realizan una tarea específica, que pueden recibir valores con los que operar y que pueden devolver un valor de retorno.

Funciones definidas por el usuario

En PHP para definir una función se utiliza la palabra reservada ‘function’ seguida del nombre de la función y paréntesis donde se indican los llamados argumentos de trabajo y que encierra las líneas de código entre llaves {}.

function NombreFuncion([Argumentos]) {
  Líneas de código
  [return [ValorRetorno]]
}

Cualquier código PHP válido puede aparecer dentro de una función, incluso otras funciones.

No es necesario definir una función antes de que sea referenciada, excepto cuando esta esté condicionalmente definida dentro de sentencias condicionales como el if() o hayan sido definidas dentro de otras funciones.

Nombre de la función

Los nombres de las funciones siguen las mismas reglas que las demás etiquetas de PHP. Un nombre de función válido comienza con una letra o guion bajo, seguido de cualquier número de letras, números o guiones bajos.

Los nombres de funciones son Case-insensitive, es decir no se hace la distinción entre mayúsculas y minúsculas, aunque en la práctica siempre las escribiremos en minúsculas.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Nombre de funciones</title>
</head>

<body>
<h1>Mi nube informática</h1>
<?php 
bienvenida();
BIENVENIDA(); // Es correcto ya que los nombres de función son case-insensitive.
# Daría como resultado dos párrafos HTML con el texto Bienvenido a mi web.
?>
</body>
</html>
<?php
function bienvenida() {
	echo '<p>Bienvenido a mi web</p>';
}
?>

Argumentos

Podemos pasar cualquier información a las funciones mediante la lista de argumentos delimitadas por comas. Los argumentos son evaluados de izquierda a derecha, lo que implica que se tienen que enviar a la función en el mismo orden en el que hayan sido definidos.

También es importante tener en cuenta que los datos que le enviemos a la función, tendrán que ser del mismo tipo que espera la función recibir, es decir; si la función espera recibir dos argumentos de tipo numérico, le enviaremos dos números, si espera recibir un string y un número, le enviaremos una cadena y un número. De lo contrario el resultado puede ser inesperado a la hora de operar con los argumentos recibidos.

function NombreFuncion($Argumento1, $Argumento2, ...) {
  Líneas de código
}
<?php
 /* 
Si tenemos una función que recibe tres argumentos: una cadena, un número y otro tipo booleano, 
los valores que le pasemos tendrán que ser en ese mismo orden y del mismo tipo. 
*/
function evalua($mensaje, $numero, $estado) { }
$cad="numero negativo";
$num=-100;
$bol=true;
evalua($bol, $cad, $num); // orden de envío de argumentos ERRONEO
evalua($cad, $num, $bol); // orden CORRECTO de envío de argumentos
evalua($cad, $num); 	   // número de argumentos ERRONEO (falta el tercer argumento)
?>

Argumentos por valor y por referencia

El paso de argumentos se realiza de forma predeterminada por valor, es decir se envía una copia del valor. También admite el paso de argumentos por referencia anteponiendo el símbolo ‘&’ al nombre del argumento y listas de argumentos de longitud variable.

function NombreFuncion($Argumento1, &$Argumento2, ...) {
  Líneas de código
}
<body>
<h1>Mi nube informática</h1>
<?php etiqueta_html('P', 'Bienvenido a mi web');?>
</body>

<?php
# Recibe dos argumentos por valor: el nombre de la etiqueta HTML y el contenido
function etiqueta_html($etiqueta, $contenido) {
	echo "<$etiqueta>$contenido</$etiqueta>";
}
?>
# Daría como resultado un párrafo HTML con el texto Bienvenido a mi web.

Por referencia sólo es posible enviar variables y objetos, pero no valores constantes como números o cadenas.

<?php
function doble(&$numero) { $numero*=2; }

$n=10;
doble(10); // Error ya que enviamos un valor
doble($n); // Correcto ya que enviamos una variable
echo $n;  // Imprimirá el valor 20 ya que la variable se ha modificado dentro de la función
?>
<?php
/* 
Recibe dos argumentos 
Por valor: un número 
Por referencia: variable donde almacenar el resultado
*/
function cuadrado($numero, &$resultado) {
    $resultado=$numero*$numero;
    $numero=100;
}

$n=5;
$val=0;
cuadrado($n, $val);
# La variable $val almacena el cuadrado de $n, ya que se envió por referencia.
echo $val; // 25
# La variable $n contendrá el mismo valor ya que se envía por valor.
echo '<br>'.$n; // 2
?>

Argumentos predeterminados

Si deseamos declarar valores por defecto o predeterminados para los argumentos, los definiremos realizando la asignación del valor. Este valor será el que se utilice en el caso de no recibirse el argumento durante la llamada a la función. Cuando se emplean argumentos predeterminados, tendrán que ser declarados a la derecha de los argumentos no predeterminados, es decir; tendrán que ser siempre los últimos de la lista de argumentos de la función.

function NombreFuncion($Argumento1, $Argumento2=Valor, $Argumento3=Valor,  ..) {
  Líneas de código
}
<body>
<h1>Mi nube informática</h1>
<?php etiqueta_html('Bienvenido a mi web');?>
</body>

<?php
# Recibe dos argumentos: el contenido y el nombre de la etiqueta HTML
# Si no se especifica el nombre de la etiqueta se utilizará el valor P (párrafo)
function etiqueta_html($contenido, $etiqueta='P') {
	echo "<$etiqueta>$contenido</$etiqueta>";
}
?>
# Daría como resultado un párrafo HTML con el texto Bienvenido a mi web.

Uso de arrays como argumentos predeterminados

PHP admite el uso de arrays para declarar argumentos predeterminados en nuestras funciones, lo que nos permite en un solo argumento enviar múltiples valores.

Si utilizamos arrays asociativos, donde la clave es el nombre del argumento y el valor el valor del argumento, podremos enviar los argumentos que deseemos y en cualquier orden.

Dentro de la función utilizaremos la función array_key_exists(), para determinar qué argumentos hemos recibido.

function NombreFuncion($Argumento=[]) {
  if(array_key_exists('NombreArgumento', $Argumento)) $Arg1=$Argumento['NombreArgumento'];
  Líneas de código
}
<?php
# Imprime una etiqueta HTML con su contenido, aplicándole opcionalmente una clase css
# Recibe los argumentos en un array
# Si no se especifica algún argumento se utilizarán valores por defecto
function eti_html($eti=[]) {
// Variables de trabajo para crear la etiqueta HTML
	$contenido='';
	$etiqueta='P';
	$clase='';
// Si se reciben los argumentos esperados, asignamos a cada variable su valor
	if(array_key_exists('contenido',$eti)) $contenido=$eti['contenido'];
	if(array_key_exists('etiqueta',$eti)) $etiqueta=$eti['etiqueta'];	
	if(array_key_exists('clase',$eti)) $clase='class="'.$eti['clase'].'"';
// Imprime la etiqueta HTML con su contenido y la clase CSS
	echo "<$etiqueta $clase>$contenido</$etiqueta>";
}
?>
<body>
<h1>Mi nube informática</h1>
<?php 
// Imprime un párrafo sin aplicarle clase CSS
$etiqueta['contenido']='Bienvenido a mi web';
eti_html($etiqueta); 

// Imprime título de nivel 2 aplicándole las clases CSS tit-nav y text-center
eti_html(['contenido'=>'Título nivel 2','etiqueta'=>'H2','clase'=>'tit-nav text-center']); 

// Imprime una etiqueta blockquote sin aplicarle clase CSS
$etiqueta['contenido']='El elemento blockquote permite a los autores insertar citas en forma de bloques de contenido …';
$etiqueta['etiqueta']='blockquote';
eti_html($etiqueta); 
?>
</body>

Devolver valores

Cuando una función tiene que devolver un valor de retorno utiliza la sentencia opcional ‘return’ seguida del valor que desea retornar. Se puede devolver cualquier tipo de datos incluso arrays y objetos.

Esta sentencia provoca la finalización de la ejecución de las líneas de código de la función y devuelve el control a la instrucción que la referenció. Si se omite return dentro de la función el valor devuelto será siempre NULL.

function NombreFuncion([Argumentos]) {
    Líneas de código
    return[ValorRetorno]
}
<?php 
# Indica si el número que se le pasa como argumento es primo
function es_primo($numero) {
   // contador de nº de veces que se ha podido dividir
   $divisiones=0;
   // Recorremos todos los números desde el 2 hasta el número recibido
   for($i=2;$i<=$numero;$i++):
      if($numero%$i==0):
         // Si se ha podido dividir más de 1 vez no es primo
         if(++$divisiones>1):
            return false;
         endif;
      endif;
   endfor;
   // Si sólo se ha podido dividir 0 o 1 vez es primo
   return true;
}

# Comprobamos si el número 467 es primo
$n=467;
if(es_primo($n)) {
   echo "El número: $n es primo.";
} else {
   echo "El número: $n no es primo.";
}
?>
<?php 
# Devuelve un array con el nombre y la extensión del fichero que se envía como argumento
function nombre_extension($fichero) {
// Troceamos la ruta por el carácter separador de directorios
  $tmp=explode(DIRECTORY_SEPARATOR,$fichero);
// Obtenemos el último elemento que se corresponde con el nombre del archivo
  $archivo=end($tmp);
// Volvemos a trocear utilizando como marca el . que separa el nombre de la extensión
  $tmp=explode('.',$archivo);
// Obtenemos el elemento actual que se corresponde con el nombre
  $valores['nombre']=current($tmp);
// Obtenemos el último elemento que se corresponde con la extensión
  $valores['extension']=end($tmp);
// Devolvemos el array asociativo con los valores
  return($valores);
}

// Obtenemos el nombre y la extensión del script actual
$partes=nombre_extension(__FILE__);
echo 'Fichero: '.__FILE__.'<br>';
echo 'Nombre: '.$partes['nombre']. '<br>';
echo 'Extensión: '.$partes['extension'];

Funciones variables

PHP admite el concepto especial de funciones variables. Esto significa que si un nombre de variable tiene paréntesis anexos a él, PHP buscará una función con el mismo nombre que lo evaluado por la variable, e intentará ejecutarla.

Básicamente el funcionamiento consiste en asignarle a una variable el nombre de la función que tiene que representar y luego invocar a la función utilizando la variable con paréntesis anexos.

Veamos algún ejemplo:

 

<?php
// Definimos dos funciones para calcular el cuadrado y el doble de un número
function cuadrado($numero) {
	return ($numero*$numero);
}
function doble($numero) { 
	return ($numero*=2); 
}

// Asignamos a la variable el nombre de la función que deseamos que represente
$func='doble';
// Invocamos a la función añadiendo paréntesis al nombre de la variable
echo $func(25); // Muestra 50
$func='cuadrado';
echo $func(8); // Muestra 64
?>

Librerias de funciones

Si tenemos funciones que nos pueden servir para los scripts de nuestro aplicativo web, lo que tenemos que hacer es agruparlas en un único archivo php, formando lo que se llama una librería.

Cada vez que necesitemos utilizar alguna de las funciones, sólo tendremos que incluir la librería al principio de nuestro script para poder utilizarlas. De esta manera evitaremos tener que reescribirlas cada vez que las vayamos a utilizar.

Si añadimos nuevas funciones o modificamos las características de alguna de las funciones de la librería, estos cambios estarán también presentes en cada uno de los scripts que la incluyeron en su código.

Para poder incluir un archivo de librería en nuestros scripts, tendremos utilizar alguna de las siguientes sentencias de PHP: include, include_once, require, require_once.

include y require

Las sentencias include y require, incluyen en nuestro código el contenido del archivo que se le pase como argumento y lo evalúan.

    include(RutaArchivoPHP)
    require(RutaArchivoPHP)

Los archivos se incluirán en nuestro script con base a la ruta de acceso dada, o si no se indica ruta de acceso se buscarán en el directorio actual o en la lista de directorios en la directiva include_path de PHP.

Cuando se incluye un archivo, el código que contiene hereda el ámbito de las variables de la línea en la cual ocurre la inclusión. Cualquier variable disponible en esa línea del archivo que hace el llamado, estará disponible en el archivo llamado, desde ese punto en adelante. Sin embargo, todas las funciones y clases definidas en el archivo incluido tienen el ámbito global.

La principal diferencia entre include y requiere, se produce en el caso de no encontrarse el archivo a incluir. Mientras que include simplemente generará un mensaje de aviso y la ejecución continua, con requiere se genera un error fatal que provoca la interrupción del código. Dicho de otro modo mientras include sólo intenta incluir el contenido del archivo en nuestro script, require lo requiere, es decir; obliga a incluir el contenido del archivo.

include_once y require_once

De funcionamiento análogo a include y require, siendo la única diferencia de que si el código del fichero ya ha sido incluido, no se volverá a incluir, e include_once y require_once devolverán TRUE. Como su nombre indica, el fichero será incluido solamente una vez.

Librería de ejemplo (ficheros.php)

Vamos a poner en práctica lo que se ha visto en este módulo creando una pequeña librería de funciones personales de nombre ficheros.php. En esta librería definiremos una serie de funciones que nos permitan obtener información y realizar operaciones con ficheros, que posteriormente podemos utilizar en los scripts que necesiten manejar ficheros.

<?php 
# DEVUELVE UN ARRAY CON EL NOMBRE Y LA EXTENSIÓN DEL FICHERO QUE SE ENVÍA COMO ARGUMENTO
function nombre_extension($fichero) {
  $tmp=explode(DIRECTORY_SEPARATOR,$fichero);
  $archivo=end($tmp);
  $tmp=explode('.',$archivo);
  $valores['nombre']=current($tmp);
  $valores['extension']=end($tmp);
  return($valores);
}

/* 
   DEVUELVE EL TAMAÑO DEL FICHERO QUE SE ENVÍA COMO ARGUMENTO. 
   
   Permite indicar la unidad en la que deseamos obtener la información: 
   B (Bytes), K (Kilobytes), M (Megabytes), G (Gigabytes)
*/
function tamaño($fichero, $unidad='B') {
  if(!file_exists($fichero)) return 0;
  $tam=filesize($fichero);
  switch($unidad) {
	case 'G':	$tam/=1024;
	case 'M':	$tam/=1024;	
	case 'K':	$tam/=1024;	
  }
  return($tam);
}

# GENERA SUBCARPETAS CON ID ÚNICAS DENTRO DE UNA CARPETA PRINCIPAL
function rnd_carpeta($dir) {
  // Si no se encuentra el carácter / al final del nombre de la carpeta principal lo añade
  if(strrpos($dir,DIRECTORY_SEPARATOR)!=strlen($dir)-1){$dir.=DIRECTORY_SEPARATOR;}
  // Genera nº aleatorio que será el id de la subcarpeta
  $IdDir=mt_rand(1,mt_getrandmax());
  // Mientras exista ese id en la carpeta principal, genera nuevos numeros aleatorios
  while(file_exists($dir.$IdDir)) {$IdDir=mt_rand(1,mt_getrandmax());}
  // Crea la subcarpeta
  mkdir($dir.$IdDir);
  // Devuelve el identificador de la nueva subcarpeta
  return $IdDir;
}

/*
   ELIMINA EL CONTENIDO DE UN DIRECTORIO DE FORMA RECURSIVA
   
   Si el argumento $Eliminar se establece a false, vaciará el directorio y los subdirectorios, conservando
   la estructura de directorios
*/
function deleteDirectory($dir, $Eliminar=true) {
  if (!file_exists($dir)) return true;
  # Si no es un directorio o es un enlace
  if (!is_dir($dir) || is_link($dir)) {
    try {
      # Intentamos eliminar el fichero o enlace
      @unlink($dir);
      # devolvemos true como indicando que se ha podido eliminar
      return true;
    } catch (Exception $e) {
      # Si se ha producido un error a la hora de eliminar devolvemos false
      return false;
    }
  }
  # Si no era un fichero o enlace, se tratará de un directorio
  # Obtenemos los ficheros y directorios de $dir
  foreach (scandir($dir) as $item) {
    # Si es la referencia del directorio actual o del anterior, continuamos con el siguiente elemento
    if ($item == '.' || $item == '..') continue;
    # Llamamos recursivamente a la función enviándole la ruta del elemento leído
    if (!deleteDirectory($dir.DIRECTORY_SEPARATOR.$item,$Eliminar)) {
      # Intentamos establecer permisos de lectura, escritura y ejecución para todos los usuarios
      chmod($dir.DIRECTORY_SEPARATOR.$item, 0777);
      # Volvemos a intentar realizar la misma operación. En caso de error devolveremos false.
      if (!deleteDirectory($dir.DIRECTORY_SEPARATOR.$item,$Eliminar)) return false;
    }
  }
  # Si está activado el indicador de eliminar directorio lo eliminamos
  if($Eliminar) return rmdir($dir);
  else return true;
}

/* 
  LISTAR EL CONTENIDO DE UN DIRECTORIO
  
  El resultado lo devuelve en forma de array asociativo con las claves:
  nombre => nombre del archivo
  extension => extensión del archivo
  fecha => fecha de la última modificación
  tamaño => tamaño en bytes del archivo
*/
function listar_ficheros($carpeta) {
  $ficheros=[];
  # Comprobamos si existe la carpeta
  if(is_dir ($carpeta)) {
    # Abrimos el directorio para ir leyendo su contenido
    $handle = opendir($carpeta);
    # Generamos bucle mientras hayan datos que leer
    while (false !== ($fichero = readdir($handle))) {
      # Si no es el directorio padre o hijo
      if($fichero!='.' && $fichero!='..')	{
	# Si es de tipo fichero el recurso leido lo añadimos al array
       $Ruta=$carpeta . DIRECTORY_SEPARATOR .$fichero;
	   if(is_file($Ruta)) {
         $nombre=nombre_extension($Ruta);
         $fecha=date("d-m-Y H:i", filemtime($Ruta));
         $tama= number_format(tamaño($Ruta),0,',','.');
         $ficheros[] = ['nombre'=>$nombre['nombre'], 
                        'extension'=>$nombre['extension'], 
                        'tamaño'=> $tama, 
                        'fecha'=>$fecha];
       }
      }
    }
    # Cerramos el directorio
    closedir($handle);
  }
  # Devolvemos el array
  return $ficheros;
}
# COPIA UN FICHERO EN UN DIRECTORIO DE DESTINO
function copia($fichero, $dir) {
	# si no existe el fichero cancelamos la copia y devolvemos false
	if (!file_exists($fichero)) return false;
	# Si no existe el directorio de destino lo creamos
	if (!file_exists($dir)) mkdir($dir);
	# Si destino no es un directorio cancelamos la copia y devolvemos false
	if (!is_dir($dir)) return false;
	# Obtenemos el nombre y la extensión de fichero
	$fich=nombre_extension($fichero);
	# Creamos ruta de destino
	$destino=$dir.DIRECTORY_SEPARATOR.$fich['nombre'].'.'.$fich['extension'];
	# Devolvemos el resultado de realizar la copia (si falla devuelve false)
	return copy($fichero,$destino);
}

Una vez creada nuestra librería de funciones ficheros.php, cada vez que deseemos utilizar sus funciones lo único que tendremos que hacer es incluirla en nuestro código utilizando la instrucción require(‘ficheros.php’).

Crearemos un script de nombre demo.php para probar las funciones de nuestra librería:

 

<?php
require("ficheros.php");

// Vaciamos el directorio borrame y todas sus ramificaciones
deleteDirectory('borrame',false);
// Obtenemos un listado del contenido del directorio contenidos
$ficheros=listar_ficheros('contenidos');
echo '<pre>';
// Creamos una carpeta de nombre único
echo 'Creada carpeta temporal: '.rnd_carpeta(__DIR__)."\n\n";
// Mostramos el listado obtenido del directorio contenidos
echo "Listado de ficheros de la carpeta <strong>contenidos</strong>\n\n";
// Creamos los encabezados del listado
echo 	sprintf("%-16s\t%10s  %-30s\n",'Fecha','Tamaño','Nombre');
// Recorremos el array asociativo y vamos mostrando la información en columnas
foreach($ficheros as $fichero => $info) {
   $nombre=$info['nombre'].'.'.$info['extension'];
   echo sprintf("%16s\t%10s  %-30s\n", $info['fecha'], $info['tamaño'], $nombre);
}
// Copiamos el script en un directorio llamado nuevo_dir. Si no existe lo crea.
if(copia(__FILE__,'nuevo_dir')) 
   echo "\nFichero <strong>".__FILE__."</strong> copiado a la carpeta <strong>nuevo_dir</strong>.\n";
else 
   echo "Error al copiar el fichero.\n";
echo '</pre>';
?>

En la documentación que acompaña a este módulo, podréis encontrar la librería ficheros.php y el script demo.php