Tuesday, February 5, 2013

Full-Text Search


CakePHP’s database abstraction is just one of the reasons to use this powerful framework for agile web development. However, it is not always obvious how to build complex queries. I recently had to implement a MySQL full text search against a music database that searched on titles, descriptions and tags. Full text search is a quick, and not too dirty way to make a pretty decent search, although it does come with a caveat, as described in the MySQL documentation:

    Full-text indexes can be used only with MyISAM tables, and can be created only for CHAR, VARCHAR, or TEXTcolumns.

It did take a little bit of experimentation to get the syntax for the query correct in CakePHP as the query is a little different from the regular “SELECT field FROM table WHERE somevalue = somevalue“.

So, for this example I will use a MyISAM table called pages and search against the title, content and tags fields for the term” jquery web development”. You should substitute that term with your dynamically passed value in the code below.

Firstly I built the conditions for my query into a “params” array.
$params['conditions'] = array(
‘MATCH(Track.title,Track.description,Track.tags)
AGAINST(“‘ . $kw_list . ‘” IN BOOLEAN MODE)’
);

    $params= array(‘conditions’ => array(
    ‘MATCH(Page.title,Page.description,Page.tags)
    AGAINST(“jquery web development” )’ ));

Finally a call to CakePHP’s native find method will perform the search:

    $this->Page->find(‘all’,$params);

One last note: MySQL full text search can be powerful when used correctly and a potential bottleneck when poorly implemented. I suggest reading the documentation for further information and more advanced usage.

Friday, February 1, 2013

CakePHP find conditions


CakePHP find conditions (plus find all, find list, and find count)

CakePHP find FAQ: Can you share some "CakePHP find conditions" examples?

One thing you need to get used to when working with CakePHP is the CakePHP find method. To that end, I thought I'd put together this page of CakePHP find conditions and find query examples.
CakePHP find conditions

To get started, here's a list of the possible CakePHP find conditions you can use:

type      

  • 'all'
  • 'first' (default)
  • 'list'

conditions

  • array containing the find (select) conditions as key/value pairs
  • default (null)

fields    

  • array specifying which fields should be retrieved in the resulting select query
  • default (null)

order

  • sql 'order by conditions. field name must be followed by ASC or DESC
  • default (null)

page      

  • page number, used for paged data
  • default (null)

limit

  •  a limit on the number of results returned, like 'select * from orders limit 20'.
  • default (1)


