Bonjour tout le monde, je travaille actuellement seul sur un micro-framework. Un des composant principal de mon framework est un Conteneur d'Injection de Dépendances, j'ai voulus en faire un facilement utilisable et ergonomique. Le conteneur fonctionne comme une collection ou toutes les valeurs sont gérées de la même manière. Il est possible d'y mapper des variables et de les retourner par la suite grâce aux méthode magiques __get et __set. Le conteneur permet aussi de mapper des méthodes qui seront accessibles grâce à __call. Pour permettre des manipulations plus avancé et créer des automatismes, il est possible d'appliquer des "filtres" aux getters et aux setters dynamiques, par exemple, on peut créer un filtre pour les getters qui vérifie si la valeur est une chaine qui est le nom d'une classe valide, et si oui, le getter retournera une instance de celle-ci. Je souhaiterais avoir des retours et avis quant à la qualités et à la conception de ma classe, je poste donc le code si-dessous avec son test unitaire, suivi de quelques exemples d'utilisations et explications.
Code du conteneur
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194 <?php // WIP DIC by TheKitsuneWithATie class Container { /** * @var array Filters. */ private $_filters = array('set' => array(), 'get' => array()); /** * @var array Mapped variables. */ private $_map = array(); public function __construct() { // Adding default classes get filter $this->addGetFilter('*', function($container, &$value, &$output) { if (is_array($value) && isset($value['class'])) { // If an instance is stored, the return it if (isset($value['instance'])) { $output = $value['instance']; return; } // Fixing parameters $args = isset($value['args']) ? $value['args'] : array(); $shared = isset($value['shared']) ? $value['shared'] : true; $inject = isset($value['inject']) ? $value['inject'] : array(); $reflection = new \ReflectionClass($value['class']); $instance = $reflection->newInstanceArgs($args); // Storing the instance if the class is shared if ($shared) $value['instance'] = $instance; if (is_subclass_of($instance, __CLASS__)) { foreach ($inject as $dependency) $instance->{$dependency} = $this->{$dependency}; } $output = $instance; } }); } public function __set($name, $value) { // Calling filters foreach ($this->_filters['set'] as $filter) { if (preg_match($filter['pattern'], $name)) { $filter['filter']($this, $value); } } $index = &$this->_goto($name, true); $index = $value; } public function __get($name) { $index = &$this->_goto($name); $return = $index; // The isset function should be used beforehand to avoid this exception if ($index === null) throw new \Exception("Cannot get unset variable '$name'."); // Calling filters foreach ($this->_filters['get'] as $filter) { if (preg_match($filter['pattern'], $name)) $filter['filter']($this, $index, $return); } return $return; } public function __call($method, $args) { $index = &$this->_goto($method); if ($index === null) throw new \Exception("Cannot call unset function '$method'."); if (!is_callable($index)) throw new \Exception("Cannot call non-callable '$method'."); return call_user_func_array($index, $args); } public function __isset($name) { return ($this->_goto($name) !== null); } public function __unset($name) { $index = &$this->_goto($name); $index = null; } /** * Adds a filter called when setting a variable. * * @param string $pattern Regex pattern of the variables to filter * @param callable $filter Filter * * @return $this */ public function addSetFilter($pattern, $filter) { return $this->_addFilter('set', $pattern, $filter); } /** * Adds a filter called when getting a variable. * * @param string $pattern Regex pattern of the variables to filter * @param callable $filter Filter * * @return $this */ public function addGetFilter($pattern, $filter) { return $this->_addFilter('get', $pattern, $filter); } /** * Adds a filter called when getting or setting a variable. * * @param string $type Either 'get' or 'set' * @param string $pattern Regex pattern of the variables to filter * @param callable $filter Filter * * @return $this */ private function _addFilter($type, $pattern, $filter) { $pattern = '#' . str_replace('*', '.*', $pattern) . '#'; $this->_filters[$type][] = array( 'pattern' => $pattern, 'filter' => $filter ); return $this; } /** * Returns a reference of mapped array index according to the path. * * @param string $path Path to go to * @param boolean $fix Will it create missing indexes from the path * * @return mixed|null Reference to the index or null if nothing matches the path */ private function &_goto($path, $fix = false) { $path = explode('_', $path); $pointer = &$this->_map; // Initializing pointer $return = $pointer; // Return value // Going throught the path foreach ($path as $index) { if (!isset($pointer[$index])) { // Create missing indexes if the path needs to be fixed if ($fix) { $pointer[$index] = null; } // Stop if the path doesn't continue else { $return = null; break; } } // Updating the pointer $pointer = &$pointer[$index]; } // Updating return value if ($return !== null) $return = &$pointer; return $return; } }
Test unitaire
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178 <?php class ContainerChild extends \Bonzai\core\di\Container { private $_property; public function __construct($value = null) { parent::__construct(); $this->_property = $value; } public function getProperty() { return $this->_property; } public function setProperty($value) { $this->_property = $value; return $this; } } class ContainerTest extends PHPUnit_Framework_TestCase { private $container; public function setUp() { $this->container = new \Bonzai\core\di\Container; } /** * Setting and getting a variable. */ public function testVariable() { $container = $this->container; $container->testVar = true; $retreived = $container->testVar; $this->assertTrue($retreived); } /** * Checking if a variable is set. */ public function testIssetVariable() { $container = $this->container; $container->testIssetVar = true; $isset = isset($container->testIssetVar); $this->assertTrue($isset); } /** * Unsetting a variable. */ public function testUnsetVariable() { $container = $this->container; $container->testUnsetVar = true; unset($container->testUnsetVar); $isset = isset($container->testUnsetVar); $this->assertFalse($isset); } /** * Mapping a function. */ public function testMapFunction() { $container = $this->container; $container->testFunction = function($int) { return $int * $int; }; $square = $container->testFunction(3); $this->assertEquals(9, $square); } /** * Mapping a class. */ public function testMapClass() { $container = $this->container; $container->testMap_class = '\ContainerChild'; $instance = $container->testMap; $this->assertInstanceOf('ContainerChild', $instance); } /** * Mapping a non shared class. */ public function testMapClassNonShared() { $container = $this->container; $container->testMapNonShared_class = '\ContainerChild'; $container->testMapNonShared_shared = false; $first = $container->testMapNonShared; $second = $container->testMapNonShared; $this->assertNotSame($first, $second); } /** * Mapping a class with "chain injection". */ public function testMapClassChainInject() { $container = $this->container; $container->testMapInject_class = '\ContainerChild'; $container->testMapInjectSecond_class = '\ContainerChild'; $container->testMapInjectSecond_inject = array('testMapInject'); $first = $container->testMapInject; $second = $container->testMapInjectSecond->testMapInject; $this->assertSame($first, $second); } /** * Adding a set filter. */ public function testAddSetFilter() { $container = $this->container; $container->addSetFilter('*', function($c, &$v) { $v = true; }); $container->testVarSetFilter = false; $retreived = $container->testVarSetFilter; $this->assertTrue($retreived); } /** * Adding a get filter. */ public function testAddGetFilter() { $container = $this->container; $container->addGetFilter('*', function($c, &$v, &$o) { $o = false; }); $container->testVarGetFilter = true; $retreived = $container->testVarGetFilter; $this->assertFalse($retreived); } }
Utilisation est exemples
Utiliser le code est assez simple, pour mapper un variable, il suffit de faire ça:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 $container->path_to_var = true;
Pour retourner une variable mappée, il suffit de faire cela:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 $retreived = $container->path_to_var;
Un filtre par défaut permet aussi de mapper des classes comme montré si-dessous:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 $container->db_pdo = array('class' => '\PDO', 'args' => array('127.0.0.1', 'root', ''), 'shared' => false); $pdo = $container->db_pdo;
Il est aussi possible de faire ce que j'ai appellé de "l'injection en chaine", c'est à dire injecté une dépendance dans une autre dépendance. C'est pratique par exemple si on a besoin d'un "dispatcher" ou d'une classe qui n'est ni statique ou ni un singleton et don la même instance doit être accessible par tout un système. Il est aussi très simple de définir les dépendances à injecter, a noter qu'il faut que la dépendance qui va ce voir injecter les autres dépendances doit être une classe fille de Container:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 $container->test1 = array('class' => '\ContainerChild'); $container->test2 = array('class' => '\ContainerChild', 'inject' => array('test1')); $test1 = $container->test2->test1;
Que pensez vous de ce Conteneur d'Injection de Dépendances, y a t'il des choses à modifier? Est il ergonomique et compréhensible? Donnez moi vos avis s'il vous plait, j'aimerais avoir des retours quant à la qualitée de cette classe. Merci d'avance!
P.S: Veuillez m'excuser si je me suis trompé de section pour poster ce sujet, mais les autres ne semblaient pas êtres adéquates pour ce sujet.
Partager