Wednesday, August 10, 2016

Near Real-Time Updates

For a browser-based application that we produced last year, we found that our end-users were spending a lot of time on the screen - or getting called away before hitting "submit" - and coming back to timed-out sessions.  As users of Google Apps for Government, our folks have gotten used to never having to hit a "save" button and, although we extended the time-out periods, it became something that I wanted to solve before the next release.

The obvious choice was AJAX, but none of us had ever mixed AJAX and CakePHP.  Here's what I worked out to address the issue.  The example that I'll be using is a simple employees table, defined as follows:

CREATE TABLE employees (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    first_name VARCHAR(40) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    street_address VARCHAR(60),
    city VARCHAR(40),
    state VARCHAR(2),
    zip_code INT,
    department_id INT NOT NULL,
    user_id INT NOT NULL,
    created DATETIME DEFAULT NULL,
    modified DATETIME DEFAULT NULL
);

Note that I have foreign keys to a departments and a users table; the users table is drawn from the CakePHP Authentication and Authorization tutorial, and the departments table is defined as follows (if you wish to include it; it won't be used for this post):

CREATE TABLE departments (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    shortcode varchar(4),
    name varchar(50) NOT NULL,
    created DATETIME DEFAULT NULL,
    modified DATETIME DEFAULT NULL
);

After baking these tables and setting up authentication (the latter is not necessary for this exercise), my first changes were at the template level.  I decided to do the employees update first, as the add requires extra logic and I wanted to put that part off while I worked out the main functionality.

In src/Template/employees/edit.ctp, I added the following code to every keyable form field:

<?= $this->Form->create($employee) ?>
<fieldset>
    <legend><?= __('Edit Employee') ?></legend>
    <?php
        echo $this->Form->input('first_name', array(
            "onblur" => "ajaxUpdate(this.id, this.value, $employee->id)"));
        echo $this->Form->input('last_name', array(
            "onblur" => "ajaxUpdate(this.id, this.value, $employee->id)"));
        echo $this->Form->input('street_address', array(
            "onblur" => "ajaxUpdate(this.id, this.value, $employee->id)"));
...

The JavaScript onblur action fires whenever a form field loses focus, so when the user tabs out of a field or mouse-clicks into another field, the JavaScript function ajaxUpdate will be called.  We're sending three parameters to ajaxUpdate:

  • this.id - the HTML "id" of whatever field we're in; e.g. first-name, last-name, etc.
  • this.value - the contents of the field.
  • $employee->id - the primary key of the row we're editing; CakePHP automatically sets this value in the controller for reference by the views (templates).
Also, at the end of my edit.ctp, I added a status field for my own debugging and feedback purposes:

    <?= $this->Form->end() ?>
    <p>Status: <span id="txtStatus"></span></p>
</div>

In webroot/js, I created a file called ajax.js (I have never been the most imaginative with naming!) which contains the following code function:

function ajaxUpdate(fieldName, fieldValue, rowId) {
     var xmlhttp = new XMLHttpRequest();
     if (fieldValue.length == 0) {
         var param2 = fieldName + "=NULL";
     } else {
         var param2 = fieldName + '=' + fieldValue;
     }
     var param1 = rowId;
     xmlhttp.onreadystatechange = function() {
         if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
             document.getElementById("txtStatus").innerHTML = xmlhttp.responseText;
         }
     };
     var postURL = "/ajaxcake/employees/ajax/" + param1 + "/" + param2;
     xmlhttp.open("POST", postURL, true);
     xmlhttp.send();
 }

Here we're doing a bit of validation (I am using the word "NULL" when a field is empty) and constructing a CakePHP-usable URL.  My application is storied in the folder "ajaxcake," we're invoking the employees controller, and my method is called "ajax".

On a normal form submit, we'd get the row ID in the URL, and we'd get a request/response object containing the entire row: field names, and all contents whether changed or not.  Here, though, the server is only going to get what we send it, and - because HTTP is stateless - the server won't know what row we're editing or which column needs changing unless we tell it.  That's why we're sending RowID as the first URL parameter and the field we're updating along with its new value (e.g. "first-name=Joe" or "street-address=1234 Main St.") as the second URL parameter.

