Boas-vindas! Sou Vinicius Dias, serei seu instrutor ao longo deste curso sobre PHP na web com foco em boas práticas e PSRs.
Vinicius Dias é uma pessoa de pele clara e olhos escuros, com cabelos pretos e curtos. Usa bigode e cavanhaque e veste uma camiseta azul escura, escrito "caelum — ensino e inovação". Há um microfone de lapela na gola de sua camiseta. Ele está sentado em uma cadeira preta e, ao fundo, há uma parede lisa com iluminação azul clara.
Neste curso, daremos continuidade ao desenvolvimento do AluraPlay, nosso projeto web de armazenamento de vídeos do Youtube, em que é possível fazer upload de arquivos, editá-los e removê-los do banco de dados.
O foco deste curso será o ecossistema PHP e as boas práticas. Aprenderemos sobre uma forma melhorar de utilizar views e templates nos nossos controllers, dado que a estrutura atual do nosso projeto ainda não é ideal.
Vamos extrair códigos e implementar a funcionalidade de flash messages, que permitem o uso de mensagens de erro que duram apenas uma requisição.
Como em PHP não é possível ter herança múltipla, exploraremos uma alternativa para cenários de reutilização de código em diversas classes: as traits e o conceito de herança horizontal, incluindo resolução de conflitos de nomes.
A partir desse ponto em que já estudamos pontos específicos da linguagem PHP, partiremos para o ecossistema PHP. Vamos aprender sobre as PSR — PHP Standards Recommendations, isto é, as recomendações padrões do PHP.
Entre essas recomendações, estudaremos interfaces, guia de estilo de código e detalhes específicos de HTTP. Neste último ponto, exploraremos interfaces de requisição e resposta, e como acoplar nossos controllers a elas, seguindo padrões de mercado, implementados por diversos frameworks.
Além disso, vamos estudar a PSR-11, que foca em contêineres de injeção de dependências. Dessa forma, nosso projeto ficará bem mais profissional!
Por fim, aprenderemos sobre templating. Ao consultar o site PHP: The Right Way, descobriremos a recomendação do uso de templating em nossos sistemas, então, aprenderemos a utilizar uma biblioteca de templating para deixar nosso da view mais profissional.
Portanto, nesse projeto, já teremos algo muito próximo do que seria uma aplicação em PHP em produção, pois:
Comparando nosso projeto desde os cursos iniciais até agora, notaremos um grande avanço na nossa aplicação!
Vale ressaltar que este curso não ensinará tudo sobre PHP. Após este curso, é importante que você continue estudando sobre boas práticas, testes e frameworks!
Como comentamos anteriormente, não adicionaremos muitas funcionalidades ao nosso projeto neste curso e focaremos mais em boas práticas. Então, começaremos implementando uma prática que nos ajudará a simplificar um trecho do nosso código.
No phpStorm, vamos acessar três arquivos de controllers:
LoginFormController.php
VideoListController.php
VideoFormController.php
Esses três controllers possuem um template HTML que é atualmente referenciado ao final do arquivo com o caminho completo. Em VideoFormController.php
, por exemplo, temos a seguinte linha:
require_once __DIR__ . '/../../views/video-form.php';
O template é apenas o arquivo ao final desse caminho (como video-form.php
) e o restante da linha é padronizado nos três controllers.
Além disso, há mais um ponto de atenção. Em VideoListController.php
, quando realizamos o require_once
do template video-list.php
, temos acesso a todas as variáveis do escopo em que ele está incluído. No caso, trata-se apenas do $videoList
, então não constitui um problema, mas haverá cenários em que mais informações ficarão à mostra.
Em VideoFormController.php
, por exemplo, teremos acesso às variáveis $id
, $video
e quaisquer outros elementos declarados nesse escopo. Isso não é necessariamente um problema, contudo acabamos "vazando" um pouco de informação.
Vale lembrar que, em video-form.php
, acabamos tendo acesso a todos os métodos (inclusive os privados) dos nossos controllers. Isso também não é necessariamente um problema, mas é interessante explorarmos como melhorar essa estrutura.
Vamos começar extraindo o código que realiza o require_once
desses arquivos para uma função específica. Considerando que estamos trabalhando com três controllers diferentes, temos algumas opções de ter apenas um método acessível para todos.
Uma delas seria ter uma classe de auxílio em que colocaríamos o método que renderiza o HTML e, nos controllers, os receberíamos por meio de injeção de dependências.
Dado que se trata de três classes que são controllers com algum HTML, outra opção é utilizar a herança! Então, vamos adotar essa abordagem, a seguir.
Na pasta "src > Controller", vamos criar uma classe chamada ControllerWithHtml.php
. Nela, desenvolveremos uma função chamada renderTemplate()
. Por ora, ela não retornará nada:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
class ControllerWithHtml
{
public function renderTemplate(string $templateName): void
{
}
}
Vamos tornar esse método protected
, assim todas as classes que estenderem ControllerWithHtml
terão acesso a esse método, porém o restante do código não terá:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
class ControllerWithHtml
{
protected function renderTemplate(string $templateName): void
{
}
}
Em seguida, vamos fazer o require_once
a partir do diretório atual, usando o caminho até a pasta "views", que corresponde ao trecho que se repete nos três controllers:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
class ControllerWithHtml
{
protected function renderTemplate(string $templateName): void
{
require_once __DIR__ . '/../../views/' . $templateName . '.php';
}
}
Note que optamos pela extensão .php
no final, mas podemos alterar esse trecho conforme nossa necessidade ou até mesmo passar o parâmetro já com a extensão.
Para melhorar ainda mais o código, podemos extrair o caminho dos templates para uma variável chamada $templatePath
:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
class ControllerWithHtml
{
protected function renderTemplate(string $templateName): void
{
$templatePath = __DIR__ . '/../../views/';
require_once $templatePath . $templateName . '.php';
}
}
Outra opção seria adicionar o caminho como uma constante privada da nossa classe, chamada TEMPLATE_PATH
, por exemplo. Como não se trata de uma constante global, será necessário acessá-la a partir da própria classe, utilizando a palavra-chave self
:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
class ControllerWithHtml
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName): void
{
require_once self::TEMPLATE_PATH . $templateName . '.php';
}
}
Com esse código simples, conseguiremos remover os trechos repetidos dos controllers.
Em LoginFormController.php
, vamos definir que a classe LoginFormController
estender ControllerWithHtml
:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
class LoginFormController extends ControllerWithHtml implements Controller
{
public function processaRequisicao(): void
{
if (array_key_exists('logado', $_SESSION) && $_SESSION['logado'] === true) {
header('Location: /');
return;
}
require_once __DIR__ . '/../../views/login-form.php';
}
}
E, em vez de fazer o require_once
com o caminho completo, chamaremos o método renderTemplate()
, passando o nome do template como parâmetro:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
class LoginFormController extends ControllerWithHtml implements Controller
{
public function processaRequisicao(): void
{
if (array_key_exists('logado', $_SESSION) && $_SESSION['logado'] === true) {
header('Location: /');
return;
}
$this->renderTemplate('login-form');
}
}
Para nos certificar de que nosso sistema está funcionando, vamos abrir a interface do AluraPlay no navegador e acessar a página de login. Ao fazer o login, notamos que a aplicação segue funcionando normalmente.
Em VideoListController.php
, definiremos que a classe VideoListController
estender ControllerWithHtml
e substituiremos o require_once
pela chamada do método renderTemplate()
:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
use Alura\Mvc\Repository\VideoRepository;
class VideoListController extends ControllerWithHtml implements Controller
{
public function __construct(private VideoRepository $videoRepository)
{
}
public function processaRequisicao(): void
{
$videoList = $this->videoRepository->all();
$this->renderTemplate('video-list');
}
}
O phpStorn mostrará um aviso de que a variável $videoList
não está mais sendo usada! Ao abrir a interface do AluraPlay na página de listagem, os vídeos não serão exibidos, pois não temos mais acesso à variável $videoList
!
Como comentamos, anteriormente o template tinha acesso a todas as variáveis do escopo. Contudo, agora o escopo é o método renderTemplate()
, que não tem nenhuma variável.
Como solução, vamos passar um segundo parâmetro para o método renderTemplate()
. Esse parâmetro será um array associativo com o contexto do template, ou seja, os recursos disponíveis:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
class ControllerWithHtml
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context): void
{
require_once self::TEMPLATE_PATH . $templateName . '.php';
}
}
Esse contexto terá um formato semelhante ao seguinte:
$context = [
'videoList' => [],
'title' => 'Título'
]
Nosso objetivo é extrair cada uma das chaves desse array associativo como se fossem variáveis. Seguindo o exemplo, o resultado seria uma variável chamada $videoList
com um array e outra variável chamada $title
cujo valor é uma string.
Existe uma função em PHP que seleciona um array associativo e extrai todas as suas chaves, tornando-as variáveis — a função extract()
. Portanto, vamos chamá-la e passar o contexto como parâmetro:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
class ControllerWithHtml
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context): void
{
extract($context);
require_once self::TEMPLATE_PATH . $templateName . '.php';
}
}
Após chamar a função extract()
, teremos acesso à variável $videoList
, desde que haja uma chave videoList
no contexto.
Caso você queira conhecer mais sobre a função
extract()
, você pode consultar a documentação oficial do PHP.
O segundo parâmetro será opcional, pois nem todo template precisará de uma variável, como é o caso da página de login. Portanto, vamos adicionar o valor padrão de array $context
para torná-lo opcional:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
class ControllerWithHtml
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context = []): void
{
extract($context);
require_once self::TEMPLATE_PATH . $templateName . '.php';
}
}
Dessa forma, não é preciso alterar nada em LoginFormController.php
, dado que o segundo parâmetro é opcional. Já em VideoListController.php
, passaremos o contexto na chamada de renderTemplate()
:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
use Alura\Mvc\Repository\VideoRepository;
class VideoListController extends ControllerWithHtml implements Controller
{
public function __construct(private VideoRepository $videoRepository)
{
}
public function processaRequisicao(): void
{
$videoList = $this->videoRepository->all();
$this->renderTemplate(
'video-list',
['videoList' => $videoList]
);
}
}
Após salvar as alterações, vamos atualizar a página de listagem do AluraPlay no navegador e os vídeos voltarão a ser exibidos, pois agora temos acesso às variáveis.
Na sequência, clicaremos no link de edição de um dos vídeos. Vamos adaptar o controller VideoFormController.php
para usar o método renderTemplate()
também.
A classe VideoFormController
estenderá ControllerWithHtml
e, em vez de require_once
, chamaremos o renderTemplate()
, passando o $video
como contexto:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
use Alura\Mvc\Entity\Video;
use Alura\Mvc\Repository\VideoRepository;
class VideoFormController extends ControllerWithHtml implements Controller
{
public function __construct(private VideoRepository $repository)
{
}
public function processaRequisicao(): void
{
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
/** @var ?Video $video */
$video = null;
if ($id !== false && $id !== null) {
$video = $this->repository->find($id);
}
$this->renderTemplate('video-form', [
'video' => $video,
]);
}
}
Ao atualizar o formulário no AluraPlay, a página continuará funcionando normalmente.
Para nos certificar de que essa view não precisa de nenhuma outra variável, podemos abrir o arquivo video-form.php
e checar se há menções a qualquer outra variável. Vamos reparar que a única variável utilizada é $video
.
Por fim, vamos atentar a um último detalhe. Atualmente, é possível instanciar uma nova classe ControllerWithHtml
usando a sintaxe new ControllerWithHtml()
. No entanto, essa classe não é realmente um controller — por exemplo, ela não possui o método processaRequisicao()
—, então não deveria ser instanciável.
Para impedir a instanciação, vamos torná-la abstrata. Basta utilizar a palavra-chave abstract
na definição da classe:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
abstract class ControllerWithHtml
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context = []): void
{
extract($context);
require_once self::TEMPLATE_PATH . $templateName . '.php';
}
}
Além disso, definiremos que ControllerWithHtml
implementa Controller
:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
abstract class ControllerWithHtml implements Controller
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context = []): void
{
extract($context);
require_once self::TEMPLATE_PATH . $templateName . '.php';
}
}
Dessa forma, todos os elementos que estenderem a classe ControllerWithHtml
terão a obrigatoriedade de implementar o método processaRequisicao()
da interface. Ou seja, é como se processaRequisicao()
se tornasse um método abstrato em ControllerWithHtml
também.
Em resumo, quando uma classe abstrata implementa uma interface mas não implementa seus métodos, é como se eles fossem métodos abstratos nessa classe. Consequentemente, todos os elementos que herdarem dessa classe abstrata precisarão implementar o método da interface.
Fizemos várias mudanças no nosso código com base em conceitos que estudamos anteriormente de orientação a objetos com PHP.
Na sequência, vamos levantar um novo questionamento: será possível retornar a string que possui o HTML do template, em vez de exibi-lo? No próximo vídeo, vamos aprender um novo conceito de PHP para responder essa questão.
No último vídeo, extraímos uma classe abstrata chamada ControllerWithHtml
para representar os controllers que possuem algum template de um HTML.
Nessa classe, temos o método renderTemplate()
bem como o caminho para os templates, de modo que é mais necessário repeti-lo em vários arquivos. Sendo assim, nos controllers que utilizam templates, basta chamar o método renderTemplate()
.
Uma das vantagens dessa abordagem é que nosso código fica mais limpo. Além disso, ela é mais próxima do que os frameworks fazem, como Laravel e Symphony.
Um detalhe comum nesses frameworks é que métodos como renderTemplate()
ou render()
não exibem o conteúdo diretamente. Em vez disso, eles retornam o conteúdo.
Similarmente, em nosso código, poderíamos fazer um echo
do conteúdo. Por exemplo, no arquivo VideoListController.php
:
// ...
public function processaRequisicao(): void
{
$videoList = $this->videoRepository->all();
echo $this->renderTemplate(
'video-list',
['videoList' => $videoList]
);
}
}
Outra opção seria armazenar o conteúdo em uma variável e fazer algum processamento. Por exemplo, comprimir o HTML, remover palavras específicas ou outro tipo de manipulação do conteúdo.
Portanto, nosso próximo objetivo será alterar o método renderTemplate()
para que ele retorne o conteúdo e possamos exibi-lo.
De início, vamos adicionar o echo
em todos os lugares que temos a chamada do renderTemplate()
— isto é, nos arquivos VideoListController.php
, VideoFormController.php
e LoginFormController.php
. Por enquanto, o echo
não exibirá nada, pois o renderTemplate()
ainda não retorna nada.
renderTemplate()
Em seguida, vamos abrir o arquivo ControllerWithHtml.php
. Atualmente, na última linha do método renderTemplate()
, realizamos o require_once
de arquivos HTML, então sabemos que estamos exibindo algo.
Antes do require_once
, vamos informar ao PHP que ele deve inicializar um buffer de saída. Ou seja, um local em que armazenaremos tudo que seria exibido na tela. Depois do require_once
, vamos recuperar o conteúdo do buffer e, para poupar memória do sistema, limparemos o buffer.
Em inglês, o nome do buffer de saída é output buffer. No PHP, temos algumas funções que se iniciam com ob
, de output buffer.
Para inicializar o buffer, utilizaremos o método ob_start()
:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
abstract class ControllerWithHtml implements Controller
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context = []): void
{
extract($context);
ob_start();
require_once self::TEMPLATE_PATH . $templateName . '.php';
}
}
Tudo que vier após a linha em que invocamos ob_start()
e que exibiria algum conteúdo — seja um echo
, um HTML ou um erro —, será armazenado no buffer, em um lugar reservado da memória.
Depois, para recuperar o conteúdo do buffer, usaremos o método ob_get_contents()
. Em nosso cenário, o conteúdo é um HTML, então vamos armazená-lo em uma variável chamada $html
:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
abstract class ControllerWithHtml implements Controller
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context = []): void
{
extract($context);
ob_start();
require_once self::TEMPLATE_PATH . $templateName . '.php';
$html = ob_get_contents();
}
}
Para limpar o buffer, chamaremos o método ob_clean()
:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
abstract class ControllerWithHtml implements Controller
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context = []): void
{
extract($context);
ob_start();
require_once self::TEMPLATE_PATH . $templateName . '.php';
$html = ob_get_contents();
ob_clean();
}
}
Podemos juntar as chamadas de ob_get_contents()
e ob_clean()
com a função ob_get_clean()
! Ela é responsável por recuperar o conteúdo e já limpar o buffer:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
abstract class ControllerWithHtml implements Controller
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context = []): void
{
extract($context);
ob_start();
require_once self::TEMPLATE_PATH . $templateName . '.php';
$html = ob_get_clean();
}
}
Por fim, o retorno da função será o $html
:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
abstract class ControllerWithHtml implements Controller
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context = []): void
{
extract($context);
ob_start();
require_once self::TEMPLATE_PATH . $templateName . '.php';
$html = ob_get_clean();
return $html;
}
}
Para deixar nosso código mais sucinto, em vez de declarar a variável $html
e depois retorná-la, vamos simplesmente retornar o resultado de ob_get_clean()
:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
abstract class ControllerWithHtml implements Controller
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context = []): void
{
extract($context);
ob_start();
require_once self::TEMPLATE_PATH . $templateName . '.php';
return ob_get_clean();
}
}
O phpStorm exibirá um erro no retorno, porque anteriormente definimos que o método renderTemplate()
não retorna nada. Como solução, substituiremos o termo void
por string
na assinatura do método:
<?php
declare(strict_types=1);
namespace Alura\Mvc\Controller;
abstract class ControllerWithHtml implements Controller
{
private const TEMPLATE_PATH = __DIR__ . '/../../views/';
protected function renderTemplate(string $templateName, array $context = []): string
{
extract($context);
ob_start();
require_once self::TEMPLATE_PATH . $templateName . '.php';
return ob_get_clean();
}
}
Em resumo, estamos inicializando um buffer de saída, exibindo dados que serão armazenados no buffer e recuperando o conteúdo. Em vez de exibi-lo diretamente, nós o retornamos. Em outras palavras, o método renderTemplate()
não exibe mais nada, ele apenas retorna o conteúdo.
Como teste, vamos abrir o navegador e acessar a página do formulário de edição de vídeos no AluraPlay. Em seguida, no arquivo VideoFormController.php
, vamos remover o echo
que inserimos há pouco. Ao atualizar a página no navegador, nada será exibido! Isso ocorre porque armazenamos o conteúdo no buffer de saída, mas não o exibimos. Inserindo o echo
novamente, a página volta a aparecer. O mesmo vale para a página de listagem e para a página de login.
Dessa maneira, podemos manipular esse conteúdo. Outra opção mais comum é, em vez de exibir o conteúdo com echo
, retornar algum tipo de resposta (response) com vários cabeçalhos (usando a função header()
) ou com conteúdo.
Assim, em index.php
, em vez de simplesmente chamar o método processaRequisicao()
ao final do arquivo, pegaríamos essa resposta e faríamos um echo $response->body()
, por exemplo.
Contudo, essa abordagem também tem desvantagens. Por padrão, o buffer de saída sempre existe e o próprio PHP exibe seu conteúdo automaticamente, quando julga necessário. Quando chamamos a função ob_start()
, estamos sobrescrevendo esse buffer e controlando o fluxo do buffer de saída.
Sem usar o ob_start()
, tínhamos a opção de usar o método flush()
para exibir o buffer padrão. Por exemplo, no arquivo inicio-html.php
, poderíamos inserir a função flush()
após o fechamendo da tag <head>
:
// CÓDIGO DE EXEMPLO
<!DOCTYPE html>
<html lang="pt-br">
<head>
// ...
</head>
<?php flush(); ?>
// ...
Dessa forma, enviaríamos apenas o trecho de entre <!DOCTYPE html>
e </head>
ao navegador e continuaríamos processando o resto.
Antes, o uso do método flush()
era uma opção. Agora que estamos usando um buffer de saída com ob_start()
, isso não é mais possível — e essa é uma desvantagem.
Não se preocupe se você não tiver compreendido as minúcias do buffer de saída e do método flush()
. Não é comum trabalhar manualmente com buffer, normalmente usa-se frameworks para lidar com essas situações. Ainda ssim, é extremamente importante saber como esses processos funcionam, por isso reservamos esse vídeo para comentar sobre ele. Você pode consultar a documentação oficial do PHP e pesquisar mais sobre o tema.
Agora, já temos nosso ControllerWithHtml
lidando com o buffer de saída e retornando o conteúdo. E nossas views, caso queiram, podem exibir esse conteúdo.
Na sequência, vamos adicionar uma funcionalidade no projeto. Na interface do AluraPlay, na página de login, vamos tentar acessar o sistema com as credenciais incorretas:
- E-mail: teste@email.com
- Senha: 123
Seremos redirecionados para a mesma página e, na URL, temos o parâmetro sucesso=0
. Em vez de usar um parâmetro na URL, vamos exibir uma mensagem de erro para o usuário.
Nossa meta é mostrar essa mensagem sem alterar a URL, porque atualmente podemos simplesmente copiar essa URL com o parâmetro sucesso=0
e acessá-la a qualquer momento no navegador e receber uma mensagem de erro, o que não é interessante.
No próximo vídeo, aprenderemos como adicionar mensagens.
O curso PHP na Web: aplicando boas práticas e PSRs possui 121 minutos de vídeos, em um total de 45 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:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.