Introducció a Laravel Service Container

Article original d'Arunas Skirius publicat al seu blog el 23 de gener de 2021.

El contenidor de serveis de Laravel pot ser una mica misteriós, especialment si no es fa servir molt sovint. En aquest post s'explica com utilitzar-lo, i quins errors comuns cal evitar.

Efectivament, el contenidor de serveis de Laravel pot ser un misteri, especialment si es fa servir gaire. La manera com funciona és bella i poderosa, cosa que li permet injectar fàcilment les dependències, resoldre els serveis personalitzats, reemplaçar els serveis en viu amb serveis falsos per a les proves, i fins i tot crear instàncies Singleton que persisteixen les seves propietats en tota l'aplicació. Si no saps el que signifiquen algunes d'aquestes coses, no et preocupis - et guiaré a través de tot.

Tot i que el tema pot ser una mica descoratjador, val la pena conèixer-lo i et convertiràs en un millor desenvolupador gràcies a això.

Què és un contenidor de serveis?

Un contenidor de servei és essencialment un mapa de tots els serveis registrats a la teva aplicació. Un servei típic podria ser el vostre ClientRepository, o un StatisticsService. Algun tipus de classe que sutilitza en moltes parts de la teva aplicació.

Però no us preocupeu, no haureu de configurar o registrar cada classe de servei que utilitzeu en l'aplicació. Laravel proporciona una forma zero-configuració dutilitzar el contenidor de serveis per resoldre els serveis necessaris o injectar una dependència, i, més que no, vostè no haurà descriure cap codi addicional per collir els beneficis dun codi més net i la composició.

Puc continuar parlant de com és d'impressionant, però és millor veure'l en acció. En aquest article us mostraré alguns exemples de codi, que podeu millorar fàcilment utilitzant el Contenidor de Serveis. Comencem.

Mostra de la classe Servei

A continuació es mostra una classe PHP molt simple amb què treballarem en aquest article. SubscriptionService és una classe personalitzada i hipotètica que s'ocupa de les subscripcions dels nostres usuaris. Coses com afegir una targeta de crèdit nova, cobrar una subscripció, o donar de baixa a l'usuari. Les implementacions aquí no importen, així que no les he incloses per fer el codi més fàcil de llegir i centrat en el tema que ens ocupa. Ara veurem algunes maneres diferents en què podem utilitzar aquesta classe i utilitzar-la en un controlador de Laravel.

<?php
namespace App\Services;

use App\Models\User;
use App\Models\CreditCard;

class SubscriptionService
{
    public function addCreditCard(User $user, CreditCard $card)
    {
        // ...
    }

    public function removeCreditCard(User $user, CreditCard $card)
    {
        // ...
    }

    public function getCreditCards(User $user)
    {
        // ...
    }

    public function setupSubscription(User $user)
    {
        // ...
    }

    public function isSubscribed(User $user)
    {
        // ...
    }

    public function chargeSubscription(User $user)
    {
        // ...
    }

    public function removeSubscription(User $user)
    {
        // ...
    }
}

Utilització del servei en un controlador

L'enfocament clàssic

Si ets nou al framework Laravel o Symfony, pots pensar que la millor manera d'utilitzar aquest servei en un controlador és simplement crear-ne una nova instància. Necessitem saber amb quin usuari està relacionada aquesta subscripció, així que busquem l'usuari que està connectat en aquest moment. Després tornem la vista i us passem algunes dades del SubscriptionService. Podeu veure aquí que el SubscriptionService és una dependència en aquest mètode del controlador. Sense ell, no podríem obtenir les dades necessàries.

<?php
namespace App\Http\Controllers;

use App\Services\SubscriptionService;

class UserBillingController extends Controller
{
    public function index()
    {
        $user = request()->user();
        $subscriptionService = new SubscriptionService();

        return view('billing.index', [
            'creditCards' => $subscriptionService->getCreditCards($user),
            'isSubscribed' => $subscriptionService->isSubscribed($user),
        ]);
    }
}

Hi ha un parell de formes millors d'incloure aquesta dependència al controlador, que tenen més poder de configuració i injecció de dependència encadenada, de les quals parlaré més endavant. Però per ara, vegem-ne alguns exemples.

