Como escrever um model melhor no CodeIgniter

Este post é uma tradução.
Link para o artigo original: http://shawnmccool.com/2009/08/28/how-to-write-a-better-model-in-code-igniter/

Desenvolver utilizando um framework MVC pode ajudar e muito para aumentar a reutilização e robustez do seu código. Neste artigo eu me concentro em métodos de escrita de models para o CodeIgniter Os conceitos em si não são específicos para o Code Igniter, mas parte do código será.

Métodos CRUD

CRUD é a sigla para Create, Retrieve, Update, and Delete. Ou seja criar, pegar, atualizar e apagar. Estes são os tipos mais básicos de interação que sua aplicação terá com o banco de dados.

Alguns exemplos:

Create – Adicionar um usuário ao seu website.
Retrieve – Buscar uma lista de produtos disponíveis.
Update – Mudar a senha de um usuário ou atualizar um produto.
Delete – Remover uma conta de usuário ou produto.

Quando você escreve seus models você geralmente começa criando seus métodos de CRUD, para cada tipo de dados. Se você está criando uma autenticação de usuário(login, logout, esqueci a senha, ativação de conta) você deverá criar um método para inserir, atualizar, apagar usuários e alem disso deverá buscar informações de vários usuários ou um grupo de usuários.

Por Exemplo:

function AddUser()
function UpdateUser()
function DeleteUser()
function GetUsers()

A maioria das interações que nós temos com contas de usuários podem ser utilizando apenas estes 4 métodos. No lugar de criarmos um método com o nome de “getActiveUsers” nós podemos simplesmente criar um parâmetro para o método GetUsers que determina se o status do usuário está ativo ou não.

A Array Options

Normalmente os parâmetros para os métodos são enviadas da seguinte forma:

function AddUser($insertData)
function UpdateUser($userId, $updateData)
function DeleteUser($userId)
function GetUsers($limit, $offset)

Naturalmente, (especialmente para GetUsers) nós iremos encontrar mais parâmetros para adicionar ao método. Isto não é somente difícil para ler como também é de difícil manutenção.

Por exemplo:

function GetUsers($limit, $offset, $status)

Nós também precisariamos de um método no singular, GetUser($userId). Isto é um problema pois estamos criando várias versões de um mesmo método. Se nós mudamos o jeito que nós queremos que a informação seja retornada nós teriamos que alterar o código em 2 lugares, GetUsers e GetUser.

Para evitar este problema proponho utilizar a array Options assim:

function AddUser($options = array())
function UpdateUser($options = array())
function DeleteUser($options = array())
function GetUsers($options = array())

Se antes nós usariamos assim: GetUsers(5,5,’active’) para buscar uma lista com usuários ativos, agora nós iremos utilizar assim GetUsers(array(‘limit’ => 5, ‘offset’ => 5, ‘status’ => ‘active));

Parâmetros passados no array Options podem ser enviados em qualquer ordem e podem até mesmo serem omitidos. Quando você precisar adicionar parâmetros você simplesmente muda um método e não precisará alterar mais nada de código.

Métodos Auxiliares

Para criarmos métodos robustos nós devemos criar alguns métodos auxiliares para implementar algumas funcionalidades.
Ou seja, a capacidade de atribuir os campos obrigatórios e os padrões de campo. Por exemplo, um campo de preenchimento obrigatório quando a adição de um usuário pode ser useremail. Para isso criamos o método “_required”.

/**
* _required method returns false if the $data array does not contain all of the keys assigned by the $required array.
*
* @param array $required
* @param array $data
* @return bool
*/
function _required($required, $data)
{
    foreach($required as $field) if(!isset($data[$field])) return false;
    return true;
}

No exemplo “AddUser” nosso código poderia ser:

/**
* AddUser method creates a record in the users table.
*
* Option: Values
* --------------
* userEmail         (required)
* userPassword
* userName
* userStatus        active(default), inactive, deleted
*
* @param array $options
*/
function AddUser($options = array())
{
    // required values
    if(!$this->_required(array('userEmail'), $options)) return false;

    // At this point we know that the key 'userEmail' exists in the $options array.
}

Agora nosso método AddUser irá retornar falso caso um de o índice ‘userEmail’ não ser enviado na array Options.

Repare que no bloco PHPdoc nós mensionamos que o status padrão para o usuário criado é “active”. Naturalmente, se nós quiséssemos criar um usuário inativo nós poderíamos passar o parâmetro ‘userStatus’ como ‘inative’. Mas como somos preguiçosos preferimos não declarar explicitamente ‘active’.

Apresentando o método “_default”:

/**
* _default method combines the options array with a set of defaults giving the values in the options array priority.
*
* @param array $defaults
* @param array $options
* @return array
*/
function _default($defaults, $options)
{
    return array_merge($defaults, $options);
}

É isso aí, este método consiste em um único comando. Eu decidi criar um método para isso, porque eu quero adicionar funcionalidades extras para o método padrão, e não quero ter que mudar a cada um dos meus métodos.

Agora vamos implementar o método “_default”:

/**
* AddUser method creates a record in the users table.
*
* Option: Values
* --------------
* userEmail         (required)
* userPassword
* userName
* userStatus        active(default), inactive, deleted
*
* @param array $options
*/
function AddUser($options = array())
{
    // required values
    if(!$this->_required(array('userEmail'), $options)) return false;

    // default values
    $options = $this->_default(array('userStatus' => 'active'), $options);
}

Neste ponto sabemos que o índice “userEmail’ existe na array $options e que se não existir “userStatus”, ele será inserido com o valor “active”.

Estes métodos podem ajudar e muito a tornar seu código mais robusto somente adicionando algumas linhas por método.

Active Record

Muitos SGDBs (MySQL, Oracle, Microsoft SQL, PostgresSQL, etc) usam variações da sintaxe do
SQL, mas todos trabalham uma forma um pouco diferente. A classe Active Record nos deixa criar uma query de forma abstrata. Em outras palavras nos criamos queries que irão funcionar para qualquer SGDB suportado. Eles também nos dão o benefício de adicionar pedaços da query para a classe um por um antes de executa-la.

Uma query Active Record no CodeIgniter seria assim:

$this->db->where('userStatus', 'active');
$this->db->get('users');

Estes 2 comandos irão criar e executar a query, “select * from users where userStatus = ‘active’”.

Colocando Tudo Junto

Usando os conceitos que temos explorado vejamos um exemplo de um simples conjunto de métodos CRUD.

/**
* AddUser method creates a record in the users table.
*
* Option: Values
* --------------
* userEmail         (required)
* userPassword
* userName
* userStatus        active(default), inactive, deleted
*
* @param array $options
*/
function AddUser($options = array())
{
    // required values
    if(!$this->_required(array('userEmail'), $options)) return false;

    // default values
    $options = $this->_default(array('userStatus' => 'active'), $options);

    // qualification (make sure that we're not allowing the site to insert data that it shouldn't)
    $qualificationArray = array('userEmail', 'userName', 'userStatus');
    foreach($qualificationArray as $qualifier)
    {
        if(isset($options[$qualifier])) $this->db->set($qualifier, $options[$qualifier]);
    }

    // MD5 the password if it is set
    if(isset($options['userPassword'])) $this->db->set('userPassword', md5($options['userPassword']));

    // Execute the query
    $this->db->insert('users');

    // Return the ID of the inserted row, or false if the row could not be inserted
    return $this->db->insert_id();
}

/**
* UpdateUser method alters a record in the users table.
*
* Option: Values
* --------------
* userId            the ID of the user record that will be updated
* userEmail
* userPassword
* userName
* userStatus        active(default), inactive, deleted
*
* @param array $options
* @return int affected_rows()
*/
function UpdateUser($options = array())
{
    // required values
    if(!$this->_required(array('userId'), $options)) return false;

    // qualification (make sure that we're not allowing the site to update data that it shouldn't)
    $qualificationArray = array('userEmail', 'userName', 'userStatus');
    foreach($qualificationArray as $qualifier)
    {
        if(isset($options[$qualifier])) $this->db->set($qualifier, $options[$qualifier]);
    }

    $this->db->where('userId', $options['userId']);

    // MD5 the password if it is set
    if(isset($options['userPassword'])) $this->db->set('userPassword', md5($options['userPassword']));

    // Execute the query
    $this->db->update('users');

    // Return the number of rows updated, or false if the row could not be inserted
    return $this->db->affected_rows();
}

/**
* GetUsers method returns an array of qualified user record objects
*
* Option: Values
* --------------
* userId
* userEmail
* userStatus
* limit             limits the number of returned records
* offset                how many records to bypass before returning a record (limit required)
* sortBy                determines which column the sort takes place
* sortDirection     (asc, desc) sort ascending or descending (sortBy required)
*
* Returns (array of objects)
* --------------------------
* userId
* userEmail
* userName
* userStatus
*
* @param array $options
* @return array result()
*/
function GetUsers($options = array())
{
    // default values
    $options = $this->_default(array('sortDirection' => 'asc'), $options);

    // Add where clauses to query
    $qualificationArray = array('userId', 'userEmail', 'userStatus');
    foreach($qualificationArray as $qualifier)
    {
        if(isset($options[$qualifier])) $this->db->where($qualifier, $options[$qualifier]);
    }

    // If limit / offset are declared (usually for pagination) then we need to take them into account
    if(isset($options['limit']) && isset($options['offset'])) $this->db->limit($options['limit'], $options['offset']);
    else if(isset($options['limit'])) $this->db->limit($options['limit']);

    // sort
    if(isset($options['sortBy'])) $this->db->order_by($options['sortBy'], $options['sortDirection']);

    $query = $this->db->get('users');
    if($query->num_rows() == 0) return false;

    if(isset($options['userId']) && isset($options['userEmail']))
    {
        // If we know that we're returning a singular record, then let's just return the object
        return $query->row(0);
    }
    else
    {
        // If we could be returning any number of records then we'll need to do so as an array of objects
        return $query->result();
    }
}

