DI - Service containter

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Senior DevOps-ontwikkelaar eIDAS

Functie­omschrijving Burgers en bedrijven veilig en betrouwbaar digitaal toegang geven tot diensten en producten van het ministerie van Economische Zaken en Klimaat. Als senior DevOps-ontwikkelaar bouw je daar letterlijk aan mee. En dat doe je bij DICTU: een van de grootste en meest vooruitstrevende ICT-dienstverleners van de Rijksoverheid. Jij werkt mee aan de doorontwikkeling van eIDAS, dat staat voor Electronic IDentification Authentication and trust Services. Deze koppeling maakt de grensoverschrijdende authenticatie op overheidswebsites binnen de Europese Unie mogelijk. Het ministerie van Economische Zaken en Klimaat heeft één moderne toegangspoort voor zijn diensten en inspecties. Enkele daarvan zijn dankzij eIDAS inmiddels

Bekijk vacature »

Pagina: 1 2 volgende »

Jasper DS

Jasper DS

07/01/2013 18:37:57
Quote Anchor link
Hoi,

ik was op aanraden van Wouter begonnen met Dependency injection (DI). Ik heb als leidraad de tutorial van Pim genomen en ik denk dat ik mee ben tot het stukje dat alles "hard coded" in de container staat. Vanaf dat de container "slim" moet worden ben ik niet meer mee.

Ik heb hier dus een aantal dependencies:
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
<?php
// User Object aanmaken
$user = new User($name);
$user->setEmail($password);
$user->setPassword($email);
$user->setID($id);

$c = new Container(array(
                             'pdo.host' => 'localhost',
                             'pdo.name' => 'test',
                             'pdo.user' => 'root',
                             'pdo.password' => ''
                            )
                        );


$userMapper = new UserMapper($c->setPDOStorage());

$userMapper->create($user)
?>


Mijn container ziet er zo uit voorlopig:
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
<?php
class container
{
    private $parameters = array();
 
    public function __construct(array $parameters = array())
    {

        $this->parameters = $parameters;
    }


    public function setPDOStorage()
    {

        try
        {
            $pdo = new PDO('mysql:host='.$this->parameters['pdo.host'].';dbname='.$this->parameters['pdo.name'].'', $this->parameters['pdo.user'], $this->parameters['pdo.password']);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
        }

        catch(PDOException $e)
        {

            echo $e->getMessage();
        }

            
        $storage = new PDODatabaseStorage($pdo);
        return $storage;
    }
}

?>


Nu is mijn vraag, kan ik de andere dependencies er ook gewoon inzetten of moet ik inderdaad een soort functie hebben die dit zelf kan?

Stel nu dat ik niet met een mvc structuur werk en dus bijvoorbeeld index.php heb en login.php kan ik dan een soort van config bestand includen dat ervoor zorgt dat er bijvoorbeeld altijd waar nodig een mysql-verbinding is?
 
PHP hulp

PHP hulp

01/10/2020 00:20:45
 
Moose -

Moose -

07/01/2013 18:42:44
Quote Anchor link
Paar dingen, classes altijd beginnen met een hoofdletter, en je combineert een setter en een getter nu. Bij setPDOStorage geef je je aangemaakte database connectie mee, bij getPDOStorage return je hem.

Het chille aan php is dat je in principe alles in 1 array kan gooien. Je zou dus iets kunnen doen als $container->set('pdo', $pdo); en $container->get('pdo'); Als je niet met een mvc structuur werkt heeft DI weinig nut, want dan kun je net zo goed een config file maken en daar alles in gooien inderdaad
 
Wouter J

Wouter J

07/01/2013 19:12:05
Quote Anchor link
Ik zou gewoon een simpele get/set structuur maken zoals Pim laat zien van zijn 'Pcms' container.

Je hebt dan iets als:
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
<?php
$container
= new Container();

$container->setParameter('database.class', 'PDO');

$container->setParameter('database.connection.host', 'localhost');
$container->setParameter('database.connection.user', 'user');
$container->setParameter('database.connection.pass', 'pass');
$container->setParameter('database.connection.db', 'db');
$container->setParameter('database.connection.driver', 'mysql');

// lazy loading (het pas aanmaken wanneer je het nodig hebt)
// is 1 van de grote voordelen van DI, laat dit zeker niet weg!

$container->set('database', function ($c) {
    return new $c->get('database.class')(
        sprintf(
            'mysql:host=%s;dbname=%s',
            $c->get('database.connection.host'),
            $c->get('database.connection.db')
        ),

        $c->get('database.connection.user'),
        $c->get('database.connection.pass')
    );
});

