Pcms container in opbouw - 1

Een nadeel van de eerder beschreven Dependency Injection Container is dat hij nog steeds weinig flexibel is. Een nieuwe service toevoegen vereist een aanpassing in de Container klasse. Mooier zou het zijn als een ander object de container zou kunnen configureren. De container moet daarvoor dynamisch worden. De services moeten via calls geconfigureerd worden.

Een mooi simpel voorbeeld van zo'n dynamische container is Pimple. Omdat ik hem iets te simpel vind, hij niet mooi om kan gaan met uitbreidingen op services en ik toch wel een beetje narcistisch ben, zal ik de container bespreken die ik geschreven heb als basis van mijn nog te voltooien (mocht dat lukken) cms, genaamd Pcms :). (Een koekje voor degene die kan bedenken hoe ik op die naam ben gekomen)

Ik zal nu stapje voor stapje bespreken hoe ik tot het eindresultaat ben gekomen.

De kern is als volgt:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
<?php
class Container
{
    protected $values;
    
    public function set($key, $value)
    {

        $this->values[$key] = $value;
    }

    
    public function get($key)
    {

        // Check of de waarde bestaat
        if(!isset($this->values[$key]))
                throw new \InvalidArgumentException(sprintf(
                        "Value %s has not been set",
                        $key
                ));
        
        $value = $this->values[$key];
        
        // Als het om een service gaat
        if(is_callable($value))
            return $value($this);
            
        // Als het om een parameter gaat
        else
            return $value;
    }
}

?>

De container bevat een array met waarden. Met de set() functie kunnen die ingesteld worden. Twee typen waarden zijn toegestaan:
- Parameters
Dit zijn gewoon waarden zoals string, ints en arrays, die als configuratie voor de services dienen. Ze worden in de get() functie gewoon onveranderd teruggegeven.

- Factories
Dit zijn php callables of callbacks. Oftwel waarden die uitgevoerd kunnen worden met $var(). Dit kan een string met de naam van een functie zijn, een object dat __invoke() implementeerd of een array met ($object, 'methodenaam') en nog een paar mogelijkheden. Ik gebruik vooral de Closure oftewel anonymous function.

$var = function() { return 'test'; };
echo $var(); // geeft: test

Zie voor de details bijvoorbeeld hier.

De closure is dan hier een factory, een methode die bedoeld is om een object aan te maken en ziet er als volgt uit:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
<?php
$factory
= function() {
    return new Zend_Mail();
};
    
?>


Maar een factory kan ook het object configureren. Het eerder beschreven Zend_Mail object krijg je zo:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$factory
= function() {
    $transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
      'auth'     => 'login',
      'username' => 'foo',
      'password' => 'bar',
      'ssl'      => 'ssl',
      'port'     => 465,
    ));

    
    $mailer = new Zend_Mail();
    $mailer->setDefaultTransport($transport);
    
    return $mailer;
};

$mailer = $factory();
?>

Het is dus mogelijk om zo'n factory bij set() mee te geven, bijvoorbeeld zo:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?php
$container
= new Container();
$container->set('mailer', function() {
    return new Zend_Mailer();
};

$mailer = $container->get('mailer');
?>

In de get() functie wordt $this als argument meegegeven aan de factory. Deze beschikt dus over de container en kan zo beschikken over andere services of parameters.

Het volledige Zend_Mail object kan dan zo gemaakt worden:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
<?php
$c
= new Container();
// Stel de parameters in
$c->set('mailer.username', 'foo');
$c->set('mailer.password', 'bar');
$c->set('mailer.class', 'Zend_Mail');

// Stel de transport service in
$c->set('mailer.transport', function($c) {
    return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
      'auth'     => 'login',
      'username' => $c->get('mailer.username'),
      'password' => $c->get('mailer.password'),
      'ssl'      => 'ssl',
      'port'     => 465,
    ));
});


// Stel de mailer service in
$c->set('mailer', function($c) {
    $class = $c->get('mailer.class');
    $mailer = new $class();
    $mailer->setDefaultTransport($c->get('mailer.transport'));
    return $mailer;
});


// De mailer roep je dan zo aan:
$mailer = $c->get('mailer');
?>


Merk ook op dat de services 'lazy' gemaakt worden. De factory wordt pas aangeroepen in de get() functie en dus pas wanneer de service nodig is.

Nu kan je gedeelde serivices implementeren op dezelfde wijze als in de hardcoded container:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
<?php
class Container
{
    protected $values        = array();
    protected $shared        = array();
    protected $instances     = array();
    
    public function set($key, $value, $shared = false)
    {

        $this->values[$key] = $value;
        $this->shared[$key] = $shared;
    }

    
    public function get($key)
    {

        if(!isset($this->values[$key]))
                throw new \InvalidArgumentException(sprintf(
                        "Value %s has not been set",
                        $key
                ));
        
        $value = $this->values[$key];
        
        // Als het een service betreft
        if(is_callable($value)) {
            // Als de service gedeeld is en al eerder opgebouwd
            if($this->shared[$key] && isset($this->instances[$key]))
                return $this->instances[$key];
            
            // CreĆ«er de service door de factor aan te roepen
            $instance = $value($this);
            
            // Sla gedeelde services op
            if($this->shared[$key])
                $this->instances[$key] = $instance;
            
            return $instance;
            
        // Als het een parameter betreft
        } else {
            return $value;
        }
    }

    
    // Maak een gedeelde service niet-gedeeld of vice versa
    public function setShared($key, $shared)
    {

        $this->shared[$key] = $shared;
    }
}

?>

Een gedeelde service maak je dan zo:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
<?php
$c
= new Container();
$c->set('mailer', function() {
    return new Zend_Mailer();
},
true);
?>

« Lees de omschrijving en reacties

Inhoudsopgave

  1. Inleiding
  2. Dependency Injection
  3. Dependency Injection Container
  4. Pcms container in opbouw - 1
  5. Pcms container in opbouw - 2
  6. De Pcms container
  7. Conclusie

PHP tutorial opties

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.