Como estender os Controllers do Code Igniter

Estender um controller do Code Igniter é uma boa forma de carregar um controller com algumas pré-definições, como validação de usuário, carregamento de bibliotecas ou definições de idioma.

Como estender um controller?

Para estender um controller (ou qualquer outra parte do Code Igniter) o primeiro passo é criar um arquivo que será carregado automaticamente e terá a classe que iremos utilizar.

No meu caso, para estender um controller vou criar um arquivo chamado MY_Controller.php no diretório:

application/core/MY_Controller.php

Dentro este arquivo crio uma classe para estender meu controller, por exemplo:

class MY_AdminController extends CI_Controller {}

E dentro desta classe eu adiciono o que for necessário para o meu controller extendido.

class MY_AdminController extends CI_Controller {
	function __construct(){
		parent::__construct();
		header('Content-type: text/html; charset=utf-8');
		$user = new User();
		if(!$user->getLogged()){
			die('There is only one god and his name is Death, and there is only one thing we say to Death: "Not today"');
		}
	}
}

E no arquivo do seu controller, ao invés de estender o controller você extende o MY_AdminController ou qualquer outro nome que você tenha utilizado.

Você pode ter mais do que uma extensão de controller, no mesmo arquivo de extensão (MY_Controller.php) é só criar uma outra classe e estender o mesmo Controller.

class MY_AdminController extends CI_Controller {}
class MY_App extends CI_Controller {}

Uma outra forma de estender é utilizar os hooks, que permitem um controle mais sensível de onde e o que carregar.

E você, como faz isso nas suas aplicações?

Utilizando memcache no CodeIgniter

Recentemente passei o And After da versão 1.7 para a versão 2.0.2 do CodeIgniter (CI),  e fiquei feliz com as facilidades do driver de cache na nova versão, recomendo a atualização do framework!

Nas versões anteriores só existia (nativamente) o cache de "output" no CI, isso significa que eu só podia cachear uma "saída" para aquela url (por método de um controller). A performance fica voando, porém isso engessava um pouco o cache, não podia fazer verificações de usuário logado, personalização, executar scripts, etc. Se já existia um cache para determinado controller ele era usado e ponto, não podia contar views, verificar data, exibir informações de acordo com o usuário, etc.

Com o novo driver de cache ficou muito mais fácil, você pode optar entre os seguintes tipos de cache:

  • Dummy – Utilizado apenas para não dar erro em ambiente de desenvolvimento, ele não cacheia nada.
  • File – Cache de arquivo, fica armazenado em /application/cache
  • Memcache – cache de memória utilizando um servidor memcached
  • APC  – Cache APC do PHP

Utilizando o memcache no CodeIgniter

O memcache é um cache de memória, e precisa de um servidor de cache. O primeiro passo é instalar o memcache.

Com o servidor instalado e configurado, você precisa configurar sua aplicação no CodeIgniter para o uso do cache, vamos lá!

Carregando o Driver de cache

A primeira coisa que você precisa é o Driver de Cache carregado:

$this->load->driver('cache');

Com isso você já pode usar os métodos de cache que o driver suporta.

Salvando um objeto de cache

O driver de cache funciona com chave->valor, para salvar um objeto é simples, o método save é chamado com 3 parâmetros nesta ordem:

  1. Chave do objeto
  2. Valor do objeto
  3. "Time to live", tempo em segundos que o cache deve permanecer salvo.

Para salvar o objeto "site" com valor "O Desenvolvedor" por 2 minutos temos o seguinte:

$this->cache->memcached->save('site', 'O Desenvolvedor', 120);

Recuperando um objeto de cache

O método get retorna o valor do objeto se ele existir e false se não existir o objeto em cache:

$site = $this->cache->get('site');

O retorno "false" permite verificação se um objeto existe, por exemplo:

if($this->cache->get('site')){
   //faz alguma coisa pois o cache existe
}else{
   //Faz uma consulta no banco e salva ela no cache
   $this->cache->memcached->save('site', 'O Desenvolvedor', 120);
}

Apagando um objeto do cache

Função delete, bastante intuitiva, só passa como parâmetro a chave do objeto a ser excluído:

$this->cache->delete('site');

 

Com essas informações tudo o que você precisa para melhorar a performance da sua aplicação é um pouco de criatividade e testes, testes e testes. No And After fiz diversos testes, optei pelo uso de file cache para os posts.