?>
 
Jasper DS

Jasper DS

08/01/2013 08:59:47
Quote Anchor link
Ik zie het voordeel nog niet.

Uiteindelijk heb je toch even veel code?

dit is toch minder code:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
<?php
$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);
?>


dan deze code met service 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
<?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');
?>
 
Moose -

Moose -

08/01/2013 10:14:18
Quote Anchor link
Het gaat niet alleen maar om de hoeveelheid code. Het gaat ook om het onderhouden. Er is niet één goede manier van programmeren, probeer gewoon je eigen stijl te vinden.

De tweede optie is beter te onderhouden, want als jij een andere mail service wil gebruiken (zeg phpmailer), hoef je dat maar op 1 plek aan te passen in je code
 
Jasper DS

Jasper DS

08/01/2013 13:10:11
Quote Anchor link
Oke ik ben mee, maar setPdoStorage mag niet hardcoded in de container zitten. Dat moet via zo'n callback gedaan worden?

Hoe gebeurt dat dan? Steek ik die functie gewoon zo in de var?

Hoe zorg ik ervoor dat mijn container mijn PDOstorage initialiseert? Of nog beter, ineens mee bijvoorbeeld mijn UserMapper initialiseert?
 
Wouter J

Wouter J

08/01/2013 15:01:40
Quote Anchor link
Je stelt gewoon een pdo.storage service in:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?php
$container
->setParameter('storage.class', 'PDO');
// ... inlog gegevens
$container->set('storage', function ($c) {
    return new $c->getParameter('storage.class')(...);
});

?>

Zodra je nu $container->get('storage') aanroept zal deze deze callback aanroepen en het resultaat terug geven, je krijgt dan dus een nieuwe instance van PDO terug. Wat je nog beter kan doen is deze service shared maken, wat betekend dat de instance van PDO wordt opgeslagen en na 1 keer aanmaken telkens die instance terug krijgt.

Nu maak je een UserMapper klasse die de storage service als dependency heeft, dus die voegen we toe in bijv. de arguments:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
<?php
$container
->setParameter('user.mapper.class', 'User\UserMapper');
$container->set('user.mapper', function ($c) {
    return $c->getParameter('user.mapper.class')($c->get('storage'));
});

?>

Zodra je nu de user.mapper service aanroept zal hij een nieuwe instance maken van User\UserMapper en de storage service meegeven als 1e argument.
 
Jasper DS

Jasper DS

08/01/2013 19:22:11
Quote Anchor link
Wouter,

ik probeer een PDO te instantieren maar ik krijg volgende foutmelding:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
Parse error: syntax error, unexpected '(' in C:\wamp\www\login_system\login.php on line 25



Hij zegt dat dat haakje voor de paramaters van de pdoclasse er niet mag staan maar hoe doe ik dat anders? Het moet toch zo niet?
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
<?php
$container
->set('storage', function($c)
        {

            return new $c->getParameter('storage.class')('mysql:host='.$c->getParameter['pdo.host'].';dbname='.$c->getParameter['pdo.name'].'', $c->getParameter['pdo.user'], $c->getParameter['pdo.password']);
        });

?>





moet

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
<?php
new PDO('mysql:host='.$c->getParameter['pdo.host'].';dbname='.$c->getParameter['pdo.name'].'', $c->getParameter['pdo.user'], $c->getParameter['pdo.password']);     
?>


er toch niet ergens hardcoded in?
Gewijzigd op 08/01/2013 19:26:51 door Jasper DS
 
Wouter J

Wouter J

