Após um longo intervalo, estou retomando os artigos sobre Laravel. Vamos dar continuidade a aplicação que vinhamos desenvolvendo como exemplo e, neste artigo, falaremos especificamente sobre relacionamentos. Atualmente na nossa aplicação não temos nenhum relacionamento entre quem escreveu um artigo (usuarios) e os artigos. Neste contexto entram os relacionamentos e as chaves estrangeiras.
Assim como nos outros artigos vou começar com um referencial das funcionalidades do Laravel. Dentro dos nossos modelos, já temos funcionalidades disponíveis para definir os relacionamentos. Essas funcionalidades fazem parte do Eloquent.
Nossa aplicação foi atualizada para a versão 4.2 do Laravel, por isso essa é a versão que vou usar para explicar este artigo. A lógica em si e os métodos do Eloquent não vão mudar no Laravel 5, então não chega a ser um problema. Depois vou fazer um tutorial explicando como migrar para o Laravel 5.
Migrações
Vamos começar pelas migrações. Já escrevi um pouco sobre as migrações no artigo Banco de Dados no Laravel, agora vou falar especificamente sobre índices e chaves estrangeiras.
Índices
Definir índices nas migrações é bem simples. Você pode definir diretamente na criação da tabela, ou logo após criar ela (caso você queira um índice com mais campos será necessário fazer isso).
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Schema::create('funcionarios', function($table) { $table->increments('id'); $table->string('nome', 100); $table->string('cpf', 20)->unique(); $table->string('estado', 2); $table->string('cidade', 100); $table->timestamps(); }); $table->index(array('estado', 'cidade'); |
Aqui temos um exemplo de cada caso. Criamos uma tabela funcionarios com o campos cpf utilizando um índice único. Ou seja, este campos erá indexado e não vai permitir dois valores iguais. Depois de criar a tabela, adicionamos um outro índice simples, que usa os campos estado e cidade (normalmente seriam chaves estrangeira para outra tabela – fiz assim apenas pelo exemplo).
Outros métodos para índice são:
- $table->primary(‘id’): definição de índice primário – lembrando que ao usar increments(‘id’) o índice primário será criado automaticamente;
- $table->primary(array(‘primeiro’, ‘ultimo’)): cria um índice primário com dois campos;
- $table->unique(‘cpf’): índice único no e-mail;
- $table->index(‘estado’): índice simples no estado.
Cada índice criado é nomeado seguindo a seguinte regra: <tabela>_<campo>_<tipo do indice>. Ou seja, no caso do CPF nosso índice teria o seguinte nome funcionarios_cpf_unique. É importante saber o nome do índice, pois também existem métodos para apagar os índices criados e eles utilizam o nome para isso:
- $table->dropPrimary(‘funcionarios_id_primary’);
- $table->dropUnique(‘funcionarios_cpf_unique’);
- $table->dropIndex(‘funcionarios_estado_index’).
Chaves Estrangeiras
Uma chave estrangeira é basicamente um índice associado a um valor de outra tabela. A maneira de criar uma chave estrangeira é bem simples:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Schema::create('cidades', function($table) { $table->increments('id'); $table->integer('estado_id')->unsigned(); $table->foreign('estado_id')-> references('id')-> on('estados')-> onDelete('cascade'); $table->string('nome', 100); $table->timestamps(); }); |
Neste exemplo estamos criando uma tabela chamado cidades, com um campo estado_id relacionado a tabela estados. Esse é um exemplo clássico e simples de relacionamento 1 para N, onde cada cidade pertence a um estado e cada estado pode ter diversas cidades. A primeira coisa que precisamos fazer é definir o campo que vai armazenar a chave estrangeira:
1 |
$table->integer('estado_id')->unsigned(); |
Definimos um campo do tipo inteiro, sem sinal. Como não teremos IDs de estados menores que zero, não precisamos de sinal nesse campo. Além disso a capacidade do campo será maior, pois não teremos os negativos.
Se você quer ser capaz de cadastrar uma cidade sem o estado, deve criar o campo adicionado ->nullable() – isso permitirá que o campo seja NULL.
Logo depois definimos a chave estrangeira:
1 2 3 4 |
$table->foreign('estado_id')-> references('id')-> on('estados')-> onDelete('cascade'); |
A primeira parte é a definição do campo que será a chave estrangeira (estado_id), depois informamos qual o campo será referenciado (id) e qual a tabela será referenciada (estados). A última parte é a definição do comportamento caso o registro relacionado seja removido – nesse caso definimos como cascade. O que vai acontecer neste caso é que, se possuirmos cidades com estado_id igual a 10 e o estado 10 for excluído, todas as cidades relacionadas serão excluídas automaticamente. Também é possível definir o método onUpdate() da mesma forma.
Para remover uma chave estrangeira você deve usar o comando: $table->dropForeign(‘cidades_estado_id_foreign’);. Mesma lógica dos outros índices.
Mas esse é um exemplo bem simples. Um caso mais complexo é o das tabelas relacionamento. Por exemplo, quando precisamos relacionar pessoas e as cidades onde já moraram, uma cidade pode estar relacionada a várias pessoas e uma pessoa pode estar relacionada a várias cidades. Neste caso temos um relacionamento N para N. O código necessário seria assim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
Schema::create('pessoas', function($table) { $table->increments('id'); $table->string('nome', 255); $table->timestamps(); }); Schema::create('cidades', function($table) { $table->increments('id'); $table->string('nome', 255); $table->timestamps(); }); Schema::create('cidade_pessoa', function($table) { $table->integer('cidade_id')->unsigned(); $table->foreign('cidade_id')-> references('id')-> on('cidades'); $table->integer('pessoa_id')->unsigned(); $table->foreign('pessoa_id')-> references('id')-> on('pessoas'); }); |
Nesse caso temos as duas tabelas pessoas e cidades, além de uma terceira tabela relacionamento cidade_pessoa. A convenção do Laravel é sempre colocar o nome das tabelas no singular e em ordem alfabética. Não é obrigatório, mas facilita a implementação dos modelos depois. Na tabela relacionamento temos uma chave estrangeira para cada tabela, assim podemos criar o relacionamento N para N.
Eloquent
Agora vamos ver como ficam os relacionamentos dentro dos nossos modelos Eloquent. É bem fácil e simplifica o desenvolvimento. As vezes não é o ideal com relação a performance. Primeiro vamos ver o código necessário para cada tipo de relacionamento:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// Relacionamento 1 para 1 class Usuario extends Eloquent { public function conta() { return $this->hasOne('Conta'); } } class Conta extends Eloquent { public function usuario() { return $this->belongsTo('Usuario'); } } // Relacionamento 1 para N class Estado extends Eloquent { public function cidades() { return $this->hasMany('Cidade'); } } class Cidade extends Eloquent { public function estado() { return $this->belongsTo('Estado'); } } // Relacionamento N para N class Pessoa extends Eloquent { public function cidades() { return $this->belongsToMany('Cidade'); } } class Cidade extends Eloquent { public function pessoas() { return $this->belongsToMany('Pessoa'); } } |
Além dessas opções, ainda é possível definir relacionamento com uma tabela intermediária (HasManyThrough) e relacionamentos polimorfos (Polymorphic Relations), mas não vou explicar esses relacionamentos em detalhe. Se você que saber mais consulte a documentação. Todos os métodos do código acima permitem definir quais os campos e tabelas você usou:
- $this->hasOne(‘Conta’, ‘usuario_id’, ‘id’): por padrão o Laravel vai usar o nome da tabela mais ‘_id’ como chave estrangeira e vai usar ‘id’ como chave local. Se você usou campos diferentes precisará preencher esses dois parâmetros adicionais;
- $this->belongsTo(‘Usuario’, ‘usuario_id’, ‘id’): o método belongsTo segue a mesma lógica;
- $this->hasMany(‘Cidade’, ‘usuario_id’, ‘id’): também segue o mesmo padrão, nenhum mistério.
- $this->belongsToMany(‘Cidade, ‘pessoa_cidade’, ‘pessoa_id’, ‘cidade_id’): como neste caso temos uma tabela intermediária, você pode usar o segundo parâmetro para definir o nome dessa tabela. O Laravel vai sempre procurar pela tabela com nomes no singular e em ordem alfabética – caso você use algo diferente, precisa especificar aqui. Os dois últimos parâmetros são as chaves estrangeiras da tabela relacionamento.
Buscando Registros
Agora que você já sabe como implementar as migrações e os models, vamos ver como se usam os relacionamentos. Os métodos podem ser acessados tanto como propriedades dos modelos ou como métodos para adicionar condições:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Acessando como uma propriedade $pessoa = Pessoa::find(1); foreach ($pessoa->cidades as $cidade) { echo $cidade->nome; } // Acessando como método $pessoa = Pessoa::find(1); $cidades = $pessoa->cidades()->where('populacao', '<', 100000)->orderBy('nome')->get(); foreach ($cidades as $cidade) { echo $cidade->nome; } |
Você também pode incrementar o método no model para incluir condições. Isso permite deixar seu controller bem limpo adicionando as buscas no model:
1 2 3 4 5 6 7 8 9 10 11 |
class Pessoa extends Eloquent { public function cidades($populacao = FALSE) { $query = $this->belongsToMany('Cidade'); if($populacao) { $query = $query->where('populacao', '<', $populacao); } return $query; } } |
Performance
Apesar de muito simples e muito claro de implementar, essa facilidade pode fazer com que você cause problemas de performance na sua aplicação. Veja o seguinte caso e tente pensar qual é o problema:
1 2 3 4 5 |
$cidades = Cidade::all(); foreach($cidades as $cidade) { echo $cidade->nome . ' / ' . $cidade->estado->nome; } |
Conseguiu descobrir? O problema deste código é que o Laravel vai fazer uma busca no banco para pegar a lista de cidades e outra busca para cada cidade para buscar as informações do estado. Se você tiver 50 cidades, serão executadas 51 buscas no banco, reduzindo a performance.
Uma maneira de resolver isso é bem simples. Quando buscar as cidades, é possível buscar os estados previamente, isso é chamado de Eager Loading. O código é quase igual, a diferença é o método with:
1 2 3 4 5 |
$cidades = Cidade::with('estado')->get(); foreach($cidades as $cidade) { echo $cidade->nome . ' / ' . $cidade->estado->nome; } |
Esse código vai executar apenas duas buscas no banco:
1 2 |
select * from `cidades`; select * from `estados` where `estados`.`id` in (?, ?, ?, ?, ?, ?, ?); |
Na segunda query serão usadas todas as chaves dos estados necessárias. Essa pequena mudança vai trazer um grande ganho de performance para sua aplicação.
Aplicação
Agora que já temos a referência para os relacionamentos, vamos adicionar um relacionamento na nossa aplicação. Se você ainda não conhecer nossa aplicação veja o repositório no GitHub. Para baixar o código que vamos usar aqui faça o download da versão 1.5.2.
Migração
O que queremos fazer é que o artigo esteja relacionado a um autor – que no caso será um usuário do sistema. Primeiro vamos criar uma migração executando o comando php artisan migrate:make relacionamento_artigo_usuario. Vamos colocar o seguinte conteúdo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class RelacionamentoArtigoUsuario extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('artigos', function($table) { $table->integer('usuario_id')->unsigned(); }); // Associando algum usuário para criar a chave estrangeira DB::table('artigos')->update(array('usuario_id' => 1)); Schema::table('artigos', function($table) { $table->foreign('usuario_id')-> references('id')-> on('usuarios')-> onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('artigos', function($table) { $table->dropForeign('artigos_usuario_id_foreign'); $table->dropColumn('usuario_id'); }); } } |
Este código é diferente dos códigos que expliquei antes. Consegue saber o por quê? No meu banco de dados (talvez no seu também) já existem artigos criados. Para poder criar a chave estrangeira temos duas opções:
- preencher o novo campo usuario_id com algum valor para associar a algum usuário – foi o que fiz aqui.
- criar o campo usuario_id com nullable(). Assim seria possível ter o valor nulo e criar a chave não seria um problema. Porém teríamos artigos sem associação a usuários.
Agora execute a migração e vamos para os models
Models
Temos dois models, Usuario e Artigo. Adicione o seguinte método no model Usuario:
1 2 3 4 5 |
// Relacionamento com artigos public function artigos() { return $this->hasMany('Artigo'); } |
E adicione o seguinte método no model Artigo:
1 2 3 4 5 |
// Relacionamento com usuários public function usuario() { return $this->belongsTo('Usuario'); } |
Controller
Agora abra o arquivo app/controllers/ArtigosController.php e altere o método getIndex() para adicionarmos o with e garantirmos uma boa performance:
1 2 3 4 5 |
public function getIndex() { $artigos = Artigo::with('usuario')->get(); return View::make('artigos.index', compact('artigos')); } |
E altere também o método postInserir() para incluir o usuário que criou o artigo.
1 2 3 4 5 6 7 8 9 10 11 12 |
public function postInserir() { $artigo = new Artigo(); $artigo->titulo = Input::get('titulo'); $artigo->conteudo = Input::get('conteudo'); $artigo->usuario_id = Auth::user()->id; $artigo->save(); return Redirect::to('/artigos'); } |
View
Para finalizar vamos modificar a lista de artigos para incluir o nome do autor. Dentro do foreach ($artigos as $artigo) coloque o seguinte código ( a diferença está no usuario->nome):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<div class="panel panel-default"> <div class="panel-heading"> <span class="glyphicon glyphicon-time"></span> {{ date('d/m/Y H:i', strtotime($artigo->created_at)) }} <div class="pull-right"> <div class="btn-group btn-group-xs"> <a href="{{ url('artigos/editar', $artigo->id) }}" title="Editar" class="btn btn-default">Editar</a> <a href="{{ url('artigos/remover', $artigo->id) }}" title="Remover" class="btn btn-default">Remover</a> </div> </div> </div> <div class="panel-body"> <h2> {{{ $artigo->titulo }}} <small>{{{ $artigo->usuario->nome }}}</small> </h2> <hr> <div> {{ $artigo->conteudo }} </div> </div> </div> |
É isso. Estamos com nossa aplicação atualizada. Ao acessar a lista de artigos em <dominio>/artigos você verá o nome do autor:
Página Inicial
Além dessas alterações, também coloquei os artigos na página inicial. Uma alteração bem simples, por isso não vou explicar aqui. Veja as modificações no repositório.
Conclusão
Neste artigo expliquei um pouco mais sobre como utilizar banco de dados no Laravel. Infelizmente tenho tido dificuldade em encontrar tempo para escrever, então vou tentar quebrar em mais artigos as próximas partes. Minha próxima tarefa é migrar essa aplicação para o Laravel 5. Se você tiver alguma dúvida, deixe nos comentários que eu respondo assim que possível.
Ola boa tarde Oscar Dias.
PARABÉNS pelo tutorial, me ajudou muito Obrigado.
SHOWWWW, Obrigado!
OLá Oscar , tenho uma dúvida , fiz um relacionamento entre as tabelas e estou tendo dificuldade para o campo estar sendo preenchido como nulo . ele é setado como nulo como padrão, agora se você preenche o campo , e depois deseja alterar , não consegue da erro no mysql . veja o código –
UP –
$table->integer(‘gallery_id’)->unsigned()->nullable()->default(null);
$table->foreign(‘gallery_id’)->references(‘id’)->on(‘photo_gallery’);
DOWN –
$table->dropForeign(‘posts_gallery_id_foreign’);
vcê saberia dizer o que é ? vc so consegue preencher o campo como nulo no início , agora se você colocar algum valor no relacioanmento e depois for em UPDATE e quiser colocar nulo de novo, não consegue .
Blz Murilo?
A migration parece estar OK. Você pode inclusive remover o “->default(null)”. Quando você seta como nullable e não atribui nada no campo ele já é criado como NULL.
O problema deve ser a atribuição. Como você está fazendo? Deveria ser algo assim:
$post = Post::find($id);
$post->gallery_id = NULL;
$post->save();