Dia 1
Instalação
Antes de instalar o Laravel no Debian, é necessário garantir que todas as dependências estejam instaladas. O Laravel depende do PHP e de algumas extensões, além de um banco de dados como MariaDB ou Sqlite. Aqui estão os principais pacotes que devem ser instalados no Debian:
sudo apt-get install php php-common php-cli php-gd php-curl php-xml php-mbstring php-zip php-sybase php-mysql php-sqlite3
sudo apt-get install mariadb-server sqlite3 git
O Composer é um gerenciador de dependências para PHP. Ele permite instalar, atualizar e gerenciar bibliotecas e pacotes de forma simples, garantindo que um projeto tenha todas as dependências necessárias. No Laravel, o Composer é usado para instalar o framework e suas bibliotecas.
curl -s https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
Além disso, é importante configurar o banco de dados, pois ele será usado para instalar o Laravel. Vamos inicialmente criar um usuário admin com senha admin e criar um banco de dados chamado treinamento:
sudo mariadb
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' IDENTIFIED BY 'admin' WITH GRANT OPTION;
create database treinamento;
quit
O comando a seguir cria um novo projeto Laravel na pasta treinamento, baixando a estrutura básica do framework e instalando todas as dependências necessárias via Composer, garantindo que o ambiente esteja pronto para o desenvolvimento:
composer create-project laravel/laravel treinamento
cd treinamento
php artisan serve
MVC
Uma rota é a forma como o framework define e gerencia URLs para acessar diferentes partes da aplicação. As rotas são configuradas no arquivo routes/web.php (para páginas web) ou routes/api.php (para APIs) e determinam qual código será executado quando um usuário acessa uma URL específica. Exemplo:
Route::get('/exemplo-de-rota', function () {
echo "Uma rota sem controller, not good!";
});
O controller é uma classe responsável por organizar a lógica da aplicação, separando as regras de negócio das rotas. Em vez de definir toda a lógica diretamente nas rotas, os controllers agrupam funcionalidades relacionadas, tornando o código mais limpo e modular. A convenção de nomenclatura para controllers segue o padrão PascalCase, onde o nome deve ser descritivo, no singular e sempre terminar com “Controller”, como ProdutoController
ou UsuarioController
. Vamos criar o LivroController com o seguinte comando que gera automaticamente o arquivo correspondente dentro de app/Http/Controllers
:
php artisan make:controller LivroController
A seguir criamos a rota livros
e a apontamos para o controller LivroController
, importando anteriormente o namespace App\Http\Controllers\LivroController
. O namespace é uma forma de organizar classes, funções e constantes para evitar conflitos de nomes em projetos grandes. Ele permite agrupar elementos relacionados dentro de um mesmo escopo, facilitando a reutilização e manutenção do código.
use App\Http\Controllers\LivroController;
Route::get('/livros', [LivroController::class,'index']);
A camada View é responsável por exibir a interface da aplicação, separando a lógica de apresentação da lógica de negócio (controller). Ela utiliza o Blade, uma linguagem de templates que permite criar páginas dinâmicas de forma eficiente. As views ficam armazenadas na pasta resources/views
e podem ser retornadas a partir de um controller usando return view('nome_da_view')
.
mkdir resources/views/livros
touch resources/views/livros/index.blade.php
No controller:
public function index(){
return view('livros.index');
}
Conteúdo mínimo de index.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Livros</title>
</head>
<body>
Memórias de um Sargento de Milícias<br>
O Primo Basílio<br>
Memórias Póstumas de Brás Cubas<br>
A Hora da Estrela<br>
</body>
</html>
O Model é uma representação de uma tabela no banco de dados e é responsável pela interação com os dados dessa tabela. Ele encapsula a lógica de acesso e manipulação dos dados, permitindo realizar operações como inserção, atualização, exclusão e leitura de registros de forma simples e intuitiva. O Laravel usa o Eloquent ORM (Object-Relational Mapping) para mapear os dados do banco de dados para objetos PHP, o que permite que você trabalhe com as tabelas como se fosse uma classe de objetos.
Criando o model chamado Livro:
php artisan make:model Livro -m
As migrations são uma forma de versionar e gerenciar o esquema do banco de dados, permitindo criar, alterar e remover tabelas de forma controlada e rastreável. Elas funcionam como um histórico de mudanças no banco de dados, ajudando a manter o controle de versões entre diferentes ambientes de desenvolvimento e produção.
Cada migration é uma classe PHP que define as operações a serem realizadas no banco de dados. As migrations são armazenadas na pasta database/migrations
. As migrations tornam o processo de gerenciamento do banco de dados mais organizado e flexível, principalmente em projetos com múltiplos desenvolvedores. Vamos colocar três colunas para o model Livro
: titulo, autor e ano.
$table->string('titulo');
$table->string('autor');
$table->integer('ano');
Depois da modificação na migration, aplicá-la no banco de dados: php artisan migrate
.
tinker
O comando
php artisan tinker
nos permite digitar comandos PHP e ver imediatamente o resultado, como se estivesse dentro da sua aplicação Laravel, ou seja, executamos comandos PHP diretamente dentro do contexto da aplicação, de forma prática e rápida.
Usando o tinker, vamos cadastrar dois livros:
$livro = new \App\Models\Livro;
$livro->titulo = "Memórias de um Sargento de Milícias";
$livro->autor = "Manuel Antônio de Almeida";
$livro->ano = 1853;
$livro->save();
$livro = new \App\Models\Livro;
$livro->titulo = "O Primo Basílio";
$livro->autor = "Eça de Queiroz";
$livro->ano = 1878;
$livro->save();
Se quisermos conferir diretamente no banco de dados os livros cadastrados, saímos do tinker com Ctrl+C e acessamos o banco com sqlite:
sqlite3 database/database.sqlite
.tables
SELECT * FROM livros;
.quit
Conferido que os livros foram cadastrados no banco de dados, na view da index podemos listar os livros cadastrados:
use App\Models\Livro;
public function index(){
return view('livros.index',[
'livros' => Livro::all()
]);
}
No blade index.blade.php, listamos os livros:
<ul>
@foreach($livros as $livro)
<li>{{ $livro->titulo }}, por <i>{{ $livro->autor }}</i> em {{ $livro->ano }}</li>
@endforeach
</ul>
Por fim, podemos criar um comando no artisan que automatiza o cadastro de livros a partir de alguma lógica que podemos desenvolver.
php artisan make:command ImportaLivros
O comando acima criará o arquivo app/Console/Commands/ImportaLivros.php
, o qual temos que mudar o $signature
e implementar a lógica do comando:
protected $signature = 'livros:importar';
public function handle()
{
$livro = new \App\Models\Livro;
$livro->titulo = "A Hora da Estrela";
$livro->autor = "Clarice Lispector";
$livro->ano = 1977;
$livro->save();
}
Ao rodarmos no terminal o comando php artisan livros:importar
o livro da Clarice será cadastrado. Essa é um implementação simples (e inútil, pois o mesmo livro é sempre cadastrado repetidamente), mas a ideia é que qualquer lógica pode ser implementada no handle()
para cadastro de muitos livros a partir de uma fonte externa, como, por exemplo, uma lista de livros oriunda de um arquivo csv.
Exercício 1 - Importação de Dados e Estatísticas com Laravel
Objetivo: Criar um sistema básico em Laravel para importar dados de um arquivo CSV e exibir estatísticas desses dados em uma view.
https://raw.githubusercontent.com/mwaskom/seaborn-data/master/exercise.csv
1) Criar o Model e a Migration:
- Crie um model chamado
Exercise
com uma migration correspondente. - Na migration, defina os campos necessários com base nas colunas do arquivo
exercise.csv
- Execute a migration para criar a tabela no banco de dados.
2) Criar um Command
para Importação
- Crie um comando
exercise:importar
. - No método
handle()
, implemente a lógica para ler o arquivoexercise.csv
e salvar os dados no banco de dados usando o modelExercise
. - Dica 1: Para zerar os registros a cada importação, pode-se usar o comando
\App\Models\Livros::truncate()
no começo do métodohandle()
. - Dica 2: Você pode usar a classe
League\Csv\Reader
(disponível via Composer) para facilitar a leitura do CSV.
3) Criar estatísticas básicas sobre os dados
- Criar o controller
ExerciseController
com um método chamadostats
. - Defina uma rota
exercises/stats
que aponte para o métodostats
. - No método
stats
, calcule as média aritmética da coluna pulse para os casos rests, walking e running, conforme tabela abaixo. - Passe esses dados para uma view chamada
resources/views/exercises/stats.blade.php
e monte finalmente a tabela com html.
Exemplo de saída:
exercise.csv | rest | walking | running |
---|---|---|---|
Qtde linhas | XX | XX | XXX |
Média Pulse | XX | XX | XXX |
Dia 2
CRUD
CRUD é um acrônimo para as quatro operações básicas utilizadas na manipulação de dados em sistemas web: Create (Criar), Read (Ler), Update (Atualizar) e Delete (Excluir). Essas operações interagem com bancos de dados, permitindo, por exemplo, que usuários possam cadastrar novas informações, visualizar registros existentes, modificar dados já salvos e remover registros.
Create
São geralmente necessárias duas rotas para salvar um registro em uma operação CRUD porque o processo é dividido em duas etapas: exibir o formulário e processar os dados enviados. A rota GET serve para exibir o formulário de criação e a rota POST serve para processar os dados enviados pelo formulário no controller:
Route::get('/livros/create', [LivroController::class,'create']);
Route::post('/livros', [LivroController::class,'store']);
Para mostrar o formulário html usamos o método create :
public function create(){
return view('livros.create');
}
Formulário html será touch resources/views/livros/create.blade.php
com o seguinte conteúdo:
<form method="POST" action="/livros">
@csrf
Título: <input type="text" name="titulo">
Autor: <input type="text" name="autor">
Ano: <input type="text" name="ano">
<button type="submit">Enviar</button>
</form>
Por fim o método store, que salva no banco de dados o cadastro do livro:
public function store(Request $request){
$livro = new Livro;
$livro->titulo = $request->titulo;
$livro->autor = $request->autor;
$livro->ano = $request->ano;
$livro->save();
return redirect('/livros');
}
Read
Já implementamos uma forma de acessar os livros em forma de listagem com o método index, podemos implementar outra forma de acesso individual para cada livro. Rota para acesso ao registro de um livro específico:
Route::get('/livros/{livro}', [LivroController::class,'show']);
Respectivo controller:
public function show(Livro $livro){
return view('livros.show',[
'livro' => $livro
]);
}
Criamos um blade para a rota show touch resources/views/livros/show.blade.php
com o seguinte conteúdo:
Título: {{ $livro->titulo }} <br>
Autor: <i>{{ $livro->autor }}</i> <br>
Ano de publicação: {{ $livro->ano }} <br>
<a href="/livros">Voltar</a>
No index.blade.php podemos criar um link para o show
de cada livro:
<a href="/livros/{{ $livro->id}}">{{ $livro->titulo }}</a>
Update
Novamente precisamos de duas rotas para atualizar um registro, uma para exibir o formulário e outra para processar os dados enviados.
Route::get('/livros/{livro}/edit', [LivroController::class,'edit']);
Route::post('/livros/{livro}', [LivroController::class,'update']);
Implementação no controller:
public function edit(Livro $livro){
return view('livros.edit',[
'livro' => $livro
]);
}
public function update(Request $request, Livro $livro){
$livro->titulo = $request->titulo;
$livro->autor = $request->autor;
$livro->ano = $request->ano;
$livro->save();
return redirect("/livros/{$livro->id}");
}
Criando o blade para edição:
touch resources/views/livros/edit.blade.php
Html para edição no edit.blade.php
:
<form method="POST" action="/livros">
@csrf
Título: <input type="text" name="titulo" value="{{ $livro->titulo }}">
Autor: <input type="text" name="autor" value="{{ $livro->autor }}">
Ano: <input type="text" name="ano" value="{{ $livro->ano }}">
<button type="submit">Enviar</button>
</form>
Vamos colocar o botão para edição no blade show.blade.php
:
Título: {{ $livro->titulo }} <br>
Autor: <i>{{ $livro->autor }}</i> <br>
Ano de publicação: {{ $livro->ano }} <br>
<a href="/livros/{{ $livro->id }}/edit">Editar</a> <br>
<a href="/livros">Voltar</a>
Delete
Rota para delete:
Route::delete('/livros/{livro}', [LivroController::class,'destroy']);
Controller para delete:
public function destroy(Livro $livro)
{
$livro->delete();
return redirect('/livros');
}
Botão html para delete que podemos colocar no blade do show.blade.php
:
<form action="/livros/{{ $livro->id }} " method="post">
@csrf
@method('delete')
<button type="submit" onclick="return confirm('Tem certeza?');">Apagar</button>
</form>
Exercício 2
- Criar um CRUD completo para cadastro de livros: https://github.com/zygmuntz/goodbooks-10k/blob/master/samples/books.csv. Dica: use outra instância de Laravel para não confundir com o
Model
livro que já estamos usando ao longo do texto. - Criar uma rotina de importação, conforme feito no exercício 1, para importação do csv: https://raw.githubusercontent.com/zygmuntz/goodbooks-10k/master/books.csv (esse é o completo!)
Criar rota, controller e view que vai mostrar:
- uma tabela com a quantidade de livros por ano
- uma tabela com a quantidade de livros por autor
- uma tabela com a quantidade de livros por idioma
Dia 3
Instalação do template USP conforme:
https://github.com/uspdev/laravel-usp-theme/blob/master/docs/configuracao.md
Validação
Validação no Controller
Quando estamos dentro de um método do controller, a forma mais rápida de validação é usando $request->validate
, que validará os campos com as condições que passarmos e caso falhe a validação, automaticamente o usuário é retornado para página de origem com todos inputs que foram enviados na requisição, além da mensagens de erro:
$request->validate([
'titulo' => 'required',
'autor' => 'required',
'isbn' => 'required|integer',
]);
Podemos usar a função old('titulo')
nos formulários, que nesse caso verifica se há input na sessão para o campo titulo
:
Título: <input type="text" name="titulo" value="{{old('titulo')}}">
Autor: <input type="text" name="autor" value="{{old('autor')}}">
ISBN: <input type="text" name="isbn" value="{{old('isbn')}}">
FormRequest
A validação, que muitas vezes será idêntica no store e no update, pode ser delegada para um FormRequest. Crie um FormRequest com o artisan:
php artisan make:request LivroRequest
Esse comando gerou o arquivo app/Http/Requests/LivroRequest.php
. Como ainda não falamos de autenticação e autorização, retorne true
no método authorize()
. As validações podem ser implementada em rules()
. Perceba que o isbn pode ser digitado com traços ou ponto, mas eu só quero validar a parte numérica do campo e ignorar o resto, para isso usei o método prepareForValidation
:
public function rules(){
$rules = [
'titulo' => 'required',
'autor' => 'required',
'isbn' => 'required|integer',
];
return $rules;
}
protected function prepareForValidation()
{
$this->merge([
'isbn' => preg_replace('/[^0-9]/', '', $this->isbn),
]);
}
Não queremos livros cadastrados com o mesmo isbn. Há uma validação chamada unique
que pode ser invocada na criação de um livro como unique:TABELA:CAMPO
, mas na edição, temos que ignorar o próprio livro assim unique:TABELA:CAMPO:ID_IGNORADO
. Dependendo do seu projeto, talvez seja melhor fazer um formRequest para criação e outro para edição. Aqui usaremos o mesmo FormRequest para ambos. As mensagens de erros podem ser customizadas com o método messages()
:
public function rules(){
$rules = [
'titulo' => 'required',
'autor' => 'required',
'isbn' => ['required','integer'],
];
if ($this->method() == 'PATCH' || $this->method() == 'PUT'){
array_push($rules['isbn'], 'unique:livros,isbn,' .$this->livro->id);
}
else{
array_push($rules['isbn'], 'unique:livros');
}
return $rules;
}
protected function prepareForValidation()
{
$this->merge([
'isbn' => str_replace('-','',$this->isbn)
]);
}
public function messages()
{
return [
'isbn.unique' => 'Este isbn está cadastrado para outro livro',
];
}
Migration de alteração
Quando o sistema está produção, você nunca deve alterar uma migration que já foi para o ar, mas sim criar uma migration que altera uma anterior. Por exemplo, se quisermos que o campo isbn guarde apenas números, faremos:
php artisan make:migration change_isbn_column_in_livros --table=livros
Alterando a coluna isbn
de string para integer na migration acima:
$table->integer('isbn')->change();
Aplique a mudança no banco de dados:
php artisan migrate
Mutators
Há situações em que queremos fazer um leve processamento antes de salvar um valor no banco de dados e logo após recuperarmos um valor. Vamos adicionar um campo para preço. Já sabemos como criar uma migration de alteração para alterar a tabela livros:
php artisan make:migration add_preco_column_in_livros --table=livros
E adicionamos na nova coluna:
$table->float('preco')->nullable();
No LivroRequest também deixaremos esse campo como opcional: 'preco' => 'nullable'
.
Queremos que o usuário digite, por exemplo, 12,50
, mas guardaremos 12.5
. Quando quisermos mostrar o valor, vamos fazer a operação inversa. Poderíamos fazer esse tratamento diretamente no controller, mas também podemos usar mutators
através no model do livro:
use Illuminate\Database\Eloquent\Casts\Attribute;
protected function preco(): Attribute
{
return Attribute::make(
get: fn($value) => number_format($value, 2, ',', ''),
set: fn($value) => str_replace(',','.',$value)
);
}
Exercício 3
Esse exercíco é referente ao arquivo: https://github.com/owid/covid-19-data/blob/master/public/data/vaccinations/country_data/Brazil.csv
- Criar model, migration, CRUD, e uma lógica de importação do csv (pode ser uma rota e controller). Na migration defina os campos total_vaccinations e people_vaccinated como string. Importe o csv.
- Faça uma migration de alteração para alterar total_vaccinations e people_vaccinated para inteiro
- Faça um mutator para mostrar o campo date com o formato brasileiro dd/mm/yyyy e outro mutator para salvá-lo commo yyyy-mm-dd (lembre-se que o formulário deve receber dd/mm/yyyy)
- Implemente o FormRequest garantindo que seja digitado dd/mm/yyyy, além implementar as outras validações
- Corriga seus formulários para sempre conterem a função old()