Wednesday, November 10, 2010

Home Page

While you can route your home page to a controller and action as we did previously, you may prefer to have a separate page set up as the leading page into your site. A good practice is to create a homepage controller and view to use as the main page to your site, this way all of the other controllers and views are tied specifically and only to the model to which they belong.

First create /app/controllers/homepages_controller.php
<?php
class HomepagesController extends AppController {

}
?>
Since the home page controller does not have an associated model, if we wanted to access any data we would define the models we want to access with the $uses variable. If the models are associated, such as our "User" model and "Carrier" model, it may not be necessary to include both, but if they are not related, such as the "Drug" model, but you want to include data from that table, you need to include both.
var $uses = array('User', 'Drug');
In our case we won't be using any data for the home page at this time so we need to create an empty array. We'll also need to create an index method.
var $uses = array();

function index() {
    $this->set('title_for_layout', 'Drug-Ed.com : Drug Education and Medication Adherence');
}
Next we need to create the home page view so create /app/views/homepages/index.ctp For now we'll leave this blank.

And the last thing we need to do is change our routing to go to the home page instead of the previously set up users page.

Edit /app/config/routes.php and replace
    Router::connect('/', array('controller'=>'users', 'action'=>'index'));
with
    Router::connect('/', array('controller'=>'homepages', 'action'=>'index'));
Now browse to http://drug-ed.com/ and verify it goes to the homepages index.

Form Focus

One thing I find annoying on web pages is when there is a form you are expected to fill out, such as a login form, and the focus is not on the first field.

Edit /app/views/users/login.ctp and add to the bottom of the page:
<script type="text/javascript">
document.getElementById('UserUsername').focus() 
</script>
Save and upload the file then browse to http://drug-ed.com/users/login and verify the username field has focus.

More Authentication

While most of the default settings for the Auth component are good enough, there are many things you can add and change to allow more flexibility. If the default setting (as seen in the examples below) is good enough, you don't need to add the code at all.

By default the Auth component uses the Security class for hashing the password and the Security class uses SHA1 for hashing. The Auth component automatically performs the hashing for password verification and user creation as long as there are both username and password fields.

As mentioned previously, these settings can go into the specific controllers, or in the app controller for use with all controllers.

You can use change the hashing method for the Security class as follows:
Security::setHash('md5'); // or sha256 or sha1 (default)
For the error message on invalid login use loginError.
$this->Auth->loginError = "Login failed. Invalid username or password.";
For the error message when trying to access a protected page use authError.
$this->Auth->authError = "You are not authorized to access that location.";
While the Auth component remembers what page you were trying to access before logging in, if you enter the site from an external link for example you can set what URL the user goes to after login, you can also set the URL after logout.
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');

Authentication

To set up a user login system for your CakePHP application, Cake comes with an authentication component built in. The Auth component requires a "user" table in the database with at least the fields of "username" and "password". Though they don't have to be named exactly that, it makes everything easier.

The first thing you need to do is to add the Auth component to your controllers. Since typically you want all your controllers to utilize the Auth component, it is best to create an App controller and include it in there.

Create /app/app_controller.php
<?php
class AppController extends Controller{
    var $components = array('Auth');
}
?>
If at any time you encounter trouble with your authentication, comment out this line to turn it off.

NOTE: When you explicitly define your components, you disable the default components in CakePHP, such as Session so if you want to use the session component, be sure to explicitly define it in your array.
    var $components = array('Auth','Session');
The next requirement is to have login() and logout methods in your users controller.

Exit /app/controllers/users_controller.php and add the login and logout methods as below.
    function login() {
        $this->set('title_for_layout', 'Login');
        /* If user is already logged in, redirect */
        if ($this->Auth->user()) {
            $this->redirect($this->Auth->redirect());
        }
    }

    function logout() {
        $this->redirect($this->Auth->logout());
    }
The last requirement is the view for the login action of the users controller.

Create /app/views/users/login.ctp
<?php
    echo $session->flash('auth');
    echo $form->create('User', array('action' => 'login'));
    echo $form->input('username');
    echo $form->input('password');
    echo $form->end('Login');
?>
The login view uses the form helper to create a form allowing entry of the username and password.

