Laravel em profundo 01: “WTF is a Service Provider?”

Vinicius Reis
by Vinicius Reis
Published in
7 min readMar 25, 2016

Laravel é o framework PHP que mais cresceu nos últimos anos.Porém, há algumas críticas a seu respeito. Muitas delas sem embasamento, como por exemplo, às facades.

Outra critica recorrente é a alegação de que o Laravel não é modular. Essa alegação se deve a não ter uma estrutura de “módulos” pré-definidos como Zend Framework e Symfony Framework.
Mas essa alegação não leva em conta o suporte completo a namespaces usados em conjunto com PSR-0/PSR-4 e Composer.

O entendimento do que são e de como usar services providers é essencial para o desenvolvimento modular no Laravel e o desenvolvimento de pacotes.

Este artigo é uma resposta a solicitação feita no artesaos/content-request. Mande suas solicitações também, ou responda a uma.

Qual o conceito de um “Provedor de serviço”?

Em um tradução direta um Service provider é um Provedor de Serviço, e se você analisar essas palavras:
Provedor: O que provê algo. Serviço: Ato ou efeito de servir.
Provedor de serviço: O que provê um serviço.

Então para entender um service provider, temos que entender (pelo menos um pouco) o que é um Service ou Serviço.

Serviços

Sem entrar em muitos detalhes e de modo direto, serviço é a camada de encapsulamento de regras de negócio de determinada operação ou sistema. Ex: em um projeto é necessário criar uma integração com a API de um banco, que emitirá os boletos.

Sem uma camada de serviços

Todas as classes e operações já estão criadas e funcionando (criar_boleto, verificar_boleto e cancelar_boleto). Em MVC “clássico” essas ações estariam em controllers.

Porém o projeto cresce e agora é necessário mudar de banco, e este banco possui uma API diferente, que apesar de receber os mesmos dados possui nomes e padrões diferentes para interagir com os boletos. Isso significa refatorar todas as ocorrências dessa interação, e um simples find replace não resolve o problema.

Com uma camada de serviços

No lugar de colocar a “integração” com a API de boletos no controller, a integração é feita em uma classe (BoletoService) que tem apenas a responsabilidade de interagir com essa API, e para deixar a classe preparada para diversos ambientes ela deve ser construída com a API_KEY de integração.

Como você pode ver, usar o serviço é algo simples, e você vai poder definir quais as assinaturas os métodos desse serviço vão precisar, sempre tendo em mente e como ter o minimo de código/regras de negócio no controller.

Há casos que você pode criar um serviço para cada ação (criar, atualizar, checar, cancelar) fica a seu critério ver qual a melhor para o projeto. Recomendo que sempre leve em consideração o S de SOLID ao criar um serviço, estará trazendo ainda mais qualidade ao seu projeto.

Laravel + Serviços = Service Container

Outro ponto importante para compreender os Services Providers do Laravel é o Service Container.

Nesse momento é MUITO importante ter um conhecimento base sobre SOLID.

Segundo o SOLID, uma classe só pode ter uma responsabilidade. Ainda pensando no nosso exemplo de uma API (REST) para gerar boletos, nosso serviço precisa fazer chamadas HTTP para se comunicar com essa API, isso significa usar curl ou alguma biblioteca como o Guzzle. Essa API precisa de parâmetros de autenticação, seja eles quais forem, nosso serviço de boletos precisará saber quais são e como trabalhar com ele a cada interação com a API.

Isso quebra o S de SOLID, já que a classe passou a fazer muitas coisas, isso ficaria muito pior se optássemos por múltiplas classes (uma para cada ação). Cada classe teria que saber como se comunicar com a API. Precisamos tirar dela essa responsabilidade do nosso serviço, criando outro serviço Services/Boletos/Client.php.

Client passa a ser o responsável por instanciar e enviar comandos para a API, os outros serviços agora só lidam com as regras de negócio, esse serviço será utilizado por todos os outros serviços. Um serviço feito para servir outros serviços.

A primeira coisa que observamos é o uso de namespaces para nossas classes de serviço, uma característica do PHP 5.3+ o que já torna no nosso código modular (dependendo do uso) principalmente quando usado de acordo com as PSRs de autoloading e em conjunto com composer.

Nosso serviço ficou exatamente como o previsto, apenas com a responsabilidade de criar um boleto, porém agora ele é dependente da classe Client que depende de $API_KEY.
Mas, acabamos ficando com um inconveniente. Quando precisarmos criar um boleto, precisarmos saber quais as dependências cada classe tem, e não apenas a que interessa a ele: Create

Nosso objetivo é poder mudar a API de boletos com o menor impacto possível no sistema, se seguirmos a linha “manual” será preciso mudar as ocorrências dessas classes, e até mesmo a maneira como elas são instanciadas, já que elas podem ter processos diferentes de comunicação.

