SQL query opmitization for HABTM in CakePHP

Posted by Jad on October 06, 2007

My last SQL optimization post found no response from the CakePHP community; which makes me wonder how many are actually using ACL with large databases. Anyhow, the ticket was submitted and gwoo seemed to approve the results. I can also now confirm that the last suggested indexes will give another push to your performance:

ARO: alias
ACO: model and foreign_key

Now what’s that about HABTM? While reading the source code for the Model class, I stumbled on the Model::_deleteLinks() method:

function _deleteLinks($id) {
   $db =& ConnectionManager::getDataSource($this->useDbConfig);
   foreach ($this->hasAndBelongsToMany as $assoc => $data) {
      if (isset($data['with'])) {
         $model =& $this->{$data['with']};
         $records = $model->findAll(array($data['foreignKey'] => $id), $model->primaryKey, null, null, null, -1);

         if (!empty($records)) {
            foreach ($records as $record) {
               $model->delete($record[$model->name][$model->primaryKey]);
            }
         }
      } else {
         $table = $db->name($db->fullTableName($data['joinTable']));
         $conditions = $db->name($data['foreignKey']) . ' = ' . $db->value($id);
         $db->query("DELETE FROM {$table} WHERE {$conditions}");
      }
   }
}

Continue reading…

Validation combined with i18n in CakePHP

Posted by Jad on October 04, 2007

Not too long ago, I wrote a quite lengthy ‘how-to use validation in CakePHP‘ post. Over the past couple of days, I had to work with a form that uses 2 models and for which i18n is combined with validation - using __() for error messages.

As I had imagined, including the magic i18n function in the Model::validate definition in my models didn’t work. However, there was the Model::beforeValidate() method that was brought to my attention by biesbjerg on IRC. Simple, no?. Creating a new method in my models User::loadValidation() and calling that with AppModel::beforeValidate() so it applies on all models.
Continue reading…

ACL tables’ optimization for CakePHP 1.2.x.x

Posted by Jad on September 30, 2007

Are you concerned about how fast your queries are handled? Do you run a website with lots of AROs & ACOs? If you answered no to any of the previous questions, you can just skip reading because it’s not for you.

Ok - so let’s dive directly in, I am short on time but started liking this sharing knowledge stuff.

Right now, whenever DB_ACL::check() kicks in (whenever a user needs to get authorized for an action), there is 2 main SELECT queries and then, depending on the number of nodes in the path to get to that user’s group, there is another query for each (so a minimum of one). When I was running some tests (debug=2) today, I just couldn’t ignore the number of queries made on each page refresh, etc.
Continue reading…

I18n with dynamic content in CakePHP

Posted by Jad on September 28, 2007

Here’s a quick one I just happened to need. When you are a nitpick like me, you sure don’t like to see code like this:

__('Logged in with', true) . $username . __('on', true) . date('Y-m-d h:i:s');

For several reasons. First, it doesn’t look slick - and yes, for you that are reading and going to annoy me for the next week, when you read code all day for a living, it matters. But most importantly, how can you then change the place of words in your sentence?

Replace that with:

