View all posts

Consultas “tipo CakePHP” en CodeIgniter

Una de las mayores facilidades con la que cuenta CakePHP es la forma en que maneja los modelos y las búsquedas en ellos. En este artículo vamos a tomar el núcleo de los modelos de CakePHP para aplicarlo a CodeIgniter y así obtener búsquedas más sencillas en este último framework.

Los Modelos de CakePHP

El concepto de CakePHP es simple: Los módelos no son archivos de funciones que consultan la base de datos si no que son mapas de datos, clases que contienen arreglos cuya estructura es una abstracción de los campos, restricciones y relaciones de las tablas en la base de datos.

Todos los modelos heredan el método find de la clase Model de CakePHP. El find es una función super poderosa que permite hacer casi cualquier consulta a la base de datos solamente variando los parámetros que se envían. Acá está la magia. El find de CakePHP evita tener que hacer una función distinta en el modelo para cada consulta que vayamos a hacer sobre cada tabla de la base de datos, y eso nos puede ahorrar muchísimo tiempo de desarrollo.

Traduciendo a CodeIgniter

En CodeIgniter (CI, para abreviar), los modelos son simples clases que deberían tener las funciones que realizarán consultas directas a la base de datos. Estas clases extienden a la librería CI_Model del núcleo de CI. Así, si tenemos una tabla usuarios y otra tabla publicaciones y queremos una consulta que nos traiga todos los elementos de cada tabla, tendríamos que crear la misma función en dos archivos diferentes, en dos modelos diferentes, con la única diferencia de la tabla a la cual consultan.

Podemos evitar esto usando Mapeo de Datos. Solamente tenemos que modificar la clase CI_Model del núcleo de CI para agregarle funciones que mediante parámentros reciban toda la información y puedan actuar sobre las distintas tablas de la base de datos.

Sin embargo, es cierto que modificar el núcelo de un framework (y en general de cualquier sistema que no hayamos creado) nunca es una buena idea. Afortunadamente, CI permite crear extensiones o reemplazar del todo sus librerías del núcleo de una forma sencilla: Si queremos extender la librería del núcleo CI_Model simplemente se coloca en la carpeta application/core un archivo llamado MY_Model (el prefijo MY_ es configurable). Este archivo debe contener una clase que extienda a CI_Model y ahí podemos agregar nuestras funciones.

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

class KAPS_Model extends CI_Model 
{
    private $_table_prefix;

    function __construct()
    {
        $this--->_table_prefix = 'my_table_prefix_';
        //IMPORTANT: runs the constructor of the CI_Model class 
        parent::__construct();
    }

    /**
     * 
     * create a query to de database and return it's results
     * @param String $type [all|first|count|list]
     * @param Array $params array([conditions|fields|order|limit|join])
     * @return Object or Array
     */
    function find($type,$params=array())
    {
        //result of the query
        $result = FALSE;

        //set the name of the table where the query will run
        $this-&gt;db-&gt;from($this-&gt;_table_prefix.$this-&gt;table);

        //creates the where clause if $params['conditions'] is defined
        if(isset($params['conditions']))
        {
            //where options =&gt; array('name !=' =&gt; $name, 'id &lt;' =&gt; $id, 'date &gt;' =&gt; $date, 'field'=&gt;$value);
            $this-&gt;db-&gt;where($params['conditions']); 
        }

        //creates the join clause if $params['join'] is defined
        if(isset($params['join']))
        {
            foreach($params['join']['clause'] as $join_table =&gt; $join_conditions)
            {
                if(isset($params['join']['type']))
                    $this-&gt;db-&gt;join($join_table, $join_conditions, $params['join']['type']);
                else
                    $this-&gt;db-&gt;join($join_table, $join_conditions);
            }
        }

        //creates the fields clause if $params['fields'] is defined
        if(isset($params['fields']))
        {
            $this-&gt;db-&gt;select($params['fields']);
        }

        //creates the order clause if $params['order'] is defined
        if(isset($params['order']))
        {
            foreach($params['order'] as $field =&gt; $sort)
            {
                $this-&gt;db-&gt;order_by($field,$sort);
            }
        }

        //creates the group clause if $params['group'] is defined
        if(isset($params['group']))
        {
            $this-&gt;db-&gt;group_by($params['group']);
        }

        if($type=='count')
            $result = $this-&gt;db-&gt;count_all_results();
        else
        {
            //creates the limit clause if $params['limit'] is defined and type!=first
            if($type=='first')
                $this-&gt;db-&gt;limit(1);
            else if(isset($params['limit']) &amp;&amp; !empty($params['limit']))
            {
                if(is_array($params['limit']))
                {
                    $cnt_params = count($params['limit']);

                    if($cnt_params)
                    {
                        if($cnt_params==1)
                            $this-&gt;db-&gt;limit($params['limit'][0]);
                        else
                            $this-&gt;db-&gt;limit($params['limit'][0],$params['limit'][1]);
                    }
                }
                else
                    $this-&gt;db-&gt;limit($params['limit']);
            }

            $query = $this-&gt;db-&gt;get();

            switch($type)
            {
                case 'list':
                {
                    $result = array();
                    //return the result in array format
                    $tmp_result = $query-&gt;result_array();
                    $keys = array_values($params['fields']);

                    foreach($tmp_result as $tmp)
                    {
                        $result[$tmp[$keys[0]]] = $tmp[$keys[1]];
                    }
                    break;
                }
                case 'first':
                {
                    //return just the first result in object format
                    $result = $query-&gt;row();
                    break;
                }
                default:
                {
                    //return the results in object format
                    $result = $query-&gt;result();
                }
            }
        }

        return $result;
    }

