Publi

Almacén de configuración para pequeños proyectos en PHP

Configuración de proyectos en PHP

Un punto importante en nuestros proyectos es la gestión de configuración. Cuando estamos delante de un gran proyecto, o empezándolo, seguro que queremos utilizar la gestión de configuración que tiene nuestro framework. Aunque, seguro que si es un proyecto pequeño, en el que, tal vez incluyamos uno o dos módulos con composer y no nos apetece engordar mucho el código con muchas dependencias, ni meter un framework muy complejo nos gustaría utilizar otra cosa. Algo sencillo, que tenga poco código.

Mi opción para esos casos es una clase estática que haga de almacén de valores. Toda la configuración la pondremos en un array dentro de dicha clase, que será accesible en cualquier punto de mi código, pero incluyo algunas utilidades para que sea relativamente fácil insertar y eliminar elementos.

Mis necesidades en la configuración

Lo que suelo necesitar en estos casos es un array multidimensional, a modo de árbol, en el que pueda construir categorías, subcategorías, sub-sub-categorías y demás. El objetivo es clasificar todos los elementos de configuración, ya sean de base de datos, usuarios, cachés, sistema, y dentro de cada uno de ellos podré introducir más cosas.
Además, toda la configuración, puede que esté almacenada en un archivo php, un json, un XML, ya depende de cada uno de nosotros.
Algo que me gusta mucho, y suelo necesitar en mis proyectos es que el acceso a cada uno de los elementos de la configuración sea rápido y como programador no me cueste demasiado. Es interesante que pueda acceder a los distintos elementos sin tener que utilizar muchos símbolos. Por ejemplo, si en el array de configuración, tengo una clave llamada Database, que luego tiene una clave llamada Main, y ésta tiene una clave llamada Server, a mí, como programador me baste decir Database.Main.Server para poder acceder al valor encerrado en la configuración.

Para ello, me he basado en un antiguo post en el que trataba el tema de lectura, escritura y eliminación de elementos de un array multidimensional usando separadores.
También es interesante este post en el que trato el tema de jerarquías en un array de PHP.

Clase Almacén estática

Antes de nada, construiremos una clase almacén con los elementos básicos que necesito:

  • Crear un nuevo nodo, ya sea array o valor, con independencia de la profundidad, si está en una rama interna deberán crearse todas las ramas intermedias hasta llegar. Si el array que introducimos tiene varias dimensiones, éstas deberán figurar como una ramificación también
  • Acceso a un nodo
  • Eliminación de un nodo
  • Obtención del array completo

En principio, podemos crear esta clase Almacen de escasas 80 líneas para ello:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?php
class Almacen {
    protected static $storage = array();
    protected const DefaultSeparator = '.';
    protected const ReturnFullArray = false;

