Primeiras aulas do curso Symfony Parte 2: Autenticação e HATEOAS

Symfony Parte 2: Autenticação e HATEOAS

Definindo um sub-recurso - Introdução

Olá pessoal, tudo bem? Meu nome é Vinicius Dias e essa é a segunda parte do treinamento Criação de uma API Rest com Symfony. Terminamos a primeira parte deste curso com uma API totalmente funcional de um consultório médico, tendo um cadastro completo de médicos e especialidades. Porém, ainda faltam muitas funcionalidades nessa API, que implementaremos nesse curso:

Preparado(a) para mais essa jornada de aprendizado com o Symfony? Bons estudos!

Definindo um sub-recurso - Preparando o ambiente

Nesse vídeo iremos repassar o ambiente que precisamos ter configurado para continuar esse treinamento. Se você acabou de terminar a primeira parte, pode pular essa etapa, pois as configurações já estão prontas. Se estiver começando a partir deste treinamento ou já tem algum tempo que fez o primeiro, sem problemas, basta seguir as instruções a seguir.

Após baixar a versão final do projeto do treinamento passado, teremos que extrair os arquivos em uma pasta qualquer. Com o PHP (pelo menos na versão 7.3) e o Composer (o gerenciador de dependências) instalados, abriremos o terminal, entraremos na pasta do projeto e executaremos composer install. Esse comando lerá o arquivo composer.json do projeto e baixará todas as dependências necessárias para continuarmos trabalhando, como o Symfony e todos os componentes que ele precisa.

Também será necessário ter o MySQL Server configurado na sua máquina, algo que é ensinado na primeira parte do treinamento. Se você já tiver o banco de dados configurado, tudo feito. Caso esteja modificado ou excluído aquele banco, ou esteja começando desse ponto, será necessário rodar as migrations, que são os arquivos que executam as modificações necessárias no banco de dados.

Nesse caso, no terminal, rodaremos php bin\console doctrine:migrations:migrate. Esse comando lerá os arquivos de migrations crioados no primeiro curso e as executará. Receberemos uma mensagem perguntando se queremos continuar, digitaremos "Y" e então "Enter" para continuar. Receberemos outro aviso, desta vez de que as migrations podem executar modificações no banco e causar perda de dados. Novamente confirmaremos com Y" as migrations finalmente serão executadas. Se você receber a mensagem "No migrations to execute", seu banco já está configurado. Do contrário, o terminal exibirá todos os comandos que foram executados.

Com o projeto configurado e executando corretamente, rodaremos o servidor de teste com php -S localhost:8080 -t public para verificarmos se o código continua funcionando. Com o servidor no ar, abriremos o Postman, o programa que temos utilizado para fazer os testes de requisição, e acessaremos http://localhost:8080/medicos. Se você já tinha o banco configurado e já tem médicos cadastrados, terá como saída uma lista destes médicos. Do contrário, receberá uma lista vazia, o que não trará problemas.

Agora que configuramos o ambiente, daremos início ao treinamento. Bons estudos!

Definindo um sub-recurso - Sub-recursos

Na nossa API, com base em um ID, conseguimos buscar todos os médicos ou um médico específico, assim como conseguimos buscar todas as especialidades ou uma especialidade específica. Mas e se quisermos buscar todos os médicos de uma especialidade específica? Esse relacionamento existe e já está mapeamento, e seria uma consulta comum no dia-a-dia de um consultório.

Como trabalhamos no primeiro curso, começaremos pensando como seria a URL para essa consulta. Criaremos então uma nova aba no Postman na qual teremos uma requisição do tipo GET para http://localhost:8080/especialidades/1/medicos. Assim, teremos uma busca pela especialidade e então por todos os médicos que pertencem a ela. Visualmente, a estrutura dessa URL parece correta, não é? Então vamos implementá-la.

No final de MedicosController.php, criaremos um novo método buscaPorEspecialidade() e mapearemos essa rota com a annotation @Route. Nela, passaremos a URL /especialidades/{especialidadeId}/medicos, na qual {especialidadeId} representa um parâmetro a ser passado pela rota (assim como aprendemos no curso anterior). É possível incluirmos também um filtro que limitará o acesso a essa rota para determinado método - no caso, o GET, afinal estamos buscando os médicos de uma especialidade.

Como já sabemos, se definimos um parâmetro na rota, poderemos acessá-lo no método, desde que ele tenha o mesmo nome. Sendo assim, passaremos o inteiro especialidadeId no nosso método buscaPorespecialidade(). Além disso, um método do Controller que será acessado por uma rota sempre devolve uma resposta, e indicaremos isso com Response.

/**
 * @Route("/especialidades/{especialidadeId}/medicos", methods={"GET"})
 */
public function buscaPorEspecialidade(int $especialidadeId): Response
{

}