Resolent el servei des del Service Container

<?php

namespace App\Http\Controllers;

use App\Services\SubscriptionService;

class UserBillingController extends Controller
{
    public function index()
    {
        $user = request()->user();
        $subscriptionService = resolve(SubscriptionService::class);

        return view('billing.index', [
            'creditCards' => $subscriptionService->getCreditCards($user),
            'isSubscribed' => $subscriptionService->isSubscribed($user),
        ]);
    }
}

L'únic que hem fet aquí és substituir la paraula clau new per una trucada a la funció resolve(), passant-li el nom de la classe. Podem fàcilment passar la cadena "App\Services\SubscriptionService" a la funció resolve() i evitar haver d'incloure la sentència use..., però prefereixo extreure els espais de noms de les classes per facilitar la navegació a l'editor de codi, així com línies de codi més curtes per a una millor comprensió.

Bé, com funciona? La funció resolve() és una funció d'ajuda, que va al contenidor de serveis, on estan registrats tots els diferents serveis, i pregunta si el contenidor de serveis té un enllaç per a la classe sol·licitada. Si la teniu, retorneu la instància vinculada, o la construïu des de zero basant-vos en les opcions configurades. En el nostre cas, no hem fet cap configuració ni vinculació. Això està bé perquè si el Contenidor de Serveis no pot trobar la classe sol·licitada, simplement crea una nova instància d'aquesta classe i te la torna.

Això no és diferent de fer servir la paraula clau new per crear una instància nova. Això és cert, i els beneficis de resoldre una instància com aquesta es fan evidents més endavant, quan els serveis es tornen més complexos i tenen les seves pròpies dependències per resoldre. Ara vegem l'altra forma, la preferida, de fer servir el Contenidor de Serveis.

La injecció de dependències

<?php

namespace App\Http\Controllers;

use App\Services\SubscriptionService;

class UserBillingController extends Controller
{
    public function index(SubscriptionService $subscriptionService)
    {
        $user = request()->user();

        return view('billing.index', [
            'creditCards' => $subscriptionService->getCreditCards($user),
            'isSubscribed' => $subscriptionService->isSubscribed($user),
        ]);
    }
}

Ara hem mogut el SubscriptionService a la llista de paràmetres de la funció. Aquest índex de mètode de controlador té ara un paràmetre i espera una instància de la classe SubscriptionService. Però com ho obté? No estem trucant a aquest mètode "index" nosaltres mateixos, oi? Això és una cosa que el Laravel Router fa automàticament fent coincidir les rutes URL sol·licitades amb els respectius Controladors i els seus mètodes. Aquests es configuren en els fitxers del router, recordes? Aleshores, com sap Laravel que aquesta funció espera un argument?

Quan Laravel aparella la petició amb un mètode de controlador, primer ho inspecciona, gràcies a les capacitats de Reflection de PHP. Aleshores sap quins paràmetres espera el teu mètode controlador, i els resol (recordes el mètode resolve()?) des del Contenidor de Serveis. Quan el Router té totes les instàncies requerides, crida al mètode controlador, passant les instàncies com a paràmetres. Això s'anomena Injecció de Dependència i Laravel hi fa un treball meravellós.

Però, quina és la diferència? Segurament això és només fer trucades a funcions addicionals i viatges d'anada i tornada al contenidor de serveis només per obtenir aquesta instància de classe, oi? És fins i tot eficient? Com o per què és millor que crear una instància de servei nova?

  • En primer lloc, sí, és eficient! PHP avui dia és tan ràpid, que ni tan sols notaràs la diferència. A més, gràcies a l'autoloader PSR-4, les classes es carreguen en mode lazy-loaded, la qual cosa significa que fins i tot si tens centenars de serveis diferents a la teva aplicació, el Contenidor de Serveis no en carregarà cap fins que realment necessitis resoldre'ls o configurar-los;
  • En segon lloc, això us ajuda a netejar el vostre codi i separar les preocupacions. En injectar un servei com un paràmetre de funció, hi pensem com una dependència, i després deixem que el cos de la funció realitzi realment les accions requerides en lloc de configurar totes les instàncies de classe necessàries.
  • Finalment, això us permetrà configurar fàcilment la injecció de dependència en cadena. Aquí és on el Contenidor de Serveis realment comença a brillar, així doncs, expliquem-ho!