In a controller, the beforeFilter() method is called automatically before any other controller action. This is where you define the settings for your Auth component.

Edit /app/app_controller.php and add the beforeFilter method below.
function beforeFilter() {
    /* Set username and password fields if not using default 'username' and 'password' */
    $this->Auth->fields = array('username' => 'email', 'password' => 'password');
    /* What actions are allowed without authentication */
    $this->Auth->allow('*'); // Allow all defined methods
}
By default, the Auth component only allows the login and logout methods. Allowing all actions will allow you to create a user and set the password so you will be able to login once you lock the site down.

If you are still using scaffolding at this point however, scaffold methods are not allowed without being explicitly defined.

Edit /app/app_controller.php and change the $this->Auth->allow() command as follows:
    $this->Auth->allow('*', 'add');
Browse to http://drug-ed.com/users/add/ and create a new user with a properly hashed password so you can login.

Edit /app/app_controller.php and comment out the $this->Auth->allow() command to prevent all access to the site unless signed in. Browse to any page then login it verify it works.

If you are not allowing all actions and are explicitly defining the actions you are allowing, remember that for static pages the default action is 'display' and is required for authorization to view those pages.

Tuesday, November 9, 2010

Pagination

With a short list of records, the basic view is good enough, however, once the number of records exceeds 50 or so, it is helpful to use CakePHP's built in pagination to display pages on multiple pages. First edit /app/controllers/users_controller.php and replace this line
$this->set('users',$this->User->find('all'));
with this one
$this->set('users', $this->paginate());
Save and upload the file. Now if you browse to http://drug-ed.com/ it still works because the data pulled from the database is the same, we just haven't taken advantage of the pagination features yet. Edit /app/views/users/index.ctp and replace
    <th>ID</th>
    <th>Username</th>
    <th>First Name</th>
    <th>Last Name</th>
    <th>Email</th>
with
    <th><?php echo $this->Paginator->sort('id');?></th>
    <th><?php echo $this->Paginator->sort('username');?></th>
    <th><?php echo $this->Paginator->sort('first_name');?></th>
    <th><?php echo $this->Paginator->sort('last_name');?></th>
    <th><?php echo $this->Paginator->sort('email');?></th>
