CRUD Básico com CodeIgniter 1.7.1

Atenção: existe uma atualização para este post: CRUD Básico com CodeIgniter 1.7.2

Estou há algumas semanas utilizando o framework PHP CodeIgniter. A documentação é incrível, muito detalhada e organizada, com muitos exemplos simples, porém poucos exemplos completos, voltados para o mundo real. Senti falta de modelos de CRUD completos, por exemplo.

Mesmo procurando em outros sites, blogs e fóruns, só vi implementações muito básicas, ou utilizando libraries antigas. O melhor que encontrei foi este, que usa uma versão anterior do CI (utiliza a classe Validation, que está deprecated). Baseado nesse exemplo, criei um outro modelo de CRUD com paginação, validação e um sistema de exibição de mensagens utilizando o CodeIgniter 1.7.1. É o exemplo que eu gostaria de ter visto quando comecei a conhecer o framework.

Antes de continuar, veja o exemplo funcionando aqui.

Disponibilizei o código fonte completo, junto com o codeigniter e o script de criação do banco aqui.

Decidi deixar o conteúdo e os comentários em inglês para alcançar mais pessoas.

Passo 1 – Criação do banco de dados

Execute o script abaixo (utilizando o phpMyAdmin, por exemplo) para criar o banco “ci_sandbox“, criar e popular a tabela “users” no MySQL 5. Note que estou utilizando codificação UTF-8, que vem configurada por padrão no CodeIgniter (no arquivo application/config/database.php).

CREATE DATABASE `ci_sandbox` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(100) NOT NULL,
  `email` varchar(100) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `users` (`id`, `name`, `email`) VALUES
(1, 'Bob', 'bob@bob.com'),
(2, 'Philip', 'philip@philip.com'),
(3, 'Paty', 'paty@paty.com'),
(4, 'Kelly', 'kelly@kelly.com'),
(5, 'Bill', 'bill@bill.com'),
(6, 'Tedd', 'tedd@tedd.com'),
(7, 'John', 'john@john.com'),
(8, 'Paul', 'paul@paul.com'),
(9, 'Anna', 'anna@anna.com'),
(10, 'Hellen', 'hellen@hellen.com'),
(11, 'Kate', 'kate@kate.com');

Passo 2 – Criação do CSS da aplicação

Veja abaixo a folha de estilo usada na listagem, paginação e mensagens (arquivo css/style.css):

*{
    margin: 0;
    padding: 0;
}

body {
    background:#FFFFFF;
    font-family:verdana,arial,helvetica,sans-serif;
    font-size:10px;
    margin:2px;
    padding:2px;
}

#container h1 {
    color:#678197;
    border: 2px solid #E5EFF8;
    margin-bottom: 10px;
    padding: 5px;
    width: 686px;
    _width: 700px;
    background-color: #F2F6F7;
    font-size: 20px;
}

#listing {
    width:700px;
    color:#678197;
    font-family:verdana,arial,helvetica,sans-serif;
    font-size:8pt;
    border-spacing: 0px;
    border: 1px solid #E5EFF8;
}

#listing td {
    padding:5px;
    font-size:8pt;
    border: 1px solid #E5EFF8;
}
#listing th {
    color:#21497D;
    font-weight:bold;
    background:#F2F6F7;
    font-size:8pt;
    padding:5px;
    text-align: left;
    border: 1px solid #E5EFF8;
}

ul {
    border:0; margin:0; padding:0;
}

#pagination li {
    border:0; margin:0; padding:0;
    font-size:11px;
    list-style:none;
    float:left;
}

#pagination a {
    border:solid 1px #DDDDDD;
    margin-right:2px;
}

#pagination .previous-off, #pagination .next-off {
    color:#666666;
    display:block;
    float:left;
    font-weight:bold;
    padding:3px 4px;
}

#pagination .next a, #pagination .previous a {
    font-weight:bold;
    border:solid 1px #FFFFFF;
}

#pagination .active {
    color:#ff0084;
    font-weight:bold;
    display:block;
    float:left;
    padding:4px 6px;
}

#pagination a:link, #pagination a:visited {
    color:#678197;
    display:block;
    float:left;
    padding:3px 6px;
    text-decoration:none;
}

