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.