Then after the users table within the users division add
    <p>
    <?php
        echo $this->Paginator->counter(array('format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true)));
    ?>
    </p>
    <div class="paging">
        <?php echo $this->Paginator->prev('<< ' . __('previous', true), array(), null, array('class'=>'disabled'));?>
        | <?php echo $this->Paginator->numbers();?>
        | <?php echo $this->Paginator->next(__('next', true) . ' >>', array(), null, array('class' => 'disabled'));?>
    </div>
Save and upload the file then browse to http://drug-ed.com/ to verify that it works. You can now click on the headers of each column to sort the users, as well as view them on multiple pages, though it requires more than 20 records for a new page.

Elements

Elements are HTML elements that you will use repeatedly within different views. You create the element then include it within your view code. They are stored within the /app/views/elements/ directory.

Create /app/views/elements/banner.ctp
<?php
echo "<div class=\"clear centerText\">
<script type=\"text/javascript\"><!--
google_ad_client = \"pub-9280152324836673\";
google_ad_slot = \"5630351766\";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type=\"text/javascript\"
src=\"http://pagead2.googlesyndication.com/pagead/show_ads.js\">
</script></div>";
?>
Save and upload the file then edit /app/views/users/index.ctp Add the following line at the bottom of the file:
<?php echo $this->element('banner'); ?>
Save and upload the file then browse to http://drug-ed.com/ to verify it works. Since the element only exists on the /app/views/users/index.ctp page, it will only appear on that page. If you include the element on the /app/views/layouts/default.ctp page, it will display on every page.

Remove that line from the bottom of /app/views/users/index.ctp and add it to the "footer" div of the /app/views/layouts/default.ctp page.

Static Pages

Not all pages in your site will be dynamic. Some pages, such as an "About" page, will be static and CakePHP has a Pages controller for just that purpose. The pages controller is located by default in /cake/libs/controllers/pages_controller.php If you need to edit it for any reason, copy the file to /app/controllers/

Static pages are stored in /app/views/pages/ and displayed using the display action. By default they are addressed using site root/pages/page name.

Create any static pages with the .ctp extension and include any html formatting you want in your page.

Create /app/views/pages/about.ctp
<?php $this->set("title_for_layout", 'About Drug-ed.com'); ?>
<h2>About Drug-Ed.com</h2>
<p>Drug-Ed.com is a free drug education and medication reminder service for your daily medication needs. Set up your drug schedule online and have reminders emailed to you or texted to your mobile phone daily. We want to make taking your medication as easy and hassle free as possible.</p>
Remember when I said we should change the page title in the controller? Well with static pages it is easier to just set them in the page itself.

Save and upload the file then browse to http://drug-ed.com/pages/about

When using the HTML helper to create links to static pages remember that pages is the controller and display is the action so they either must be explicitly declared, or create a hard link instead.
<?php echo $this->Html->link('Downloads', array('controller' => 'pages', 'action'=>'display', 'downloads')); ?>
<?php echo $this->Html->link('Downloads', '/pages/downloads'); ?>

Layout: Default

Further changes to the default layout that we created previously will affect all page views, so develop your default layout with that in mind, putting elements on the page that you would like to be on every page of your site, such as a site logo, main navigation, and copyright notice.

First though, a word about images. Static image files for the site should go into the directory /app/webroot/img/ as this directory allows direct access to the images without processing through a controller and when using the HTML helper (see below) you don't have to remember the beginning path to the image.

The site "favicon.ico" file should go in the /app/webroot/ directory. There is a default file already there, so when you have an image for your site simply replace it.

Edit /app/views/layouts/default.ctp

Inside the "header" div at the top remove the line beginning with "<h1>" and add the following:
    <div class="logo"><?php echo $this->Html->link($this->Html->image('drug-ed-logo.png', array("alt" => "Drug-Ed.com")), Router::url('/'), array('escape' => false)); ?></div>
The Html Helper is included in CakePHP to make creating links and other Html elements easier to code dynamically. The default format for using a helper is with $this->HelperName. Observe in the above code that an image element is contained within an link element. Since the image is stored within the /app/webroot/img/ directory as mentioned above, the path to the image is not required since the helper automatically includes it.

If your Html helper link includes html entities, such as the above image, by default the code is escaped out. To turn that feature off and allow the html in the title to be displayed properly add the options array to the link function as above.

Save and upload the file then browse to site root to observe the changes.

Page Title

From your home page observe the page title, it should be something like this: "CakePHP: the rapid development php framework: Users"

The first part of the title is defined in the default layout. The layout predefines the wrapper HTML code of every page. This file is located in /cake/libs/view/layouts/default.ctp and before editing it you should move it to /app/views/layouts/default.ctp

Next, edit the page and remove the following line:
<?php __('CakePHP: the rapid development php framework:'); ?>
Upload the file and test it and your home page title should just read "Users".

Now click on the username and the title will read "Scaffold :: View :: Users" While the default titles can be helpful, you will want to change them to reflect your site's method.

Edit /app/controllers/users_controller.php and for the first line in the index() method add the following:
$this->set("title_for_layout", 'Drug-ed.com : Drug Education and Medication Adherence');
Save the changes upload the file and test it at site root. This command will also work if you add it to /app/views/users/index.ctp, however, since you are modifying variables it is best practice to do it within the controller.

Routing

By default, requests to your site root are routed by CakePHP to a home view, /apps/views/pages/home.ctp. By changing the routing, you can direct the request to any controller and action you choose.

Edit /app/config/routes.php Comment out
    Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Then add
    Router::connect('/', array('controller'=>'users', 'action'=>'index'));
Now browse to http://drug-ed.com/ and verify it goes to the users index.

Thursday, November 4, 2010

Validate Model: User

Rule 1: Never trust user input
Rule 2: When in doubt, see rule 1

For every model you should create a validate variable which will be used to validate input fields. For our user model we need to verify that username and email are unique on creation.

Since validation can be quite complex, building the validation variable one field at a time can be helpful. For full details on data validation see http://book.cakephp.org/view/1143/Data-Validation

Edit /app/models/user.php and add
    var $validate = array(
        'username' => array(
            'alphanumeric' => array(
                'rule' => '/^[a-z0-9]{3,}$/i',
                'required' => true,
                'message' => 'Username must contain only letters and numbers.'
            ),
            'unique' => array(
                'rule' => 'isUnique',
                'message' => 'This username is already taken.'
            ),
            'minlength' => array(
                'rule' => array('minlength', 5),
                'message' => 'Username must be at least 5 characters.'
            ) 
        )
    );
Upload the file then browse to http://drug-ed.com/users/add and observe how the "Username" field label is bolded and followed by a red asterisk helping to identify the required aspect of that field. Test your validation rule by trying a 2 letter username, or one composed of special characters.

Now that you are sure that field validation is working properly, add the next field. Edit /app/models/user.php and add at the end of the validate array
        ),
        'email' => array(
            'email' => array(
                'rule' => 'email',
                'required' => true,
                'message' => 'You must enter a valid email address.'
            ),
            'unique' => array(
                'rule' => 'isUnique',
                'on' => 'create',
                'message' => 'This email address is already on record.'
            )
        )
    );