Em alguns casos de posts com centenas de comentários o ganho de performance foi muito grande, no post de cartão de visita por exemplo a performance de back-end ficou mais de 10 vezes mais rápida para entrega dos objetos. Agora estou trabalhando no cache do O Desenvolvedor e do Eu Compraria! Shop.

A melhoria de performance e redução de gasto com servidor valem qualquer tempo investido no estudo e aplicação do cache!

Sugestões, críticas e melhorias nos comentários! 🙂

Referências

Como internacionalizar as mensagens nas views do Code Igniter

Estive fazendo um job junto com o @gserrano usando o Code Igniter, e tivemos a necessidade de fazer a view com mensagens internacionalizadas, uma vez que o site terá versão em espanhol e português. Mais uma vez, o framework facilitou muito a nossa vida.

Vamos lá, é simples:

  • dentro da pasta application, há uma chamada language
  • dentro de language, deve haver uma pasta para cada um dos seus idiomas
  • crie em cada uma dessas pasta um arquivo chamado {nomedocliente}_lang.php (claro, {nomedocliente} pode ser trocado por qualquer coisa. No exemplo a seguir, usarei meujob_lang.php)
  • o conteúdo do arquivo deve seguir a estrutura:
    $lang["chave1]    = "mensagem 1";
    $lang["chave2]    = "mensagem 2";
    $lang["chave3]    = "mensagem 3";
    ?>

    Ou seja, é um arquivo que conterá chaves (usadas na view) e os valores. As chaves serão sempre iguais nos diversos arquivos de internacionalização, e as mensagens, serão as mesmas, traduzidas para o idioma em questão
  • antes de usar na view essas mensagens, você deve adicionar as mesmas no controller.
    Imagine que você tenha o seguinte método em um controller:
    public function formulario() {
       $this->load->view('passo1');
    }
    O que você precisa é, antes de carregar a view, adicionar o idioma que será usado. No caso abaixo, o que fiz é carregar a view dependendo do terceiro parâmetro vindo na url (por exemplo: /meucontroller/formulario/pt_BR)
    public function formulario() {
       $this->lang->load('meujob', $this->uri->segment(3));
       $this->load->view('passo1');
    }
    Note que (1) falta uma validação do terceiro parâmetro e (2) meujob é o nome do arquivo criado lá no terceiro passo.
  • na view, basta você chamar lang->line("chave1"); ?> por exemplo, que será printada na tela a mensagem referente a chave1 do arquivo de mensagens correto.

Refefência: http://codeigniter.com/user_guide/libraries/language.html

Salvando objetos com ID definido no Data Mapper (CI)

Desde que comecei a estudar e utilizar o Code Igniter eu recomendo o uso do ORM Data Mapper, que facilita a vida nas consultas ao banco de dados (leia: tutorial do Data Mapper no Code Igniter), esta semana ele mais uma vez se mostrou muito útil enquanto eu fazia a migração do banco antigo do And After para o novo.

Depois de dominar (yeaaaah) relacionamento de tabelas com o Code Igniter (usando o Data Mapper) e finalizar o sistema do And After neste framework estou fazendo a migração de todo o conteúdo para a nova plataforma: textos, comentários, cadastro de usuários, logs, tags, blogs…

Achei que seria extremamente trabalhoso, pois toda a estrutura de banco foi alterada – recomecei o sistema do zero sem pensar na migração, fiz isso para evitar "puxadinhos" e gambiarras (que com o crescimento de sistema, estava cheio) e desenvolver tudo direito, do zero, um sistema novo.

No ônibus voltando para São Paulo fiz em alguns minutos a migração dos usuários e iniciei a dos textos (que será um pouco mais complicada devido aos relacionamentos), mas tive um problema com o Data Mapper: não perder os ID"s dos objetos que estão sendo migrados.

Como salvar um objeto com um ID existente?

O Data Mapper usa o atributo ID do objeto para verificar se ele deve fazer um insert ou um update, se o objeto ID existe ele fará um UPDATE, portanto você não consegue setar o ID de um objeto e usar o $object->save();, pois ele fará um update mesmo que o objeto não exista (e não vai acontecer nada no seu banco).

Para isso o Data Mapper tem a função save_as_new, segue um exemplo abaixo:

$user = new User();
$user->id = 1;
$user->login = "Admin";
$user->password = "password";
$success = $user->save_as_new();

Se não deu nenhum problema no seu banco (neste caso ele deveria estar limpo) você acabou de criar um usuário "Admin" com o id 1na sua tabela.

Problemas comuns

Após fazer a migração pode acontecer problemas ao tentar salvar novos objetos na tabela, isso pode acontecer porque o auto_increment (do ID) não foi atualizado – e está tentando inserir um novo objeto na tabela com um ID que já foi registrado "na mão".

Para facilitar o entendimento vamos a ao exemplo do caso da migração do And After. Existem aproximadamente 1.500 usuários cadastrados no site, após a migração o último ID utilizado na tabela de usuários foi o 1600 (devido a usuários excluídos, testes no banco, etc).

Preciso avisar a minha tabela que o próximo auto_increment (ID) é 1601, e não 1 (que seria o primeiro inserido de forma "natural", sem ID setado). O código abaixo executa esta atualização no banco:

// Update MySQL AUTO_INCREMENT:
$user->db->query("ALTER TABLE `users` AUTO_INCREMENT = 1601;");

Agora a migração da tabela users está completa e é possível utilizar ela normalmente.

Para mais dúvidas sobre o save leia a documentação do data mapper (EN).

Relacionamento de tabelas no Data Mapper (CI)

Se você não entendeu muito bem o post explicando como melhorar as recomendações de posts relacionados com o Data Mapper, este tutorial poderá ajudar.

Criei um material para apoiar a documentação do Data Mapper sobre o relacionamento de tabelas no banco de dados. Se você não está entendendo nada, recomendo que antes de continuar você leia:

  1. Usando o Data Mapper no Code Igniter – como instalar e configurar o Data Mapper
  2. Entendo o relacionamento de banco de dados

 

Nomenclatura das tabelas relacionais no Data Mapper

O primeiro passo para fazer o relacionamento de tabelas funcionar é estruturar o banco de dados para isso. Para ilustrar este post vamos criar um relacionamento "one to many" de usuários para posts.

Na nossa estrutura do banco de dados temos a tabela users e a tabela posts.

Por regra do Data Mapper as tabelas de relacionamento devem ser estruturadas da seguinte forma:

  1. Nome da primeira tabela do relacionamento (ordem alfabética)
  2. Underline "_"
  3. Nome da segunda tabela do relacionamento

No nosso caso temos as tabelas "users" e "posts", portanto nossa tabela de relacionamento que deve ser criada no banco de dados se chamará "posts_users" e terá três colunas:

id - INT, auto increment

user_id - INT

post_id - INT

Pronto, com esta tabela criada no nosso banco de dados podemos configurar nossas classes "User" e "Post" no Data Mapper.

 

Configurando as classes para relacionamento

Continuando o exemplo anterior da classe User, vamos adicionar a configuração de relacionamento, ela ficará assim:

class User extends DataMapper {
    var $table = "users";

    var $has_many= "posts, comments";

    function User()
    {
        parent::DataMapper();
    }
}

E agora nossa clase Post:

class Post extends DataMapper {
    var $table = "posts";

    var $has_one= "user";
    var $has_many= "comments";

    function User()
    {
        parent::DataMapper();
    }
}

 

Pronto, suas tabelas estão relacionadas pelo Data Mapper e você pode usufruir as facilidades do ORM para criar "innerjoins" que eu não entendo!

 

Salvando dados de relacionamento

Para salvar um relacionamento é simples, criamos os dois objetos que vamos relacionar e usamos o método save do Data Mapper.

É mais fácil exemplificar do que explicar:


$u = new User();
$u->where("login", "odesenvolvedor")->get();

$p = new Post();
$p->title = "Novo post"; $p->content = "Conteúdo html do nosso post"

// Salvamos o novo post e relacionamos ao nosso User
$p->save($u);

Para relacionar dois objetos já existentes a lógica é a mesma:

$u = new User();
$u->where("login", "odesenvolvedor")->get();

$p = new Post();
$p->
where("title", "Novo Post"); $p->get();

// Relacionamos o Post que já existia ao nosso User
$p->save($u);

Este é o básico do relacionamento de dados utilizando o Data Mapper. Para entender recomendo a leitura da documentação do DM.

Para um exemplo prático mais avançado veja como selecionar posts mais relevantes através da comparação de tags.