Ara, ampliem els requisits (com sempre passa a la vida real) del SubscriptionService. L'empresa ha decidit que no podem guardar les dades de les targetes de crèdit als nostres servidors, i que necessitem utilitzar un servei extern diferent per fer-ho. Potser hem decidit utilitzar un servei com a Stripe per emmagatzemar les targetes de crèdit dels usuaris i així no haver de bregar amb les regulacions financeres que comporta l'emmagatzematge d'aquestes dades. En aquest cas, el SubscriptionService necessita accedir a aquest servei extern per realitzar aquestes tasques. Necessita una dependència pròpia.

Introduïm un StripeService:

<?php

namespace App\Services\External;

use App\Models\User;
use App\Models\CreditCard;

class StripeService
{
    public function addCreditCard(User $user, CreditCard $card)
    {
        // ...
    }

    public function removeCreditCard(User $user, CreditCard $card)
    {
        // ...
    }

    public function getCreditCards(User $user)
    {
        // ...
    }
}

La classe té un aspecte molt semblant al SubscriptionService, però la implementació s'ocupa d'accedir a les targetes de crèdit des de Stripe en comptes de la nostra base de dades.

I aquí hi ha el SubscriptionService actualitzat amb la dependència StripeService:

<?php

namespace App\Services;

use App\Models\User;
use App\Models\CreditCard;
use App\Services\External\StripeService;

class SubscriptionService
{
    /** @var StripeService */
    protected $stripeService;

    public function __construct(StripeService $stripeService)
    {
        $this->stripeService = $stripeService;
    }

    public function addCreditCard(User $user, CreditCard $card)
    {
        $this->stripeService->addCreditCard($user, $card);

        // ...
    }

    // ...
}

Ara el constructor de SubscriptionService espera una instància de StripeService per accedir a les targetes de crèdit de Stripe. Perfecte. Ara, què passa amb el controlador? Vegem primer el mètode tradicional.

El mètode tradicional

<?php

namespace App\Http\Controllers;

use App\Services\SubscriptionService;
use App\Services\External\StripeService;

class UserBillingController extends Controller
{
    public function index()
    {
        $user = request()->user();
        $stripeService = new StripeService;
        $subscriptionService = new SubscriptionService($stripeService);

        return view('billing.index', [
            'creditCards' => $subscriptionService->getCreditCards($user),
            'isSubscribed' => $subscriptionService->isSubscribed($user),
        ]);
    }
}

Pots veure que el controlador creix amb un nombre creixent d'instàncies que necessites crear per recuperar algunes dades sobre l'usuari. És clar, això és només dues noves instàncies en aquest moment, però pot créixer fàcilment a mesura que expandeixes el servei amb més dependències. Potser tens altres dependències en el futur. Per exemple, un servei de PayPal per a alguns usuaris que decideixin subscriure's utilitzant els comptes de PayPal.

Ara vegem si podem netejar una mica de codi usant el Contenidor de Servei.

Resolent des del Contenidor de Serveis

M'agrada el mètode d'Injecció de Dependències, la injecció de serveis afegint-los com a paràmetres de tipus als mètodes del controlador, així que moguem primer el SubscriptionService a la llista de paràmetres.

<?php

namespace App\Http\Controllers;

use App\Services\SubscriptionService;

class UserBillingController extends Controller
{
    public function index(SubscriptionService $subscriptionService)
    {
        $user = request()->user();

        return view('billing.index', [
            'creditCards' => $subscriptionService->getCreditCards($user),
            'isSubscribed' => $subscriptionService->isSubscribed($user),
        ]);
    }
}

Hem pogut eliminar el codi per a la instanciació de StripeService, perquè aquí no estem instant el servei SubscriptionService. Però un moment... El constructor de SubscriptionService requereix una instància de StripeService, així que com funcionarà?

Service Container se n'encarregarà. I no hi ha cap configuració necessària perquè això passi.

Aquí tens un desglossament de com el Service Container resol aquest servei per tu i com s'ocupa de les dependències en cadena.

