Custom DAO


1Presentation

As you have seen in the introduction and in the generic DAO documentation, Temma provides a mechanism that allows you to easily manipulate the data in a table, using generic methods.

However, in the context of a "real" project, you will want to go further. You can choose two directions, which can complement each other:

  • Instead of using the get(), search(), update(), etc. methods, you may want to use your own methods, which will be easier to use.
  • You may need to manipulate data that is stored in multiple tables, and therefore do some joins.

In either case, the first step will be to create your own DAO object, which will derive from the standard \Temma\Dao\Dao object.


2Mono-table

2.1Creation

Here is an example of a custom DAO object, which simply provides an additional method:

class ArticleDao extends \Temma\Dao\Dao {
    /**
     * Returns the list of articles written for less than a day.
     * @return  array  List of associative arrays.
     */
    public function getLastArticles() {
        // date and time corresponding to 24 hours earlier
        $yesterday = date('c', time() - 86400);

        // define the search criteria, using this date
        $criteria = $this->criteria()
                    ->greaterThan('date', $yesterday);

        // perform the search using this criterion
        $articles = $this->search($criteria);

        // return the result of this search
        return ($articles);
    }
}

You can see that this object extends the \Temma\Dao\Dao object in a very "light" way, adding only one method, and that this one uses the search criteria proposed by Temma.

To be usable, this object must be saved in a file named ArticleDao.php, placed in the lib/ directory of the project.


2.2Configuration

If you need to specify one or more CAD creation parameters, you can add the following attributes:

  • _disableCache: Set it to true to disable the DAO's use of the cache.
  • _tableName: Name of the table that the DAO will use.
  • _dbName: Name of the database that contains the table.
  • _idField: Name of the field that contains the primary key.
  • _fields: Associative array containing the list of fields to retrieve from the database. If the fields need to be renamed, all you have to do is declare an associative pair whose key is the name of the field in the table, and the associated value is the name as it should be renamed.
  • _criteriaObject: see below

All of these attributes are optional, and must be declared with protected visibility.

Example of a DAO object using these parameters:

class ArticleDao extends \Temma\Dao\Dao {
    // deactivate the cache
    protected $_disableCache = true;
    // configure the name of the database
    protected $_dbName = 'cms';
    // configure the name of the table to use
    protected $_tableName = 'content';
    // configure the name of the control containing the primary key
    protected $_idField = 'cid';
    // list of fields to retrieve, some being renamed
    protected $_fields = [
        'cid'     => 'contentId',
                     'title',
                     'login',
        'content' => 'text',
        'date'    => 'creationDate',
                     'status'
    ];

    /* the rest of the code */
}

In this example we can see that the cache is disabled, that it is the content table of the cms database that will be used, whose field containing the primary key is called cid. We can see that the get() and search() queries will get 6 columns for each row of the table (the table may have other columns, but they will not be read), and that three of these columns will be renamed.


2.3Usage

To use the DAO we just created, you must specify its name in the controller, using the _temmaAutoDao attribute already seen before:

class Article extends \Temma\Web\Controller {
    /** Indicates that the framework should automatically create
      * an instance of ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

    /** Action that displays the latest articles. */
    public function showLast() {
        $data = $this->_dao->getLastArticles();
        $this['articles'] = $data;
    }
}

3Advanced criteria

In the example we saw previously, it should be understood that it is still possible to use the standard methods of the \Temma\Dao\Dao object: get(), search(), update(), and and so on.

For example, we could write the controller like this:

class Article extends \Temma\Web\Controller {
    /** Indicates that the framework should automatically create
     *  an instance of ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

    /** Action that displays the latest articles. */
    public function showLast() {
        $data = $this->_dao->getLastArticles();
        $this['articles'] = $data;
    }

    /** Action that displays the 5 oldest articles
     *  which are not empty. */
    public function showOlders() {
        $crit = $this->_dao->criteria()->different('content', '');
        $data = $this->_dao->search($crit, sort: 'date', offset: null, limit: 5);
        $this['articles'] = $data;
    }
}

You can see that the first action uses the new getLastArticles() method, while the second action uses the usual search() method.

Creating specific methods (such as the getLastArticles() method in our example) can be quite tedious. Instead, we may prefer another way of doing things, which is to extend the criteria management object. By doing so, we can continue to use the standard methods (search(), update()) of the \Temma\Dao\Dao object, but by writing more explicit search criteria because they are closer to the business logic.


3.1Creating criteria

Let's create an ArticleDaoCriteria object, which inherits from the \Temma\Dao\Criteria object, and add specific criteria to it:

class ArticleDaoCriteria extends \Temma\Dao\Criteria {
    /** Criterion that takes the articles that have been written
     *  less than 24 hours ago. */
    public function mostRecent() {
        // create a simple criterion
        $yesterday = date('c', time() - 86400);
        $this->greaterThan('date', $yesterday);

        // always return the instance of the current object
        return ($this);
    }