Notice the line 'on' => 'create' in the email validation. When editing a user the isUnique rule will fail because the user's email is already in the database! Changing it to only verify when you create an new user will prevent this problem.

Upload the file then browse to http://drug-ed.com/users/add to again verify it works as intended.

Model: App Model

The find() method, as used previously, returns not only the data from the table/model identified, but all related data as well. This is usually a good thing, but typically it returns far too many levels of data than is necessary.

Edit /app/views/users/index.ctp and add the following line to the end of the file then browse to http://drug-ed.com/users/ to see what is returned for just one record:
<?php print_r($user); ?>
It may look something like this:
Array ( [User] => Array ( [id] => 1 [slug] => admin [username] => admin [password] => test [first_name] => test [last_name] => test [email] => naidim@gmail.com [email_verified] => 1 [carrier_id] => 1 [cell_number] => 5205551234 [created] => 2010-11-03 21:12:40 ) [Carrier] => Array ( [id] => 1 [name] => Verizon PCS [extension] => vtext.com ) [Reminder] => Array ( ) )
By default it should contain only what the current view requires, only returning more if the Containable behavior is set.

To limit the find() method for all models, create /app/app_model.php This model overrides the default AppModel and allows you to control the functionality of all models.
<?php
class AppModel extends Model {
    var $recursive = -1;
    var $actsAs = array('Containable');
}
?>
Now browse to http://drug-ed.com/users/ and you should see something more like this:
Array ( [User] => Array ( [id] => 1 [slug] => admin [username] => admin [password] => test [first_name] => test [last_name] => test [email] => naidim@gmail.com [email_verified] => 1 [carrier_id] => 1 [cell_number] => 5205551234 [created] => 2010-11-04 21:12:40 ) ) 
Go back and remove that last line from /app/views/users/index.ctp.

For details on Containable behavior http://book.cakephp.org/view/1323/Containable and using Containable with paginate http://book.cakephp.org/view/1232/Controller-Setup

Don't forget to add var $actsAs = array('Containable'); to the appropriate model (or AppModel) before you try to use contain();

Thanks to Matt Curry

View: User Index

Views control the display of your application. When the controller renders views, Cake will automatically look for a file name the same as the controller action. The index method is the default method called when the controller is called without an explicit action defined, so index.ctp is the default view.

Create /app/views/users/index.ctp

This file contains any actual display code, such as html, that will be used to view the results of the index() method.
<div class="users index">
    <table cellpadding="0" cellspacing="0">
    <tr>
        <th>ID</th>
        <th>Username</th>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Email</th>
        <th class="actions">Actions</th>
    </tr>
    <?php
    $i = 0;
    foreach ($users as $user):
        $class = null;
        if ($i++ % 2 == 0) {
            $class = ' class="altrow"';
        }
    ?>
    <tr<?php echo $class;?>>
        <td><?php echo $user['User']['id'];?></td>
        <td><?php echo $this->Html->link($user['User']['username'], array('controller' => 'users', 'action' => 'view', $user['User']['id'])); ?></td>
        <td>%lt;?php echo $user['User']['first_name']; ?></td>
        <td>%lt;?php echo $user['User']['last_name']; ?></td>
        <td>%lt;?php echo $user['User']['email']; ?></td>
        <td class="actions">
            <?php echo $this->Html->link(__('View', true), array('controller' => 'users', 'action' => 'view', $user['User']['id'])); ?>
            <?php echo $this->Html->link(__('Edit', true), array('controller' => 'users', 'action' => 'edit', $user['User']['id'])); ?>
            <?php echo $this->Html->link(__('Delete',true), array('action' => 'delete', $user['User']['id']), null, sprintf('Are you sure you want to delete # %s?', $user['User']['id'])); ?>
        </td>
    </tr>
