2015-05-27 15 views
10

Sto costruendo un'API RESTful utilizzando laravel 5.laravel 5.0 Applicazione Struttura

Cercando di mantenere i controllori HTTP per essere come minimo possibile, in modo da sto usando un livello di servizio (e Repository) per gestire più della logica.

Poiché la maggior parte dei controller ha metodi simili (ad esempio show, index, update), ho scritto alcuni tratti che gestiscono ciascuno di essi. Poiché questi parlano direttamente al servizio, posso riutilizzarli per ciascun controller.

Ad esempio:

<?php namespace API\Http\Controllers\Restful; 

trait UpdateTrait 
{ 
    protected $updater; 

    public function update($itemID) 
    { 
     if (!$this->updater->authorize($itemID)) { 
      return response(null, 401); 
     } 

     if (!$this->updater->exists($itemID)) { 
      return response(null, 404); 
     } 

     $data = $this->request->all(); 
     $validator = $this->updater->validator($data); 

     if ($validator->fails()) { 
      return response($validator->messages(), 422); 
     } 

     $this->updater->update($itemID, $data); 

     return response(null, 204); 
    } 
} 

Perché tutti i controllori condividono gli stessi tratti tutti possono dipendere da una singola interfaccia.

Ad esempio:

<?php namespace API\Services\Interfaces; 

interface UpdaterServiceInterface 
{ 
    public function validator(array $data); 
    public function exists($itemID); 
    public function update($itemID, array $data); 
    public function authorize($itemID); 
} 

Tuttavia, questo fa sì che alcuni problemi con l'iniezione automatica delle dipendenze.

1) devo usare contesto consapevoli vincolante:

$this->app->when("API\Http\Controllers\ThingController") 
      ->needs("API\Services\Interfaces\UpdateServiceInterface") 
      ->give("API\Services\Things\ThingUpdateServiceInterface") 

Questo non è un problema in sé e per sé - anche se non portare a qualche piuttosto grande codice di Service Provider, che non è l'ideale. Tuttavia, significa che non riesco a utilizzare il metodo di iniezione, in quanto la risoluzione di dipendenza automatica non sembra funzionare per i metodi del controller quando si utilizza l'associazione a riconoscimento contesto: Ho appena ricevuto un messaggio could not instantiate API\Services\Interfaces\UpdateServiceInterface.

Ciò significa che il costruttore controllore deve gestire tutte le iniezione di dipendenza, che ottiene piuttosto confusa:

class ThingsController extends Controller 
{ 
    use Restful\IndexTrait, 
     Restful\ShowTrait, 
     Restful\UpdateTrait, 
     Restful\PatchTrait, 
     Restful\StoreTrait, 
     Restful\DestroyTrait; 

    public function __construct(
     Interfaces\CollectionServiceInterface $collection, 
     Interfaces\ItemServiceInterface $item, 
     Interfaces\CreatorServiceInterface $creator, 
     Interfaces\UpdaterServiceInterface $updater, 
     Interfaces\PatcherServiceInterface $patcher, 
     Interfaces\DestroyerServiceInterface $destroyer 
    ) { 
     $this->collection = $collection; 
     $this->item = $item; 
     $this->creator = $creator; 
     $this->updater = $updater; 
     $this->patcher = $patcher; 
     $this->destroyer = $destroyer; 
    } 
} 

Questo non è buono - è difficile da testare e tutte quelle dipendenze deve arrivare istanziato, anche quando viene utilizzato solo uno di essi.

Ma non riesco a pensare ad un modo più bello intorno ad esso.

Potrei usare un'interfaccia più specifica, ad es. ThingUpdateServiceInterface, (quindi non avrei bisogno del binding contestuale e potrei iniettare direttamente nei tratti), ma poi avrei molte interfacce che sono solo diverse nel nome. Che sembra sciocco.

L'altra alternativa che ho pensato era di utilizzare molti controller più piccoli, quindi uno Things\UpdateController e uno Things\ShowController - almeno in questo modo le dipendenze non necessarie non verranno istanziate ogni volta.