    /** Criterion that takes the articles talking about PHP or
     *  from Linux. */
    public function aboutRealStuff() {
        // in case of criteria combined by a logical "OR"
        // (and not a logical "AND"), you have to create a
        // criteria sub-object, specifying the type of
        // combination between the criteria
        $subCrit = $this->subCriteria('or')
                   ->like('title', '%PHP%')
                   ->like('title', '%Linux%');

        // we then add this criterion
        $this->_addCriteria($subCrit);

        return ($this);
    }
}

To be usable, this object must be saved in a file named ArticleDaoCriteria.php, placed in the lib/ directory of the project.


3.2Configuration and use of criteria

To use the new criteria object, we need to specify its name to the custom DAO object:

class ArticleDao extends \Temma\Dao\Dao {
    // configuration of the criterion
    protected $_criteriaObject = 'ArticleDaoCriteria';
}

Now that this criterion has been created, we can use it in our controller:

class Article extends \Temma\Web\Controller {
    /** Indicates that the framework should automatically create
      * an instance of ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

    /** Action that displays the latest articles. */
    public function execShowLast() {
        $data = $this->_dao->search(
            $this->_dao->criteria()->mostRecent()
        );
        $this['articles'] = $data;
    }

    /** Action that displays recent articles that speak
      * from PHP or Linux. */
    public function execGoodNews() {
        $data = $this->_dao->search(
            $this->_dao->criteria()
            ->mostRecent()
            ->aboutRealStuff()
        );
        $this['articles'] = $data;
    }
}
  • Line 4: We configure the DAO to use the ArticleDao custom DAO object.
  • Line 8: We use the usual search() method, but with the new mostRecent() criterion, to retrieve new articles.
  • Lines 17 to 20: We still use the search() method, with the new criteria mostRecent() and aboutRealStuff().

The advantage of this approach is obvious. When you use the aboutRealStuff() criterion, you don't have to worry about the details of its implementation.


3.3Configuration without custom DAO object

In case you have not created a custom DAO object, you can configure the criteria object directly at the controller level.

class Article extends \Temma\Web\Controller {
    /** Tells that the framework should use the object
      * of ArticleDaoCriteria criteria.*/
    protected $_temmaAutoDao = [
        'criteria' => 'ArticleDaoCriteria',
    ];

    /** rest of the code... */
}

Using the _temmaAutoDao attribute allows you to add other specific parameters (see generic DAO documentation).


4Multi-tables

In case you need to make joins, we reach the limits of the simplification allowed by DAO. We advise you to write your own SQL queries, then you will have access to total freedom.

In this case, it is also advisable to deactivate the use of the cache, to avoid the risk of having large data inconsistencies.

Here is a simple example of DAO making queries on the article table with a join on the user table:

class ArticleDao extends \Temma\Dao\Dao {
    // deactivate the cache
    protected $_disableCache = true;

    /**
     * Returns the list of articles, with information on
     * their authors, most recent first.
     * @return  array  List of associative arrays.
     */
    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);
    }

    /**
     * Returns the articles that belong to a user,
     * from his email address.
     * @param  string  $email  Email address of the author.
     * @return array   List of associative arrays.
     */
    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);
    }
}

The controller is then very simple:

class Article extends \Temma\Web\Controller {
    /** Tells that the framework should automatically create
      * an instance of ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

    /** Action that displays all articles. */
    public function showAll() {
        $data = $this->_dao->getArticles();
        $this['articles'] = $data;
    }

    /**
     * Action that searches for a user's articles.
     * @param  string  $email  Email address of the author.
     */
    public function search($email) {
        $data = $this->_dao->getArticlesFromEmail($email);
        $this['articles'] = $data;
    }
}

5Load multiple DAOs

It often happens that a controller needs to use several DAO objects during its processing. The _loadDao() method is made for that; it expects a configuration parameter, which can be of two types:

  • A string containing the name of the custom DAO object to load.
  • An associative array containing all the parameters, as seen on the previous page.

Here is an example of creating multiple DAOs, using custom DAOs:

class Article extends \Temma\Web\Controller {
    /** Tells that the framework should automatically create
      * an instance of ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';
    /** Attribute that will contain an instance of \MyApp\UserDao. */
    private $_userDao = null;

    /** Controller initialization. */
    public function init() {
        $this->_userDao = $this->_loadDao('\MyApp\UserDao');
    }

    /**
     * Action that searches for a user's articles.
     * @param  string  $email  User's email address.
     */
    public function execSearch($email) {
        // check the email address
        if ($this->_userDao->checkEmail($email) {
            // data recovery
            $data = $this->_dao->getArticlesFromEmail($email);
            $this['articles'] = $data;
        }
    }
}

Here is a second example, using an associative array of parameters:

class Article extends \Temma\Web\Controller {
    /** Tells that the framework should automatically create
      * an instance of ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';
    /** Attribute that will contain an instance of \MyApp\UserDao. */
    private $_userDao = null;

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

    /** ... rest of the code ... */
}