Our new XMLHttpRequest (xmlhttp) needs a function that will execute on response; that is what is being prepared by xmlhttp.onreadystatechange.  Mostly for debugging purposes, I'm sending status information back to the textStatus to confirm the updates.

If param2 has spaces, that's okay; the browser will fill it in with space codes (%20) and CakePHP will translate them back.

(Credit to W3 Schools for the basis of my AJAX code!)

Okay, so we're almost there.  Now we just need the method in our employees controller that will accept the URL that we've created and called in our Javascript function.  Here is is:

/**
     * Ajax method
     *
     * @param string|null $id Employee ID.
     * @param string|null $field Field Value.
     * @return string|null.
     */
    public function ajax($id = null, $field = null)
    {
        $this->autoRender = false;
        $fieldSplit = explode('=', $field, 2);
        $fieldToUpdate = str_replace('-', '_', $fieldSplit[0]);
$employee = $this->Employees->get($id, [ 'contain' => [] ]); if ($this->request->is(['post', 'get'])) { $employee->$fieldToUpdate = $fieldSplit[1]; if ($this->Employees->save($employee)) { echo 'Updated ' . $id . '.' . $fieldToUpdate . ' is now ' . $fieldSplit[1]; } else { echo 'Failed to update ' . $id . '.' . $field; } } else { echo 'Update code bypassed.'; } }

Stepping through, we first turn off autoRender, as CakePHP by default will try to find and render an ajax.ctp at the end of this method, and we don't want to render anything new.  We explode the second parameter so that instead of, for example, "street-address=1234 Main St." we instead get the $fieldSplit array with "street-address" and "1234 Main St.".  Next we replace any dashes that came from the HTML "id" with underscores, to match Cake's database naming conventions.

It's pretty standard CakePHP from here on out; we get the employee by it's id and then we replace the contents of the employee row with the new contents of the field we're updating.  PHP allows for the use of variable variables and that's what we're doing with:

$employee->$fieldToUpdate = $fieldSplit[1];

$fieldToUpdate contains the underscored field name that we're updating, and by expressing it in this context with its dollar-sign, the new data ($fieldSplit[1]) ends up in the $employee object in the appropriate column.

All that's left is the persist the data and then echo a message that will go back to our temporary debugging/status field on the screen (textStatus).  What we set out to achieve has taken place without  the user having pressed a button and with no concentration-disrupting refresh of the screen.

With thousands or tens of thousands of users, I have no idea how this would scale; my organization deals with relatively small numbers.  But in our tests, users tab happily from field-to-field at a normal pace, their data always saved up-to-the-minute... even if they never hit the submit button at the bottom of the form.

Sunday, January 31, 2016

AFK

I've been lax in posting lately because I've been - among other things - learning, discovering, and playing with CakePHP too much to take time to stop and write about it.

Back soon with lotsa new, cool stuff.

Friday, January 1, 2016

A CakePHP Contact Form

Contact forms... simple, right?  In CakePHP, it's not hard, but it requires a couple of techniques that seemed worth a post.  Because a contact form normally collects data that we're not going to store in a database, a modelless form is a good choice.  And since we'll be emailing the contact info, we have to configure our CakePHP app to send email.

I'll be using the "CakeHRMS Tutorial" application for my contact form; the app code (with the addition of the contact form) is available via my GitHub repository.

Configuring CakePHP for Email


In this example, I configured my CakePHP app to use SMTP via my web host.  Chances are, for most production applications, you'd want something similar.

Edit config/app.php and find the "EmailTransport" section:

    'EmailTransport' => [
        'default' => [
            'className' => 'Smtp',
            // The following keys are used in SMTP transports
            'host' => 'mail.my.domain',
            'port' => 26,
            'timeout' => 30,
            'username' => 'myaddress@my.domain',
            'password' => 'myPassword',
            'client' => null,
            'tls' => null,
        ],
    ],