08/01/2013 19:28:06
Quote Anchor link
Hmm, nu je het zegt, PHP wil waarschijnlijk dat je $c->getParameter(...) tussen accoloades zet (het wordt dan {$c->getParameter(...)}(...)

Anders kun je $c->getParameter(...) even opslaan in een variabele en de klasse dan instantiëren.
 
Jasper DS

Jasper DS

08/01/2013 19:35:21
Quote Anchor link
Goed nieuws en slecht nieuws.
Er zijn geen errors meer door volgende code te gebruiken:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
<?php
$container
->set('storage', function($c)
        {

            $class = $c->getParameter('storage.class');
            return new $class('mysql:host='.$c->getParameter['pdo.host'].';dbname='.$c->getParameter['pdo.name'].'', $c->getParameter['pdo.user'], $c->getParameter['pdo.password']);
        });


        echo $container->getStorage();
?>


nu zie je dat ik onderaan even willen checken of het werkt door het te echoën maar daar verschijnt... NIETS!

Er loopt dus blijkbaar nog iets mis hé? Maar wat?
 
Wouter J

Wouter J

08/01/2013 19:39:01
Quote Anchor link
Nou, hoe kom je aan de functie getStorage? Die is toch nergens gedefinieerd? Je moet dan toch $container->get('storage') aanroepen?
 
Jasper DS

Jasper DS

08/01/2013 19:44:12
Quote Anchor link
Oei oei, dat was wel erg dom van me... :s

Uiteindelijk nog steeds geen output maar ik vraag me af, kan ik $container->get('storage') eigenlijk wel echoën? Print_r levert ook niks op...
 
Wouter J

Wouter J

08/01/2013 19:45:50
Quote Anchor link
Hoe ziet je get functie eruit?
 
Jasper DS

Jasper DS

08/01/2013 19:47:31
Quote Anchor link
Overgenomen van Pim zijn tut:

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
<?php
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;
            
        
        }

        else // Als het een parameter betreft
        {
            return $value;
        }
    }

?>
 
Wouter J

Wouter J

08/01/2013 19:53:29
Quote Anchor link
En als je var_dump eromheen zet, weet je ook wel zeker dat die functie klopt? (met andere woorden, heb je hem gecontroleerd?) Want bestaat $this->values, $this->shared en $this->instances wel? En sla je de services ook op in de $this->values? En weet je zeker dat je de parameter in de $this->values opslaat en geen gebruik maakt van een getParameter functie?
 
Jasper DS

Jasper DS

08/01/2013 20:04:37
Quote Anchor link
Geen idee wat ik gedaan heb maar mijn var_dump geeft nu object(PDO)[3] en dat lijkt me wel vrij juist. :-)

Nu ga ik proberen mijn UserMapper door de container te laten behandelen.
 
Wouter J

Wouter J

08/01/2013 20:11:06
Quote Anchor link
Ik raad je toch aan even je code van de container na te kijken, zo op het eerste gezicht lijkt hij me niet te kloppen (zomaar een vermoeden, maar toch...).
 
Jasper DS

Jasper DS

08/01/2013 20:55:13
Quote Anchor link
Hmm dus jij denkt dat die var_dump output niet klopt? Ik heb de container recht gekopieerd van pims tutorial.




Wouter,

onderstaande code werkt voor mij. Kan jij eens nakijken of dat technisch oké is?

Bedankt!

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
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
<?php

    include('lib/Jds/Loader/AutoLoader.php');
    use \Jds\Container\Container;

    # errors weergeven
    ini_set('display_errors',1); // 1 == aan , 0 == uit
    error_reporting(E_ALL | E_STRICT);

    #### CONFIGURATIE #####
    $container = new Container();

    $container->set('db.class', 'PDO');
    $container->set('storage.class', 'Jds\Store\Storage\PDODatabaseStorage');

    $container->set('pdo.host' , 'localhost');
    $container->set('pdo.name' , 'test');
    $container->set('pdo.user' , 'root');
    $container->set('pdo.password' , '');

    $container->set('db', function($c)
    {

            $class = $c->get('db.class');
        return new $class('mysql:host='.$c->get('pdo.host').';dbname='.$c->get('pdo.name').'', $c->get('pdo.user'), $c->get('pdo.password'));
    });


    $container->set('dbStorage', function($c)
    {

        $class = $c->get('storage.class');
        $dependency = $c->get('db');
        return new $class($dependency);
    });

            
    $container->set('user.mapper.class', 'Jds\User\UserMapper');

    $container->set('user.mapper', function ($c)
    {

        $class = $c->get('user.mapper.class');
        return new $class($c->get('dbStorage'));
    });


    $userMapper = $container->get('user.mapper');

    $container->set('sessionStorage.class' , 'Jds\Store\Storage\SessionStorage');

    $container->set('sessionStorage', function($c)
    {

        $class = $c->get('sessionStorage.class');
            return new $class();    
    });


    $container->set('user.mapper.session', function($c)
    {

        $class = $c->get('user.mapper.class');
            return new $class($c->get('sessionStorage'));
    });


    #### END CONFIG #####

    if($_SERVER['REQUEST_METHOD'] === 'POST')
    {

        $user = $userMapper->find(array('id', 'name'), array('email' => $_POST['email'], 'password' => $_POST['wachtwoord']));

        if(count($user) == 1)
        {

            echo 'Welkom ' . $user[0]->getName() . '<br/>';

            

            $session = $container->get('user.mapper.session');
            $session->create($user[0]);
        }

        else
        {
            echo 'Uw gegevens zijn niet correct';
        }    
        
    }

    
    $userSession = $session->find(array('id', 'name'));

    foreach($userSession as $user)
    {

        echo 'De sessie bevat gebruiker ' . $user->getId() . ' met als naam ' . $user->getName();
    }


    