<?php endforeach; ?>
    </table>
</div>
<div class="actions">
    <h3><?php __('Actions'); ?></h3>
    <ul>
        <li><?php echo $this->Html->link(__('New User', true), array('action' => 'add')); ?></li>
        <li><?php echo $this->Html->link(__('List Carriers', true), array('controller' => 'carriers')); ?></li>
        <li><?php echo $this->Html->link(__('New Carrier', true), array('controller' => 'carriers', 'action' => 'add')); ?></li>
        <li><?php echo $this->Html->link(__('List Reminders', true), array('controller' => 'reminders')); ?></li>
        <li><?php echo $this->Html->link(__('New Reminder', true), array('controller' => 'reminders', 'action' => 'add')); ?></li>
    </ul>
</div>
Edit /app/controller/users_controller.php Add the following method
    function index() {
        $this->set('users',$this->User->find('all'));
    }
As mentioned previously, any defined methods will override scaffolding so browse to the site root/users and view the results.

Browse to http://drug-ed.com/users/ to test the page. Observe your default user then click on the link for viewing and editing. Notice how the scaffolding still works for all methods other than those defined.

The double underscore (__) is a global CakePHP method that handles localization. Feel free to ignore it at this point.

Wednesday, November 3, 2010

Controller: Reminders

Create /app/controllers/reminders_controller.php
<?php
class RemindersController extends AppController {
    var $name = 'Reminders';
    var $scaffold;
}
?>
Upload the file and browse to http://drug-ed.com/reminders to verify it works. Again the defined relationship to drugs and users should cause action links to show.

Controller: Drugs

Create /app/controllers/drugs_controller.php
<?php
class DrugsController extends AppController {
    var $name = 'Drugs';
    var $scaffold;
}
?>
Upload the file and browse to http://drug-ed.com/drugs to verify it works. Again the defined relationship to reminders should cause action links to show. Add a drug.
NameBrand NameIndicationsAdverse ReactionsCounseling PointsNotes
Diclofenac NaCataflamDiclofenac is used to relieve pain, tenderness, swelling, and stiffness caused by osteoarthritis, rheumatoid arthritis, ankylosing spondylitis, painful menstrual periods and pain from other causes.diarrhea, constipation, gas or bloating, headache, dizziness, ringing in the earsNSAIDs such as diclofenac may cause ulcers, bleeding, or holes in the stomach or intestine. People who take nonsteroidal anti-inflammatory drugs (NSAIDs) (other than aspirin) such as diclofenac may have a higher risk of having a heart attack or a stroke than people who do not take these medications.Diclofenac is in the class of Non-Steroidal Anti-Inflammatory Drugs (NSAIDs)

Controller: Users

Create /app/controllers/users_controller.php
<?php
class UsersController extends AppController {
    var $name = 'Users';
    var $scaffold;
}
?>
Upload the file and browse to http://drug-ed.com/users to verify it works. Since Users have defined relationships with Carriers and Reminders observe the action links. Click "New User" and observe the option to select the carriers entered in earlier. Create a default user for administration and testing of your site.

Controller: Carriers

Controllers provide the functionality of your application. Controllers are named plural and "controller" is explicitly listed in the name.

Create /app/controllers/carriers_controller.php
<?php
class CarriersController extends AppController {
    var $name = 'Carriers';
    var $scaffold;
}
?>
If the controller is using a model other than, or in addition to, the one with the same name as its name, you can access that model in one of two ways.
  1. Add var $uses = array('Carrier', 'Other');
  2. Access the other model directly in code via $this->Carrier->Other...