You'll need to look at your web host's email configuration information in order to fill this in.  The "host" field is your web hosting account's SMTP server address, the "port" as required for connecting to is, and the username and password are for an email address that you've configured through your web host.

The next block in config/app.php is "Email":

    'Email' => [
        'default' => [
            'transport' => 'default',
            'from' => 'mySenderAddress@my.domain',
            //'charset' => 'utf-8',
            //'headerCharset' => 'utf-8',
        ],
    ],

This sets the "from" address for "default," the transport that we configured in the previous block.  You can configure multiples of both "EmailTransport" and "Email" which allows you the flexibility in your application to use different addresses, web hosts, etc.

Our app is ready to send emails; now we need to give the user a way to interface with it.

Adding a Modelless Form


A modelless form is a form that isn't associated with a database table.  Most forms are used to manipulate data that we store in a database; that's what we get out of the "bin/cake bake" process.  In this case, however, there's no "contact" table, hence no model (i.e. no table or entity).

My contact form is very similar to the one found in the CakePHP documentation on modelless forms.  The code for the form lives in src/Form, a directory that doesn't exist until you create it.  Let's take a look at the code:

<?php
// in src/Form/ContactForm.php
namespace App\Form;

use Cake\Form\Form;
use Cake\Form\Schema;
use Cake\Validation\Validator;
use Cake\Mailer\Email;

class ContactForm extends Form
{

    protected function _buildSchema(Schema $schema)
    {
        return $schema->addField('name', 'string')
            ->addField('email', ['type' => 'string'])
            ->addField('body', ['type' => 'text']);
    }

    protected function _buildValidator(Validator $validator)
    {
        return $validator->add('name', 'length', [
                'rule' => ['minLength', 10],
                'message' => 'Please enter your name'
            ])->add('email', 'format', [
                'rule' => 'email',
                'message' => 'Please enter a valid email address',
            ])->add('body', 'length', [
                'rule' => ['minLength', 25],
                'message' => 'Please enter your message text',
            ]);
    }

    protected function _execute(array $data)
    {
        $email = new Email();
        $email->profile('default');
        
        $email->from([$data['email']])
            ->to('my.address@my.domain')
            ->subject('Web Site Contact Form')
            ->send([$data['body']]);
  
        return true;
    }
}

In function _buildSchema, we define the fields that we want to use for our form; we're going to collect the sender's name ("name"), his or her email address ("email"), and then the text of their message ("body").

In _buildValidator we set validation rules for our fields and create messages to explain errors to our users.

Finally, _execute will send the email for us.  Our email object ($email) is an instance of the Email class, and we're telling it to use the "default" profile that we set up in config/app.php.  We'll send the email using the "from" address and body text submitted via the form.

The Controller


We'll need a controller for our contact form.  Remember "convention over configuration" -- since our form is "ContactForm," our controller should be ContactController:

<?php
// In a controller
namespace App\Controller;

use App\Controller\AppController;
use App\Form\ContactForm;

class ContactController extends AppController
{
    public function index()
    {
        $contact = new ContactForm();
        if ($this->request->is('post')) {
            if ($contact->execute($this->request->data)) {
                $this->Flash->success('Your message has been sent; we\'ll get back to you soon!');
                $this->request->data['name'] = null;
                $this->request->data['email'] = null;
                $this->request->data['body'] = null;
            } else {
                $this->Flash->error('There was a problem submitting your form.');
            }
        }
        $this->set('contact', $contact);
    }
}

We've created an index method that creates a contact object ($contact) from the ContactForm class, and if our request is a POST we call the execute method of $contact with the data from the HTTP request.  This sends our email so we flash a "success" message and then clear out the form fields (as we're not redirecting to another screen).

We still need one other piece: the template for the form.

The Template


In src/Template, create a "Contact" folder and create an index.ctp like this:

<nav class="large-3 medium-4 columns" id="actions-sidebar">
...
(your sidebar navigation goes here)
...
</nav>
</div>
<div class="employees view large-9 medium-8 columns content">
    <?= $this->Form->create($contact); ?>
    <?= $this->Form->input('name'); ?>
    <?= $this->Form->input('email'); ?>
    <?= $this->Form->input('body'); ?>
    <?= $this->Form->button('Submit'); ?>
    <?= $this->Form->end(); ?>