Primer, gràcies a les habilitats de PHP Reflection, veieu que el mètode index() del controlador requereix un paràmetre i que aquest paràmetre és del tipus SubscriptionService.

Aleshores intenta resoldre aquest servei des del Service Container. Com que no hem fet cap configuració especial per registrar aquest servei amb el Contenidor de Serveis, simplement intenta crear una nova instància d'aquest servei.

Abans de crear una instància nova d'una classe, el Service Container inspecciona el mètode __constructor() d'aquesta classe. Per refrescar la memòria, a continuació es mostra el constructor de la classe SubscriptionService:

class SubscriptionService
{
    /** @var StripeService */
    protected $stripeService;

    public function __construct(StripeService $stripeService)
    {
        $this->stripeService = $stripeService;
    }

    // ...
}

El contenidor de serveis ara veu que aquest constructor requereix un paràmetre i també està indicat pel tipus. Això ajuda el Contenidor de Servei a esbrinar quina instància de classe s'espera aquí.

  • El contenidor de serveis sap ara que aquest SubscriptionService requereix una instància de la classe StripeService, per la qual cosa resol també l'StripeService. Un cop més, com que no hem fet cap configuració especial per establir l'StripeService amb el contenidor de serveis, simplement crea una nova instància i la torna.
  • Ara que tenim una instància de StripeService, el Contenidor de Servei la passa al mètode __constructor() del SubscriptionService i així completa la inicialització del servei.
  • El Contenidor de Serveis té ara una instància funcional del SubscriptionService i ha completat la resolució del mateix, que era requerida pel mètode del controlador. El router de Laravel pot ara cridar al mètode del controlador, passant-li la instància de SubscriptionService com a paràmetre.

Tot això passa automàticament, entre bastidors. Així que essencialment, podries reemplaçar codi com aquest:

$firstDependency = new FirstDependency;
$secondDependency = new SecondDependency;
$service = new Service($firstDependency, $secondDependency);

Amb una simple crida de resolució com aquesta:

$service = resolve(Service::class);

El contenidor de serveis resoldrà qualsevol dependència d'aquesta classe automàticament i la injectarà al constructor. D'aquesta manera, passaràs menys temps configurant aquest servei i més temps en altres aspectes més importants de la teva aplicació.

Els advertiments, els "gotchas"

D'acord, tot això sembla un munt de l'anomenada "màgia de Laravel" que passa entre bastidors. T'he donat una visió abstracta i d'alt nivell del que està passant, i encara que no necessites conèixer el funcionament intern del Contenidor de Serveis per poder utilitzar-lo, sí que necessites conèixer algunes gotxes, o regles, sobre el Contenidor de Serveis per ajudar-te a estalviar hores de depuració en el futur.

Aprèn on funciona realment la Injecció de Dependències

Encara que no hi ha límits quant a on pots trucar als mètodes resolve() o app() per resoldre una instància de classe amb totes les seves dependències, injectar dependències com a paràmetres de funció pot ser una mica limitant. En poques paraules, pots estar gairebé segur que el paràmetre de la funció es resoldrà automàticament si es tracta d'un mètode que és cridat pel mateix framework Laravel, no per tu. Permet-me explicar-ho.

Aquí hi ha un exemple de classe Laravel Job. Una classe hipotètica per afegir la targeta de crèdit donada a la subscripció de l'usuari usant la classe SubscriptionService (que té una dependència pròpia, recordes?)

<?php

namespace App\Jobs;

use App\Models\User;
use App\Models\CreditCard;
use Illuminate\Bus\Queueable;
use App\Services\SubscriptionService;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

class AddCreditCardToSubscription implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /** @var User */
    public $user;

    /** @var CreditCard */
    public $creditCard;

    /** @var SubscriptionService */
    public $subscriptionService;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(User $user, CreditCard $creditCard)
    {
        $this->user = $user;
        $this->creditCard = $creditCard;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $this->subscriptionService->addCreditCard($this->user, $this->creditCard);
        
        // ...
    }
}

Ara pots veure que el constructor daquest treball requereix 2 paràmetres - un usuari, i la targeta de crèdit que safegeix a la subscripció daquest usuari. Observeu que el mètode handle() requereix que tinguem una instància del SubscriptionService, que fem servir per manejar totes les activitats relacionades amb la targeta de crèdit i la subscripció. Com podem fer-ho correctament utilitzant la injecció de dependències?