Como queremos buscar médicos, precissremos do $repositorioDeMedicos, que por enquanto copiaremos do código buscaMedico. A partir do repositório, queremos realizar uma busca com base em um critério. Se quiséssemos bsucar por todos os médicos, faríamos $repositorioDeMedicos->findAll(), e então retornaríamos uma new JsonResponse() passando o conteúdo $medicos.

/**
 * @Route("/especialidades/{especialidadeId}/medicos", methods={"GET"})
 */
public function buscaPorEspecialidade(int $especialidadeId)
{
    $repositorioDeMedicos = $this
        ->getDoctrine()
        ->getRepository(Medico::class);
    $medicos = $repositorioDeMedicos->findAll();

    return new JsonResponse($medicos);
}

Se acessarmos a URL http://localhost:8080/especialidades/1/medicos no Postman, os médicos terão sido buscados com sucesso. Entretanto, queremos apenas os médicos que tenham a especialidade de id 1 - no caso, o "Primeiro Médico". Como podemos implementar um filtro utilizando um repositório?

Ao invés de chamarmos o método findAll(), temos acesso ao findBy(), que recebe por parâmetro um critério de busca. Passaremos esse critério como um array associativo contendo a chave especialidade e o valor será $especialidadeId, ou seja, o parâmetro recebido na URL.

public function buscaPorEspecialidade(int $especialidadeId)
{
    $repositorioDeMedicos = $this
        ->getDoctrine()
        ->getRepository(Medico::class);
    $medicos = $repositorioDeMedicos->findBy([
        'especialidade' => $especialidadeId
    ]);

Dessa forma teremos nosso filtro implementado, e isso abstraindo a responsabilidade de trabalharmos com SQL, já que o Doctrine, nosso ORM, cuida do processo. Se acessarmos http://localhost:8080/especialidades/1/medicos, teremos como retorno somente os médicos da especialidade 1. Da mesma forma, se usarmos http://localhost:8080/especialidades/2/medicos, teremos somente os médicos da especialidade 2. Para termos ainda mais certeza de que nosso código está funcionando, vamos adicionar um novo médico com uma requisição POST para http://localhost:8080/medicos:

{
    "crm": 7563389,
    "nome": "Terceiro Médico",
    "especialidadeId": 1
}

Agora, se buscarmos novamente os médicos com a especialidade 1, receberemos tanto o "Primeiro Médico" quanto o "Terceiro Médico". Antes de prosseguirmos para o próximo passo, repare que temos um código repetido no nosso MedicosController.php, no qual buscamos pelo $repositorioDeMedicos:

/**
 * @param int $id
 * @return object|null
 */
public function buscaMedico(int $id)
{
    $repositorioDeMedicos = $this
        ->getDoctrine()
        ->getRepository(Medico::class);
    $medico = $repositorioDeMedicos->find($id);

    return $medico;
}

/**
 * @Route("/especialidades/{especialidadeId}/medicos", methods={"GET"})
 */
public function buscaPorEspecialidade(int $especialidadeId)
{
    $repositorioDeMedicos = $this
        ->getDoctrine()
        ->getRepository(Medico::class);
    $medicos = $repositorioDeMedicos->findBy([
        'especialidade' => $especialidadeId
    ]);

    return new JsonResponse($medicos);
}

Porém, em EspecialidadesController.php, nós recebemos esse repositório de especialidades por injeção de dependência. Por que não podemos fazer isso também com o $repositórioDeMedicos? Como criamos a entidade Medico.php manualmente, não temos um MedicoRepository.php, enquanto o EspecialidadeRepository.php foi gerado quando criamos a entidade Especialidade. Sendo assim, vamos criar esse repositório manualmente. Antes disso, vamos analisar o código do repositório de especialidades:

<?php

namespace App\Repository;

use App\Entity\Especialidade;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;

/**
 * @method Especialidade|null find($id, $lockMode = null, $lockVersion = null)
 * @method Especialidade|null findOneBy(array $criteria, array $orderBy = null)
 * @method Especialidade[]    findAll()
 * @method Especialidade[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class EspecialidadeRepository extends ServiceEntityRepository
{
    public function __construct(RegistryInterface $registry)
    {
        parent::__construct($registry, Especialidade::class);
    }

    // /**
    //  * @return Especialidade[] Returns an array of Especialidade objects
    //  */
    /*
    public function findByExampleField($value)
    {
        return $this->createQueryBuilder('e')
            ->andWhere('e.exampleField = :val')
            ->setParameter('val', $value)
            ->orderBy('e.id', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult()
        ;
    }
    */

    /*
    public function findOneBySomeField($value): ?Especialidade
    {
        return $this->createQueryBuilder('e')
            ->andWhere('e.exampleField = :val')
            ->setParameter('val', $value)
            ->getQuery()
            ->getOneOrNullResult()
        ;
    }
    */
}

Perceberemos que a classe EspecialidadeRepository estende da ServiceEntityRepository, uma classe da integração do Doctrnie com o Symfony e que vem do pacote Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository. Essa classe não é padrão do Doctrine, e, sendo assim, projetos que não utilizam o Symfony não precisarão dessa herança.