/**
* DeleteUser method removes a record from the users table
*
* @param array $options
*/
function DeleteUser($options = array())
{
    // required values
    if(!$this->_required(array('userId'), $options)) return false;

    $this->db->where('userId', $options['userId']);
    $this->db->delete('users');
}

Aqui estão alguns exemplos de como podemos utilizar estes méotod para interajir com seu banco de dados:

Adicionando um usuario

$userId = $this->user_model->AddUser($_POST);

if($userId)
    echo "The user you have created has been added successfully with ID #" . $userId;
else
    echo "There was an error adding your user.";

Atualizando um Usuário

if($this->user_model->UpdateUser(array('userId' => 3, 'userName' => 'Shawn', 'userEmail' => 'not telling')))
    // The user has been successfully updated
else
    // The user was not updated

Autenticação de Usuário (retornando um usuário único)

$user = $this->user_model->GetUsers(array('userEmail' => $userEmail, 'userPassword' => md5($userPassword), 'userStatus' => 'active'));
if($user)
    // Log the user in
else
    // Sorry, your user / password combination isn't correct.

Buscar um grupo de usuários

$users = $this->user_model->GetUsers(array('userStatus' => 'active'));

if($users)
{
    echo "Active Users<br />";
    foreach($users as $user)
    {
        echo $user->userName . "<br />";
    }
}
else
{
    echo "There are no active users.";
}

Apagando um usuário

$this->user_model->DeleteUser(array('userId' => $userId));

Espero que isso continue um diálogo sobre a melhor forma de escrever códigos para serem reaproveitados. Se você tiver alguma sugestão ou comentário, por favor me avise. Eu adoraria ouvir sobre os algoritmos que você está projetando para lidar com problemas semelhantes.

Autor: http://shawnmccool.com/

Fim!

Lembrando que este é um artigo traduzido e não é de minha autoria, mas como achei muito interessante estou disponibilizando aqui! 😀

9 Comentários

  1. Ultimamente meus models são sempre no padrão DataMapper, mas seu post É é muito bom para quem quer manter o padrão CI. Valew por compartilhar.

    Uma pequena obs. nas funções eu uso o padrão do CI (get_user ao invés de getUser)!

    Abs.

    • williamhrssays:

      Bacana, Eu pessoalmente não sou muito fan de ORM ainda, tenho que estudar mais a fundo, e em questão ao get_user nao curto mesmo, eu so escrevo função com camelCase.

  2. Olá William, seu artigo está ótimo, porém eu tenho uma dúvida. Gostaria de criar um model padrão para todos os outros, pensei em criar uma classe abstrata, porém após algumas pesquisas e testes tive alguns problemas, gostaria de saber se você tem alguma idéia sobre isso. Obrigado cara.

  3. Muito bom. Adotei esse estilo para os meus models.

    Só que ainda não tô entendendo como fazer consultas com LIKE. Se eu tiver um campo de busca no site, gostaria de fazer uma busca no bd usando LIKE e não sei como fazer utilizando este padrão.

    Tem algum idéia?

    • Denessays:

      Leandro, você pode fazer tais consultas usando o active record do codeigniter dessa forma:
      $this->db->like(‘title’, ‘match’);
      // Produz: WHERE title LIKE ‘%match%’.
      Qualquer dúvida, veja o guia do usuário do codeigniter na seção active record class.

  4. Marcelo Fabianosays:

    Parabéns pelo post msm sendo uma tradução iniciativas assim fazem a diferença !!! Parabéns

  5. Sensacional cara, só uma dúvida, pois estou começando com MVC, desculpe minha ignorância. Neste caso como irei usar este modelo em vários controles? Abraço

  6. Danilo Causersays:

    Obrigado por compartilhar com a gente William. Ainda estou apanhando pra entender o code igniter, mas acho que estou começando a pegar o jeito!

Deixe um comentário

Por favor, seja educado. Seu e-mail não será publicado e os campos obrigatórios estão marcados.