?>


Edit:

Zoals je ziet zijn er twee usermappers nodig, één voor de PDOStorage en één voor de SessionStorage. Maak ik via de container dan ook daadwerkelijk 2 mapper instanties (zoals hierboven) of is er een andere manier?
Gewijzigd op 08/01/2013 22:45:23 door Jasper DS
 
Wouter J

Wouter J

08/01/2013 23:01:37
Quote Anchor link
Nu ik je code zie zal je klasse wel goed zijn. In mijn voorbeelden maakte ik namelijk onderscheid tussen een parameter en een service in de setters en getters, de container van Pim doet dat niet. Vandaar dat ik dacht dat er iets fout was.

Dan wat tips:
- Deel het op in meerdere bestanden. Je hebt vaak een bootstrap.php file die alles bevat wat elke pagina moet hebben en dan heb je een config.php of in dit geval een services.php file met de container configuratie en dan heb je het bestand waar je de code uitvoert. Je moet eigenlijk nooit configuratie en uitvoerende code in 1 document doen.
Plaats aan het einde van services.php `return $container;`. Dan kun je in bootstrap.php de container inladen met `$container = require_once 'services.php';`
- Ik zou eerder beide vormen van storage aan de UserMapper toevoegen dan 2 UserMappers aanmaken. In de UserMapper kan je dan een temporaryStorage en een databaseStorage hebben, welke die 2 zijn (kan ook beide een dbstorage instance zijn) bepaal je dan in de container.
- Dan zou ik ook gaan nadenken over shared services, of eerder gezegd kijken welke geen shared service moet zijn. Over het algemeen is bijna elke service shared.

Dan wat persoonlijke voorkeuren:
- Hou alles bij elkaar, haal bijv. 14 naar 27 ect.
- Bedenk 1 methode van benoemen van services. Ik gebruik altijd alleen kleine letters, punten om 'namespaces' aan te geven en underscores om spaties aan te geven. Zo zou ik hebben over een storage.db service en een storage.session service.
 
Jasper DS

Jasper DS

09/01/2013 17:17:32
Quote Anchor link
Laten we zeggen dat dit beter is?

Ik vul alle parameters bovenaan en daaronder maak ik dan de instanties.

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
52
53
54
55
56
57
<?php
#### SERVICES.PHP #####
    $container = new Container();

    # Database gegevens
    $container->set('pdo.host' , 'localhost');
    $container->set('pdo.name' , 'test');
    $container->set('pdo.user' , 'root');
    $container->set('pdo.password' , '');

    # Classen aanmaken
    $container->set('db.class', 'PDO');
    $container->set('storage.db.class', 'Jds\Store\Storage\PDODatabaseStorage');
    $container->set('user.mapper.class', 'Jds\User\UserMapper');
    $container->set('storage.session.class' , 'Jds\Store\Storage\SessionStorage');

    $container->set('db', function($c)
    {

            $class = $c->get('db.class');
        return new $class('mysql:host='.$c->get('pdo.host').';dbname='.$c->get('pdo.name').'', $c->get('pdo.user'), $c->get('pdo.password'));
    });


    $container->set('storage.db', function($c)
    {

        $class = $c->get('storage.db.class');
        $dependency = $c->get('db');
        return new $class($dependency);
    });


    $container->set('user.mapper.databaseStorage', function ($c)
    {

        $class = $c->get('user.mapper.class');
        return new $class($c->get('storage.db'));
    });


    $container->set('storage.session', function($c)
    {

        $class = $c->get('storage.session.class');
            return new $class();    
    });


    $container->set('user.mapper.temporaryStorage', function($c)
    {

        $class = $c->get('user.mapper.class');
            return new $class($c->get('storage.session'));
    });


    return $container;

    ### END SERVICES.PHP ###

    $container = require_once 'services.php';

    # de instanties waar we met gaan werken
    $db = $container->get('user.mapper.databaseStorage');
    $session = $container->get('user.mapper.temporaryStorage');
?>
Gewijzigd op 09/01/2013 17:18:30 door Jasper DS
 
Wouter J

Wouter J

09/01/2013 17:36:16
Quote Anchor link
Nee, stop beide storages in de UserMapper.
 

Pagina: 1 2 volgende »



Overzicht Reageren

 
 

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.