eigen framework / beheersysteem
Pagina: « vorige 1 2 3 4 5 6 7 8 ... 10 11 12 volgende »
Die test unit laat ik vooralsnog even achterwege.
Wat is het verschil tussen een container of registry?
1) MVC framework link
2) Router vervangen door betere versie zie comments
3) Database active record link
4) method chaining $this->db->select('title')->from('mytable')->where('id', $id)->limit(10, 20);
5) Lazy registreren objecten link en Pimple
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
// Registry
$config = new Config;
$registry = new Registry;
$pdo = new PDO($config->db_dsn);
// Configureren
$pdo->setErrorhandler($config->db_pdoErrorHandler);
$registry->pdo = $pdo;
// Container
$c = new Container;
$c->setParameters(new Config('conf.ini'));
$c->set('pdo', function($c) {
$pdo = new PDO($c->db_dsn);
$pdo->setErrorHandler($c->db_pdoErrorHandler);
return $pdo;
});
?>
// Registry
$config = new Config;
$registry = new Registry;
$pdo = new PDO($config->db_dsn);
// Configureren
$pdo->setErrorhandler($config->db_pdoErrorHandler);
$registry->pdo = $pdo;
// Container
$c = new Container;
$c->setParameters(new Config('conf.ini'));
$c->set('pdo', function($c) {
$pdo = new PDO($c->db_dsn);
$pdo->setErrorHandler($c->db_pdoErrorHandler);
return $pdo;
});
?>
De tweede kan evenveel als de eerste, is flexibeler, mooier (vind ik) en lazy.
Unit testen is niet moeilijk en het stelt je in staat te controleren of je klassen werken nadat je ze geschreven hebt, in plaats van eerst een complete applicatie te maken om er dan achter te komen dat er dingen niet werken, die je dan moet opzoeken en oplossen.
Gewijzigd op 04/01/2011 11:39:21 door Pim -
Die container snap ik nog niet helemaal. Vind het een beetje abstract en snap niet echt wat er nou eigenlijk gebeurt.
Toevoeging op 04/01/2011 11:46:40:
Pim - op 04/01/2011 11:37:10:
Misschien heel kort door de bocht, maar hoe kan je eigen code nou niet werken? Dan gaat er toch iets mis? Foutmelding of iets doet niet wat je verwacht?Unit testen is niet moeilijk en het stelt je in staat te controleren of je klassen werken nadat je ze geschreven hebt, in plaats van eerst een complete applicatie te maken om er dan achter te komen dat er dingen niet werken, die je dan moet opzoeken en oplossen.
Over DIC
De syntax bij $c->set is nieuw in PHP5.3 en daarom niet zo bekend. Het is een anonymous functie als tweede argument. Dit is eigenlijk een factory. Die functie wordt pas geroepen bij het ophalen van de 'service'. Die anonymous functie krijgt als argument de container zelf, waardoor je de 'dependencies' of afhankelijkheden, andere services of configuratie parameters, kan gebruiken.
In de container gebeurt dan dit:
Code (php)
De anonieme functie wordt dus opgeslagen als variabele in de container.
Het argument $this stelt de factory functie in staat de container te gebruiken.
Het mooie van Unit Testing is dat je die foutmelding krijgt direct nadat je de klasse geschreven hebt (als je hem dan meteen Unit test, zoals het hoort) en niet pas nadat je 8 andere klassen hebt geschreven waar die klasse mee samenwerkt en die nodig zijn om het te laten functioneren. En hoe sneller je een fout ziet, hoe beter je hem op kan lossen.
Wat kan UNIT TESTING dan wat ik zelf niet kan??
Ozzie PHP op 04/01/2011 12:08:52:
Wat kan UNIT TESTING dan wat ik zelf niet kan??
Is je huiswerk altijd zonder fouten? Dat je grammaticaal correcte zinnen kan schrijven wil nog niet betekenen dat dat wat ze zeggen waar is.
Unit testing is fact checking, testen of het waar is. Als je het echt goed wilt doen schrijf je eerst de test, waarin je zet hoe je je class wilt gaan gebruiken en wat hij allemaal moet kunnen, wat je ervan verwacht. Dan implementeer je hem, en dan kijk je of dat wat je geschreven hebt doet wat je verwachtte dat het zou doen.
Jelmer rrrr op 04/01/2011 12:28:00:
Is je huiswerk altijd zonder fouten? Dat je grammaticaal correcte zinnen kan schrijven wil nog niet betekenen dat dat wat ze zeggen waar is.
Unit testing is fact checking, testen of het waar is. Als je het echt goed wilt doen schrijf je eerst de test, waarin je zet hoe je je class wilt gaan gebruiken en wat hij allemaal moet kunnen, wat je ervan verwacht. Dan implementeer je hem, en dan kijk je of dat wat je geschreven hebt doet wat je verwachtte dat het zou doen.
Ozzie PHP op 04/01/2011 12:08:52:
Wat kan UNIT TESTING dan wat ik zelf niet kan??
Is je huiswerk altijd zonder fouten? Dat je grammaticaal correcte zinnen kan schrijven wil nog niet betekenen dat dat wat ze zeggen waar is.
Unit testing is fact checking, testen of het waar is. Als je het echt goed wilt doen schrijf je eerst de test, waarin je zet hoe je je class wilt gaan gebruiken en wat hij allemaal moet kunnen, wat je ervan verwacht. Dan implementeer je hem, en dan kijk je of dat wat je geschreven hebt doet wat je verwachtte dat het zou doen.
vooral om later te controleren of alles nog werkt... als je a aanpast.. werkt b dan ook nog?
Hoe kun je het beste variabelen doorgeven aan een view? Gewoon via een array? Of wellicht via een object?
Dus via array:
$variables['name']
of object:
$variables->name;
Zelf vind ik in de view extract() wel mooi, dan kan je gewoon $name gebruiken.
Oh, wist niet dat dat kan... is inderdaad wel erg mooi :)
- topic 1
- topic 2
En verder helpt deze tutorial om de echte beginselen te planten.
Maar goed dat was even offtopic :)
Quote:
unit testing
En ga fouten maken, daar leer je het meeste van. Al moet je dan niet bang zijn om regelmatig even een stap terug te doen en eerst even de gemaakte fouten te gaan herstellen. Neem dus ook een platform die je op jouw fouten wijst en zorg dat de fouten z.s.m. zichtbaar worden. Test driven development, TDD, is hier ideaal voor, dit is gemaakt om fouten direct aan het licht te brengen.
http://en.wikipedia.org/wiki/Test-driven_development
Quote:
Ik maak gebruik altijd $this->name. Dat vind ik wel netjes.
Quote:
Ik vind het maar lastig met die container en registries
Eerst een verhaaltje: Bijna 100 jaar geleden (September 1908 om precies te zijn) bracht Henry Ford zijn eerste Ford Model T (ook wel Tin Lizzie genoemd) op de markt. Zoals waarschijnlijk wel bekend, werd de Model T de eerst op zeer grote schaal geproduceerde auto. De vraag naar auto’s steeg tijdens de begindagen van de Model T enorm. Auto’s werden voor meer mensen dan alleen de elite beschikbaar.
Henry Ford had moeite om aan de vraag te voldoen. In de eerste maanden dat de Model T werd produceerd werden er slechts 11 auto’s per maand geproduceerd. Dit alles gebeurde in de fabriek van Ford op ‘Piquette Avenue’ in Detroit. In 1910 waren er ‘slechts’ 12.000 Model T Ford auto’s geproduceerd en dat was het moment dat Henry Ford de productie verplaatste naar het Highland Park Complex, waar tot op de dag van vandaag de archivering plaatsvindt voor Ford.
In het Highland Park Complex werd de assemblagelijn ontwikkeld. Henry Ford deed dit niet zelf, het was één van zijn medewerkers die in een frabriek in Chicage een straat gezien had die kippen deassembleerde. De deassemblage (klinkt mooi, niet waar?) van de kip gebeurde in deze fabriek op een gecontroleerde manier, stapje voor stapje en volledig automatisch. Tegenwoordig worden op de meeste gruwelijke manieren kippen middels uitbeenmachines, ontbeenmachines en nog veel meer machines met gruwelijk namen gedeassembleerd. Met meestelijke efficiëntie worden miljoenen kippen (gok ik) per dag gedeassembleerd. Maar goed, terug in het onderwerp. De persoon die dit allemaal zag dacht: Als we dat nou in de omgekeerde volgorde doen (assemblage in plaats van deassemblage) maar dan voor auto’s. Het was dus niet the boss himself die de assemblagelijn introduceerde, maar goed, hij was wel degene die de gok durfde te nemen deze revolutionaire manier van produceren uit te proberen.
Gauw naar het jaar 1914. Dit was het jaar dat er elke 93 minuten een Model T Ford van de band rolde. Een paar jaar eerder waren dit zoals al verteld slechts 11 per maand geweest. Als je ervan uitgaat dat men 8 uur per dag en 5 dagen per week assembleert betekend dit dat Ford meer dan 10 keer zoveel auto’s kon produceren per maand. De assemblagelijn had dus zijjn succes bewezen! Natuurlijk was niet alleen de assemblagelijn de grote reden dat er elke 93 minuten een Model T Ford van de band rolde. Ook de introductie van de 5 daagse werkweek en het vaste loon, onafhankelijk van de ‘productie’ van de medewerker waren belangrijke factoren die meehielpen bij een snelle producten van de Model T Ford. Die Henry Ford had de zaakjes goed voor mekaar dus.
Even iets dieper inzoomend op de assemblagelijn. Deze lijn klikt verschillende onderdelen aan elkaar, om uiteindelijk een auto te fabriceren. De onderdelen zelf zijn op een ander tijdstip gemaakt en zijn zo gemaakt dat ze op andere onderdelen aansluiten (boutjes, moertjes, schroefjes, pasvormen). Zo zal een kogelvrije vooruit voor een Model T Ford (zouden ze die al hebben gehad?) dezelfde vorm gehad hebben gehad als een vooruit waarmee een gewone man het moest doen. Ditzelfde geldt voor de wielen. Winterbanden (geloof niet dat ze die hadden) zul je op ongeveer dezelfde manier op de Model T Ford gezet hebben als normale banden.
Als eerste kunnen we constateren dat de assemblage van de verschillende componenten waaruit de auto bestaat volledig en voor 100% losgekoppeld is van het daadwerkelijk functioneren van deze componenten. Dit is logisch lijkt mij. Een cake bakt zichzelf niet, daar heb je een recept voor nodig en iemand die de eieren, bloem, suiker en dergelijke door de mixer haalt en in de over zet. Hetzelfde geldt voor een huis, dat bouwt zichzelf ook niet. Je hebt daar architecten voor nodig die een bouwtekening maken, metselaars en timmermannen voor de bouw van het huis. En ook een auto bouwt zichzelf niet, die wordt geassembleerd.
Een interessante vraag is waarom die assemblagelijn nu eigenlijk zo succesvol is. De algemene mening hierover is dat specialisatie efficientie bevorderd. Als je je als persoon ergens in specialiseert wordt je er beter in. Dat kunnen we ook over machines zeggen. Als je een machine bouwt die specifiek moertjes op boutjes draait, zal het gemakkelijker zijn deze machine hierop te specialiseren.
De algemene mening binnen de productiewereld is ook, dat het zich specialiseren in geval van mensen ook nadelen heeft. Overspecialisatie kan leiden tot mensen die hun werk saai gaan vinden, specifieke blessures krijgen door steeds het zelfde werk te doen (RSI, etc).
Maar goed laten we nog even bij de assemblagelijn blijven. Het onderliggende princiepe van de assemblagelijn is dus specialisatie. Dus met andere woorden; het zich richten op één deelprobleem tegelijkertijd zorgt voor een efficienter en gemakkelijker manier van het oplossen van een groter probleem. Door verschillende delen van de assemblagelijn zo in te richten dat ze elk één klein deel problemen oplossen (het op de auto schroeven van de banden, het plaatsen van het motorblok, etc) en de assemblage in zijn geheel los te koppelen van de eigenlijke functies van de auto, wordt het mogelijk om:
- Elk individuele stap in de assemblage te optimaliseren
- Elke individuele stap in de assemblage te beschrijven en herhaalbaar te maken zodat bij het inrichten van een nieuwe assemblagelijn er slechts een copy-paste actie uitgevoerd hoeft te worden.
- De individuele stap te observeren ten einde kwaliteitstestjes in te voeren die specifiek gericht zijn om deze stap in het proces te controleren (is de schroef wel goed vastgedraaid, enzovoort).
- Ten einde het hele proces van het in elkaar zetten van de auto (bijna) oneindig veel te optimaliseren en controleren.
Mmm.. Leuk dit allemaal maar ik geloof dat we op een php forum zitten? Juist ja, maar als je het bovenstaande stuk nog eens leest in het licht van php, dan moeten er toch wel lichtjes gaan branden nietwaar? Maar goed nog even terug naar de auto: die bestaat uit een (zeer grote) verzameling onderdelen die allemaal een belangrijke rol spelen in het laten rijden van de auto. De auto dient geassembleerd te worden. We zouden middels object-orientatie ook de auto kunnen modelleren:
- Car
- Engine
- Wiel
Maar als we dat doen, dan moeten we nog wel iemand hebben die voor ons die auto (de applicatie) assembleert. Dat is waar Dependency Injection, afgekort DI, om de hoek komt kijken.
Echter Dependecy Injection lost het probleem van de assemblage van een applicatie op, niet het gedrag van de componenten die zijn geassembleerd, of de communicatie tussen deze componenten. Separation of Concerns (problemen opdelen in sub-problemen) is een goed principe en ik zou bijna zo arrogant willen zijn door te zeggen dat niemand meer een applicatie moet willen bouwen zonder zich op één deelprobleem tegelijkertijd zou moeten focusen en de individuele componentjes die deze deelproblemen oplossen te assembleren doormiddel van Dependency Injection.
Hoe je die assemblage precies vormgeeft is natuurlijk vraag twee. Maar daar heb je volgens mij al genoeg informatie over gehad. Welke container ik overigens aanraad is de volgende van het sphoof framework:
Code (php)
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
<?php
/**
* This file is part of the Sphoof framework.
* Copyright (c) 2010, Sphoof
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. You can also view the
* LICENSE file online at http://www.sphoof.nl/new-bsd.txt
*
* @category Sphoof
* @copyright Copyright (c) 2010 Sphoof (http://sphoof.nl)
* @license http://sphoof.nl/new-bsd.txt New BSD License
* @package Container
*/
/**
* Exception that will be thrown upon encountering an unknown scalar parameter.
*
* @package Container
* @subpackage Exception
*/
class SpUnknownScalar extends SpException { }
/**
* Exception that will be thrown upon encountering an unresolvable parameter.
*
* @package Container
*/
class SpUnknownDependency extends SpException { }
/**
* Exception that will be thrown upon requesting a class that does not exist.
*
* @package Container
*/
class SpUnknownClass extends SpException { }
/**
* Exception that will be thrown upon registering an injecting method that does not exist.
*
* @package Container
*/
class SpUnknownSetterMethod extends SpException { }
/**
* Exception that will be thrown upon requesting an interface that has no
* concrete implementation.
*
* @package Container
*/
class SpUnknownImplementation extends SpException { }
/**
* An exception thrown when using incorrect arguments.
*
* @package Container
*/
class SpInvalidArgumentException extends SpException { }
/**
* The dependency injection container.
*
* @package Container
*/
class SpContainer {
/**
* An optional context to look into.
*
* @var SpContainer
*/
protected $context;
/**
* An array of instances
*
* @var array
*/
protected $instances;
/**
* An array of scalar values.
*
* @var array
*/
protected $scalars;
/**
* An array of implementations.
*
* @var array
*/
protected $implementations;
/**
* An array of ReflectionClass instances.
*
* @var array
*/
protected $reflectedClasses;
/**
* An array of ReflectionMethod instances.
*
* @var array
*/
protected $reflectedMethods;
/**
* An array of class-specific overwrites.
*
* @var array
*/
protected $overwrites;
/**
* An array of methods to inject into after instantation.
*
* @var array
*/
protected $injectMethods;
/**
* Construct the container.
*
* @param SpContainer $context
*/
public function __construct( SpContainer $context = null ) {
$this->context = $context;
}
/**
* Creates a new instance of class $classname
*
* @param string $classname
* @return object
*/
public function create( $classname ) {
if( !$this->isAbstract( $concrete = $this->concrete( $classname ) ) ) {
return $this->injectSetters(
$this->build(
$this->reflect( $concrete ),
$this->resolve( $concrete, $this->parameters( $this->constructor( $concrete ) ) )
)
);
}
}
/**
* Returns an object of the specified type or one of it's concrete implementations.
*
* @param String $abstract
* @return Object
* @throws SpUnknownDependency if a class or one of it's dependencies could not be found.
*/
public function get( $abstract, $requester = null ) {
if( ( $abstract = strtolower( $abstract ) ) && $this->exists( $concrete = $this->concrete( $abstract, $requester ) ) ) {
if( ( $object = $this->instance( $concrete ) ) ) {
return $object;
}
throw new SpUnknownImplementation( 'Could not find implementation for ' . $abstract );
}
throw new SpUnknownClass( sprintf( 'Could not find the class %s', $abstract ) );
}
/**
* Creates a subcontainer which can be further configured and override specific values.
*
* @return SpContainer
*/
public function getSubcontainer( ) {
return new SpContainer( $this );
}
/**
* Tell the container to call and inject $methods on classes of type $classname.
*
* @param string $classname
* @param string|array $methods
* @return SpContainer
*/
public function injectMethods( $classname, $methods ) {
$this->injectMethods[$this->concrete( $classname )] = (array) $methods;
return $this;
}
/**
* Registers an instance to use for class $abstract.
*
* @param string $abstract
* @param object $concrete
* @return SpContainer
*/
public function registerInstance( $abstract, $concrete ) {
if( is_object( $concrete ) && is_string( $abstract ) ) {
$this->instances[$this->concrete( $abstract )] = $concrete;
return $this;
}
throw new SpInvalidArgumentException( );
}
/**
* Sets the scalar values a class that should be passed onto the class.
*
* @param String $classname
* @param Array $scalars
* @return SpContainer
*/
public function setScalars( $classname, Array $scalars ) {
$this->scalars[strtolower( $classname )] = $scalars;
return $this;
}
/**
* Specifies which concrete implementation of an abstract class, base class or interface should be used.
*
* @param String $abstract
* @param String $concrete
* @return SpContainer
*/
public function useImplementation( $abstract, $concrete, $forClass = null ) {
if( null !== $forClass ) {
$this->overwrites[strtolower( $forClass )][strtolower( $abstract )] = strtolower( $concrete );
return $this;
}
$this->implementations[strtolower( $abstract )] = strtolower( $concrete );
return $this;
}
/**
* Tries to instantiate the class $class with parameters $parameters.
*
* @param ReflectionClass $class
* @param array $parameters
* @return object
*/
protected function build( ReflectionClass $class, $parameters ) {
return count( $parameters ) > 0 ? $class->newInstanceArgs( $parameters ) : $class->newInstance( );
}
/**
* Determines which concrete value should be injected into the class $requester.
*
* @param string $abstract
* @param string $requester
* @return string
*/
protected function concrete( $abstract, $requester = null ) {
if( false === ( $overwrite = $this->overwrite( $abstract, $requester ) ) ) {
if( ( $abstract = strtolower( $abstract ) ) && isset( $this->implementations[$abstract] ) ) {
return $this->concrete( $this->implementations[$abstract] );
}
return strtolower( $abstract );
}
return $overwrite;
}
/**
* Gets and returns the constructor for the class $classname.
*
* @param string $classname
* @return ReflectionMethod
*/
protected function constructor( $classname ) {
return $this->constructors[$classname] = $this->reflect( $classname )->getConstructor( );
}
/**
* Determines whether or not the class with the name $class exists.
*
* @param string $class
* @return boolean
*/
protected function exists( $class ) {
return ( class_exists( $class ) || interface_exists( $class ) );
}
/**
* Tries to find a classname for the passed parameter $parameter.
*
* @param Reflectionparameter $parameter
* @return String
*/
protected function findClass( ReflectionParameter $parameter ) {
try {
return $parameter->getClass( );
}
catch( ReflectionException $e ) {
throw new SpUnknownDependency( $e->getMessage( ) );
}
}
/**
* Determines whether or not an instance of class $concrete was already made.
*
* @param string $concrete
* @return boolean
*/
protected function has( $concrete ) {
return isset( $this->instances[$concrete] );
}
/**
* Determines wether or not there is a scalar value to inject.
*
* @param string $classname
* @param string $scalar
* @return boolean
*/
protected function hasScalar( $classname, $scalar ) {
$classname = strtolower( $classname );
return isset( $this->scalars[$classname][$scalar] ) ? $this->scalars[$classname][$scalar] : null;
}
/**
* Tries to find methods that start with "set", and tries to inject the
* correct values into those methods.
*
* @param Object $object
* @return Object
*/
protected function injectSetters( $object ) {
if( ( $classname = $this->concrete( get_class( $object ) ) ) && isset( $this->injectMethods[$classname] ) ) {
foreach( $this->injectMethods[$classname] as $method ) {
if( !method_exists( $object, $method ) || !( $reflectedMethod = $this->method( $classname, $method ) ) ) {
throw new SpUnknownSetterMethod( sprintf( 'Method %s does not exist in class %s', $method, $classname ) );
}
$reflectedMethod->invokeArgs( $object, $this->resolve( $classname, $this->parameters( $reflectedMethod ) ) );
}
}
return $object;
}
/**
* Creates or retrieves an instance of type $abstract, to inject into $requester
*
* @param string $abstract
* @param string $requester
* @return object
*/
protected function instance( $abstract, $requester = null ) {
if( ( $concrete = $this->concrete( $abstract, $requester ) ) && !$this->has( $concrete ) ) {
if( ( $object = $this->create( $concrete ) ) ) {
return $this->instances[strtolower( $abstract )] = $object;
}
return isset( $this->context ) ? $this->context->get( $abstract ) : false;
}
return $this->instances[$concrete];
}
/**
* Determines whether or not a class is either an abstract class or an interface.
*
* @param string $classname
* @return boolean
*/
protected function isAbstract( $classname ) {
return $this->reflect( $classname )->isAbstract( ) || $this->reflect( $classname )->isInterface( );
}
/**
* Creates and caches a reflection method.
*
* @param string $classname
* @param string $method
* @return ReflectionMethod
*/
protected function method( $classname, $method ) {
return $this->reflectedMethods[$classname][$method] = new ReflectionMethod( $classname, $method );
}
/**
* Retrieves a potentially overwritten value, or false if there is no
* overwrite.
*
* @param string $abstract
* @param string $requester
* @return string|false
*/
protected function overwrite( $abstract, $requester ) {
$abstract = strtolower( $abstract );
$requester = strtolower( $requester );
return isset( $this->overwrites[$requester][$abstract] ) ? $this->overwrites[$requester][$abstract] : false;
}
/**
* Returns an array of ReflectionParameters, or an empty array if there are
* none.
*
* @param ReflectionMethod $method
* @return Array
*/
protected function parameters( $method ) {
return ( $method instanceof ReflectionMethod ) ? $method->getParameters( ) : array( );
}
/**
* Returns a ReflectionClass for class $classname
*
* @param string $classname
* @return ReflectionClass
*/
protected function reflect( $classname ) {
if( !isset( $this->reflectedClasses[$classname] ) ) {
$this->reflectedClasses[$classname] = new ReflectionClass( $classname );
}
return $this->reflectedClasses[$classname];
}
/**
* Loops through the parameters of a given method and tries to resolve the
* values.
*
* @param string $classname
* @param array $parameters
* @return array
*/
protected function resolve( $classname, $parameters ) {
foreach( $parameters as $parameter ) {
if( $value = $this->value( $classname, $parameter ) ) {
$values[] = $value;
}
}
return isset( $values ) ? $values : array( );
}
/**
* Retrieves a scalar value for a class, returns null if it optional and not set.
*
* @param string $classname
* @param ReflectionParameter $parameter
* @return mixed
*/
protected function scalar( $classname, ReflectionParameter $parameter ) {
if( ( $scalar = $this->hasScalar( $classname, $parameter->getName( ) ) ) || $parameter->isOptional( ) ) {
return ( null !== $scalar ) ? $scalar : null;
}
throw new SpUnknownScalar( sprintf( 'Unknown scalar values "%s" on class "%s"', $parameter->getName( ), $classname ) );
}
/**
* Tries to find the value for parameter $parameter, which can be either an
* object or a scalar value.
*
* @param string $classname
* @param ReflectionParameter $parameter
* @return mixed
*/
protected function value( $classname, ReflectionParameter $parameter ) {
if( $class = $this->findClass( $parameter ) ) {
return $this->get( $class->getName( ), $classname );
}
return $this->scalar( $classname, $parameter );
}
}
?>
/**
* This file is part of the Sphoof framework.
* Copyright (c) 2010, Sphoof
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. You can also view the
* LICENSE file online at http://www.sphoof.nl/new-bsd.txt
*
* @category Sphoof
* @copyright Copyright (c) 2010 Sphoof (http://sphoof.nl)
* @license http://sphoof.nl/new-bsd.txt New BSD License
* @package Container
*/
/**
* Exception that will be thrown upon encountering an unknown scalar parameter.
*
* @package Container
* @subpackage Exception
*/
class SpUnknownScalar extends SpException { }
/**
* Exception that will be thrown upon encountering an unresolvable parameter.
*
* @package Container
*/
class SpUnknownDependency extends SpException { }
/**
* Exception that will be thrown upon requesting a class that does not exist.
*
* @package Container
*/
class SpUnknownClass extends SpException { }
/**
* Exception that will be thrown upon registering an injecting method that does not exist.
*
* @package Container
*/
class SpUnknownSetterMethod extends SpException { }
/**
* Exception that will be thrown upon requesting an interface that has no
* concrete implementation.
*
* @package Container
*/
class SpUnknownImplementation extends SpException { }
/**
* An exception thrown when using incorrect arguments.
*
* @package Container
*/
class SpInvalidArgumentException extends SpException { }
/**
* The dependency injection container.
*
* @package Container
*/
class SpContainer {
/**
* An optional context to look into.
*
* @var SpContainer
*/
protected $context;
/**
* An array of instances
*
* @var array
*/
protected $instances;
/**
* An array of scalar values.
*
* @var array
*/
protected $scalars;
/**
* An array of implementations.
*
* @var array
*/
protected $implementations;
/**
* An array of ReflectionClass instances.
*
* @var array
*/
protected $reflectedClasses;
/**
* An array of ReflectionMethod instances.
*
* @var array
*/
protected $reflectedMethods;
/**
* An array of class-specific overwrites.
*
* @var array
*/
protected $overwrites;
/**
* An array of methods to inject into after instantation.
*
* @var array
*/
protected $injectMethods;
/**
* Construct the container.
*
* @param SpContainer $context
*/
public function __construct( SpContainer $context = null ) {
$this->context = $context;
}
/**
* Creates a new instance of class $classname
*
* @param string $classname
* @return object
*/
public function create( $classname ) {
if( !$this->isAbstract( $concrete = $this->concrete( $classname ) ) ) {
return $this->injectSetters(
$this->build(
$this->reflect( $concrete ),
$this->resolve( $concrete, $this->parameters( $this->constructor( $concrete ) ) )
)
);
}
}
/**
* Returns an object of the specified type or one of it's concrete implementations.
*
* @param String $abstract
* @return Object
* @throws SpUnknownDependency if a class or one of it's dependencies could not be found.
*/
public function get( $abstract, $requester = null ) {
if( ( $abstract = strtolower( $abstract ) ) && $this->exists( $concrete = $this->concrete( $abstract, $requester ) ) ) {
if( ( $object = $this->instance( $concrete ) ) ) {
return $object;
}
throw new SpUnknownImplementation( 'Could not find implementation for ' . $abstract );
}
throw new SpUnknownClass( sprintf( 'Could not find the class %s', $abstract ) );
}
/**
* Creates a subcontainer which can be further configured and override specific values.
*
* @return SpContainer
*/
public function getSubcontainer( ) {
return new SpContainer( $this );
}
/**
* Tell the container to call and inject $methods on classes of type $classname.
*
* @param string $classname
* @param string|array $methods
* @return SpContainer
*/
public function injectMethods( $classname, $methods ) {
$this->injectMethods[$this->concrete( $classname )] = (array) $methods;
return $this;
}
/**
* Registers an instance to use for class $abstract.
*
* @param string $abstract
* @param object $concrete
* @return SpContainer
*/
public function registerInstance( $abstract, $concrete ) {
if( is_object( $concrete ) && is_string( $abstract ) ) {
$this->instances[$this->concrete( $abstract )] = $concrete;
return $this;
}
throw new SpInvalidArgumentException( );
}
/**
* Sets the scalar values a class that should be passed onto the class.
*
* @param String $classname
* @param Array $scalars
* @return SpContainer
*/
public function setScalars( $classname, Array $scalars ) {
$this->scalars[strtolower( $classname )] = $scalars;
return $this;
}
/**
* Specifies which concrete implementation of an abstract class, base class or interface should be used.
*
* @param String $abstract
* @param String $concrete
* @return SpContainer
*/
public function useImplementation( $abstract, $concrete, $forClass = null ) {
if( null !== $forClass ) {
$this->overwrites[strtolower( $forClass )][strtolower( $abstract )] = strtolower( $concrete );
return $this;
}
$this->implementations[strtolower( $abstract )] = strtolower( $concrete );
return $this;
}
/**
* Tries to instantiate the class $class with parameters $parameters.
*
* @param ReflectionClass $class
* @param array $parameters
* @return object
*/
protected function build( ReflectionClass $class, $parameters ) {
return count( $parameters ) > 0 ? $class->newInstanceArgs( $parameters ) : $class->newInstance( );
}
/**
* Determines which concrete value should be injected into the class $requester.
*
* @param string $abstract
* @param string $requester
* @return string
*/
protected function concrete( $abstract, $requester = null ) {
if( false === ( $overwrite = $this->overwrite( $abstract, $requester ) ) ) {
if( ( $abstract = strtolower( $abstract ) ) && isset( $this->implementations[$abstract] ) ) {
return $this->concrete( $this->implementations[$abstract] );
}
return strtolower( $abstract );
}
return $overwrite;
}
/**
* Gets and returns the constructor for the class $classname.
*
* @param string $classname
* @return ReflectionMethod
*/
protected function constructor( $classname ) {
return $this->constructors[$classname] = $this->reflect( $classname )->getConstructor( );
}
/**
* Determines whether or not the class with the name $class exists.
*
* @param string $class
* @return boolean
*/
protected function exists( $class ) {
return ( class_exists( $class ) || interface_exists( $class ) );
}
/**
* Tries to find a classname for the passed parameter $parameter.
*
* @param Reflectionparameter $parameter
* @return String
*/
protected function findClass( ReflectionParameter $parameter ) {
try {
return $parameter->getClass( );
}
catch( ReflectionException $e ) {
throw new SpUnknownDependency( $e->getMessage( ) );
}
}
/**
* Determines whether or not an instance of class $concrete was already made.
*
* @param string $concrete
* @return boolean
*/
protected function has( $concrete ) {
return isset( $this->instances[$concrete] );
}
/**
* Determines wether or not there is a scalar value to inject.
*
* @param string $classname
* @param string $scalar
* @return boolean
*/
protected function hasScalar( $classname, $scalar ) {
$classname = strtolower( $classname );
return isset( $this->scalars[$classname][$scalar] ) ? $this->scalars[$classname][$scalar] : null;
}
/**
* Tries to find methods that start with "set", and tries to inject the
* correct values into those methods.
*
* @param Object $object
* @return Object
*/
protected function injectSetters( $object ) {
if( ( $classname = $this->concrete( get_class( $object ) ) ) && isset( $this->injectMethods[$classname] ) ) {
foreach( $this->injectMethods[$classname] as $method ) {
if( !method_exists( $object, $method ) || !( $reflectedMethod = $this->method( $classname, $method ) ) ) {
throw new SpUnknownSetterMethod( sprintf( 'Method %s does not exist in class %s', $method, $classname ) );
}
$reflectedMethod->invokeArgs( $object, $this->resolve( $classname, $this->parameters( $reflectedMethod ) ) );
}
}
return $object;
}
/**
* Creates or retrieves an instance of type $abstract, to inject into $requester
*
* @param string $abstract
* @param string $requester
* @return object
*/
protected function instance( $abstract, $requester = null ) {
if( ( $concrete = $this->concrete( $abstract, $requester ) ) && !$this->has( $concrete ) ) {
if( ( $object = $this->create( $concrete ) ) ) {
return $this->instances[strtolower( $abstract )] = $object;
}
return isset( $this->context ) ? $this->context->get( $abstract ) : false;
}
return $this->instances[$concrete];
}
/**
* Determines whether or not a class is either an abstract class or an interface.
*
* @param string $classname
* @return boolean
*/
protected function isAbstract( $classname ) {
return $this->reflect( $classname )->isAbstract( ) || $this->reflect( $classname )->isInterface( );
}
/**
* Creates and caches a reflection method.
*
* @param string $classname
* @param string $method
* @return ReflectionMethod
*/
protected function method( $classname, $method ) {
return $this->reflectedMethods[$classname][$method] = new ReflectionMethod( $classname, $method );
}
/**
* Retrieves a potentially overwritten value, or false if there is no
* overwrite.
*
* @param string $abstract
* @param string $requester
* @return string|false
*/
protected function overwrite( $abstract, $requester ) {
$abstract = strtolower( $abstract );
$requester = strtolower( $requester );
return isset( $this->overwrites[$requester][$abstract] ) ? $this->overwrites[$requester][$abstract] : false;
}
/**
* Returns an array of ReflectionParameters, or an empty array if there are
* none.
*
* @param ReflectionMethod $method
* @return Array
*/
protected function parameters( $method ) {
return ( $method instanceof ReflectionMethod ) ? $method->getParameters( ) : array( );
}
/**
* Returns a ReflectionClass for class $classname
*
* @param string $classname
* @return ReflectionClass
*/
protected function reflect( $classname ) {
if( !isset( $this->reflectedClasses[$classname] ) ) {
$this->reflectedClasses[$classname] = new ReflectionClass( $classname );
}
return $this->reflectedClasses[$classname];
}
/**
* Loops through the parameters of a given method and tries to resolve the
* values.
*
* @param string $classname
* @param array $parameters
* @return array
*/
protected function resolve( $classname, $parameters ) {
foreach( $parameters as $parameter ) {
if( $value = $this->value( $classname, $parameter ) ) {
$values[] = $value;
}
}
return isset( $values ) ? $values : array( );
}
/**
* Retrieves a scalar value for a class, returns null if it optional and not set.
*
* @param string $classname
* @param ReflectionParameter $parameter
* @return mixed
*/
protected function scalar( $classname, ReflectionParameter $parameter ) {
if( ( $scalar = $this->hasScalar( $classname, $parameter->getName( ) ) ) || $parameter->isOptional( ) ) {
return ( null !== $scalar ) ? $scalar : null;
}
throw new SpUnknownScalar( sprintf( 'Unknown scalar values "%s" on class "%s"', $parameter->getName( ), $classname ) );
}
/**
* Tries to find the value for parameter $parameter, which can be either an
* object or a scalar value.
*
* @param string $classname
* @param ReflectionParameter $parameter
* @return mixed
*/
protected function value( $classname, ReflectionParameter $parameter ) {
if( $class = $this->findClass( $parameter ) ) {
return $this->get( $class->getName( ), $classname );
}
return $this->scalar( $classname, $parameter );
}
}
?>
Mooi verhaal Niels van de Ford :) Op deze manier snap ik een beetje wat een container doet. Maar ik merk dat het feit dat ik geen IT opleiding heb gehad en nooit eerder dit soort design patterns gebruikt heb me wel parten speelt. Ik wil in mijn framework dan ook op een wat simpelere manier te werk gaan. Misschien wel vanuit het zelfde principe, maar dan op een manier dat ik het zelf begrijp. De functie omschrijvingen moeten voor mij dan ook dummy proof zijn. Voorbeeld, $row = $database->select('name, age')->from('users'). Kijk dit is voor mij lekker makkelijk te gebruiken omdat ik precies zie wat er gebeurt. Het moet bij mij vooral simpel en effectief zijn. Ander voorbeeld... in mijn eigen framework zou ik niet willen zeggen Framework_Registry::set('object'), maar gewoon simpel $set('object') en om weer op te halen $get('object'). Zou fantastisch zijn als ik zo'n container ook op zo'n simpele manier kan toepassen. Probleem is alleen dat ik te weinig besef heb van wat het (de container) precies doet en hoe je het in de praktijk gebruikt. Maar als jij me kan helpen bij het maken van een hele simpele variant met simpele naamgeving dan zou het me misschien wel lukken...
Quote:
Op deze manier snap ik een beetje wat een container doet
Een beetje? Je moet het helemaal snappen, want dat was de bedoeling van mijn post. Simpelweg een dependency injection container assembleert je applicatie.
Quote:
Misschien wel vanuit het zelfde principe, maar dan op een manier dat ik het zelf begrijp
Tja, jouw kennis schiet met de uren omhoog als ik het zo eens zie. Dus wat je nu schrijft is over een aantal uren weer oud en wil je anders doen. Je kan je beter eerst helemaal laten informeren en alles uitzoeken en dit topic nog tweemaal zo lang laten worden en dan pas aan de slag gaan. Ik weet niet of je precies een deadline aan jouw framework hebt zitten? Zo nee dan zou ik het volgende doen de komende dagen / weken:
- Alle gegeven informatie verder onderzoeken en afwegen wat het beste in jouw straatje ligt. Vervolgens pak je een kladblok of whatever en je gaat helemaal uitschrijven hoe het Ozzie framework eruit komt te zien. Tot in het detail.. :) Dikke shizzle allemaal .. Daarna plaats je dat ontwerp hier en geven wij onze complimenten / commentaar. Nadat verbeter / upgrade jij weer heel de bende en laat je het nogmaals controleren totdat er geen negatief commentaar meer wordt geplaatst. (Je hebt natuurlijk wel altijd nog mensen die commentaar geven, maar dat is dan meer persoonlijk) Daarna gooi je heel je framework in de script library en we hebben we een mooie toevoeging in de library :) Die kan wel wat moois gebruiken, na al de rotzooi wat afgelopen maanden is geplaatst. Uitzonderingen daar gelaten uiteraard.
Maar goed, je vroeg om een voorbeeld van het gebruik van een dependency injection container. Laat er nu eentje geschreven zijn door een vriend van mij genaamd: Berry Langerak, klik
Gewijzigd op 04/01/2011 20:07:42 door Niels K
Ik heb niet echt een deadline... maar wil eigenlijk wel zsm er mee aan de slag :) Om toch nog een wat beter beeld te krijgen... stel je moet een webshop maken. Stel nu dat we even heel simpel zeggen dat we 4 hoofd-onderdelen hebben:
- database
- sessie
- product
- winkelmandje
Oke, normaal gesproken komt er nog meer bij kijken maar ik probeer het idee voor mezelf helder te krijgen :) Momenteel zou ik dit dat ongeveer maken zoals hieronder beschreven. Graag hoor ik van jullie of, waar en waarom je gebruik zou maken van DI (eventueel met een klein voorbeeld). Ik ben benieuwd... :) Hieronder dus zoals ik het nu ongeveer zou doen.
Ik kan me voorstellen dat je een DatabaseModel hebt. In dit model zit een functie die een ini bestandje (dat altijd in een bepaalde folder staat) uitleest waarin de default databasenaam, gebruikersnaam, wachtwoord en host staan. Vervolgens wordt een instanstie gemaakt van de default database die je met de get() functie kunt ophalen (singleton principe dus). Als ik de default database nodig heb zeg ik $database = Database::get() en als ik een select moet doen zou ik zeggen $database->select('id, titel, omschrijving')->from('products')->where('category', $category). Je kunt overigens via de set functie een tweede of derde database aanmaken. Ik geef dan aan de set functie de naam op die ik voor de database wil gebruiken + de naam van het ini bestand waarin de configuratie gegevens staan. Deze database haal ik op via $database2 = Database::get{'Database2'}. Het is niet mogelijk om een database aan te maken die een naam heeft die je al eerder hebt gebruikt. Dan wordt een exception gegooid. Je kunt dus niet 2x Database2 setten.
Voor de sessie gebruik ik gewoon een class Session.php en dit bestand zet ik in m'n library. Alle functies in dit bestand zijn statisch. Wat kun je met deze class? Iets setten, iets getten (met een controle of de key in de sessie wel bestaat), sessie id opvragen, sessie destroyen etc. Stel ik wil het winkelmandje in de sessie zetten dan krijg je bijvoorbeeld Session::set('winkelmandje', $winkelmandje).
Oke, dan de productcontroller... hierin staan acties voor het tonen van een productoverzicht en een productpagina. Afhankelijk van de url, bijvoorbeeld www.mijnsite.nl/product/toon/32/theedoekje :) wordt een product getoond of een compleet productoverzicht. Het ophalen van een product zou ik doen aan de hand van het product id, bijvoorbeeld $row = $database->select('id, titel, omschrijving')->from('products')->where('id', $id).
Voor het winkelmandje zou ik een controller gebruiken met functies om het winkelmandje te tonen en functies om het productaantal op te hogen of te verminderen of een product weg te gooien. Het winkelmandje zou ik dan uit de sessie halen en als er bijvoorbeeld een product verwijderd moet worden dan zou ik dat product verwijderen uit de gegevens die ik uit de sessie heb opgehaald en vervolgens het winkelmandje opnieuw opslaan in de sessie.
Bovenstaande, zij het in dit voorbeeld even wat kort door de bocht, werkt prima... maar nu de cruciale vraag: hoe zou je dit voorbeeld met DI doen? En alsjeblieft... hou het een beetje simpel zodat ik het kan begrijpen :)
Met de structuur van je applicatie (het 'domain model', denk aan de klasse 'product') heeft DI niet zo veel te maken. Het is meer het op een handige wijze voorzien van services aan de ondersteunende klassen.
Probeer eerst een (heel basaal) framework te maken, dan pas een applicatie daarbovenop.
Heb je trouwens die tutorialreeks van Fabien Potencier gelezen die ik 2x heb gepost in dit topic?
Ja ik heb de tutorial bekeken, maar deze is voor mij behoorlijk (te) abstract en daardoor lastig te begrijpen. Ik moet echt duidelijke concrete omschrijvingen hebben van hoe iets werkt. Denk bijvoorbeeld aan het voorbeeld van Niels met de T-Ford. Door dat soort verhalen begrijp ik steeds beter waar het nou eigenlijk over gaat. (Hou alsjeblieft in gedachten dat ik geen IT opleiding heb gevolgd en ik sommige zaken daardoor minder snel begrijp dan iemand die wel een IT achtergrond heeft of meer ervaring op dit gebied.)
Quote:
Hou alsjeblieft in gedachten dat ik geen IT opleiding heb gevolgd en ik sommige zaken daardoor minder snel begrijp dan iemand die wel een IT achtergrond heeft of meer ervaring op dit gebied
Dit zeg je haast elke post :) Je kan het gewoon aanleren het is niet iets wat je hokuspokus erin gepompt krijgt :) Wat dacht je hoe een leraar het doet?
Ik weet het, programmeren is in het begin niet gemakkelijk en daardoor haken er veel af. Je moet gewoon veel herhalen, op ten duur valt het kwartje.
Over die voorbeelden: klik.
Gewijzigd op 05/01/2011 20:49:29 door Niels K