#pagination a:hover {
    border:solid 1px #666666;
}

.error_field {
    padding:5px;
    margin-bottom:3px;
    margin-top:3px;
    border: 1px solid red;
    background: yellow;
    width: 300px;
}

/* Content Elements: Messages */

.message, .warning, .success, .error {
    width: 690px;
    _width: 700px;
    padding: 5px;
    margin-bottom: 3px;
    font-size: 12px;
}

.message {
    color: #ffffff;
    background-color: #aaa9a6;
}

.warning {
    color: #ffffff;
    background-color: #ff9900;
}

.success {
    color: #ffffff;
    background-color: #009000;
}

.error {
    color: #ffffff;
    background-color: #900000;
}

Passo 3 – Instalação da Library e do Helper Message

Para facilitar o envio e a exibição de mensagens para o usuário, encontrei esta prática biblioteca chamada Message no Wiki (confira) do Codeigniter, e no fórum encontrei um Helper (confira) para reduzir a exibição de código nas views.

Crie o arquivo application/libraries/messages.php com o conteúdo abaixo:

<?php if (!defined('BASEPATH')) exit('No direct script access allowed');

/**
* Message:: a class for writing feedback message information to the session
*
* Copyright 2006 Vijay Mahrra &amp; Sheikh Ahmed <webmaster@designbyfail.com>
*
* See the enclosed file COPYING for license information (LGPL).  If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author  Vijay Mahrra &amp; Sheikh Ahmed <webmaster@designbyfail.com>
* @url http://www.designbyfail.com/
* @version 1.0
*/

class Messages
{
    var $_ci;
    var $_types = array('success', 'error', 'warning', 'message');

    function Messages($params = array())
    {
        $this->_ci =&amp; get_instance();
        $this->_ci->load->library('session');
        // check if theres already messages, if not, initialise the messages array in the session
        $messages = $this->_ci->session->userdata('messages');
        if (empty($messages)) {
            $this->clear();
        }
    }

    // clear all messages
    function clear()
    {
        $messages = array();
        foreach ($this->_types as $type) {
            $messages[$type] = array();
        }
        $this->_ci->session->set_userdata('messages', $messages);
    }

    // add a message, default type is message
    function add($message, $type = 'message')
    {
        $messages = $this->_ci->session->userdata('messages');
        // handle PEAR errors gracefully
        if (is_a($message, 'PEAR_Error')) {
            $message = $message->getMessage();
            $type = 'error';
        } else if (!in_array($type, $this->_types)) {
            // set the type to message if the user specified a type that's unknown
            $type = 'message';
        }
        // don't repeat messages!
        if (!in_array($message, $messages[$type]) &amp;&amp; is_string($message)) {
            $messages[$type][] = $message;
        }
        $messages = $this->_ci->session->set_userdata('messages', $messages);
    }

    // return messages of given type or all types, return false if none
    function sum($type = null)
    {
        $messages = $this->_ci->session->userdata('messages');
        if (!empty($type)) {
            $i = count($messages[$type]);
            return $i;
        }
        $i = 0;
        foreach ($this->_types as $type) {
            $i += count($messages[$type]);
        }
        return $i;
    }

    // return messages of given type or all types, return false if none, clearing stack
    function get($type = null)
    {
        $messages = $this->_ci->session->userdata('messages');
        if (!empty($type)) {
            if (count($messages[$type]) == 0) {
                return false;
            }
            return $messages[$type];
        }
        // return false if there actually are no messages in the session
        $i = 0;
        foreach ($this->_types as $type) {
            $i += count($messages[$type]);
        }
        if ($i == 0) {
            return false;
        }

        // order return by order of type array above
        // i.e. success, error, warning and then informational messages last
        foreach ($this->_types as $type) {
            $return[$type] = $messages[$type];
        }
        $this->clear();
        return $return;
    }
}

Crie o arquivo application/helpers/msg_helper.php com o conteúdo abaixo:

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

