DAO personalizado


1Presentación

Como has visto en la introducción y en la documentación del DAO genérico, Temma ofrece un mecanismo que te permite manipular fácilmente los datos de una tabla, usando métodos genéricos.

Sin embargo, en el contexto de un proyecto "real", querrás ir más allá. Puedes elegir dos direcciones, que pueden complementarse:

  • En lugar de usar los métodos get(), search(), update(), etc., puede que quieras usar tus propios métodos, que serán más fáciles de usar.
  • Puede que necesites manipular datos almacenados en varias tablas, y por lo tanto hacer algunas uniones (joins).

En cualquiera de los dos casos, el primer paso será crear tu propio objeto DAO, que derivará del objeto estándar \Temma\Dao\Dao.


2Tabla única

2.1Creación

Aquí tienes un ejemplo de objeto DAO personalizado, que simplemente proporciona un método adicional:

class ArticleDao extends \Temma\Dao\Dao {
    /**
     * Devuelve la lista de artículos escritos hace menos de un día.
     * @return  array  Lista de arrays asociativos.
     */
    public function getLastArticles() {
        // fecha y hora correspondientes a 24 horas antes
        $yesterday = date('c', time() - 86400);

        // define el criterio de búsqueda, usando esta fecha
        $criteria = $this->criteria()
                    ->greaterThan('date', $yesterday);

        // ejecuta la búsqueda usando este criterio
        $articles = $this->search($criteria);

        // devuelve el resultado de esta búsqueda
        return ($articles);
    }
}

Puedes ver que este objeto extiende el objeto \Temma\Dao\Dao de una manera muy "ligera", añadiendo solo un método, y que este usa los criterios de búsqueda propuestos por Temma.

Para poder usarse, este objeto debe guardarse en un archivo llamado ArticleDao.php, ubicado en el directorio lib/ del proyecto.


2.2Configuración

Si necesitas especificar uno o varios parámetros de creación del DAO, puedes añadir los siguientes atributos:

  • _disableCache: Defínelo como true para desactivar el uso de la caché por parte del DAO.
  • _tableName: Nombre de la tabla que usará el DAO.
  • _dbName: Nombre de la base de datos que contiene la tabla.
  • _idField: Nombre del campo que contiene la clave primaria.
  • _fields: Array asociativo que contiene la lista de campos que se deben recuperar de la base de datos. Si los campos necesitan renombrarse, basta con declarar un par asociativo cuya clave es el nombre del campo en la tabla, y el valor asociado es el nombre por el que debe renombrarse.
  • _criteriaObject: ver más abajo

Todos estos atributos son opcionales, y deben declararse con visibilidad protected.

Ejemplo de un objeto DAO usando estos parámetros:

class ArticleDao extends \Temma\Dao\Dao {
    // desactiva la caché
    protected $_disableCache = true;
    // configura el nombre de la base de datos
    protected $_dbName = 'cms';
    // configura el nombre de la tabla a usar
    protected $_tableName = 'content';
    // configura el nombre del campo que contiene la clave primaria
    protected $_idField = 'cid';
    // lista de campos a recuperar, algunos de ellos renombrados
    protected $_fields = [
        'cid'     => 'contentId',
                     'title',
                     'login',
        'content' => 'text',
        'date'    => 'creationDate',
                     'status'
    ];

    /* el resto del código */
}

En este ejemplo podemos ver que la caché está desactivada, que se usará la tabla content de la base de datos cms, cuyo campo que contiene la clave primaria se llama cid. Podemos ver que las consultas get() y search() obtendrán 6 columnas por cada fila de la tabla (la tabla puede tener otras columnas, pero no se leerán), y que tres de esas columnas se renombrarán.


2.3Uso

Para usar el DAO que acabamos de crear, debes especificar su nombre en el controlador, usando el atributo _temmaAutoDao que ya vimos antes:

class Article extends \Temma\Web\Controller {
    /** Indica que el framework debe crear automáticamente
      * una instancia de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

    /** Acción que muestra los últimos artículos. */
    public function showLast() {
        $data = $this->_dao->getLastArticles();
        $this['articles'] = $data;
    }
}

3Criterios avanzados

En el ejemplo que vimos antes, debe entenderse que todavía es posible usar los métodos estándar del objeto \Temma\Dao\Dao: get(), search(), update(), etcétera.

