Laravel DI troubles

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • scosec
  • Registratie: Februari 2016
  • Laatst online: 21:09
Hi,

Wederom bezig met het Dependency Injection gedeelte van Laravel.

Het moet een builder worden om Stored Procedures af te vuren.
Middels de interface wil de interface kunnen binden aan een andere implementatie. Bijv. MySQL in plaats van MSSQL.

Mijn inziens zou dit moeten werken. Ziet iemand wat ik niet zie? 8)7

Ik hoor graag jullie reactie.

PHP:
1
2
// waarom gaat dit mis? In de Procedure class laad ik juist de interface: public function __construct(ProcedureInterface $proc){
Argument 1 passed to App\Classes\Proc\Procedure::__construct() must be an instance of App\Classes\Proc\ProcedureInterface, string given, called in C:\Program Files (x86)\Ampps\www\sql\app\Http\Controllers\HomeController.php on line 22 and defined


PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
// controller
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Classes\Proc\Procedure;

class HomeController extends Controller
{
    //
    public function index(){
        
        $proc = new Procedure('tmp_test');
        $proc->string('testvansec');
        $proc->number('19.95');
        $proc->exec();
        
    }
}


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
<?php
// Repo
namespace App\Classes\Proc;
use App\Classes\Proc\ProcedureInterface;

class Procedure  { 
    
    protected $procedure;
    
    // construct with procedure
    public function __construct(ProcedureInterface $proc){ //$pro, 
        
        // interface
        $this->procedure = $proc;
        dd($proc);
        exit;
        // set procedure
       // $this->procedure->set($pro);
    }
    
    // add string
    public function string($str){
        $this->procedure->string($str);
    }
    
    // add number
    public function number($number){
        $this->procedure->number($str);
    }
    
    // execute
    public function exec(){
        
        return $this->procedure->exec();
    }
    
}


PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
// interface /contract
namespace App\Classes\Proc;

interface ProcedureInterface
{
    
    public function set($procedure);
    
    public function string($str);
    
    public function number($number);
    
    public function exec();
}


PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

namespace App\Classes\Proc;

interface ProcedureInterface
{
    
    public function set($procedure);
    
    public function string($str);
    
    public function number($number);
    
    public function exec();
}


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

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Classes\Proc\ProcedureInterface;
use App\Classes\Proc\SqlSrvClass;

class ProcedureServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        //
        $this->app->bind(ProcedureInterface::class, SqlSrvClass::class);
    }
}

Acties:
  • 0 Henk 'm!

  • JJerome
  • Registratie: Oktober 2007
  • Laatst online: 27-07 15:33
Je moet de Procedure (bijvoorbeeld d.m.v. method injection) wel uit de container halen i.p.v. instantiëren.

Voorbeeld:

PHP:
1
2
3
4
5
6
public function index(ProcedureInterface $proc)
{
        $proc->string('testvansec');
        $proc->number('19.95');
        $proc->exec();
}



Edit: Ik zie overigens wel wat depedency recursion maar ik vermoed dat dit copy paste fouten zijn? Je Procedure verwacht de ProcedureInterface, maar ik neem aan dat de Procedure juist deze interface moet implementeren.

Als ik het fout heb dan zou je eerder dit moeten doen (en dan zie je gelijk dat je naamgeving verwarrend is):

PHP:
1
2
3
4
5
6
7
public function index(ProcedureInterface $procedure)
{
        $proc = new Procedure($procedure);
        $proc->string('testvansec');
        $proc->number('19.95');
        $proc->exec();
}

[ Voor 54% gewijzigd door JJerome op 20-07-2016 14:38 ]


Acties:
  • 0 Henk 'm!

  • scosec
  • Registratie: Februari 2016
  • Laatst online: 21:09
Dat zou ik inderdaad kunnen proberen maar dat zou geen ideale situatie opleveren.

De reden dat ik de interface in een aparte class zet is omdat ik er mogelijk meerdere wil initiëren op een pagina. In het bovenste voorbeeld dat je geeft zou dat al geen mogelijkheid meer zijn. Die kan ik namelijk niet hergebruiken indien in meerdere procedures wil afvuren.

Thanks voor je reactie!

Acties:
  • 0 Henk 'm!

  • Merethil
  • Registratie: December 2008
  • Laatst online: 20:24
