Quantcast
Channel: Planeta Código
Viewing all articles
Browse latest Browse all 2718

Poesía Binaria: Lectura, escritura y eliminación de elementos de un array multidimensional en PHP usando separadores

$
0
0

14300968086_927be23a6f_o

Puede parecer muy complejo así dicho. Pero de lo que se trata es de proporcionar una forma más natural para acceder a lo elementos de un array en PHP. Nos podemos imaginar un array de configuración de una aplicación, donde encontremos apartados como cookies, idiomas, usuarios, rutas, urls, apis externas, bases de datos e infinidad de cosas más. Hace un tiempo veíamos una función para acceder a una clave de un array, comprobando antes la existencia de esa clave y dándonos la opción de devolver un valor por defecto en caso de que dicha clave no exista.

Nota: si ya eres usuario de un Framework PHP, seguramente tengas métodos para hacer esto mismo, y ya estará todo hecho, a lo mejor con más opciones, aunque si quieres ver cómo está hecho, continúa leyendo. :)

Aquí recuerdo dicha función (porque el post anterior es muy largo):

1
2
3
4
5
<?php
function av($array,$key,$default=null)
{
  return(isset($array[$key]))?$array[$key]:$default;
}

De esta forma, en lugar de hacer $array[‘clave’] para leer un valor del array, haremos av($array, ‘clave’, 123), de esta forma podremos devolver 123 en caso de que la clave del array no exista, además de verificar la existencia de la clave previa al acceso, con lo que ahorraremos tiempo de ejecución (no es una eternidad, pero todo lo que podamos optimizar, mejor). En el post vemos que la mejor forma es no utilizar la función pero sí comprobar la existencia de la variable, aunque de esta forma estaremos escribiendo el array y la clave dos veces, y de cara a la programación eso no es muy útil y puede dar lugar a errores.

A lo que vamos hoy es a un array como el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$datos=array('usuarios'=>array('admin'=>array('email'=>'admin@miweb.com',
                                                    'nombre'=>'Administrador',
                                                    'nivel'=>999,
                                                    'ultima_entrada'=>'2015-07-30 10:10:10',
                                                    ),
                                  'bartolome'=>array('email'=>'bartolome@miweb.com',
                                                       'nombre'=>'Bartolomé Zancajo',
                                                       'ultima_entrada'=>'2015-08-01 10:10:10',
                                                       ),
                                   'carlos'=>array('email'=>'carlos@miweb.com',
                                                     'nombre'=>'Carlos Yuste',
                                                     'nivel'=>101,
                                                     'ultima_entrada'=>'2015-08-02 10:10:10',
                                                     ),
                                   'diego'=>array('email'=>'diego@miweb.com',
                                                    'nombre'=>'Diego Ximenez',
                                                    'ultima_entrada'=>'2015-08-03 10:10:10',
                                                    ),
                                   'ernesto'=>array('email'=>'ernesto@miweb.com',
                                                      'nombre'=>'Ernesto Wisconsin',
                                                      'nivel'=>103,
                                                      'ultima_entrada'=>'2015-08-04 10:10:10',
                                                      ),
                                   ),
                 );