Temos então um construtor que recebe o RegistryInterface, um registro de entidades que faz o mapeamento dos objetos para as tabelas. Ele chama o construtor da classe pai passando como segundo parâmetro a entidade que esse repositório controla.

Agora que temos uma noção do que teremos que fazer, criaremos uma classe MedicosRepository que estenderá ServiceEntityRepository. Então, criaremos o construtor. Repare que, no PhpStorm, se digitarmos __cons e pressionarmos "Tab", o construtor baseado na classe pai será criado automaticamente. Como já sabemos a classe de entidade que esse repositório controlará, alteraremos $entityClass para Medico::class e removeremos o parâmetro string $entityClass do construtor.

class MedicoRepository extends ServiceEntityRepository
{
   public function __construct(ManagerRegistry $registry)
   {
       parent::__construct($registry, Medico::class);
   }

}

Repare que o primeiro parâmetro está diferente: em EspecialidadeRepository nós temos uma interface (RegistryInterface), e em MedicoRepository temos uma implementação concreta (ManagerRegistry). Já que o Symfony consegue resolver o parâmetro baseado na interface, vamos utilizá-la:

class MedicoRepository extends ServiceEntityRepository
{
   public function __construct(RegistryInterface $registry)
   {
       parent::__construct($registry, Medico::class);
   }

}

Feito isso, nosso repositório teoricamente funcionará. Antes de prosseguirmos, vamos analisar a entidade Especialidade. Quando a no primeiro curso, além da anotação @ORM\Entity, ela possui um parâmetro que informa qual a classe que gerencia o repositório dessa entidade.

/**
 * @ORM\Entity(repositoryClass="App\Repository\EspecialidadeRepository")
 */
class Especialidade implements \JsonSerializable
{
//...
}

Sendo assim, voltaremos à nossa entidade Medico e, dentro da anotação @ORM\Entity, informaremos que a repositoryClass é a nossa EspecialidadeRepository.

/**
 * @ORM\Entity(repositoryClass="App\Repository\MedicoRepository")
 */
class Medico implements \JsonSerializable
{
//...
}

Agora que temos um repositório de médicos criado e mapeamos a entidade Medico para essa classe, podemos, em MedicosController, utilizar a injeção de dependência no nosso construtor para pedir um $medicosRepository, e inicializaremos um campo na classe com esse valor.

    /**
     * @var MedicosRepository
     */
    private $medicosRepository;

    public function __construct(
        EntityManagerInterface $entityManager,
        MedicoFactory $medicoFactory,
        MedicosRepository $medicosRepository
    ) {
        $this->entityManager = $entityManager;
        $this->medicoFactory = $medicoFactory;
        $this->medicosRepository = $medicosRepository;
    }

Agora, sempre que precisarmos de um repositório, poderemos utilizar $this->medicosRepository diretamente da classe.

public function buscarTodos(): Response
{

    $medicoList = $this->medicosRepository->findAll();

    return new JsonResponse($medicoList);
}
public function buscaMedico(int $id)
{

        $medico = $this->medicosRepository->find($id);

        return $medico;
}
public function buscaPorEspecialidade(int $especialidadeId)
{
    $medicos = $this->medicosRepository->findBy([
        'especialidade' => $especialidadeId
    ]);

    return new JsonResponse($medicos);
}

Assim, criamos uma classe de repositório nossa, na qual poderíamos, inclusive, definir métodos de busca específicos. Por enquanto não faremos isso, mas já conseguimos incluir uma injeção de dependência para recebermos o repositório no nosso controller, e não temos mais a necessidade de chamar o método da classe pai. Se acessarmos alguma URL de busca da nossa aplicação, como http://localhost:8080/especialidades/1/medicos, tudo continuará funcionando corretamente.

Nessa aula já conseguimos melhorar o nosso código e implementar uma funcionalidade de busca na qual buscamos um médico a partir de uma especialidade. Como todo médico possui uma especialidade, pelo menos no nosso domínio, podemos afirmar, de acordo com os padrões de REST, que "médico" é um subrecurso de "especialidade". Mapeando a URL da forma que fizemos, deixamos isso bem claro.

Na próxima aula continuaremos a melhorar o nosso código.

Sobre o curso Symfony Parte 2: Autenticação e HATEOAS

O curso Symfony Parte 2: Autenticação e HATEOAS possui 180 minutos de vídeos, em um total de 63 atividades. Gostou? Conheça nossos outros cursos de PHP em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda PHP acessando integralmente esse e outros cursos, comece hoje!

  • 1112 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

Premium

  • 1112 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$75
à vista R$900
Matricule-se

Premium Plus

  • 1112 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$100
à vista R$1.200
Matricule-se

Max

  • 1112 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$120
à vista R$1.440
Matricule-se
Procurando planos para empresas?
Acesso por 1 ano
Estude 24h/dia onde e quando quiser
Novos cursos toda semana