2015-04-22 5 views
25

Ich habe ein BaseController, die die Grundlage für die meisten HTTP Methoden für meinen API-Server bietet, z.B. die store Methode:Laravel 5: Typ-Hinting FormRequest eine Klasse innerhalb einer Steuerung, die von Base erstreckt

BaseController.php

/** 
* Store a newly created resource in storage. 
* 
* @return Response 
*/ 
public function store(Request $request) 
{ 
    $result = $this->repo->create($request); 

    return response()->json($result, 200); 
} 

I dann auf dieser BaseController in einer spezifischeren Controller erstrecken, wie beispielsweise die UserController, etwa so:

UserController.php

class UserController extends BaseController { 

    public function __construct(UserRepository $repo) 
    { 
     $this->repo = $repo; 
    } 

} 

Das funktioniert super. Allerdings möchte ich jetzt UserController erweitern Laravel 5 Der neue FormRequest Klasse zu injizieren, die sich um Dinge wie Validierung und Authentifizierung für die User Ressource nimmt. Ich würde das gerne so machen, indem ich die Speichermethode überschreibe und Laravel's Type-Hint-Dependency-Injection für seine Form Request-Klasse verwende.

UserController.php

public function store(UserFormRequest $request) 
{ 
    return parent::store($request); 
} 

Wo die UserFormRequest von Request erstreckt, die sich von FormRequest erstreckt:

UserFormRequest.php

class UserFormRequest extends Request { 

    /** 
    * Determine if the user is authorized to make this request. 
    * 
    * @return bool 
    */ 
    public function authorize() 
    { 
     return true; 
    } 

    /** 
    * Get the validation rules that apply to the request. 
    * 
    * @return array 
    */ 
    public function rules() 
    { 
     return [ 
      'name' => 'required', 
      'email' => 'required' 
     ]; 
    } 

} 

Das Problem ist, dass die BaseController erfordert ein Illuminate\Http\Request Objekt während ich ein UserFormRequest Objekt übergeben. Deshalb bekomme ich diesen Fehler:

in UserController.php line 6 
at HandleExceptions->handleError('2048', 'Declaration of Bloomon\Bloomapi3\Repositories\User\UserController::store() should be compatible with Bloomon\Bloomapi3\Http\Controllers\BaseController::store(Illuminate\Http\Request $request)', '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php', '6', array('file' => '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php')) in UserController.php line 6 

Also, wie kann ich geben andeuten den UserFormRequest injizieren, während immer noch auf die Anforderung Anforderung des Base haften? Ich kann den BaseController nicht zwingen, ein UserFormRequest zu verlangen, weil es für irgendeine Ressource funktionieren sollte.

konnte ich eine Schnittstelle wie RepositoryFormRequest sowohl in der BaseController und der UserController verwenden, aber dann das Problem, dass Laravel nicht mehr die UserFormController durch seine Art Hinting Dependency Injection einspritzt.

+0

Sie können die Methoden von Ihrer Base zu einem Trait bewegen und sie dann an den anderen Controller verwenden. – Ravan

+0

Verwenden Sie Laravels RESTful Resource Controller? Und was bleibt bei den voreingestellten Namen wie 'store'? – Chris

+0

@Chris ja ich bin. Ich bin mir nicht sicher, ob ich deiner Frage folge, kannst du das bitte umformulieren? – Tom

Antwort

7

Im Gegensatz zu vielen ‚echten‘ objektorientierten Sprachen, diese Art von in überschriebenen Methoden Hinting Bauart ist in PHP einfach nicht möglich, siehe:

class X {} 
class Y extends X {} 

class A { 
    function a(X $x) {} 
} 

class B extends A { 
    function a(Y $y) {} // error! Methods with the same name must be compatible with the parent method, this includes the typehints 
} 

Dies die gleiche Art von Fehlern, wie Ihr Code erzeugt . Ich würde einfach nicht eine store() Methode in Ihrem BaseController setzen. Wenn Sie das Gefühl haben, dass Sie Code wiederholen, sollten Sie beispielsweise eine Serviceklasse oder eine Eigenschaft vorstellen.

Mit einer Serviceklasse