Sería muy interesante poder acceder al nivel del usuario carlos especificando la ruta: “usuarios/carlos/nivel”. Por un lado, podemos pensar que es inmediato hacer $datos[‘usuarios’][‘carlos’][‘nivel’]. Pero tenemos el problema de comprobar que todos los elementos de la ruta existen (si miráis el array, no todos los usuarios tienen nivel, por lo que puede haber un nivel por defecto para todos los usuarios que no lo tengan, y así ahorramos algo de memoria; por lo que para poder acceder correctamente, es decir, sin producir errores, deberíamos hacer lo siguiente:

1
2
3
<?php
if((isset($datos['usuarios']))&&(isset($datos['usuarios']['carlos']))&&(isset($datos['usuarios']['carlos']['nivel'])))
   echo$datos['usuarios']['carlos']['nivel'];

y eso es muy largo. Es más, no es muy costoso generar la cadena “usuarios/carlos/nivel” o “usuarios.carlos.nivel” y pasársela a una función tipo:

1
2
<?php
  echo ag($datos,'usuarios/carlos/nivel');

es más, dicha cadena puede ser construida de forma fácil y dinámica, por ejemplo con los valores de un formulario. En fin, tiene muchas posibilidades.

Dejo aquí un ejemplo completo para la función ag(), para hacer copia y pega directamente (el array utilizado varía ligeramente del anterior, para hacerlo un poco más largo y meter índices numéricos, sólo por hacer un test más):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php
function av($array,$key,$default=null)
{
  return(isset($array[$key]))?$array[$key]:$default;
}

function ag($data,$path,$default=null,$sep='/')
{
  if(empty($data))
    return$default;

  $components=explode($sep,$path);

  foreach($componentsas$k)
    {
      if(!isset($data[$k]))
        return$default;
      $data=$data[$k];
    }
  return$data;
}

$usuarios=array('admin'=>array('email'=>'admin@miweb.com',
                                   'nombre'=>'Administrador',
                                   'nivel'=>999,
                                   'ultima_entrada'=>'2015-07-30 10:10:10',
                                   ),
                  'bartolome'=>array('email'=>'bartolome@miweb.com',
                                   'nombre'=>'Bartolomé Zancajo',
                                   'ultima_entrada'=>'2015-08-01 10:10:10',
                                   ),
                  'carlos'=>array('email'=>'carlos@miweb.com',
                                   'nombre'=>'Carlos Yuste',
                                   'nivel'=>101,
                                   'ultima_entrada'=>'2015-08-02 10:10:10',
                                   ),
                  'diego'=>array('email'=>'diego@miweb.com',
                                   'nombre'=>'Diego Ximenez',
                                   'ultima_entrada'=>'2015-08-03 10:10:10',
                                   ),
                  'ernesto'=>array('email'=>'ernesto@miweb.com',
                                   'nombre'=>'Ernesto Wisconsin',
                                   'nivel'=>103,
                                   'ultima_entrada'=>'2015-08-04 10:10:10',
                                   ),
                  );
$larga=array('datos'=>array('colecciones'=>array($usuarios),
                                'permisos'=>'read,write'),
               );

echo"Email existente: ".ag($usuarios,'ernesto/email')."\n";
echo"No existe user: ".ag($usuarios,'gabriel/email')."\n";
echo"No existe elemento: ".ag($usuarios,'carlos/apellido')."\n";
echo"Devuelvo array en cadena larga: ";
print_r(ag($larga,'datos/colecciones/0/admin'));

Un vistazo a la función

¿ Cómo estamos haciendo esto ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function ag($data,$path,$default=null,$sep='/')
{
  if(empty($data))
    return$default;

  $components=explode($sep,$path);

  foreach($componentsas$k)
    {
      if(!isset($data[$k]))
        return$default;
      $data=$data[$k];
    }
  return$data;
}

La clave de la realización de esta función radica en la separación de los elementos de la ruta (con explode() y el hacer corresponder cada uno de los elementos de la ruta con las claves del array, para eso usamos el foreach(). Para cada $k que obtenemos, realizamos la comprobación de existencia de dicha clave en el array de datos, en caso de no existir, sabemos que ya no vamos a encontrar más elementos de la ruta, y por tanto, devolvemos el valor por defecto; en caso contrario (si la clave existe en el array), haremos que el array con el que trabajamos se reduzca al elemento $k de nuestro array.

¡ Queremos algo más !

Vale, pues qué tal la creación y eliminación de elementos dentro de ese array. Es decir, que con una sola línea de código podamos dar valor a la ruta “datos/colecciones/0/felipe/email”, es decir, debería crearse el elemento “felipe” y luego el elemento “email”, o, complicándolo más, podemos crear la ruta: “datos/config/database/username, es decir, crear config, database y username, eso sí, aunque eso podríamos hacerlo de forma normal:

1
$array['datos']['config']=array('database'=>array('username'=>'nombre'));

podríamos tener un problema si no sabemos seguro si algún elemento de la cadena existe, por ejemplo, que ‘config’ pueda existir o no.

Para ello, tenemos la función aset():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
function aset(&$data,$path,$newData,$sep='/')
{
  $_data=&$data;               /* Hacemos copia de la referencia de data, para poder  */
                                /* operar con $_data sin miedo a perder la posición original */
  $components=explode($sep,$path);
  $ccount=count($components);

  for($i=0;$i<$ccount;$i++)
    {
      $key=$components[$i];
      if($i==$ccount-1)
          $_data[$key]=$newData;
      elseif(!isset($_data[$key]))
        $_data[$key]=array();

      $_data=&$_data[$key];  /* Movemos el puntero de $data a $data[$key] */
    }

  return$data;                /* Devolvemos el $data original */
}

Aquí, la clave está en que, en lugar de machacar nuestro array con $data[$k], estamos diciendo que $_data coja la referencia de $_data[$key], es decir, el array entero se mantiene en memoria, nosotros simplemente navegamos por él, por el árbol original, y no por copias de sub-árboles (y todo esto, sólo poniendo un &, esto se hace mucho en C, en PHP no tanto, pero podemos).

Para hacer una pequeña prueba podemos:

1
2
3
4
5
<?php
/* Definimos un elemento existente */
aset($larga,'datos/colecciones/0/ernesto/email','ernest@hemingway.hem');
/* Definimos un elemento no existente */
aset($larga,'datos/colecciones/1/fernando/nombre','Fernando Vicario');

símplemente, copiando la función y estas líneas en el ejemplo de ag().

¿Y si quiero borrar una rama?

Pues también podemos, de una forma muy parecida,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
function adel(&$data,$path,$sep='/')
{
  $_data=&$data;

  $components=explode($sep,$path);
  $ccount=count($components);

  for($i=0;$i<$ccount;$i++)
    {
      $key=$components[$i];
      if(!isset($_data[$key]))
        returnfalse;
      elseif($i==$ccount-1)
        {
          unset($_data[$key]);
          returntrue;
        }

      $_data=&$_data[$key];
    }

  returnfalse;                /* Nunca llegaremos aquí porque si el elemento no existe,
                                   el return false lo haremos arriba, y si existe, alguno
                                   tendrá que ser el último existe y ese devolverá true.*/

                                /* Siempre podemos poder un break en lugar de return false dentro
                                   del bucle. */

}

Y también dejo algo de código para probar esta función:

1
2
3
4
5
6
7
<?php
/* Borramos un elemento existente */
echo"Borro un elemento existente: ";
var_dump(adel($larga,'datos/colecciones/0/bartolome/ultima_entrada'));
echo"Borro un elemento inexistente: ";
var_dump(adel($larga,'datos/elemento/no/existe'));
print_r($larga);

Algunas notas más

Como vemos, estas funciones tienen un argumento llamado $sep, que por defecto vale una barra “/”, aunque no es necesario utilizarlo, puede que nos interese separar los elementos de la ruta por un punto en lugar de una barra, basta con poner ese valor a “.”.

Aunque las funciones no son especialmente lentas, si vamos a hacer muchas llamadas (imaginemos dentro de un array), es inevitable que se retrase la ejecución del código, por lo que es una buena técnica que en lugar de esto:

1
2
3
4
echo"Nombre: ".ag($larga,'datos/colecciones/0/ernesto/nombre')."\n";
echo"Nivel: ".ag($larga,'datos/colecciones/0/ernesto/nivel')."\n";
echo"E-mail: ".ag($larga,'datos/colecciones/0/ernesto/email')."\n";
echo"Última entrada: ".ag($larga,'datos/colecciones/0/ernesto/ultima_entrada')."\n";

hagáis esto:

1
2
3
4
5
$usuario= ag($larga,'datos/colecciones/0/ernesto');
echo"Nombre: ".av($usuario,'nombre')."\n";
echo"Nivel: ".ag($usuario,'nivel')."\n";
echo"E-mail: ".ag($usuario,'email')."\n";
echo"Última entrada: ".av($usuario,'ultima_entrada')."\n";

Por lo que la búsqueda grande sólo la hacemos una vez… y, si de verdad estamos seguros de que el usuario devuelve todos los datos sin problema, podemos prescindir de av() y hacer $usuario[‘nombre’], $usuario[‘nivel’]…

Por último, indicar que todos los nombres (con apellidos) utilizados en los ejemplos son inventados, o de personas famosas (lo digo por ernest@hemingway, si os fijáis, quitando Administrador, el nombre que empieza por B, su apellido por Z, el que empieza por C, su apellido por Y, y así sucesivamente.

Foto: Carrie Roy (Flickr CC)

The post Lectura, escritura y eliminación de elementos de un array multidimensional en PHP usando separadores appeared first on Poesía Binaria.


Viewing all articles
Browse latest Browse all 2718

Trending Articles


Girasoles para colorear


mayabang Quotes, Torpe Quotes, tanga Quotes


Tagalog Quotes About Crush – Tagalog Love Quotes


OFW quotes : Pinoy Tagalog Quotes


Long Distance Relationship Tagalog Love Quotes


Tagalog Quotes To Move on and More Love Love Love Quotes


5 Tagalog Relationship Rules


Best Crush Tagalog Quotes And Sayings 2017


Re:Mutton Pies (lleechef)


FORECLOSURE OF REAL ESTATE MORTGAGE


Sapos para colorear


tagalog love Quotes – Tiwala Quotes


Break up Quotes Tagalog Love Quote – Broken Hearted Quotes Tagalog


Patama Quotes : Tagalog Inspirational Quotes


Pamatay na Banat and Mga Patama Love Quotes


Tagalog Long Distance Relationship Love Quotes


BARKADA TAGALOG QUOTES


“BAHAY KUBO HUGOT”


Vimeo 10.7.0 by Vimeo.com, Inc.


Vimeo 10.7.1 by Vimeo.com, Inc.