if ( ! function_exists('output_msg')) {
    function output_msg($type = null) {
        $CI =&amp; get_instance();

        if ($CI->messages->sum($type) > 0) {
        $messages = $CI->messages->get($type);
        // display all messages of the type
        if (is_array($messages)) {
            $output = '';
            foreach ($messages as $type => $msgs) {
                if (count($msgs) > 0) {
                    $output .= '<div class=&quot;' . $type . '&quot;>';
                    $output .= '<ul>';
                    foreach ($msgs as $message) {
                        $output .= $message;
                    }
                    $output .= '</ul>';
                    $output .= '</div>';
                }
            }
        }
        return $output;
        }
    }
}

Passo 4 – Criação do Model

O model anêmico abaixo (que está mais para um Active Record Layer), incrivelmente reusável, foi inspirado no copiado do exemplo de CRUD que mencionei acima. Controller gordo + Model magro é considerado um anti-pattern, mas eu gosto que as coisas estejam distribuídas assim, e o CodeIgniter, pelo que vi no manual, também. (O que você acha?)

Arquivo application/models/user_model.php

<?php
class User_model extends Model {

    private $table = 'users';

    function User_model() {
        parent::Model();
    }

    function list_all($order_by = 'id') {
        $this->db->order_by($order_by,'asc');
        return $this->db->get($table);
    }

    function count_all() {
        return $this->db->count_all($this->table);
    }

    function get_paged_list($limit = 10, $offset = 0, $order_by = 'id') {
        $this->db->order_by($order_by,'asc');
        return $this->db->get($this->table, $limit, $offset);
    }

    function get_by_id($id) {
        $this->db->where('id', $id);
        return $this->db->get($this->table);
    }

    function save($obj) {
        $this->db->insert($this->table, $obj);
        return $this->db->insert_id();
    }

    function update($id, $obj) {
        $this->db->where('id', $id);
        $this->db->update($this->table, $obj);
    }

    function delete($id) {
        $this->db->where('id', $id);
        $this->db->delete($this->table);
    }
}
?>

Passo 5 – Criação da view para listagem

Nesta view é exibida a listagem paginada. Note a simplicidade da exibição de mensagens através da função output_msg, do Helper que instalamos acima.

Arquivo application/views/user_list.php

<html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;>
<head>
    <meta http-equiv=&quot;content-type&quot; content=&quot;text/html; charset=utf-8&quot; />
    <title>User List</title>
    <link href=&quot;<?php echo base_url(); ?>css/style.css&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; />
    <script>
        function remove(id) {
            if (confirm(&quot;Are you sure?&quot;)) {
                window.location = '<?php echo base_url(); ?>user/delete/'+id;
            }
        }
    </script>
</head>

<body>

<?php
    // show user messages sent by controller
    echo output_msg($type = null);
?>

<div id=&quot;container&quot;>
    <h1>USER LIST</h1>
    <table id=&quot;listing&quot; cellspacing=0>
        <tr>
            <th width=45%>Name</th>
            <th width=45%>E-mail</th>
            <th width=10%>Actions</th>
        </tr>
        <?php foreach($list as $user): ?>
            <tr>
                <td><?php echo $user->name; ?></td>
                <td><?php echo $user->email; ?></td>
                <td align=&quot;center&quot;>
                    <a href=&quot;<?php echo base_url(); ?>user/prepareUpdate/<?php echo $user->id; ?>&quot;><img src=&quot;<?php echo base_url(); ?>i/icon/b_edit.png&quot; border=0 title=&quot;Edit&quot;></a>
                    <a href=&quot;#&quot; onclick=&quot;remove(<?php echo $user->id; ?>);&quot;><img src=&quot;<?php echo base_url(); ?>i/icon/b_drop.png&quot; border=0 title=&quot;Delete&quot;></a>
                </td>
            </tr>
        <?php endforeach ?>
    </table>
    <div style=&quot;padding:10px;&quot;><?php echo $pagination; ?><br clear=&quot;all&quot; /></div>
    <input type=button onclick=&quot;location.href='<?php echo base_url(); ?>user/prepareInsert/';&quot; value=&quot;New User&quot;>
</div>

</body>
</html>

Passo 6 – Criação da view contendo o formulário de cadastro e edição

Esta view contém o formulário que será exibido para criar e editar usuários. Observe o uso da variável $values, que contém as informações para popular os campos com os dados do item.