sprintf(__("Logged in with '%s on %s", true), $username, date('Y-m-d h:i:s');

Another gem found by reading the source code. Which reminds me that I should maybe make a post with all the little techniques I am learning from reading the code. I know not everyone enjoys reading the API, let alone the code itself. N’est-ce pas?

How to use AuthComponent in CakePHP

Posted by Jad on September 24, 2007

Wherever I went - irc, trac, google groups and certain blogs - I noticed people complaining about either not knowing how to use the AuthComponent or having problems with it.

After reading the only tutorials available across all usual blogs I rely on for all my CakePHP needs, I realized their was a gap I could fill. The reasons are:

  1. Lack of a CPC way for quick and easy implementation.
  2. No real explanation or solution for using in a real application environment (multiple controllers, beforeFiters, actions, etc.).
  3. Not covering the different authorization methods it’s capable of: default, controller, actions, crud, model and object

I know I can’t cover everything at once, I just don’t have the time for it, but I will start with the first two and probably cover the third part in a serie of posts.
Continue reading…

CakePHP URL validation bug fix and enhancement

Posted by Jad on September 23, 2007

Update: 25/10/2007 Bug appears to have been fixed in the latest pre-beta release. Looks like this was the correct regex.

When I started parsing the millions of Google pages scraped, I came across a bug with the URL validation. To fix that, I overwrote the url validation method in app_model.php:

   function url($check)
   {
      $validation =& new Validation;
      $validation->check = $check;
      $validation->regex = '/^((https?|ftps?|file|news|gopher)://)?'  //protocol
                            . '('
                              . '(?:(?:25[0-5]|2[0-4]d|(?:(?:1d)?|[1-9]?)d).){3}(?:25[0-5]|2[0-4]d|(?:(?:1d)?|[1-9]?)d)' //ip 199.194.52.184
                              . '|' //ip or domain
                              . '([0-9a-z]{1}[0-9a-z-]*.)*' //subdomain(s) www.
                              . '([0-9a-z]{1}[0-9a-z-]{0,56}).' //domain
                              . '([a-z]{2,6}|[a-z]{2}.[a-z]{2,6})' //tld
                              . '(:[0-9]{1,4})?' //port
                           . ')'
                           . '('
                              . '/?|' //ending-slash
                              . '/[w-.,'@?^=%&:;/~+#]*[w-@?^=%&/~+#]' //path
                           . ')$/i';
      return $validation->_check();
   }

Continue reading…

CakePHP’s advanced model fields validation

Posted by Jad on September 20, 2007

After checking different blogs and tutorials, the bakery, API and IRC channel, it was obvious that some kind of documentation for the validation methods available in the Model was necessary. I can’t say that I will fulfill this mission but I’ll at least share what I came up with for future reference.

Here is the ‘User’ model I will be using in my example:

class User extends AppModel
{
   var $name = 'User'; //optional

   var $validate = array(
                     'username' => array(
                                       array(
                                          'allowEmpty' => false,
                                          'required' => true,
                                          'rule' => 'alphaNumeric',
                                          'message' => 'Username should only contain alpha-numeric characters.',
                                          ),
                                       array(
                                          'rule' => array('between', 3, 10),
                                          'message' => 'User should be between 3 and 10 characters long.',
                                          ),
                                       array(
                                          'rule' => 'isUnique',
                                          'message' => 'Username is already in use.',
                                          ),
                                       ),
                     'passwd' => array(
                                    'alphaNumeric' => array(
                                                         'allowEmpty' => false,
                                                         'required' => true,
                                                         'rule' => 'alphaNumeric',
                                                         'message' => 'Username should only contain alpha-numeric characters.',
                                                         ),
                                    'validLength' => array(
                                                         'rule' => array('between', 3, 10),
                                                         'message' => 'User should be between 3 and 10 characters long.',
                                                         ),
                                    ),
                     'website' => array(
                                       array(
                                          'rule' => 'url',
                                          'on' => 'update',
                                          'message' => 'Invalid URL.',
                                          ),
                                       ),
                     'agree_tos' => array(
                                       array(
                                          'allowEmpty' => false,
                                          'required' => true,
                                          'on' => 'create',
                                          ),
                                       ),
                     );

);
}

That’s a lot of validation rules, I know - I just wanted to try covering the multiple ways of using the Model->validates() method.
Continue reading…

Domain TLD Parser

Posted by Jad on September 17, 2007

Parsing URLs in PHP isn’t perfect. Don’t get me wrong here, it does the job when it comes to breaking the URL in logical parts, but, it doesn’t have any options to parse the host into domain name, TLD and sub-domain(s). Most probably because new TLDs are coming out from time to time and they want to avoid having to update that same function with every new TLD release.

To over-come this limitation and because I needed some way of extracting the domain, sub-domain and TLD out of each given URL, I came up with the following class: Domain TLD Parser

It parses hosts with all kinds of different TLDs, even the country-specific ones like ‘.co.za’, ‘.ne.jp’ or ‘.ltd.uk’. Here is an example:

<?php
$url = $_SERVER['HTTP_REFERER'];
include('/path/to/domain_tld_parser.class.php');
$domain = new DomainTldParser;
echo '<pre>';
print_r($domain->parse($url));
echo '</pre>';
?>

DocBlock and svn:keywords

Posted by Jad on September 04, 2007

In the application’s coding conventions (which I am almost done writing), I took the time to elaborate about code documentation and the use of phpDocumentor. Among the things discussed, there is the DocBlock, the header template of each PHP file and which looks something like that:

/**
 * Short description for file.
 *
 * Long description for file
 *
 * PHP 5
 *
 * Copyright (c) 2007, Company Name
 *                     Street address
 *                     City, State, Zip
 *
 *
 * @filesource   $HeadURL$
 * @copyright    Copyright (c) 2007, Company Name
 * @link         http://www.companywebsite.com CompanyName
 * @package      #### PACKAGE NAME ####
 * @sub-package  #### SUBPACKAGE NAME ####
 * @since        #.#.#  //Correct version number as needed
 * @version      $Revision$
 * @author       Your Name
 * @modifiedby   $LastChangedBy$
 * @lastmodified $Date$
 */

Now you might be asking yourself what are all those $HeadURL$, $Revision$, etc. Those are ‘keywords’ for Subversion which can be dynamically updated on every commit. By default, Subversion doesn’t substitute those keywords but you can easily set that directly from the shell using:

$ svn propset --recursive svn:keywords 'HeadURL Revision LastChangedBy Date' /path/to/repo

Or, in case you are using TortoiseSVN, from the right-click menu of your repository’s folder - TortoiseSVN > Properties > Add. You can then enter ’svn:keywords’ in the ‘property name’ field and ‘HeadURL Revision LastChangedBy Date’ in the ‘property value’ field. Don’t forget to check the ‘apply property recursively’, otherwise, make sure you are only setting it on a file not a directory.

From the svn propset help shell command:

The svn:keywords, svn:executable, svn:eol-style, svn:mime-type and svn:needs-lock properties cannot be set on a directory. A on-recursive attempt will fail, and a recursive attempt will set the property only on the file children of the directory.

SELECT DISTINCT in CakePHP

Posted by Jad on August 31, 2007

Even though CakePHP’s model already includes many of the database query functions, I found that the SELECT DISTINCT was missing. Ok, I know that you can always do it using either Model->query('SELECT DISTINCT `c1`, `c2`) or Model->findAll(null, 'DISTINCT `c1`, `c2`') but that would be like saying use Model->query() instead of Model->findAll().

The cool thing in CakePHP is that you can add your own functions to use in your app on top of the ones that come bundled in the core. For the Model, you just create an ‘app_model.php’ file that you place in our app’s main folder. The empty file should look like this:

/**
 * Custom AppModel that adds functionality to the core Model
 */
class AppModel extends Model
{
   //empty
}

Now inside your new AppModel class, add the following function:

   /**
    * Returns a resultset array with DISTINCT fields from database matching given conditions.
    *
    * @param   mixed    $conditions SQL conditions as a string or as an array('field' =>'value',...)
    * @param   mixed    $fields Either a single string of a field name, or an array of field names
    * @return  array    Array of records
    */
   function findDistinct($conditions = null, $fields = null)
   {
      $db =& ConnectionManager::getDataSource($this->useDbConfig);

      $str = 'DISTINCT ';
      if (!is_array($fields))
      {
         $str .= '`' . $fields . '`';
      }
      else
      {
         foreach ($fields as $field)
         {
            $str .= '`' . $field . '`, ';
         }
         $str = substr($str, 0, -2);
      }

      $queryData = array(
                     'conditions'   => $conditions,
                     'fields'       => $str,
                     );
      $data = $db->read($this, $queryData, false);

      return $data;
   }

You can now use Model->findDistinct('c1') or Model->findDistinct(array('c1', 'c2', 'c3')) to retrieve DISTINCT columns values.

Hoping you find it useful.