O forse cercare di astrarre l'uso dei tratti è il modo sbagliato di fare le cose. A volte i tratti sembrano come potenzialmente anti-modello.

Qualsiasi consiglio sarebbe apprezzato.

+1

Grazie per la bella domanda dettagliata. Che ne dici di creare 'UpdateService' con il codice nel tuo UpdateTrait e digitarlo nel tuo metodo di aggiornamento RESTful, come' aggiornamento della funzione pubblica ($ itemID, UpdateService) 'Non ho testato questo impasto ma penso che possa essere non . Potresti avere problemi nei tuoi test se continui con quello che hai finora – Digitlimit

+2

In L5 puoi fare quello che viene chiamato Method Injection vedi [qui] (https://mattstauffer.co/blog/laravel-5.0-method-injection) inoltre, creo middleware di autorizzazione + middleware "exists" e utilizzo la classe di richieste il più possibile. I tratti sono una buona astrazione del codice, ma in seguito ti troverai ad affrontare problemi che penso semplicemente perché finora l'applicazione è "non complessa", il tempo passa e l'app diventa sempre più complessa per cui avrai bisogno di un aggiornamento "unico", patcher forse distruttore (diciamo che fai delle relazioni M: N). Che ne dici di creare RestfullController? E fare un po 'di estensione? – Kyslik

+0

- L'iniezione del metodo non funziona quando si utilizza l'associazione contestuale, o almeno non riesco a ottenerla. - Non voglio creare un singolo RestfulController ed estenderlo perché alcuni di essi non usano la maggior parte dei metodi (ad esempio alcuni hanno solo 'index' e 'show'). - Sto già utilizzando il middleware auth, ma ho ancora senso averlo nel servizio, penso (ad esempio se si utilizza il servizio per Console anziché HTTP) –

risposta

1

Il tuo codice sembra ottimo e ben pensato alla generalizzazione.

Vorrei suggerire questa opzione per l'utilizzo di una Factory per la creazione dei servizi e non per predefinire tutte le iniezioni di dipendenza.

interface UpdateServiceInterface { 
    public function action($itemID); 
    //.... 
} 

class ThingUpdateServiceInterface implements UpdateServiceInterface { 
    public function action($itemID) 
    { 
     // TODO: Implement exists() method. 
    } 
} 

class ApiServiceFactory { 

    protected $app; 

    public function __construct(Application $app) { 
     $this->app = $app; 
    } 

    public function getUpdateService($type) { 
     if ($type == 'things') 
      return $this->app->make('ThingUpdateServiceInterface'); 
    } 

    public function getCreateService($type) { 
     if ($type == 'things') { 
      //same idea here and for the rest 
     } 
    } 

} 




class ApiController { 

    protected $factory; 
    protected $model; 

    public function __construct(ApiServiceFactory $factory) { 
     $this->factory = $factory; 
    } 

    public function update($data) { 
     $updater = $this->factory->getUpdateService($this->model); 
     $updater->action($data); 
    } 
} 

class ThingsController extends ApiController { 
    protected $model = 'App\Thing'; 
} 

qual è l'idea di tutto questo è:

  • ApiController base che contiene tutti i metodi: aggiornamento, inserimento, elimina ...
  • reciprocamente controllore per oggetto specifico estenderà il controller api e sovrascrive il modello $ con il nome completo del modello.
  • nell'azione del controller utilizza la fabbrica per creare il servizio per quell'oggetto specifico.
  • dopo la creazione del servizio lo utilizza per l'azione desiderata.
  • è possibile creare un'azione generale per l'azione, come

    DeleteServiceInterface 
    

sarà attuato da

class GeneralDeleteService implements DeleteServiceInterface { 
      public function delete($id){......} 
    } 

e in fabbrica quando si richiede per quella DeleteService se viene passato nessun nome quindi restituirai il servizio predefinito

spero che questo post non sia "TLDR"

Problemi correlati