Nesse momento que entra o conceito de service container em particular do Laravel.

IoC + DI = Service Container

Service Container é composto de dois conceitos: IoC (Inversion of Control) e DI (Dependency Injection). Eles se misturam muito, e nem cabe detalhar eles, mas é importante você entender quando e onde eles estão agindo.

Imagine o Service Container como uma “classe que sabe como criar outras classes” a partir de “receitas” que fornecemos a ele. Essas receitas são os binds.

Um bind é composto de duas partes, a abstract e concrete. Abstrata será a chave da resolução, basicamente quando o container receber um “pedido de construção/resolução” ele vai consultar sua lista de resoluções (abstract) e devolver a solução (concrete) do que foi solicitado.

Agora as coisas ficaram mais simples, mas reparem em uma diferença entre os dois binds. Nos dois o abstract é o contrato (interface) porém o ClientContract recebeu como concrete uma função anônima, que cria e retorna um objeto Client. Isso aconteceu por que a classe Client precisa receber um parâmetro e o container não sabe como “montar” esse parâmetro.

Você pode imaginar que o mesmo deveria ocorrer com Create, já que ele precisa de um objeto Client para ser criado. Nesse momento que o Service Container mostra seu poder e praticidade. Ao pedir para criar algo, ele vai procurar em sua “lista de receitas” como criar aquilo que foi solicitado. No nosso caso estamos pedindo para ele criar algo a partir de um contrato (interface) que previamente definimos que ele deveria retornar um objeto da classe Create.
Nesse momento ele vai analisar as dependências de criação da classe Create, que por sua vez é uma interface de Client, ele recursivamente cria um objeto Client e passa para o construtor da classe.

Se algo que não está na lista for solicitado ele tentará criar algo diretamente pelo que foi passado, sendo assim podemos instanciar classes (não interfaces) sem dizer qual é a receita, e ele recursivamente vai “construindo” as dependências.

Há dois tipos de binds, o comum e o singleton (shared). O comum retornará um nova instância/objeto sempre que for solicitada sua criação, já o singleton, como o próprio nome indica, retornará apenas uma vez a “resolução” . Ou seja, sempre a mesma instancia que foi “resolvida” epenas uma vez. Isso é útil para compartilhar estados ou diminuir a carga de determinadas tarefas.

quado declaramos uma classe como dependência de um controller é assim que o Laravel nos entrega os objetos “prontos”.

Service Provider

Finalmente entramos no “assunto” do artigo. Infelizmente essa introdução é necessária para entender qual o trabalho de um service provider.

O papel de um service provider é interagir com o service container, definindo “receitas” de como nossos serviços (classes) serão revolvidas. Além de interagir com o Laravel e outros serviços, até mesmo pacotes de terceiros.

Em um projeto Laravel temos o arquivo config/app.php e nele temos uma lista de providers que estarão disponíveis na nossa aplicação.
Os primeiros são providers do próprio framework, eles são responsáveis por preparar serviços como auth, view, mail e database.

O service provider possui duas etapas register e boot.
A diferença entre eles é bem simples e lógica: register é onde nossos serviços são registrados, apenas binds devem ocorrer nessa etapa e em boot é onde podemos interagir com os serviços.
O motivo é bem simples, não há como garantir que um serviço já esteja disponível, pois pode haver uma race condition na criação dos diversos serviços, por isso o processo de bootstrap do Laravel é dividido nessas duas etapas.

Há também a opção defer (deferred/adiado). Com ela um service provider não é invocado no bootstrap da aplicação e sim quando ele se torna “necessário”. Para isso você cria um método público chamado provides que retorna um array listando todas as “assinaturas” atreladas a esse service provider. Você pode ver um exemplo no pacote artesaos/seotools.
Essa é uma opção importante que pode otimizar sua aplicação.

Apesar do tamanho do artigo ainda há muito sobre o Service Container, e como usar services providers para modularizar sua aplicação. Sobre a modularização, recomendo a série de videos que fiz sobre o assunto e sobre Service Container, se o feedback desse formato de artigo for bom esperem mais em um futuro próximo.

Meus agradecimentos ao Igor Santos pela ajuda na revisão!

Se quiser saber mais sobre meu trabalho visite codecasts.com.br. Lá você vai ver vídeos sobre Javascript, jQuery, Gulp, ES6, Vue.JS, Docker e muito mais.

That’s all, folks!

--

--

Vinicius Reis
by Vinicius Reis

Fiquei sem meus peões, meu cavalo, minha torre, meu bispo… E até a rainha… Mas ainda é muito cedo para um xeque-mate. Roy Mustang — Fullmetal Alchemist