offset

  • sql offset value (i haven't used this query much myself, but i think it refers to skipping X number of rows returned in a query)

recursive

  • the cakephp recursive value, relating to associated model data


CakePHP find condition examples

Given that reference information, let's take a look at some CakePHP find conditions examples.

First, a very simple CakePHP find query that retrieves all records from the Post model (probably not something you'll want to do in a production application):

$this->Post->find('all');

Next, a "CakePHP find all" query with one find condition:

$this->Post->find('all', array('conditions'=>array('User.id'=>5)));

A CakePHP find query with one "not equal to" find condition:

$this->Post->find('all', array('conditions'=>array('User.id'=>'<> 5')));

A find query with multiple CakePHP find conditions:

# this is a little lame, but i'm trying to avoid dates
$this->Post->find('all', array('conditions'=>array('User.id'=>1, 'Post.id'=>'> 50')));

A CakePHP find query that uses all the find function parameters:

# TODO - i'm not sure that this is right; i think 'fields' is supposed to be an array
$this->Post->find('all',
                  array('conditions'=>array('User.id'=>5),
                        'fields'=>'Post.name',
                        'order'=>'Post.id ASC',
                        'limit'=>20,
                        'recursive'=>0));

A CakePHP find conditions query using a date:

# note: you can search for date or datetime fields by enclosing the table's field name
#       in the SQL DATE() function.
$this->Post->find('all',
                   array('conditions'=>array('User.id'=>5,
                   'DATE(Post.date)'=>'CURDATE()')));

# TODO demonstrate "date >" and "date <" conditions

CakePHP find conditions arrays with ORDER BY examples:

array('order'=>'date ASC')
array('order'=>'date DESC')
array('order'=>'User.id DESC')

A collection of other CakePHP find conditions/examples:

These CakePHP find examples are lines of code that would be used in an OrderController class:

$this->Order->find('all');
$this->Order->find(null, null, 'date DESC');
$this->Order->find('all', array('conditions'=>array('User.id'=>1)));
$this->Order->find('all', array('conditions'=>array('User.id'=>array(1,2,3,4))));
$this->Order->find('all', array('conditions'=>array('User.id'=>'<> 1')));
$this->Order->find('all', array('conditions'=>array('User.id'=>1, 'DATE(Post.date)'=>'CURDATE()')));
$this->Order->find('all', array('order'=>'date ASC', 'limit'=>20, 'recursive'=>0);

Here are some CakePHP find examples from the CakePHP retrieving your data book page:

$params can contain all these:

array(
  'conditions' => array('Model.field' => $thisValue), //array of conditions
  'recursive' => 1, //int
  'fields' => array('Model.field1', 'DISTINCT Model.field2'), //array of field names
  'order' => array('Model.created', 'Model.field3 DESC'), //string or array defining order
  'group' => array('Model.field'), //fields to GROUP BY
  'limit' => n, //int
  'page' => n, //int
  'offset'=>n, //int  
  'callbacks' => true //other possible values are false, 'before', 'after'
)

Here's a CakePHP function showing several different CakePHP find examples:

# find('first', $params) syntax

function some_function() {
  ...
  $this->Article->order = null; // resetting if it's set
  $semiRandomArticle = $this->Article->find();
  $this->Article->order = 'Article.created DESC'; // simulating the model having a default order
  $lastCreated = $this->Article->find();
  $alsoLastCreated = $this->Article->find('first', array('order' => array('Article.created DESC')));
  $specificallyThisOne = $this->Article->find('first', array('conditions' => array('Article.id' => 1)));
  ...
}

CakePHP find count examples

Here's a CakePHP find count example:

# find('count', $params)

# more cakephp find count examples
function some_function() {
   ...
   $total = $this->Article->find('count');

   $pending = $this->Article->find('count',
                                    array('conditions' => array('Article.status' => 'pending')));
   $authors = $this->Article->User->find('count');
   $publishedAuthors = $this->Article->find('count',
                                             array('fields' => 'DISTINCT Article.user_id',
                                                   'conditions' => array('Article.status !=' => 'pending')
   ));
   ...
}

CakePHP find all examples

Here is a list of some CakePHP find all examples:

# find('all', $params) syntax

# a cakephp find all example
function some_function() {
   ...
   $allArticles = $this->Article->find('all');
   $pending = $this->Article->find('all',
                                    array('conditions' => array('Article.status' => 'pending')));

   $allAuthors = $this->Article->User->find('all');

   $allPublishedAuthors =
       $this->Article->User->find('all',
                                   array('conditions' => array('Article.status !=' => 'pending')));
   ...
}

CakePHP find list examples

Here are some CakePHP find list examples, useful for creating select boxes:

# find('list', $params) syntax

function some_function() {
   ...
    $allArticles = $this->Article->find('list');
    $pending = $this->Article->find('list', array(
        'conditions' => array('Article.status' => 'pending')
    ));
    $allAuthors = $this->Article->User->find('list');
    $allPublishedAuthors = $this->Article->find('list', array(
        'fields' => array('User.id', 'User.name'),
        'conditions' => array('Article.status !=' => 'pending'),
        'recursive' => 0
    ));
   ...
}

See the CakePHP retrieving your data book page for many more CakePHP find examples, including:

    find threaded
    find neighbors
    findAllBy
    findBy
    query
    field
    read
    More complex CakePHP find examples

Thursday, January 31, 2013


CakePHP find conditions (plus find all, find list, and find count)

CakePHP find FAQ: Can you share some "CakePHP find conditions" examples?
One thing you need to get used to when working with CakePHP is the CakePHP find method. To that end, I thought I'd put together this page of CakePHP find conditions and find query examples.

CakePHP find conditions

To get started, here's a list of the possible CakePHP find conditions you can use:
Name        Default  Description
type        'first'  can be 'all', 'first', or 'list'. determines what type of
                     find operation to perform. (TODO - more info here)

conditions  null     array containing the find (select) conditions as
                     key/value pairs

fields      null     array specifying which fields should be retrieved
                     in the resulting select query

order       null     sql 'order by conditions. field name must be
                     followed by ASC or DESC

page        null     page number, used for paged data

limit       null     a limit on the number of results returned, like
                     'select * from orders limit 20'.

offset      null     sql offset value (i haven't used this query much
                     myself, but i think it refers to skipping X 
                     number of rows returned in a query)

recursive      1     the cakephp recursive value, relating to associated
                     model data

CakePHP find condition examples

Given that reference information, let's take a look at some CakePHP find conditions examples.
First, a very simple CakePHP find query that retrieves all records from the Post model (probably not something you'll want to do in a production application):
$this->Post->find('all');
Next, a "CakePHP find all" query with one find condition:
$this->Post->find('all', 
                   array('conditions'=>array('User.id'=>5)));
A CakePHP find query with one "not equal to" find condition:
$this->Post->find('all', 
                   array('conditions'=>array('User.id'=>'<> 5')));
A find query with multiple CakePHP find conditions:
# this is a little lame, but i'm trying to avoid dates
$this->Post->find('all', 
                   array('conditions'=>array('User.id'=>1, 
                                             'Post.id'=>'> 50')));
A CakePHP find query that uses all the find function parameters:
# TODO - i'm not sure that this is right; i think 'fields' is supposed to be an array
$this->Post->find('all',
                  array('conditions'=>array('User.id'=>5),
                        'fields'=>'Post.name',
                        'order'=>'Post.id ASC',
                        'limit'=>20,
                        'recursive'=>0));
A CakePHP find conditions query using a date:
# note: you can search for date or datetime fields by enclosing the table's field name
#       in the SQL DATE() function.
$this->Post->find('all', 
                   array('conditions'=>array('User.id'=>5, 
                                             'DATE(Post.date)'=>'CURDATE()')));

# TODO demonstrate "date >" and "date <" conditions
CakePHP find conditions arrays with ORDER BY examples:
array('order'=>'date ASC')
array('order'=>'date DESC')
array('order'=>'User.id DESC')
A collection of other CakePHP find conditions/examples:
These CakePHP find examples are lines of code that would be used in an OrderController class:

$this->Order->find('all');
$this->Order->find(null, null, 'date DESC');
$this->Order->find('all', array('conditions'=>array('User.id'=>1)));
$this->Order->find('all', array('conditions'=>array('User.id'=>array(1,2,3,4))));
$this->Order->find('all', array('conditions'=>array('User.id'=>'<> 1')));
$this->Order->find('all', array('conditions'=>array('User.id'=>1, 'DATE(Post.date)'=>'CURDATE()')));
$this->Order->find('all', array('order'=>'date ASC', 'limit'=>20, 'recursive'=>0);
Here are some CakePHP find examples from the CakePHP retrieving your data book page:
$params can contain all these:

array(
  'conditions' => array('Model.field' => $thisValue), //array of conditions
  'recursive' => 1, //int
  'fields' => array('Model.field1', 'DISTINCT Model.field2'), //array of field names
  'order' => array('Model.created', 'Model.field3 DESC'), //string or array defining order
  'group' => array('Model.field'), //fields to GROUP BY
  'limit' => n, //int
  'page' => n, //int
  'offset'=>n, //int   
  'callbacks' => true //other possible values are false, 'before', 'after'
)
Here's a CakePHP function showing several different CakePHP find examples:
# find('first', $params) syntax

function some_function() {
  ...
  $this->Article->order = null; // resetting if it's set
  $semiRandomArticle = $this->Article->find();
  $this->Article->order = 'Article.created DESC'; // simulating the model having a default order
  $lastCreated = $this->Article->find();
  $alsoLastCreated = $this->Article->find('first', array('order' => array('Article.created DESC')));
  $specificallyThisOne = $this->Article->find('first', array('conditions' => array('Article.id' => 1)));
  ...
}

CakePHP find count examples

Here's a CakePHP find count example:
# find('count', $params)

# more cakephp find count examples
function some_function() {
   ...
   $total = $this->Article->find('count');

   $pending = $this->Article->find('count', 
                                    array('conditions' => array('Article.status' => 'pending')));
   $authors = $this->Article->User->find('count');
   $publishedAuthors = $this->Article->find('count', 
                                             array('fields' => 'DISTINCT Article.user_id',
                                                   'conditions' => array('Article.status !=' => 'pending')
   ));
   ...
}

CakePHP find all examples

Here is a list of some CakePHP find all examples:
# find('all', $params) syntax

# a cakephp find all example
function some_function() {
   ...
   $allArticles = $this->Article->find('all');
   $pending = $this->Article->find('all', 
                                    array('conditions' => array('Article.status' => 'pending')));

   $allAuthors = $this->Article->User->find('all');

   $allPublishedAuthors = 
       $this->Article->User->find('all', 
                                   array('conditions' => array('Article.status !=' => 'pending')));
   ...
}

CakePHP find list examples

Here are some CakePHP find list examples, useful for creating select boxes:
# find('list', $params) syntax

function some_function() {
   ...
    $allArticles = $this->Article->find('list');
    $pending = $this->Article->find('list', array(
        'conditions' => array('Article.status' => 'pending')
    ));
    $allAuthors = $this->Article->User->find('list');
    $allPublishedAuthors = $this->Article->find('list', array(
        'fields' => array('User.id', 'User.name'),
        'conditions' => array('Article.status !=' => 'pending'),
        'recursive' => 0
    ));
   ...
}
See the CakePHP retrieving your data book page for many more CakePHP find examples, including:
  • find threaded
  • find neighbors
  • findAllBy
  • findBy
  • query
  • field
  • read
  • More complex CakePHP find examples

Multi Select List and Checkbox


Multiple SelectList
$list = array('1' => '山田', '2' => '佐藤', '5' = > '高橋', '7' => '橋本');
echo $form->input('list1', array('type' => 'select',
          'options' => $list, 'multiple' => true,
          'size' => 5, 'value' => array('2', '5')));

Multiple CheckBox
$list = array('1' => '山田', '2' => '佐藤', '5' = > '高橋', '7' => '橋本');
echo $form->input('list1', array('type' => 'select',
          'options' => $list, 'multiple' => 'checkbox',
          'size' => 5, 'value' => array('2', '5')));

Excel Export



app/Controller/[Name]sController.php 
<?php echo $this->Html->link(__('Export to excel'), array('action' => 'export_xls')); ?>

app/View/Layouts/export_xls.ctp
<?php
header ("Expires: Mon, 28 Oct 2008 05:00:00 GMT");
header ("Last-Modified: " . gmdate("D,d M YH:i:s") . " GMT");
header ("Cache-Control: no-cache, must-revalidate");
header ("Pragma: no-cache");header ("Content-type: application/vnd.ms-excel");
header ("Content-Disposition: attachment; filename=\"Report.xls" );
header ("Content-Description: Generated Report" );
?>
<?php echo $content_for_layout ?>

app/View/[Name]/export_xls.ctp
<STYLE type="text/css">
 .tableTd {
     border-width: 0.5pt;
  border: solid;
 }
 .tableTdContent{
  border-width: 0.5pt;
  border: solid;
 }
 #titles{
  font-weight: bolder;
 }
 
</STYLE>
<table>
 <tr>
  <td><b>[Name]s Data Export<b></td>
 </tr>
 <tr>
  <td><b>Exported Date:</b></td>
  <td><?php echo date("F j, Y, g:i a"); ?></td>
 </tr>
 <tr>
  <td><b>Number of Rows:</b></td>
  <td style="text-align:left"><?php echo count($rows);?></td>
 </tr>
 <tr>
  <td></td>
 </tr>
  <tr id="titles">
            <td class="tableTd">Id</td>
            <td class="tableTd">Name</td>
  </tr>
  <?php foreach($rows as $row):
   echo '<tr>';
   echo '<td class="tableTdContent">'.$row['[Name]']['id'].'</td>';
            echo '<td class="tableTdContent">'.$row['[Name]']['name'].'</td>';
   echo '</tr>';
   endforeach;
  ?>
</table>


app/View/[Name]/index.ctp

<?php echo $this->Html->link(__('Export to excel'), array('action' => 'export_xls')); ?>

HABTM

Part 1. The basics

HABTM seems to give a lot of people trouble, so I wanted to cover a few points that may or may not be in the manual. And I will assume here that you have basic understanding or some knowledge of HABTM, otherwise you should really go and read the manual first… (By the way, do read the manual if you are having trouble. It’s constantly evolving and some points you’ve missed before may very well be covered).
OK, so let’s begin with a good ol’ Post HABTM Tag.
Here’s the simplest definition of the Post model:


class Post extends AppModel {
     var $name = 'Post';
     var $hasAndBelongsToMany = array('Tag');
}

(In PHP5 you won’t even need $name, but let’s leave it in for a few years).
To make HABTM work you have to have a very similar definition of the Tag model:

class Tag extends AppModel {
     var $name = 'Tag';
     var $hasAndBelongsToMany = array('Post');
}

And the magic has happened already…
However, unless you know a thing or two about magic, it can be hard to figure out what’s going on because CakePHP makes a lot of assumptions if you do not manually override the defaults.
I’m going to cover here with and joinTable as I find these to be the most often overlooked/misunderstood HABTM basics.
with
The with key specifies an auto-model, which cake creates to represent the join table (joinTable in CakePHP syntax). If we follow the defaults the joinTable model is named PostsTag and you can use it like any other regular CakePHP model. Yes, it is automagically created for you.
joinTable
Still following conventions, CakePHP will assume that the joinTable is named posts_tags. Table name consists of plural model names involved in HABTM and is always in alphabetical order. So, P goes before T, therefore posts_tags.
Well, let’s say you don’t like the name posts_tags and instead would like to rename your joinTable to my_cool_join_table…
Off you go and modify your Post model like so:

class Post extends AppModel {
     var $name = 'Post';
     var $hasAndBelongsToMany = array('Tag'=>array(
                                               'joinTable'=>
                                               'my_cool_join_table'));
}

Guess what? You’ve just messed with the magic.
First of all CakePHP will now rename your auto-model to MyCoolJoinTable, so if you had some references in the code to PostsTag (the default with model) you have to go and change them.
Secondly, and maybe more importantly, you’ve probably forgot about your Tag model. If you haven’t made any changes, Tag will still expect all the defaults. This can create a lot of mess and bizarre, unexpected results. So the point here is that since HABTM goes in both directions any changes you apply to one model definition should most likely be manually applied to another.
Now, what if you don’t like PostsTag name and would like to rename your join (with) model to PostTagJoin?

class Post extends AppModel {
      var $name = 'Post';
      var $hasAndBelongsToMany = array('Tag'=>array('with'=>'PostTagJoin'));
}

Changing the with key will not affect the joinTable value (i.e. CakePHP will not assume posts_tags_joins or something) so if you don’t change the default, CakePHP will still expect posts_tags.
In other words, with only changes the name of the auto-model, so in reality it’s not something one would or should bother to do.
Now onto some good stuff…
Having this auto-model is quite handy if you are interested in the joinTable data and becomes even more powerful when you have some additional fields in the joinTable, which you need queried or saved.
In the Posts controller you could do:

$this->Post->PostsTag->find(‘all’);

Or you could even apply some conditions to find the most popular tag ID’s:

$this->Post->PostsTag->find('all', array('fields'=>array('tag_id','COUNT(tag_id) AS total'),
                                                      'group'=>'tag_id')));
One caveat to note here is that PostsTag is not automagically associated with neither Post nor Tag. So in order to fetch some associated data, you’ll have to manually do something like:

$this->Post->PostsTag->bindModel(array(‘belongsTo’=>array(‘Tag’)));

Now, using the above example, you can even get the most popular Tag names by using Containable in the above query or simply increasing the recursive of PostsTag.

Part 2. Saving data
The next topic I wanted to cover is how to go about saving the HABTM data. Actually it is really not that complicated, as most of the work is taken care of for you.
Probably the most important thing is to make sure that your data arrives in the correct format for the save() method. The rest is really pretty easily handled by cake.
So let’s take a look at a few examples (still using our Post HABTM Tag)…
First, we’ve got a case where we know a Post ID and we are creating a single, new tag for it. We’d like to save the tag name and have CakePHP automagically associate it with a given post.
The form would look something like this:

echo $form->create('Tag');
echo $form->input('tag');
echo $form->input('Post.id', array('type'=>'hidden', 'value'=>5));
echo $form->end('Add tag');

And the controller action would be:

function add() {
$this->Tag->save($this->data);
}

Let’s also take a look at our data:

Array
(
    [Tag] => Array
        (
            [tag] => new one
        )

    [Post] => Array
        (
            [id] => 5
        )

)
To clear things up quickly, CakePHP will insert the new tag, grab the ID of the last insert and save the ID of the known post plus the ID of the newly created tag into the join table.
Now, let’s say you need to associate that same tag with a few different posts. Well, the only thing you need to do is to, again, make sure that you have correct fields in your form… so:

echo $form->create('Tag');
echo $form->input('tag');
echo $form->input('Post.1', array('type'=>'hidden', 'value'=>5));
echo $form->input('Post.2', array('type'=>'hidden', 'value'=>6));
echo $form->input('Post.3', array('type'=>'hidden', 'value'=>7));
echo $form->end('Add tag');

Will produce a data array that looks like:

Array
(
    [Tag] => Array
        (
            [tag] => my brand new tag
        )

    [Post] => Array
        (
            [1] => 5
            [2] => 6
            [3] => 7
        )

)

Basically this will save a new tag and associate that newly created tag with posts, which happen to have ID’s 5,6 and 7. I should mention that in the previous example the field could be named Post.1 instead of Post.id, but I find that ‘id’ is more descriptive and works just as well.
Also, in the example I’m using a hidden field with a given value for the ID, in the real life it would probably be a few checkboxes, with values for ID’s.
And lastly it’s worth to note that the above can be reversed, just as well you could setup a quick form to create a new Post and associate some known tags with it.
P.S. Be sure to check out Habtamable behavior, which helps to avoid some of the pain when working with HABTM models.

Introduction

Introduction

Official Website : http://cakephp.org/