Por ejemplo, podríamos escribir el controlador así:

class Article extends \Temma\Web\Controller {
    /** Indica que el framework debe crear automáticamente
     *  una instancia de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

    /** Acción que muestra los últimos artículos. */
    public function showLast() {
        $data = $this->_dao->getLastArticles();
        $this['articles'] = $data;
    }

    /** Acción que muestra los 5 artículos más antiguos
     *  que no están vacíos. */
    public function showOlders() {
        $crit = $this->_dao->criteria()->different('content', '');
        $data = $this->_dao->search($crit, sort: 'date', limitOffset: null, limit: 5);
        $this['articles'] = $data;
    }
}

Puedes ver que la primera acción usa el nuevo método getLastArticles(), mientras que la segunda acción usa el método habitual search().

Crear métodos específicos (como el método getLastArticles() de nuestro ejemplo) puede resultar bastante tedioso. En su lugar, quizá prefiramos otra forma de hacer las cosas, que consiste en extender el objeto de gestión de criterios. Al hacerlo, podemos seguir usando los métodos estándar (search(), update()) del objeto \Temma\Dao\Dao, pero escribiendo criterios de búsqueda más explícitos, porque están más cerca de la lógica de negocio.


3.1Creación de criterios

Vamos a crear un objeto ArticleDaoCriteria, que hereda del objeto \Temma\Dao\Criteria, y a añadirle criterios específicos:

class ArticleDaoCriteria extends \Temma\Dao\Criteria {
    /** Criterio que toma los artículos que se han escrito
     *  hace menos de 24 horas. */
    public function mostRecent() {
        // crea un criterio simple
        $yesterday = date('c', time() - 86400);
        $this->greaterThan('date', $yesterday);

        // siempre devuelve la instancia del objeto actual
        return ($this);
    }

    /** Criterio que toma los artículos que hablan sobre PHP o
     *  sobre Linux. */
    public function aboutRealStuff() {
        // en caso de criterios combinados por un "OR" lógico
        // (y no un "AND" lógico), hay que crear un
        // subobjeto de criterios, especificando el tipo de
        // combinación entre los criterios
        $subCrit = $this->subCriteria('or')
                   ->like('title', '%PHP%')
                   ->like('title', '%Linux%');

        // después añadimos este subcriterio (combinado con "AND")
        $this->and($subCrit);

        return ($this);
    }
}

Para poder usarse, este objeto debe guardarse en un archivo llamado ArticleDaoCriteria.php, ubicado en el directorio lib/ del proyecto.


3.2Configuración y uso de criterios

Para usar el nuevo objeto de criterios, necesitamos indicar su nombre al objeto DAO personalizado:

class ArticleDao extends \Temma\Dao\Dao {
    // configuración del criterio
    protected $_criteriaObject = 'ArticleDaoCriteria';
}

Ahora que este criterio se ha creado, podemos usarlo en nuestro controlador:

class Article extends \Temma\Web\Controller {
    /** Indica que el framework debe crear automáticamente
      * una instancia de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

    /** Acción que muestra los últimos artículos. */
    public function execShowLast() {
        $data = $this->_dao->search(
            $this->_dao->criteria()->mostRecent()
        );
        $this['articles'] = $data;
    }

    /** Acción que muestra artículos recientes que hablan
      * sobre PHP o Linux. */
    public function execGoodNews() {
        $data = $this->_dao->search(
            $this->_dao->criteria()
            ->mostRecent()
            ->aboutRealStuff()
        );
        $this['articles'] = $data;
    }
}
  • Línea 4: Configuramos el DAO para que use el objeto DAO personalizado ArticleDao.
  • Línea 8: Usamos el método habitual search(), pero con el nuevo criterio mostRecent(), para recuperar los artículos nuevos.
  • Líneas 17 a 20: Seguimos usando el método search(), con los nuevos criterios mostRecent() y aboutRealStuff().

La ventaja de este enfoque es evidente. Cuando usas el criterio aboutRealStuff(), no tienes que preocuparte por los detalles de su implementación.


3.3Configuración sin objeto DAO personalizado

En caso de que no hayas creado un objeto DAO personalizado, puedes configurar el objeto de criterios directamente a nivel del controlador.