Arquivo application/views/user_form.php

<html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;>
<head>
    <meta http-equiv=&quot;content-type&quot; content=&quot;text/html; charset=utf-8&quot; />
    <title><?php echo $title; ?></title>
    <link href=&quot;<?php echo base_url(); ?>css/style.css&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; />
</head>

<body>

<?php
    // show user messages sent by controller
    echo output_msg($type = null);
?>

<form method=&quot;post&quot; action=&quot;<?php echo $action; ?>&quot;>
    <div id=&quot;container&quot;>
        <h1><?php echo $title; ?></h1>
                <label>Name*</label><br>
                <input type=&quot;text&quot; name=&quot;name&quot; value=&quot;<?php echo $values['name']; ?>&quot; style=&quot;width:500px;&quot; maxlength=&quot;100&quot;>
                <?php echo form_error('name'); ?><br><br>

                <label>E-mail*</label><br>
                <input type=&quot;text&quot; name=&quot;email&quot; value=&quot;<?php echo $values['email']; ?>&quot; style=&quot;width:500px;&quot; maxlength=&quot;100&quot;>
                <?php echo form_error('email'); ?><br><br>

                <input type=submit value=&quot; Save &quot;>

                <input type=button onclick=&quot;location.href='<?php echo base_url(); ?>user/index/';&quot; value=&quot;Back to listing&quot; style=&quot;margin-left:20px;&quot;>
    </div>
    <input type=hidden name=id value=&quot;<?php echo $values['id']; ?>&quot;>
</form>
</body>
</html>

Passo 7 – O Controller

O controller começa com duas variáveis de configuração: quantidade de linhas exibidas por página na listagem ($limit) e nome da coluna que determinará a ordenação da listagem ($order_by).

No construtor, carregamos as libraries e helpers necessários.

Confira o papel de cada método:

O index() prepara os dados da paginação, invoca os dados do modelo e transfere esses dados para a view user_list.php.

O prepareInsert() monta um formulário de cadastro em branco.

O prepareUpdate() consulta o modelo e monta um formulário preenchido com os dados do $id solicitado.

O insert() e o update() são apenas chamadas para o _save(). Foram criados assim para facilitar a visualização e compreensão do fluxo da aplicação.

O método privado _save() cria ou edita um item, de acordo com a $action solicitada. Nesse método são configuradas as regras de validação dos dados vindos do formulário.

O delete() exclui o item com o $id informado, e retorna para a listagem de usuários.

Arquivo application/controllers/user.php