</div>

Nothing unusual here; this is pretty much just a normal form template.  When we invoke create($contact) we're referencing the variable that we "set" in our controller.  The template's "Submit" button invokes the index method of the ContactController, just like we'd expect.  Mostly, the big difference in "modelless" is that you don't have a model or entity, so you have to put that code elsewhere; that's the role of the code in src/Form/ContactForm.php.



If all went well - and assuming you have an email server upon which to test - you should now have a fully-operational "Contact" form.


Sunday, December 27, 2015

Using CKEditor with CakePHP

CKEditor is a popular FOSS rich-text editor used in many software products and by a whole slew of companies.  This tutorial will show you how to include it in your CakePHP applications.

I’ll be using an app baked off the “articles” table provided in the CakePHP Blog Tutorial.

Downloading CKEditor

Visit the CKEditor web site and download one of the packages (Basic, Standard, or Full).  Unzip the package and copy it over to your project’s webroot folder.


Configuring your App

After baking (“bin/cake bake all articles”) the articles table, you will have a set of CRUD screens.  We’re going to add CKEditor to the “create” (add) and “update” (edit) screens, but first let’s go to our default.ctp and include the CKEditor JavaScript file.

// src/Template/Layout/default.ctp

    <?= $this->Html->css('base.css') ?>
    <?= $this->Html->css('cake.css') ?>
    <?= $this->Html->script('/ckeditor/ckeditor.js') ?>

    <?= $this->fetch('meta') ?>
    <?= $this->fetch('css') ?>
    <?= $this->fetch('script') ?>
...

Next, modify edit.ctp so that it can use CKEditor.  Still using the articles table from the CakePHP Blog Tutorial, the default form code looks like this:

// src/Template/Articles/edit.ctp
...
    <?= $this->Form->create($article) ?>
    <fieldset>
        <legend><?= __('Edit Article') ?></legend>
        <?php
            echo $this->Form->input('title');
            echo $this->Form->input('body');
        ?>
    </fieldset>
    <?= $this->Form->button(__('Submit')) ?>
    <?= $this->Form->end() ?>
...

We need to modify the form input for body so that it has an ID:

// src/Template/Articles/edit.ctp
...
    <?= $this->Form->create($article) ?>
    <fieldset>
        <legend><?= __('Edit Article') ?></legend>
        <?php
            echo $this->Form->input('title');
            echo $this->Form->input('body', ['id' => 'richTextEditor']);
        ?>
    </fieldset>
    <?= $this->Form->button(__('Submit')) ?>
    <?= $this->Form->end() ?>
...

By associating an ID with the form input, we can then replace the default editor box with CKEditor.  I used “richTextEditor” as my ID name, but it can be anything you choose; it just has to made the name used in the script call (below).  Add the following to the bottom of your edit.ctp:

// src/Template/Articles/edit.ctp

<script>
    CKEDITOR.replace('richTextEditor');
</script>

That’s it; now when you visit the edit page in your browser, you should see something like this:


In the above screenshot, the Basic editor is shown; the Standard and Full versions add more buttons and widgets.

Do the same thing to your add.ctp so that when we add a new article we can use CKEditor as well.  Be sure to add something that uses the rich text features so that we can fully test it.


Displaying the Content

If you added or updated an article to use rich text and you then visit the view page, you’ll see something like this:


Not pretty!  We’re seeing the HTML tags on our view screen.  Fortunately, there’s an easy fix.  Edit your view.ctp file and change this line:

    <div class="row">
        <h4><?= __('Body') ?></h4>
        <?= $this->Text->autoParagraph(h($article->body)); ?>
    </div>

To this:

    <div class="row">
        <h4><?= __('Body') ?></h4>
        <?= $this->Text->autoParagraph($article->body); ?>
    </div>

And now your view screen should display the content with the HTML tags interpreted rather than displayed:


