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 & 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 & 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 =& 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]) && 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 =& 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="' . $type . '">';
$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="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>User List</title>
<link href="<?php echo base_url(); ?>css/style.css" rel="stylesheet" type="text/css" />
<script>
function remove(id) {
if (confirm("Are you sure?")) {
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="container">
<h1>USER LIST</h1>
<table id="listing" 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="center">
<a href="<?php echo base_url(); ?>user/prepareUpdate/<?php echo $user->id; ?>"><img src="<?php echo base_url(); ?>i/icon/b_edit.png" border=0 title="Edit"></a>
<a href="#" onclick="remove(<?php echo $user->id; ?>);"><img src="<?php echo base_url(); ?>i/icon/b_drop.png" border=0 title="Delete"></a>
</td>
</tr>
<?php endforeach ?>
</table>
<div style="padding:10px;"><?php echo $pagination; ?><br clear="all" /></div>
<input type=button onclick="location.href='<?php echo base_url(); ?>user/prepareInsert/';" value="New User">
</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="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title><?php echo $title; ?></title>
<link href="<?php echo base_url(); ?>css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<?php
// show user messages sent by controller
echo output_msg($type = null);
?>
<form method="post" action="<?php echo $action; ?>">
<div id="container">
<h1><?php echo $title; ?></h1>
<label>Name*</label><br>
<input type="text" name="name" value="<?php echo $values['name']; ?>" style="width:500px;" maxlength="100">
<?php echo form_error('name'); ?><br><br>
<label>E-mail*</label><br>
<input type="text" name="email" value="<?php echo $values['email']; ?>" style="width:500px;" maxlength="100">
<?php echo form_error('email'); ?><br><br>
<input type=submit value=" Save ">
<input type=button onclick="location.href='<?php echo base_url(); ?>user/index/';" value="Back to listing" style="margin-left:20px;">
</div>
<input type=hidden name=id value="<?php echo $values['id']; ?>">
</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="error_field">', '</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.
Categorias: CodeIgniter, PHP
Muito legal Bob este post… se garantiu. Bem didático e objetivo.
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.
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)
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.
[...] Fiz algumas melhorias no código do post anterior: [...]
Valeu, Adail.
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.
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.