<?php
    class User extends Controller {

        // records per page
        private $limit = 5;

        // column to order by at listing
        private $order_by = 'name';

        function User()  {
            parent::Controller();

            $this->load->database();
            $this->load->library('form_validation');
            $this->load->helper('url');

            // send and show messages to user
            $this->load->library('messages');
            $this->load->helper('msg');

            $this->load->model('user_model', '', TRUE);
        }

        function index($offset = 0) {
            $data = array();

            // http://localhost/ci_sandbox/index/(offset)
            $uri_segment = 3;
            // where this page begins
            $offset = $this->uri->segment($uri_segment);

            // load data list
            $data['list'] = $this->user_model->get_paged_list($this->limit, $offset, $this->order_by)->result();

            // generate pagination
            $this->load->library('pagination');
            $config['base_url'] = site_url('user/index/');
             $config['total_rows'] = $this->user_model->count_all();
             $config['per_page'] = $this->limit;
            $config['uri_segment'] = $uri_segment;
            $this->pagination->initialize($config);
            $data['pagination'] = $this->pagination->create_links();

            $this->load->view('user_list',$data);

        }

        function prepareInsert() {
            // set validation properties
            $data['values']['id'] = '';
            $data['values']['name'] = '';
            $data['values']['email'] = '';

            // set common properties
            $data['title'] = 'NEW USER';
            $data['action'] = site_url('user/insert');

            $this->load->view('user_form', $data);
        }

        function prepareUpdate($id) {

            // prefill form values
            $obj = $this->user_model->get_by_id($id)->row();
            $data['values']['id'] = $id;
            $data['values']['name'] = $obj->name;
            $data['values']['email'] = $obj->email;

            // set common properties
            $data['title'] = 'EDIT USER';
            $data['action'] = site_url('user/update');

            $this->load->view('user_form', $data);
        }

        function insert() {
            $this->_save('insert');
        }

        function update() {
            $this->_save('update');
        }

        function _save($action = 'insert') {
            // set common properties
            if ($action == 'update') {
                $data['title'] = 'EDIT USER';
                $data['action'] = site_url('user/update');
            } else {
                $data['title'] = 'NEW USER';
                $data['action'] = site_url('user/insert');
            }

            // set validation properties
            $this->form_validation->set_rules('id','','');
            $this->form_validation->set_rules('name','Name','trim|required|max_length[100]|xss_clean');
            $this->form_validation->set_rules('email','E-mail','trim|required|max_length[100]|valid_email|xss_clean');
            $this->form_validation->set_error_delimiters('<p class=&quot;error_field&quot;>', '</p>');

            // run validation
            if ($this->form_validation->run() == FALSE) {
                $data['values']['name'] = set_value('name');
                $data['values']['email'] = set_value('email');
                $data['values']['id'] = set_value('id');

                $this->load->view('user_form', $data);
            } else {
                // save data
                if ($action == 'update') {
                    $id = $this->input->post('id');
                }
                $obj = array(
                    'name' => $this->input->post('name'),
                    'email' => $this->input->post('email')
                );
                if ($action == 'update') {
                    $this->user_model->update($id, $obj);
                } else {
                    $id = $this->user_model->save($obj);
                }

                // set user message
                if ($action == 'update') {
                    $this->messages->add('User updated', 'success');
                } else {
                    $this->messages->add('User created', 'success');
                }

                // redirect to list page
                redirect('user/index/','refresh');
            }

        }

        function delete($id) {
            $this->user_model->delete($id);

            // set user message
            $this->messages->add('User removed', 'success');

            // redirect to list page
            redirect('user/index/','refresh');
        }

    }
?>

Espero que esse exemplo simples seja útil para quem está iniciando, e que os mais experientes comentem e sugiram melhorias.

8 já comentaram sobre “CRUD Básico com CodeIgniter 1.7.1”

  1. Muito legal Bob este post… se garantiu. Bem didático e objetivo.

  2. Realmente, Bob, o CodeIgniter carece de exemplos “passo-a-passo” (os chamado cookbooks) da coisa funcionando mesmo.

    Apesar da documentação ser muito boa, você fica ainda dependente da comunidade para esclarecer conceitos básicos simples do FrameWork. Seu artigo vem a calhar muito bem, pois engloba um CRUD básico e está muito bem descrito.

    Se todos que aprendessem PHP já começassem utilizando um FrameWork como o CodeIgniter, com certeza a “má fama” do PHP como linguagem de “novato” daria lugar ao que ela realmente é: uma linguagem poderosa e de fácil manutenção.

  3. Nice example!

    Is it possible that set_value() doesn’t return a value when validation rules are setup in a config file?

    I tried your example but instead of defining the rules inside the controller, I defined them in a config file, using the controller/method auto-detection method. (as explained in the CI 1.7.1 userguide > Form Validation)

  4. correction, it does work with a config file but I could only make it work if I called the config file like this:

    $this->form_validation->run(’save’)

    Automatic rule detection ( $config = array(’user/save’ = >array(…)) didn’t work. I think it’s because the form doesn’t post directly to the controller method that does the validation.

  5. [...] Fiz algumas melhorias no código do post anterior: [...]

  6. Valeu, Adail.

  7. Henrique Gogó,

    É questão de tempo. Nas últimas semanas tenho visto muitos tutoriais e exemplos de CodeIgniter pipocando por aí.

    Sobre a qualidade dos códigos, os novos frameworks estão educando os programadores PHP. Isso também está melhorando.

    Um abraço.

  8. Joris,

    You’re right about the use of set_value. There is a new version of this example using set_value and CI 1.7.2.

    Thanks for the comments.

Leave a Reply

Lomadee, uma nova espécie na web. A maior plataforma de afiliados da América Latina