El teu primer instint podria dir-te que ho injectis als paràmetres __constructor(), oi? El Contenidor de Serveis, després de tot, resol les dependències del constructor automàticament, oi? Doncs no. El Contenidor de Serveis només resol els paràmetres del constructor si aquesta classe és resolta pel Contenidor de Serveis. En altres paraules, si anéssim a construir la feina així:

$job = resolve(App\Jobs\AddCreditCardToSubscription::class);

// enfocament incorrecte: com passem l'usuari i la targeta de crèdit?

Però l'anterior no és la manera de crear o despatxar un Laravel Job. Vegem les formes correctes:

use App\Jobs\AddCreditCardToSubscription;

// option 1 - use the dispatch() helper method
dispatch(new AddCreditCardToSubscription($user, $creditCard));

// option 2
AddCreditCardToSubscription::dispatch($user, $creditCard);

Tots dos funcionen molt bé i de la mateixa manera - només es veu diferent i depèn del gust. Encara que, tingueu en compte, que la segona opció requereix la seva classe de treball per utilitzar l'Illuminate\Foundation\Bus\Dispatchable trait.

L'enfocament equivocat

Podem injectar nosaltres mateixos una instància de SubscriptionService? És clar que podem, però on és la màgia en això:

/**
 * Create a new job instance.
 *
 * @return void
 */
public function __construct(User $user, CreditCard $creditCard, SubscriptionService $subscriptionService)
{
    $this->user = $user;
    $this->creditCard = $creditCard;
    $this->subscriptionService = $subscriptionService;
}

Com que no estem demanant a Laravel que resolgui una nova classe de treball i estem creant una nova instància del treball nosaltres mateixos – hem de proporcionar tots els paràmetres del constructor nosaltres mateixos. Això significa que necessitem una instància de SubscriptionService:

use App\Services\SubscriptionService;
use App\Jobs\AddCreditCardToSubscription;

// ...

$subscriptionService = resolve(SubscriptionService::class);
dispatch(new AddCreditCardToSubscription($user, $creditCard, $subscriptionService));

No és l'enfocament més elegant. Podem fer-ho millor?

La forma correcta

Recordes que hem dit que Laravel resoldria les dependències d'una funció que el mateix framework Laravel anomena? Doncs bé, en una classe job, hi ha un mètode especial anomenat handle(), que és cridat pel Worker de Laravel quan comença a processar el job. Podem utilitzar aquest fet per utilitzar la Injecció de Dependències afegint el paràmetre de la funció aquí:

<?php

namespace App\Jobs;

use App\Models\User;
use App\Models\CreditCard;
use Illuminate\Bus\Queueable;
use App\Services\SubscriptionService;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

class AddCreditCardToSubscription implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /** @var User */
    public $user;

    /** @var CreditCard */
    public $creditCard;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(User $user, CreditCard $creditCard)
    {
        $this->user = $user;
        $this->creditCard = $creditCard;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle(SubscriptionService $subscriptionService)
    {
        $subscriptionService->addCreditCard($this->user, $this->creditCard);
        
        // ...
    }
}

Com que ara tenim una instància del SubscriptionService en el mètode handle() com a paràmetre, ja no ho necessitem al constructor i ja no necessitem desar aquesta instància en una propietat de classe $subscriptionService.

No només el codi sembla més net sense la propietat de classe extra, sinó que també ens allibera dhaver de proporcionar la instància del servei nosaltres mateixos.

Ara podem despatxar fàcilment la feina així:

use App\Jobs\AddCreditCardToSubscription;

// ...

dispatch(new AddCreditCardToSubscription($user, $creditCard));

I el Contenidor de Serveis s'encarregarà d'injectar les dependències correctes al mètode handle() d'aquest treball. Molt millor, oi?

A quins altres llocs puc utilitzar la injecció de dependències?

Aquestes són algunes de les parts de Laravel on pots utilitzar la Injecció de Dependències proporcionant paràmetres de funció de tipus hinted:

  • Controller constructors.
  • Controller action/route methods.
  • Event listeners.
  • Job handlers.
  • View Composer constructors.
  • Form Request constructors.
  • Command handlers.
  • Command closures.