class Article extends \Temma\Web\Controller {
    /** Indica que el framework debe usar el objeto
      * de criterios ArticleDaoCriteria.*/
    protected $_temmaAutoDao = [
        'criteria' => 'ArticleDaoCriteria',
    ];

    /** resto del código... */
}

Usar el atributo _temmaAutoDao te permite añadir otros parámetros específicos (ver la documentación del DAO genérico).


4Múltiples tablas

En caso de que necesites hacer uniones (joins), llegamos a los límites de la simplificación que permite el DAO. Te recomendamos escribir tus propias consultas SQL; así tendrás acceso a total libertad.

En este caso, también es recomendable desactivar el uso de la caché, para evitar el riesgo de tener grandes inconsistencias de datos.

Aquí tienes un ejemplo simple de DAO que hace consultas en la tabla article con una unión con la tabla user:

class ArticleDao extends \Temma\Dao\Dao {
    // desactiva la caché
    protected $_disableCache = true;

    /**
     * Devuelve la lista de artículos, con información sobre
     * sus autores, los más recientes primero.
     * @return  array  Lista de arrays asociativos.
     */
    public function getArticles() {
        $sql = 'SELECT *
                FROM article
                    INNER JOIN user ON (article.user_id = user.id)
                ORDER BY article.date DESC';
        $articles = $this->_db->queryAll($sql);
        return ($articles);
    }

    /**
     * Devuelve los artículos que pertenecen a un usuario,
     * a partir de su dirección de email.
     * @param  string  $email  Dirección de email del autor.
     * @return array   Lista de arrays asociativos.
     */
    public function getArticlesFromEmail($email) {
        $sql = "SELECT *
                FROM article
                    INNER JOIN user ON (article.user_id = user.id)
                WHERE user.email = " . $this->_db->quote($email) . "
                ORDER BY article.date DESC";
        $articles = $this->_db->queryAll($sql);
        return ($articles);
    }
}

El controlador entonces es muy simple:

class Article extends \Temma\Web\Controller {
    /** Indica que el framework debe crear automáticamente
      * una instancia de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

    /** Acción que muestra todos los artículos. */
    public function showAll() {
        $data = $this->_dao->getArticles();
        $this['articles'] = $data;
    }

    /**
     * Acción que busca los artículos de un usuario.
     * @param  string  $email  Dirección de email del autor.
     */
    public function search($email) {
        $data = $this->_dao->getArticlesFromEmail($email);
        $this['articles'] = $data;
    }
}

5Cargar varios DAO

Es habitual que un controlador necesite usar varios objetos DAO durante su procesamiento. El método _loadDao() está hecho para eso; espera un parámetro de configuración, que puede ser de dos tipos:

  • Una cadena que contiene el nombre del objeto DAO personalizado a cargar.
  • Un array asociativo que contiene todos los parámetros, tal como se vio en la página anterior.

Aquí tienes un ejemplo de creación de varios DAO, usando DAO personalizados:

class Article extends \Temma\Web\Controller {
    /** Indica que el framework debe crear automáticamente
      * una instancia de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';
    /** Atributo que contendrá una instancia de \MyApp\UserDao. */
    private $_userDao = null;

    /** Inicialización del controlador. */
    public function init() {
        $this->_userDao = $this->_loadDao('\MyApp\UserDao');
    }

    /**
     * Acción que busca los artículos de un usuario.
     * @param  string  $email  Dirección de email del usuario.
     */
    public function execSearch($email) {
        // verifica la dirección de email
        if ($this->_userDao->checkEmail($email)) {
            // recuperación de datos
            $data = $this->_dao->getArticlesFromEmail($email);
            $this['articles'] = $data;
        }
    }
}

Aquí tienes un segundo ejemplo, usando un array asociativo de parámetros:

class Article extends \Temma\Web\Controller {
    /** Indica que el framework debe crear automáticamente
      * una instancia de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';
    /** Atributo que contendrá una instancia de \MyApp\UserDao. */
    private $_userDao = null;

    /** Inicialización del controlador. */
    public function init() {
        $this->_userDao = $this->_loadDao([
            'cache'  => false,
            'base'   => 'cms',
            'table'  => 'content',
        ]);
    }

    /** ... resto del código ... */
}