Adding the Carrier model in the Carriers controller in option 1 is unnecessary since it is automatic, but is listed only for clarity.

The variable $scaffold turns on automated scaffolding for testing and viewing of database data. After testing is complete, ensure to remove the scaffolding line to prevent unauthorized access. If you test with scaffolding, any defined methods will override the scaffolding.

Upload the file and browse to http://drug-ed.com/carriers to verify it works, then add a few carriers.
NameExtension
Verizon PCSvtext.com
AT&T PCStxt.att.net


Observe that since there is a defined relationship between Carriers and Users the actions "New User" and "List Users" are available on the Carriers page. Since there is no Users controller the links don't work yet.

Update Model: Drug

As mentioned previously, to coordinate with the reminder model, add the relationship to the drug model. Edit /app/models/drug.php and add
    var $hasMany = array('Reminder');
Save and upload the file.

Update Model: Carrier

As mentioned previously, to coordinate with the user model, add the relationship to the carrier model. Edit /app/models/carrier.php and add
    var $hasMany = array('User');
Upload the file.

Update Model: User

As mentioned previously, to coordinate with the reminder model, add the relationship to the user model. In addition while each carrier can have many users, each user belongs to a carrier so indicate that as follows as well:

Edit /app/models/user.php and add
    var $hasMany = array('Reminder');
    var $belongsTo = array('Carrier');
Upload the file.

Like when a model belongs to another model, if the foreign key is not standard you need to explicitly define it. You can also explicitly define the class name if that isn't the default also.
    var $hasMany=> array(
        'Reminder' => array(
            'className' => 'Reminder',
            'foreignKey' => 'UniqueUserID'
        )
    );

Model: Reminder

Create /app/models/reminder.php
<?php
class Reminder extends AppModel {
    var $name = 'Reminder';
}
?>
Each reminder "belongs to" a user, and each user can "have many" reminders, so there is a one to many relationship between the two tables. To indicate this to Cake, add the following information below the $name variable:
    var $belongsTo = array('User');
In addition, each reminder has a one to many relationship with drugs, so it "belongs to" a drug. Add 'Drug' after 'User' in the above line.

When designing your database, ensure that each model that "belongs to" another model has the appropriate foreign key field. For example since each reminder belongs to a user, that user is identified by the key "user_id" in the reminder table. If you stray from convention (classname_id) you have to define the foreign key explicitly in your model.
    var $belongsTo => array(
        'User' => array(
            'foreignKey' => 'UniqueUserID'
        )
    );
Upload the file.

Model: Carrier

Create /app/models/carrier.php
<?php
class Carrier extends AppModel {
    var $name = 'Carrier';
}
?>
Upload the file.

Model: Drug

Create /app/models/drug.php
<?php
class User extends AppModel {
    var $name = 'Drug';
}
?>
Save and upload the file.

Model: User

Models provide the interface to the database. While the tables they refer to are named plurally, the models are named singularly.

Create /app/models/user.php
<?php
class User extends AppModel {
    var $name = 'User';
}
?>
If the primary key field of your table is not "id" then you need to add the variable $primaryKey, otherwise you can leave it out.
    var $primaryKey = 'unique_id';
If you didn't design your tables around CakePHP (why wouldn't you?) then you can explicitly specify the table name for your model here as well.
    var $useTable = 'table_name';
If you don't want a model to use a table, such as for display purposes only, or when pulling data from an external source, you can set the useTable variable to false.
    var $useTable = false;
Once you do that, you can set the schema for the non-table data with the _schema variable.
    var $_schema = array(
        'last_name' => array('type' => 'strong', 'length' => 50),
        'date_of_birth' => array('type' => 'date')
    );
Upload the file.

Database: Setup Reminders Table