Unterhalb einer Lösung, die Verwendung eines zusätzlichen Serviceklasse macht. Dies könnte für Ihre Situation übertrieben sein. Aber wenn Sie mehr Funktionalität auf die store() Methode s StoringService hinzufügen (wie Validierung), könnte es nützlich sein.Sie können dem StoringService wie destroy(), update(), create() auch weitere Methoden hinzufügen, aber wahrscheinlich möchten Sie den Dienst dann anders benennen.

class StoringService { 

    private $repo; 

    public function __construct(Repository $repo) 
    { 
     $this->repo = $repo; 
    } 

    /** 
    * Store a newly created resource in storage. 
    * 
    * @return Response 
    */ 
    public function store(Request $request) 
    { 
     $result = $this->repo->create($request); 

     return response()->json($result, 200); 
    } 
} 

class UserController { 

    // ... other code (including member variable $repo) 

    public function store(UserRequest $request) 
    { 
     $service = new StoringService($this->repo); // Or put this in your BaseController's constructor and make $service a member variable 
     return $service->store($request); 
    } 

} 

ein Merkmal Mit

Sie auch ein Merkmal verwenden können, aber Sie müssen das dann store() Methode der Eigenschaft umbenennen:

trait StoringTrait { 

    /** 
    * Store a newly created resource in storage. 
    * 
    * @return Response 
    */ 
    public function store(Request $request) 
    { 
     $result = $this->repo->create($request); 

     return response()->json($result, 200); 
    } 
} 

class UserController { 

    use { 
     StoringTrait::store as baseStore; 
    } 

    // ... other code (including member variable $repo) 

    public function store(UserRequest $request) 
    { 
     return $this->baseStore($request); 
    } 

} 

Der Vorteil dieser Lösung ist, wenn Sie, dass müssen nicht zusätzliche Funktionalität zu der store() Methode hinzufügen, können Sie nur use das Merkmal ohne Umbenennung und Sie müssen nicht eine zusätzliche store() Methode schreiben.

Vererbung

Meiner Meinung nach, ist die Vererbung nicht so geeignet für die Art der Wiederverwendung von Code, die Sie hier brauchen, zumindest nicht in PHP. Wenn Sie jedoch nur Vererbung für dieses Problem der Codewiederverwendung verwenden möchten, geben Sie die store() Methode in Ihrem BaseController einen anderen Namen, stellen Sie sicher, dass alle Klassen ihre eigene store() Methode haben, und rufen Sie die Methode in BaseController. Etwas wie folgt aus:

BaseController.php

/** 
* Store a newly created resource in storage. 
* 
* @return Response 
*/ 
protected function createResource(Request $request) 
{ 
    $result = $this->repo->create($request); 

    return response()->json($result, 200); 
} 

UserController.php

public function store(UserFormRequest $request) 
{ 
    return $this->createResource($request); 
} 
+0

Denkst du nicht, dass es seltsam ist, eine "store" - und eine "createResource" -Methode zu erstellen, die einen identischen Zweck, aber sehr unterschiedliche Namen haben? Es scheint eher ein Workaround als eine Lösung zu sein? – Tom

+1

Ja, es ist keine gute Lösung. Wie gesagt, Vererbung in PHP ist nicht der beste Weg, um dieses Problem zu lösen. Dies funktioniert jedoch am ähnlichsten mit dem, was Sie versucht haben. Aber es gibt sicher bessere Lösungen. –

+0

@Tom, habe ich eine andere Lösung hinzugefügt, die eine zusätzliche Serviceklasse verwendet. –

4

Sie können Ihre Logik von Base bewegen trait, Service, Fassade.

Sie können die vorhandene Funktion nicht überschreiben und zwingen, andere Arten von Argumenten zu verwenden. Zum Beispiel, wenn Sie dies später würden schreiben:

function foo(BaseController $baseController, Request $request) { 
    $baseController->store($request); 
} 

es mit Ihren UserController brechen würde und OtherRequest weil UserControllerUserController erwartet, nicht OtherRequest (das Request erstreckt und gültiges Argument von foo() Perspektive).

+0

Wenn ich die Methode aus dem BaseController in ein Merkmal verschiebe, wie behebt dies das Problem, dass die 'store' Methode des BaseControllers (oder des neuen Merkmals) ein' Request' Objekt erwartet und nicht ein 'FormRequest' wie vom 'UserController's Speichermethode? – Tom

+0