scosec schreef op woensdag 20 juli 2016 @ 14:46:
Dat zou ik inderdaad kunnen proberen maar dat zou geen ideale situatie opleveren.

De reden dat ik de interface in een aparte class zet is omdat ik er mogelijk meerdere wil initiëren op een pagina. In het bovenste voorbeeld dat je geeft zou dat al geen mogelijkheid meer zijn. Die kan ik namelijk niet hergebruiken indien in meerdere procedures wil afvuren.

Thanks voor je reactie!
PHP:
1
$proc = new Procedure('tmp_test');


Je stopt een String

PHP:
1
2
3
4
5
6
7
8
9
 public function __construct(ProcedureInterface $proc){ //$pro, 
        
        // interface
        $this->procedure = $proc;
        dd($proc);
        exit;
        // set procedure
       // $this->procedure->set($pro);
    }


waar je constructor een ProcedureInterface verwacht.
Dus daar zou je een andere oplossing voor moeten maken, of je __construct een String als parameter geven ipv een ProcedureInterface.

Acties:
  • 0 Henk 'm!

  • scosec
  • Registratie: Februari 2016
  • Laatst online: 21:09
Goede opmerking inderdaad. Nu begrijp ik de error ook.

Voorheen was dat als volgt:

PHP:
1
 public function __construct($pro, ProcedureInterface $proc){ // 


Alleen dan krijg je de melding none given. Wat hier dan ook niet geheel vreemd is. Zoals ik DI begrepen heb uit de Laravel omgeving moet de container toch een instance van die interface / implementatie terug geven? Hij geeft nu aan aan dat hij niets kan vinden terwijl hij die in de container moet zoeken.

Kan ik hem niet via een ServiceProvider terug geven?

PHP:
1
2
3
4
5
6
7
8
public function register()
{
    //
    $this->app->bind(Procedure::class, function($app){
        $class = $this->app->make('App\Classes\Proc\ProcedureInterface');
        return new Procedure($class);
    });
}


Alleen is dit geen mogelijkheid omdat ik hier de (constructor naam) procedure niet kan meegeven. Hoe kan ik dit oplossen?
Merethil schreef op woensdag 20 juli 2016 @ 14:51:
[...]


PHP:
1
$proc = new Procedure('tmp_test');


Je stopt een String

PHP:
1
2
3
4
5
6
7
8
9
 public function __construct(ProcedureInterface $proc){ //$pro, 
        
        // interface
        $this->procedure = $proc;
        dd($proc);
        exit;
        // set procedure
       // $this->procedure->set($pro);
    }


waar je constructor een ProcedureInterface verwacht.
Dus daar zou je een andere oplossing voor moeten maken, of je __construct een String als parameter geven ipv een ProcedureInterface.

Acties:
  • 0 Henk 'm!

  • Merethil
  • Registratie: December 2008
  • Laatst online: 20:24
scosec schreef op woensdag 20 juli 2016 @ 15:03:
Goede opmerking inderdaad. Nu begrijp ik de error ook.

Voorheen was dat als volgt:

PHP:
1
 public function __construct($pro, ProcedureInterface $proc){ // 


Alleen dan krijg je de melding none given. Wat hier dan ook niet geheel vreemd is. Zoals ik DI begrepen heb uit de Laravel omgeving moet de container toch een instance van die interface / implementatie terug geven? Hij geeft nu aan aan dat hij niets kan vinden terwijl hij die in de container moet zoeken.

Kan ik hem niet via een ServiceProvider terug geven?

PHP:
1
2
3
4
5
6
7
8
public function register()
{
    //
    $this->app->bind(Procedure::class, function($app){
        $class = $this->app->make('App\Classes\Proc\ProcedureInterface');
        return new Procedure($class);
    });
}


Alleen is dit geen mogelijkheid omdat ik hier de (constructor naam) procedure niet kan meegeven. Hoe kan ik dit oplossen?


[...]
Ik ben verder totaal niet bekend met Laravel, maar met Dependency Injection in Java moet je je object meegeven bij aanroepen van de constructor, zoiets:

PHP:
1
2
3
4
5
6
7
8
9
10
public function index(){
        
        // An implementation of the ProcedureInterface Interface.
        $procInterface = new ProcedureInterfaceImplementation(/*args here*/)
        $proc = new Procedure('tmp_test', $procInterface);
        $proc->string('testvansec');
        $proc->number('19.95');
        $proc->exec();
        
    }


Op deze manier maak je dat je de dependency meegeeft ipv een nieuwe instantiatie te moeten maken in je __construct.
Ik zie verder geen class die jouw ProcedureInterface implementeert, dus ik heb maar even een random naam aan iets wat een implementatie van die interface zou kunnen zijn gegeven.

Zoiets kan ook gebeuren via bijvoorbeeld een superclass (bijvoorbeeld wanneer je een class extended hebt) of via injection door bijvoorbeeld annotations. Injection via annotation is hoe veel frameworks 't doen om ervoor te zorgen dat je minder boilerplate in je controllers hebt bijvoorbeeld, en je sneller zoiets als een logger kan initialiseren met de default values.

[ Voor 14% gewijzigd door Merethil op 20-07-2016 15:15 ]


Acties:
  • 0 Henk 'm!

  • Ventieldopje
  • Registratie: December 2005
  • Laatst online: 19:53

Ventieldopje

I'm not your pal, mate!

Ten eerste lijkt het mij de bedoeling dat class Procedure óf de interface implementeerd óf een abstract class moet zijn...

Verder lijkt het mij niet de bedoeling dat een Procedure class zichzelf nodig heeft (en dus bij de constructor een ProcedureInterface argument moet hebben).

Het is de bedoeling dat een andere class/method (die procedures gebruikt) zoals bijv je index method van je controller dat als argument heeft en het geinjecteerd krijgt door het als argument te specificeren.

Misschien verstandig om Laravel even te laten voor wat het is en uberhaubt wat tutorials over DI in PHP te zoeken? Dan hoef je geen rekening te houden met Laravel specifieke zaken die er niet toe doen en heb je in ieder geval de basis van DI goed onder de knie. Dat kun je vervolgens weer gebruiken om DI toe te passen in Laravel.

www.maartendeboer.net
1D X | 5Ds | Zeiss Milvus 25, 50, 85 f/1.4 | Zeiss Otus 55 f/1.4 | Canon 200 f/1.8 | Canon 200 f/2 | Canon 300 f/2.8


Acties:
  • 0 Henk 'm!

Verwijderd

Wat je wilt, is:

PHP:
1
$proc = App::make(Procedure::class);


Leesvoer:

Service Container
What is dependency injection?

Acties:
  • 0 Henk 'm!

  • Merethil
  • Registratie: December 2008
  • Laatst online: 20:24
Verwijderd schreef op woensdag 20 juli 2016 @ 15:26:
Wat je wilt, is:

PHP:
1
$proc = App::make(Procedure::class);


Leesvoer:

Service Container
What is dependency injection?
In hoeverre is dat iets anders dan gewoon de constructor van een class aanroepen?

[ Voor 5% gewijzigd door Merethil op 20-07-2016 15:31 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Merethil schreef op woensdag 20 juli 2016 @ 15:30:
[...]


In hoeverre is dat iets anders dan gewoon de constructor van een class aanroepen?
Omdat de dependencies door de service container worden opgelost, in plaats van dat je zelf een instantie van
code:
1
ProcedureInterface
moet meegeven. Dit principe heet ook wel Inversion of control.

Acties:
  • 0 Henk 'm!

  • Merethil
  • Registratie: December 2008
  • Laatst online: 20:24
Verwijderd schreef op woensdag 20 juli 2016 @ 15:47:
[...]


Omdat de dependencies door de service container worden opgelost, in plaats van dat je zelf een instantie van
code:
1
ProcedureInterface
moet meegeven. Dit principe heet ook wel Inversion of control.
Ik las zoiets, maar hoe kan je framework verzinnen wat de constructor van de dependencies dan weer aan dependencies heeft? Ik gok dat het ding niet doodleuk native variables voor je gaat staan lopen vullen met random data?

Edit: Ik zie dat in Spring Framework (voor mij meer herkenbaar, Java ken ik een stuk beter) ze default waardes gebruiken die je zet in je configuratie voor de specifieke bean (oftewel IoC container of hoe je het ook wilt noemen) en op die manier dependency issues resolved. Ik gok dat Laravel dat ook doet?

[ Voor 20% gewijzigd door Merethil op 20-07-2016 16:36 ]


Acties:
  • 0 Henk 'm!

  • Barryvdh
  • Registratie: Juni 2003
  • Laatst online: 10-10 23:00
Merethil schreef op woensdag 20 juli 2016 @ 16:09:
[...]


Ik las zoiets, maar hoe kan je framework verzinnen wat de constructor van de dependencies dan weer aan dependencies heeft? Ik gok dat het ding niet doodleuk native variables voor je gaat staan lopen vullen met random data?

Edit: Ik zie dat in Spring Framework (voor mij meer herkenbaar, Java ken ik een stuk beter) ze default waardes gebruiken die je zet in je configuratie voor de specifieke bean (oftewel IoC container of hoe je het ook wilt noemen) en op die manier dependency issues resolved. Ik gok dat Laravel dat ook doet?
Dat vertel je hem. En anders probeert hij recursief te bepalen wat je allemaal wil hebben, door middel van de typehints. Als die class bij hem geregistreerd is, gebruikt hij dat. Anders kijkt hij of hij hem zonder parameters kan instantieren.

Ik denk dat het voorbeeld van OP niet helemaal klopt, maar grofweg gaat het dus meestal zo:

PHP:
1
2
3
interface ProcedureInterface { .. }
class MySQLProcedure extends ProcedureInterface {..}
class SQLiteProcedure extends ProcedureInterface {..}

Uiteraard kan je dan niet zomaar 'new ProcedureInterface' doen, of app('ProcedureInterface'), want de Container weet niet wat je dan wil hebben. Je kan hem dan vertellen welke implementatie je wil gebruiken.
https://laravel.com/docs/...rfaces-to-implementations
PHP:
1
2
3
4
5
6
7
// In een ServiceProvider geven we aan welke class je wil gebruiken.
$this->app->bind('ProcedureInterface', 'MySQLProcedure');

// Via App::make() of app()
$procedure = app('ProcedureInterface');
// Via injection
public function getIndex(ProcedureInterface $procedure) { .. }


Bij de eerste vraag je het expliciet op aan de container. Bij de 2de, laat je het framework dit injecten, wat hetzelfde resultaat heeft. Maar dit is dus alleen mogelijk via bepaalde classes, zoals de controller. Je kan dus aanhouden dat je in principe in je code nooit 'new MyClass()' hoeft te doen, behalve misschien in je ServiceProvider, waarbij je deze waarde gebruikt om juist aan de container te geven.

Als je instance zelf ook parameters heeft, kan je een callback meegeven bijvoorbeeld, of die argumenten ook aan de container vertellen.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SomeProcedure implements ProcedureInterface {
    public function __construct(SomeClass $param) { .. }
}

// In ServiceProvider vertellen we hoe hij SomeProcedure moet resolven, geven dat als alias aan ProcedureInterface
$this->app->bind('SomeProcedure', function($app){
   $param = new SomeClass(...);
   return new SomeProcedure($param);
});
$this->app->alias('ProcedureInterface', 'SomeProcedure');

// Of door alleen zijn dependencies te vertellen:
$this->app->bind('ProcedureInterface', 'SomeProcedure');
$this->app->bind('SomeClass', function($app){
   return new SomeClass(...); // Of ook recursief laten bepalen
});

[ Voor 3% gewijzigd door Barryvdh op 20-07-2016 20:18 ]


Acties:
  • 0 Henk 'm!

  • Merethil
  • Registratie: December 2008
  • Laatst online: 20:24
Barryvdh schreef op woensdag 20 juli 2016 @ 20:15:
[...]


Dat vertel je hem. En anders probeert hij recursief te bepalen wat je allemaal wil hebben, door middel van de typehints. Als die class bij hem geregistreerd is, gebruikt hij dat. Anders kijkt hij of hij hem zonder parameters kan instantieren.

Ik denk dat het voorbeeld van OP niet helemaal klopt, maar grofweg gaat het dus meestal zo:

PHP:
1
2
3
interface ProcedureInterface { .. }
class MySQLProcedure extends ProcedureInterface {..}
class SQLiteProcedure extends ProcedureInterface {..}

Uiteraard kan je dan niet zomaar 'new ProcedureInterface' doen, of app('ProcedureInterface'), want de Container weet niet wat je dan wil hebben. Je kan hem dan vertellen welke implementatie je wil gebruiken.
https://laravel.com/docs/...rfaces-to-implementations
PHP:
1
2
3
4
5
6
7
// In een ServiceProvider geven we aan welke class je wil gebruiken.
$this->app->bind('ProcedureInterface', 'MySQLProcedure');

// Via App::make() of app()
$procedure = app('ProcedureInterface');
// Via injection
public function getIndex(ProcedureInterface $procedure) { .. }


Bij de eerste vraag je het expliciet op aan de container. Bij de 2de, laat je het framework dit injecten, wat hetzelfde resultaat heeft. Maar dit is dus alleen mogelijk via bepaalde classes, zoals de controller. Je kan dus aanhouden dat je in principe in je code nooit 'new MyClass()' hoeft te doen, behalve misschien in je ServiceProvider, waarbij je deze waarde gebruikt om juist aan de container te geven.

Als je instance zelf ook parameters heeft, kan je een callback meegeven bijvoorbeeld, of die argumenten ook aan de container vertellen.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SomeProcedure implements ProcedureInterface {
    public function __construct(SomeClass $param) { .. }
}

// In ServiceProvider vertellen we hoe hij SomeProcedure moet resolven, geven dat als alias aan ProcedureInterface
$this->app->bind('SomeProcedure', function($app){
   $param = new SomeClass(...);
   return new SomeProcedure($param);
});
$this->app->alias('ProcedureInterface', 'SomeProcedure');

// Of door alleen zijn dependencies te vertellen:
$this->app->bind('ProcedureInterface', 'SomeProcedure');
$this->app->bind('SomeClass', function($app){
   return new SomeClass(...); // Of ook recursief laten bepalen
});
Dit snap ik opzich, maar stel: jouw class heeft een constructor met twee parameters, een int en een string. Hoe weet de IoC service wat voor inputs daarin gaan?
Die dependencies zijn dus niet simpelweg te initialiseren zonder enige nuttige parameters mee te geven. Hoe doet laravel dat?

Edit: Ah, via callback dus. Ik las niet goed. Oke, dan begrijp ik het principe in laravel.

Acties:
  • 0 Henk 'm!

  • scosec
  • Registratie: Februari 2016
  • Laatst online: 21:09
Het voorbeeld dat Barry geeft is zoals een gedeelte van mijn code in elkaar zit.
PHP:
1
2
3
interface ProcedureInterface { .. }
class MySQLProcedure extends ProcedureInterface {..}
class SQLiteProcedure extends ProcedureInterface {..}


De interface is nu gekoppeld aan de class SqlSrv. Die wordt dan ook automatisch geresolved door de IOC container. Dat werkt prima en geeft ook het gewenste resultaat. Enkel wanneer ik dit laat resolven in de container heb ik 1 instance van de interface / SqlSrv.php.

Dat betekend als ik meerdere keren een Procedure wil samenstellen via de container moet ik deze meerdere malen resolven via de parameters van de controller.

PHP:
1
public function __construct(ProcedureInterface $proc, ProcedureInterface $proc2){


Dat is dus niet mijn bedoeling. Daarom mijn keuze om de interface nog te verwerken in een class zodat ik deze los kan initiëren via new Procedure(); De constructor bevat dan een instantie van de interface en daarmee SqlSrv.

Alleen omdat de Procedure class niet uit de IOC komt kan hij nu de interface niet injecteren in de class. Daar loopt het nu op stuk.

@Ventieldopje je kunt het zo gek niet bedenken of ik heb de documentatie / tutorials / naslag gelezen over dependency injection en de toepassing daarvan in Laravel. Ik blijk hier toch enige moeite mee te hebben om dit volledig op te pikken. Vandaar mijn vragen met bijbehorende voorbeelden.

De case die ik nu heb wijkt af van voorbeelden in de documentatie en andere packages die ik gezien heb. Nu moet ik dus naar een oplossing toe werken zoals die goed is voor mijn case. Wellicht ben ik nu niet op het juiste spoor maar daar heb ik dan weer van geleerd.
Pagina: 1