Create reminders table
CREATE TABLE IF NOT EXISTS `reminders` (
  `id` int(11) NOT NULL auto_increment,
  `user_id` int(11) NOT NULL,
  `drug_id` int(11) NOT NULL,
  `days_of_week` varchar(7) NOT NULL,
  `time` time NOT NULL,
  `created` datetime NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Database: Setup Carriers Table

Create carriers table
CREATE TABLE IF NOT EXISTS `carriers` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(40) NOT NULL,
  `extension` varchar(30) NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

Database: Setup Drugs Table

Create drugs table
CREATE TABLE IF NOT EXISTS `drugs` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(50) NOT NULL,
  `brand_name` varchar(50) NULL,
  `indications` varchar(255) NULL,
  `adverse_reactions` varchar(255) NULL,
  `counseling_points` varchar(500) NULL,
  `notes` varchar(255) NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Database: Setup Users Table

Create users table
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL auto_increment,
  `slug` varchar(40) NOT NULL,
  `username` varchar(30) NOT NULL,
  `password` varchar(40) NOT NULL,
  `first_name` varchar(40) NOT NULL,
  `last_name` varchar(40) NOT NULL,
  `email` varchar(100) NOT NULL,
  `email_verified` tinyint(1) NOT NULL default '0',
  `carrier_id` int(11) NOT NULL,
  `cell_number` varchar(10) NOT NULL,
  `modified` datetime NULL,
  `created` datetime NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `username` (`username`),
  UNIQUE KEY `slug` (`slug`),  
  UNIQUE KEY `email` (`email`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
The created and modified (or updated, either name works) fields will be filled in by CakePHP automatically, as long as they are set as DATETIME fields and default to NULL. Long field names are separated with underscores '_' so CakePHP can easily understand them.

Dreamweaver and CakePHP (.ctp) Files

Dreamweaver does not recognize .ctp files by default, so to get them to color code as PHP files, make some small changes in the configuration files.

Edit C:\Documents and Settings\user\Application Data\Adobe\Dreamweaver 9\Configuration\Extensions.txt Add CTP to the All Documents and PHP Files lines.

Edit C:\Program Files\Adobe\Adobe Dreamweaver CS3\configuration\Extensions.txt Add CTP to the All Documents and PHP Files lines.

Edit C:\Documents and Settings\user\Application Data\Adobe\Dreamweaver 9\Configuration\DocumentTypes\MMDocumentTypes.xml Add ctp to the documenttype id="PHP MySQL"

Edit C:\Program Files\Adobe\Adobe Dreamweaver CS3\configuration\DocumentTypes\MMDocumentTypes.xml Add ctp to the documenttype id="PHP MySQL"

Restart Dreamweaver

CakePHP: Configuration

CakePHP uses the /app/tmp directory for a number of different operations. Model descriptions, cached views, and session information are just a few examples.

Make sure the /app/tmp directory and all subdirectories in the Cake installation are writable by the web server user (apache).
chown -R apache /app/tmp
The Security seeds need to be set for uniqueness. Edit /app/config/core.php Comment out the following lines, duplicate them and change their values:
Configure::write('Security.salt', 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi');
Configure::write('Security.cipherSeed', '76859309657453542496749683645');
Upload file.

Create site database in MySQL.

Copy /app/config/database.php.default to /app/config/database.php

Edit /app/config/database.php Change values for 'host', 'login', 'password', and 'database' to match your database configuration.

Save and upload the file and test by browsing to the root page.

CakePHP: Installation

Go to http://www.cakephp.org/ and download the latest version of CakePHP (currently 1.3.11)

Extract to web root folder, upload and browse to the root page to test installation.

For shared hosting, the /.htaccess file and the /app/webroot/.htaccess file will need some modification for CakePHP to work.

Edit /.htaccess and within the <IfModule> block add RewiteBase / after RewriteEngine on. Repeat for /app/webroot/.htaccess

For 1and1.com shared hosting php4 is default unless you add the following line to the top of your .htaccess file:
AddType x-mapp-php5 .php
Save and upload the file.

About: Drug-ed

This blog is my step-by-step process of learning CakePHP, and designing and building Drug Education, a drug education and compliance assistance website.

I'm posting it both as a record of my path for myself, as well as to help any other aspiring web developers who want to work with CakePHP.

The idea of Drug-ed is to assist in drug compliance and education. A study from 1977 (Improved Patient Compliance through Use of a Daily Drug Reminder Chart) showed a 23-26% increase in drug compliance when a reminder was given. In this digital age an SMS message sent to a cell phone would work for me and may work just as well for others.