Easy, huh?  CKEditor is a great way to add functionality to your web site or application, improving the appearance of large text blocks and giving your end-users a capability that they've come to expect.

Sunday, December 13, 2015

Custom Finder Methods, Part Two

Our next custom finder is very similar to the first, only this one executes a where with a SQL join.  In the last tutorial, we modified our table and controller to allow for finding all employees by last name; this time we'll show all employees for a given department.  Rather than use the department's name field, which may contain spaces, I've opted to select using code, a 1-4 character short-code from the departments table.

Model


Since we're still displaying employees, we've chosen the employees model and controller for the new code.  First, the custom finder:

// src/Model/Table/EmployeesTable.php
...
    public function findByDepartment(Query $query, array $options)
    {
        $departments = TableRegistry::get('Departments');
        
        $query
            ->where(['1' => '1'])
            ->join([
                'table' => 'departments',
                'alias' => 'd',
           'type' => 'INNER',
           'conditions' => 
              ['d.id = Employees.department_id',
              'd.code' => $options['dcode']]
        ]);
        return $query;
    }

Because we're in the model for the employees table but will be accessing department information as well, we need to load departments with TableRegistry.

In our query, you'll see the very strange "where 1=1" reference.  Even though this where doesn't need a "where" clause, I included it here for reference purposes; "1" will always equal "1" so it doesn't affect the result set.

As the example shows, we've passed the name of the table to which we're joining, the type of join (in this case, "INNER"), and the conditions of the join.  The id key field of departments should match the foreign key department_id of employees, and the code should match what was passed to us in the URL.

Controller


In the employees controller, I've added a method called "indept" so that one can access "employees/indept/XYZ":

    public function indept($dcode = null)
    {
        $this->set('employees', $this->paginate(
            $this->Employees->find(
                'byDepartment', 
                ['dcode' => $dcode])
                ->contain(['Departments'])
                ));
        $this->set('_serialize', ['employees']);
        
        if (is_null($dcode)) {
            $this->Flash->error(__('No value passed for department code.'));
        }       

        $this->render('index');
    }

What should immediately jump out to us is that the code here is virtually identical to what we used in the previously-created named method.  Not very DRY, huh?  Go ahead and put your code in as shown above and make sure that everything works; then we'll clean it up.

What we've done do far should allow both of the following URLs to produce the desired results:

  • /employees/named/Smith
  • /employees/indept/DIT

But we probably want to give our end-users a little more direct method of accessing these queries.

View (Template)


I thought it would be cool to use Google's Material Icons for the view changes, and this requires a quick side-trip into our default template.  Edit default.ctp and put in a line to include the Google Material Icons style sheet after cake.css:

// src/Template/Layout/default.ctp
...
    <?= $this->Html->css('base.css') ?>
    <?= $this->Html->css('cake.css') ?>
    <?= $this->Html->css('https://fonts.googleapis.com/icon?family=Material+Icons') ?>

    <?= $this->fetch('meta') ?>
    <?= $this->fetch('css') ?>
...

Now we have access all of the icon set and we can use them in our other templates.

Edit index.ctp in the employees folder and make the following changes:

// src/Template/Employees/index.ctp
...
<thead>
    <tr>
        <th><?= $this->Paginator->sort('first_name') ?></th>
        <th><?= $this->Paginator->sort('last_name') ?></th>
        <th><?= __(' ') ?></th>
        <th><?= $this->Paginator->sort('hire_date') ?></th>
        <th><?= $this->Paginator->sort('department_id') ?></th>
        <th><?= __(' ') ?></th>
        <th><?= $this->Paginator->sort('birth_date') ?></th>
        <th class="actions"><?= __('Actions') ?></th>
    </tr>