    /**
     * 
     * saves or updates a record in the database
     * @param Array $params fields to be saved
     * @return Int the id of the modified record in the database if success, FALSE otherwise
     */
    function save($params)
    {
        $already_exists = FALSE;

        if(count($params))
        {
            if(isset($params['id']))
            {
                //check if the record already exists in the database
                $found_record = $this-&gt;find('count', array('conditions'=&gt;array('id'=&gt;$params['id'])));
                if($found_record)
                    $already_exists = TRUE;
            }

            //set the fields to save
            $this-&gt;db-&gt;set($params);

            //if the record already exists, just do an update, otherwise, do an insert
            if($already_exists)
            {
                $this-&gt;db-&gt;where('id',$params['id']);
                if($this-&gt;db-&gt;update($this-&gt;_table_prefix.$this-&gt;table))
                    return $params['id'];
                else
                    return FALSE;
            }
            else
            {
                $this-&gt;db-&gt;insert($this-&gt;_table_prefix.$this-&gt;table); 

                if($this-&gt;db-&gt;affected_rows())
                {
                    //make a query to find the id of the newly inserted record
                    $new_record_id = $this-&gt;find('first',array('fields'=&gt;array('id'),'order'=&gt;array('id'=&gt;'DESC')));
                    return $new_record_id-&gt;id;
                }
                else
                    return FALSE;
            }

        }

        return FALSE;
    }

    /**
     *
     * deletes records in the database
     * @param Array $params conditions to match the record(s) that will be deleted
     * @return INT the number of deleted rows if success, FALSE otherwise 
     */
    function delete($params)
    {
        if($this-&gt;db-&gt;delete($this-&gt;_table_prefix.$this-&gt;table, $params))
            return $this-&gt;db-&gt;affected_rows();
        else
            return FALSE;

    }
}
?&gt;

De este modo, el modelo de usuarios sería construido así:

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

/**
 * User Model
 *
 * @package		CodeIgniter
 * @author		Daniel Obando
 * @subpackage	Models
 * @category	Models
 * @copyright  Copyright (c) 2012, IGD.
 * @version 1.0
 * 
 */

class User_model extends MY_Model 
{
    public $table;

    function __construct()
    {
        parent::__construct();
        //IMPORTANT: Tells MY_Model which table are we going to query
        $this--->table = 'user';
    }

}
?&gt;

Así de sencillo. Ahora, si desde un controlador queremos hacer una consulta, lo hacemos en la forma CakePHP:

//load the model
$this-&gt;load-&gt;model('User_model','User');

//run a query to find all the users in the table
$users = $this-&gt;User-&gt;find('all',array(
     'conditions'=&gt;array(
         'is_active'=&gt;1
      ),
      'limit'=&gt;10,
      'order'=&gt;array(
         'name'=&gt;'DESC'
      )
));

Y esta misma función find estará disponible para cada modelo que creemos. Similarmente, con la nueva librería también tendremos disponibles funciones para eliminar o salvar nuevos registros en la base de datos.

//save a record in the database
$user_id = $this-&gt;User-&gt;save(array('name'=&gt;'User1','is_active'=&gt;0));

//update a record in the database using the same SAVE function
$this-&gt;User-&gt;save(array('id'=&gt;$user_id, 'name'=&gt;'User2'));

//delete a record, accept an array with all the params to match with the record to delete
$this-&gt;User-&gt;delete(array('id'=&gt;$user_id));

Para más información, puede consultar la guía de consultas de CakePHP y el manual de CodeIgniter sobre extenciones a las librerías del núcleo.

¿Y si vamos aún más lejos?

Sí, sería perfectamente posible también copiar la estructura de modelos que tiene CakePHP en CodeIgniter. Para ello, recomendamos la librería DataMapper. En palabras del autor:

El DataMapper es un Mapeador Relacional de Objetos escrito en PHP para CodeIgniter. Está diseñado para mapear sus tablas de base de datos a objetos fáciles de trabajar, encargándose por completo de las relaciones entre cada uno de ellos.

Este es solo un ejemplo de como se pueden tomar algunas buenas ideas y unirlas para crear algo poderoso. Es importante estar pendiente de las genialidades que aparecen todos los días en el mundo web, de modo que podamos empezar un proceso de investigación y pruebas para finalmente implementarlas en nuestros proyectos y obtener los másximos réditos de ellas.