Wenn Sie Eigenschaft mit der gleichen Methode verwenden, müssen Sie den Namen im Controller ändern 'controllerTrait {ControllerTrait :: store as traitStore; } '. Persönlich würde ich mit etwas gehen wie: '$ this-> helper-> store ($ request)'. Sie können dies sogar in Ihrem 'BaseController' erstellen, wenn jedes Kind es benutzen wird. –

4

Wie andere erwähnt haben, können Sie aus vielen Gründen nicht tun, was Sie tun wollen. Wie bereits erwähnt, können Sie dieses Problem mit Merkmalen oder ähnlichem lösen. Ich stelle einen alternativen Ansatz vor.

Bei einer Vermutung, es klingt wie Sie versuchen, die Namenskonvention durch die Laravel RESTful Resource Controllers, die Art, die Sie ist die weiter zu folgen zwingt eine bestimmte Methode auf einem Controller zu verwenden, in diesem Fall store.

Mit Blick auf die Quelle von ResourceRegistrar.php können wir sehen, dass in der getResourceMethods Methode Laravel entweder ein Diff oder Schnittpunkt mit den Optionen Array, die Sie übergeben und gegen die Standardwerte. Diese Standardwerte sind jedoch geschützt und umfassen store.

Das bedeutet, dass Sie nichts an Route::resource übergeben können, um eine Überschreibung der Routennamen zu erzwingen. Also lasst uns das ausschließen.

Ein einfacher Ansatz wäre, einfach eine andere Methode nur für diese Strecke einzurichten.

Route::post('user/save', '[email protected]'); 
Route::resource('users', 'UserController'); 

Hinweis: Dies kann, indem Sie erreicht werden Gemäß der Dokumentation, die benutzerdefinierten Routen kommen müssen vor der Strecke :: Ressource Anruf.

+0

Dazu müsste ich die Speicherfunktion komplett neu schreiben, oder? Ich kann die Speicherlogik nicht wiederverwenden, was dazu führt, dass der Code – Tom

+1

wiederholt wird. Laravel bietet Benutzerfreundlichkeit, die auf Kosten der Flexibilität geht. Wenn Sie nach entkoppelten, meist unabhängigen Komponenten suchen, schauen Sie in symfony2 nach. – Mysteryos

2

Die Erklärung von UserController::store() sollte mit BaseController::store(), was bedeutet (unter anderem), dass die angegebenen Parameter sowohl für die BaseController sowie UserController sollte genau das gleiche kompatibel sein.

Sie können tatsächlich die Base zwingen, eine UserFormRequest zu verlangen, es ist nicht die schönste Lösung, aber es funktioniert.

Durch das Überschreiben gibt es keine Möglichkeit, Request durch UserFormRequest zu ersetzen, also warum nicht beide verwenden? Geben Sie beiden Methoden einen optionalen Parameter für die Injektion des Objekts UserFormRequest.

BaseController.php

class BaseController { 

    public function store(Request $request, UserFormRequest $userFormRequest = null) 
    { 
     $result = $this->repo->create($request); 
     return response()->json($result, 200); 
    } 

} 

UserController.php

class UserController extends BaseController { 

    public function __construct(UserRepository $repo) 
    { 
     $this->repo = $repo; 
    } 

    public function store(UserFormRequest $request, UserFormRequest $userFormRequest = null) 
    { 
     return parent::store($request); 
    } 

} 

können Sie die Parameter ignorieren bei der Verwendung von BaseController::store() und injizieren es diese Weise, wenn UserController::store() mit: Was in ergäbe.

2

Die einfachste und sauberste Art, wie ich dieses Problem zu umgehen, fand, war die übergeordneten Methoden mit einem Unterstrich Präfix. Zum Beispiel:

Base:

  • _store(Request $request) { ... }
  • _update(Request $request) { ... }

Usercontroller:

  • store(UserFormRequest $request) { return parent::_store($request); }
  • update(UserFormRequest $request) { return parent::_update($request); }

Ich fühle mich wie den Dienstleister zuviel des Guten ist. Was wir hier zu umgehen versuchen, ist nicht das Liskov-Substitutionsprinzip, sondern einfach das Fehlen einer korrekten PHP-Reflexion. Typhinweisen sind an sich schon ein Hack.

Dies erzwingt die manuelle Implementierung eines store und update in jedem untergeordneten Controller. Ich weiß nicht, ob das für Ihr Design störend ist, aber in meinem verwende ich benutzerdefinierte Anfragen für jeden Controller, also musste ich es trotzdem machen.