</thead>
<tbody>
    <?php foreach ($employees as $employee): ?>
    <tr>
        <td><?= h($employee->first_name) ?></td>
        <td><?= h($employee->last_name) ?></td>
        <td><i class="material-icons"><?= $this->Html->link(__('search'), ['action' => 'named', $employee->last_name]) ?></i></td>
        <td><?= h($employee->hire_date) ?></td>
        <td><?= $employee->has('department') ? $this->Html->link($employee->department->name, ['controller' => 'Departments', 'action' => 'view', $employee->department->id]) : '' ?></td>
        <td><i class="material-icons"><?= $this->Html->link(__('search'), ['action' => 'indept', $employee->department->code]) ?></i></td>
        <td><?= h($employee->birth_date) ?></td>
        <td class="actions">
            <?= $this->Html->link(__('View'), ['action' => 'view', $employee->id]) ?>
            <?= $this->Html->link(__('Edit'), ['action' => 'edit', $employee->id]) ?>
            <?= $this->Form->postLink(__('Delete'), ['action' => 'delete', $employee->id], ['confirm' => __('Are you sure you want to delete # {0}?', $employee->id)]) ?>
        </td>
    </tr>
    <?php endforeach; ?>
</tbody>
...

I ditched the "id" field to make more room, moved birth_date to where "created" was, then added blank headings and the material icon for "search".  The search icon after last_name links into the named method, passing the last name, and the search icon after the department name links to the indept method with the department's short code.

It should all look something like this:



Clicking on the search icons will now take you to another screen, showing the fruits of our labors... and more search icons (because we're using index.ctp for our search results).

This, too, is something we can clean up in part three.

Thursday, December 10, 2015

A DBMS Side Trip

A little break from the direction of my last post; in finishing up the Custom Finder Methods tutorial I realized that I really wanted to work with a database that was configured for CakePHP's naming conventions.  Plus I decided to make a couple of other modifications, such as dropping the dept_emp linking table (instead, employees are owned by departments) and adding a users table for subsequent authentication.

Here's the modified database, from which I'll be doing several future tutorials.

An Employee Database


DROP DATABASE IF EXISTS employees;
CREATE DATABASE IF NOT EXISTS employees;
USE employees;

SELECT 'CREATING DATABASE STRUCTURE' as 'INFO';


DROP TABLE IF EXISTS dept_emp_links,

                     dept_managers,
                     titles,
                     salaries, 
                     employees, 
                     departments;

   set storage_engine = InnoDB;


select CONCAT('storage engine: ', @@storage_engine) as INFO;


CREATE TABLE users (

    id          INT             NOT NULL AUTO_INCREMENT,
    username    VARCHAR(50)     NOT NULL,
    password    VARCHAR(255)    NOT NULL,
    role        ENUM ('administrator','editor', 'contibutor', 'viewer')  NOT NULL,
    created     DATETIME DEFAULT NULL,
    modified    DATETIME DEFAULT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE departments (

    id          INT             NOT NULL AUTO_INCREMENT,
    code        CHAR(4)         NOT NULL,
    name        VARCHAR(40)     NOT NULL,
    created     DATETIME DEFAULT NULL,
    modified    DATETIME DEFAULT NULL,
    PRIMARY KEY (id),
    UNIQUE  KEY (code)
);

CREATE TABLE employees (

    id          INT             NOT NULL AUTO_INCREMENT,
    birth_date  DATE            NOT NULL,
    first_name  VARCHAR(20)     NOT NULL,
    last_name   VARCHAR(30)     NOT NULL,
    gender      ENUM ('M','F')  NOT NULL,    
    hire_date   DATE            NOT NULL,
    department_id   INT         NOT NULL,
    created     DATETIME DEFAULT NULL,
    modified    DATETIME DEFAULT NULL,
    KEY         (department_id),
    FOREIGN KEY (department_id) REFERENCES departments (id) ON DELETE CASCADE,
    PRIMARY KEY (id)
);

CREATE TABLE dept_managers (

    id           INT             NOT NULL AUTO_INCREMENT,
    department_id    INT         NOT NULL,
    employee_id      INT         NOT NULL,
    from_date    DATE            NOT NULL,
    to_date      DATE            NOT NULL,
    created     DATETIME DEFAULT NULL,
    modified    DATETIME DEFAULT NULL,
    KEY         (employee_id),
    KEY         (department_id),
    FOREIGN KEY (employee_id)  REFERENCES employees (id)    ON DELETE CASCADE,
    FOREIGN KEY (department_id) REFERENCES departments (id) ON DELETE CASCADE,
    PRIMARY KEY (id)
); 


CREATE TABLE positions (

    id          INT             NOT NULL AUTO_INCREMENT,
    employee_id INT             NOT NULL,
    title       VARCHAR(50)     NOT NULL,
    from_date   DATE            NOT NULL,
    to_date     DATE,
    created     DATETIME DEFAULT NULL,
    modified    DATETIME DEFAULT NULL,
    KEY         (employee_id),
    FOREIGN KEY (employee_id)  REFERENCES employees (id)    ON DELETE CASCADE,
    PRIMARY KEY (id)
); 
        
CREATE TABLE salaries (
    id          INT             NOT NULL AUTO_INCREMENT,
    employee_id INT             NOT NULL,
    salary      INT             NOT NULL,
    from_date   DATE            NOT NULL,
    to_date     DATE            NOT NULL,
    created     DATETIME DEFAULT NULL,
    modified    DATETIME DEFAULT NULL,
    KEY         (employee_id),
    FOREIGN KEY (employee_id)  REFERENCES employees (id)    ON DELETE CASCADE,
    PRIMARY KEY (id)
);

For convenience, you can copy the above into a file and run the whole thing from the command line.  For example, if you store the script as "employees.sql" you could use:

mysql -u root -p < employees.sql

An App for the Tutorials


Create a CakePHP skeleton app:

composer create-project --prefer-dist cakephp/app cakehrms

Then edit your config/app.php file with the correct parameters needed for your new database:

    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            //'port' => 'nonstandard_port_number',
            'username' => 'root',
            'password' => 'yourpasswordhere',
            'database' => 'employees',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
            'cacheMetadata' => true,
            'log' => false,

Next, bake:

bin/cake bake all --everything

Now you have a CRUD app on the new database from which you can work through several upcoming tutorials from this site.


Tuesday, December 8, 2015

Custom Finder Methods, Part One

Custom finder methods in CakePHP "are the ideal way to package up commonly used queries, allowing you to abstract query details into a simple to use method."

The examples below use the MySQL employee demo database; after running the scripts to install the database, bake up some basic CRUD with:

bin/cake bake all --everything

This will provide the basis upon which to follow many of the tutorials on this site.

A Simple Finder on Name


Let's start with a finder method, defined in the model for the employees table, that will return employees based on their last name:

/src/Model/Table/EmployeesTable.php
...
    public function findByName(Query $query, array $options)
    {
        $query->where([
            'Employees.last_name' => $options['lname']
        ]);
        return $query;
    }

We've created a new function, "findByName" that compares last_name in the Employees table to a passed value ('lname') from the $options array.

In our employees controller, we need a corresponding method:

/src/Controller/EmployeesController.php
...
    public function named($lname = null)
    {
        $this->set('employees', $this->paginate(
            $this->Employees->find(
                'byName', 
                ['lname' => $lname])));
        $this->set('_serialize', ['employees']);
        
        if (is_null($lname)) {
            $this->Flash->error(__('No value passed for last name.'));
        }       

        $this->render('index');
    }

You'll notice that this is almost identical to the index method with the exception of the finder method.  We could have put this in index and executed it conditionally, but I thought it cleaner to have a separate method.

Following CakePHP's idea of "convention over configuration," our finder method - named "findByName," is evoked by calling "find('byName')".  We're passing the last name which will become part of the $options array.

If no name was passed, we're flashing an error message.

We'll need a view for this, but the view in this case is identical to the existing "index.ctp" for employees; for now, let's just use it.

You should now be able to see a list of employees with a given last name by passing the last name in the URL, such as:

http://localhost:8765/employees/named/Facello



If you call "named" without passing a value, you'll see our error message:



More Complex Finders


So far we've walked through the logic of a finder method and we've created a method that can be called from anywhere to return a list of employees by last name.

In part two of this tutorial, we'll produce a list of all employees by their department number.