    public static function set($path, $content, $sep=self::DefaultSeparator) {
        $_data=&self::$storage;

        if (!$path)
        {
            $_data = $content;
            return true;
        }
        $components = explode($sep, $path);
        $ccount = count($components);

        for ($i=0; $i<$ccount; $i++)
        {
            $key = $components[$i];
            if ($i == $ccount-1)
                $_data[$key] = $content;

            elseif (!isset($_data[$key]))
                $_data[$key] = array();

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

        return true;
    }

    public static function del($path, $sep=self::DefaultSeparator) {
        $_data = &self::$storage;

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

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

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

    public static function exists($path, $sep=self::DefaultSeparator) {
        if (!$path)
            return true;        /* Siempre existe la raíz. */
        return (self::get($path, false));
    }

    public static function get($path, $default=null, $sep=self::DefaultSeparator)
    {
        $data = &self::$storage;
        if (empty($data))
            return $default;

        /* ¿Nos interesa devolver el array completo? */
        if (!$path)
            return (self::ReturnFullArray)?self::$storage:false;

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

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

class Config extends Almacen {
};

$config = array('Database' => array('Main' => array('server'=> 'database.domain.com',
                                                    'user' => 'usuario',
                                                    'pass' => 'password'),
                                    'Archive' => array('server' => 'archive.domain.com',
                                                       'user' => 'arcuser',
                                                       'pass' => 'arcpass')),
                'User' => array('Session' => array('cache' => '120M',
                                                   'lifetime' => 3600,
                                                   'reload' => 600),
                                'Database' => array('table' => 'USERS',
                                                    'extinfo' => 'USER_EXT')),
);
Config::set('', $config);
Config::set('App.CLI.enabled', true);
Config::set('App.Cache', array('engine' => 'Redis',
                                'server' => 'localhost'));
Config::set('App.Paths', array('static' => 'st/',
                                'temp' => 'var/tmp'));
Config::set('App.Cache.expiration', 100);

/* Borramos una clave configuración */
Config::del('Database.Archive.pass');
/* Borramos una rama entera */
Config::del('App.Paths');
echo "Main database server: ".Config::get('Database.Main.server')."\n";
echo "App Cache engine: ".Config::get('App.Cache.engine')."\n";

Además, en el ejemplo he creado una clase derivada llamada Config. A partir de ahí podremos incluir la configuración de nuestra aplicación y cargarla en la parte del código que queramos.

Gestor de almacenes

Pero el acceso a las variables no resulta del todo cómodo al tener que andar con un get() y un set(). Además, en una aplicación normalmente tenemos varios almacenes. De esta forma, podemos crear una clase un poco más grande que gestione varios almacenes. Ahora los métodos no serán estáticos, por lo que tendremos más posibilidades.

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<?php
class Almacen implements ArrayAccess {

    protected $storage = array();
    protected const DefaultSeparator = '.';
    protected const ReturnFullArray = false;

    public function set($path, $content, $sep=self::DefaultSeparator) {
        $_data=&$this->storage;

        if (!$path)
        {
            $_data = $content;
            return true;
        }
        $components = explode($sep, $path);
        $ccount = count($components);

        for ($i=0; $i<$ccount; $i++)
        {
            $key = $components[$i];
            if ($i == $ccount-1)
                $_data[$key] = $content;

            elseif (!isset($_data[$key]))
                $_data[$key] = array();

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

        return true;
    }

    public function del($path, $sep=self::DefaultSeparator) {
        $_data = &$this->storage;

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

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

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

    public function exists($path, $sep=self::DefaultSeparator) {
        if (!$path)
            return true;        /* Siempre existe la raíz. */
        return ($this->get($path, false, $sep));
    }

    public function get($path, $default=null, $sep=self::DefaultSeparator)
    {
        $data = &$this->storage;
        if (empty($data))
            return $default;

        /* ¿Nos interesa devolver el array completo? */
        if (!$path)
            return (self::ReturnFullArray)?$this->$storage:false;

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

        foreach ($components as $k)
        {
            if (!isset($data[$k]))
                return $default;
            $data=&$data[$k];
        }
        return $data;
    }
    /* Implementamos ArrayAccess */

    public function offsetSet($offset, $valor) {
        $this->set((is_null($offset))?'':$offset, $valor);
    }

    public function offsetExists($offset) {
        return ($this->exists($offset));
    }

    public function offsetUnset($offset) {
        $this->del($offset);
    }

    public function offsetGet($offset) {
        return $this->get($offset);
    }
};

class AppGestor {
    protected static $instance = null;
    protected $almacenes = null;
    /* ¿Queremos permitir que se defina un almacén completo? */
    protected $allowFullSet = true;

    public function __construct() {
        $this->almacenes = array('config' => new Almacen);
    }
    public static function getInstance() {
        if (!self::$instance)
            self::$instance = new self;

        return self::$instance;
    }

    public function __set($element, $value) {
        if ((isset($this->almacenes[$element])) && ($this->allowFullSet) )
            return $this->almacenes[$element][] = $value;
        else
            throw new Exception('No deberías hacer esto...');
    }

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

function App() {
    return AppGestor::getInstance();
}

$config = array('Database' => array('Main' => array('server'=> 'database.domain.com',
                                                    'user' => 'usuario',
                                                    'pass' => 'password'),
                                    'Archive' => array('server' => 'archive.domain.com',
                                                       'user' => 'arcuser',
                                                       'pass' => 'arcpass')),
                'User' => array('Session' => array('cache' => '120M',
                                                   'lifetime' => 3600,
                                                   'reload' => 600),
                                'Database' => array('table' => 'USERS',
                                                    'extinfo' => 'USER_EXT')),
);
App()->config = $config;
App()->config['App.CLI.enabled'] = true;
App()->config['App.Cache'] = array('engine' => 'Redis',
                                'server' => 'localhost');
App()->config['App.Paths'] = array('static' => 'st/',
                                'temp' => 'var/tmp');
App()->config['App.Cache.expiration'] = 100;

/* Borramos una clave configuración */
App()->config->del('Database.Archive.pass');
/* Borramos una rama entera */
unset(App()->config['App.Paths']);

echo "Main database server: ".App()->config->get('Database.Main.server')."\n";
echo "App Cache engine: ".App()->config->get('App.Cache.engine')."\n";
var_dump(App()->config['App.Paths']);

Aquí nos aprovechamos de la implementación de ArrayAccess, que hace que podamos acceder a un objeto como si fuera un array. De este modo, podemos utilizar el almacén de configuración de manera más intuitiva, como si fuera un array, y para no complicarlo mucho, podemos poner la ruta completa dentro del índice.

Dentro de la gestión de almacenes, la clase AppGestor define los métodos __get y __set para poder acceder a los diferentes almacenes. Ahora mismo sólo tenemos el almacén de configuración, aunque podemos crear la cantidad de almacenes que consideremos oportuna.

Foto principal: unsplash-logoMr Cup / Fabien Barral

También podría interesarte....

There are 13 comments left Ir a comentario

  1. Pingback: Almacén de configuración para pequeños proyectos en PHP | PlanetaLibre /

  2. fix ceiling /
    Usando Google Chrome Google Chrome 116.0.0.0 en Windows Windows NT

    Thanks for sharing your method!

  3. Roulette Strategies /
    Usando Google Chrome Google Chrome 117.0.0.0 en Windows Windows NT

    Master the art of roulette with proven Roulette Strategies. Faktor.ba is your trusted source for a plethora of strategies that can enhance your gameplay. From Martingale to Fibonacci, Faktor.ba provides expert advice to help you make calculated bets and increase your odds of winning.

  4. tommchris /
    Usando Google Chrome Google Chrome 118.0.0.0 en Windows Windows NT

    Organiza tus archivos de configuración en una carpeta específica, por ejemplo, Uno Online en el directorio raíz de tu proyecto. Esto ayuda a mantener tus configuraciones separadas y accesibles.

  5. fix website /
    Usando Google Chrome Google Chrome 118.0.0.0 en Windows Windows NT

    Interesting. I’ll check it out after fixing a website. Thank you so much!

  6. 먹튀신고 /
    Usando Google Chrome Google Chrome 118.0.0.0 en Windows Windows NT

    Good Service and very instructive site! Great hand for people looking for someone like them! They have helped me a lot to get me out of the dilemma! Suggested to all!먹튀신고

  7. housemarch /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    It is a great informative website with excellent service! Perfect hand for those who are looking for someone who is similar to them! They have been of basketball stars great assistance to me in rescuing me from the predicament!

  8. Visit Fmovies /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    The examples and insights you’ve shared truly enrich the content. Thank you for this valuable resource; it’s a gem in the realm of topic discussions. fmovies

  9. Lubbock Drywall Contractors /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    Keep up the good work in streamlining configuration management for small-scale PHP projects!

  10. amelia /
    Usando Google Chrome Google Chrome 121.0.0.0 en Windows Windows NT

    If you like to watch sex movies, you are welcome to our site. Twitter amateur porn

  11. Brad Pitt Wolves Black Leather Jacket /
    Usando Google Chrome Google Chrome 121.0.0.0 en Windows Windows NT

    This is excellent article, thank you for the share! This is what I am looking for, hope in future you will continue sharing such an superb work.

  12. Younggunzkennel.com /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    Hello, Neat post. There is an issue with your website in internet explorer, might check this?

    IE nonetheless is the marketplace chief and a good portion of people will omit your wonderful writing due to this problem.

  13. Martin Klay /
    Usando Google Chrome Google Chrome 124.0.0.0 en Windows Windows NT

    Understanding the significance of configuration management in projects is crucial, whether large or small. While larger projects may benefit from the robust features of existing frameworks, smaller projects require a more lightweight approach. It’s essential to balance dependencies and complexity accordingly. Similarly, tailored support is invaluable in navigating complex legal landscapes, such as company law. Seeking assistance from reputable company law assignment help services can provide clarity and guidance, ensuring compliance and understanding of legal intricacies. Just as projects require meticulous management, legal matters demand attention to detail and expertise. Trusting project management and company law experts ensures success and adherence to best practices.

Leave a Reply to Anónimo Cancle Reply