Per a qualsevol altra cosa, podeu utilitzar fàcilment els mètodes d'ajuda resolve() o app() per resoldre una instància completa d'una classe donada, incloent totes les seves dependències.

Què NO pot resoldre?

Si, a qualsevol punt de la cadena de dependència, una classe requereix un paràmetre que no és una classe de tipus indicat, o si el paràmetre és de tipus escalar (sencer, cadena, booleà, matriu, etc.), el contenidor de serveis no podrà resoldre aquests paràmetres i llançarà una excepció Illuminate\Contracts\Container\BindingResolutionException, proporcionant-li els detalls de la classe i el paràmetre que no va poder resoldre.

La solució més comuna seria fer aquests tipus escalars opcionals donant-los valors per defecte:

class SubscriptionService
{
    /** @var StripeService */
    protected $stripeService;

    /** @var bool */
    protected $shouldNotifyUser;

    public function __construct(StripeService $stripeService, bool $shouldNotifyUser = true)
    {
        $this->stripeService = $stripeService;
    }

    // ...
}

Compte amb la vinculació de paràmetres de ruta (Route Parameter Binding)

Potser també heu vist "Injecció de Dependència" en la manera d'enllaçar paràmetres de ruta a la instància de model de Eloquent. Per exemple:

<?php

// routes/web.php

Route::get('client/{client}', 'ClientsController@show');

L'única cosa amb què cal anar amb compte és amb els noms dels paràmetres. Els noms dels paràmetres a la definició de l'URL han de coincidir amb els noms dels paràmetres al mètode del controlador. Si els noms no coincideixen, la vinculació del model fallarà i simplement rebràs una instància buida del model directament des del contenidor de serveis.

<?php

namespace App\Http\Controllers;

class ClientsController
{
    public function show(Client $differentParamName)
    {
        // ...
    }
}

L'exemple anterior NO funcionarà, perquè el nom del paràmetre del mètode del controlador és diferent de la definició d'URL de l'encaminador. El nom del paràmetre s'ha de canviar a $client perquè funcioni la resolució d'enllaç de model correcta.

Nota sobre les Facades

Les facades són una altra gran manera de vincular un contracte a una implementació específica. Aporta característiques addicionals útils a l'hora de fer proves, i permet intercanviar fàcilment les classes d'implementació subjacents sense canviar l'ús del contracte.

Per exemple, si teniu 2 controladors diferents per gestionar la subscripció de l'usuari, com ara StripeSubscriptionService i ArraySubscriptionService (per a propòsits de prova), podríeu tenir una facade SubscriptionService que resolgui una vinculació particular quan sigui necessari:

use Illuminate\Support\Facades\Facade;

class SubscriptionService extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'subscription-service';

        // Alternatively, you can just return the implementation
        // class name, but you'll need to use that when switching
        // the bindings.
        return StripeSubscriptionService::class;
    }
}

A continuació, només heu de registrar la vinculació 'subscription-service' a la seva AppServiceProvider d'aquesta manera:

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->app->bind('subscription-service', StripeSubscriptionService::class);
    }
}

Un cop configurat, podeu utilitzar fàcilment la vostra nova façana per trucar a qualsevol mètode d'instància de la classe d'implementació:

$isSubscribed = SubscriptionService::isSubscribed($user);

Hi ha una advertència amb Laravel Facades, i és el fet que les instàncies resoltes s'emmagatzemen en memòria cau i es tornen en futures trucades. Pots aprendre més sobre això aquí.

Resum

Han sigut moltes coses per assimilar, però espero que s'hagi après alguna cosa nova! Fins ara, hem parlat sobre l'ús del Contenidor de Servei (Service Container) amb zero configuració —que és una bona manera de començar a utilitzar algunes de les “màgies de Laravel” per fer la teva vida més fàcil. La majoria de vegades, no necessitaràs configurar res.

Però si la teva aplicació és més complexa (i ho serà en algun moment!) i desitges aprendre'n més, fes un cop d'ull al blog de l'autor original d'aquest article.

Comentaris