Units

A unit allows you to package up something like a form or table into something that will appear on a webpage (potentially many times).

Each unit contains its own PHP code and HTML output.

The PHP code will often use helpers, but it is possiblt to also call in other units as well.

For example, you might want a simple search form (e.g. a single text field and submit button), which uses the form helper, where it can be used in other units that list articles, customers, etc.


Files

To create a unit, you can either use the CLI:

./cli --new

Or simply add a 'php' and a 'ctp' file to the appropriate folder:

/app/unit/search/search-form.php
/app/unit/search/search-form.ctp

The .php file contains a class of the same name (with underscores and 'unit' suffix).

The .ctp file contains the HTML, and is optional if you have provided a html() method.


Example setup

To create a self contained "contact us" form, first create the PHP:

/app/unit/contact/contact-form.php

<?php

    class contact_form_unit extends unit {

        protected $config = array(
                'dest_url' => array('type' => 'url'),
            );

        protected function authenticate($config) {
            return true; // Anyone can use
        }

        protected function setup($config) {

            //--------------------------------------------------
            // Config

                $now = new timestamp();

            //--------------------------------------------------
            // Record

                $record = record_get(DB_PREFIX . 'log_contact');
                $record->value_set('ip', config::get('request.ip'));

            //--------------------------------------------------
            // Form setup

                $form = new form();
                $form->form_class_set('basic_form');
                $form->form_button_set('Send');
                $form->db_record_set($record);

                $field_name = new form_field_text($form, 'Name');
                $field_name->db_field_set('name');
                $field_name->min_length_set('Your name is required.');
                $field_name->max_length_set('Your name cannot be longer than XXX characters.');

                $field_email = new form_field_email($form, 'Email');
                $field_email->db_field_set('email');
                $field_email->format_error_set('Your email does not appear to be correct.');
                $field_email->min_length_set('Your email is required.');
                $field_email->max_length_set('Your email cannot be longer than XXX characters.');

                $field_message = new form_field_textarea($form, 'Message');
                $field_message->db_field_set('message');
                $field_message->min_length_set('Your message is required.');
                $field_message->max_length_set('Your message cannot be longer than XXX characters.');
                $field_message->cols_set(40);
                $field_message->rows_set(5);

            //--------------------------------------------------
            // Form submitted

                if ($form->submitted()) {

                    //--------------------------------------------------
                    // Spam detection

                        if (is_spam_like($field_message->value_get())) {

                            $field_human = new form_field_checkbox($form, 'I am a Human', 'human_' . $now->format('Ymd'));
                            $field_human->db_field_set('human');
                            $field_human->text_values_set('true', 'false');
                            $field_human->required_error_set('This message looks like Spam, can you confirm you are human?');

                            if ($field_human->value_get() == 'false') {
                                $form->db_insert();
                            }

                        }

                    //--------------------------------------------------
                    // Validation

                        // $form->error_add('Example error');

                    //--------------------------------------------------
                    // Form valid

                        if ($form->valid()) {

                            //--------------------------------------------------
                            // Email

                                $values = $form->data_array_get();

                                $email = new email();
                                $email->subject_set('Contact us');
                                $email->request_table_add($values);
                                $email->send(config::get('email.contact_us'));

                            //--------------------------------------------------
                            // Save

                                $record_id = $form->db_insert();

                            //--------------------------------------------------
                            // Next page

                                redirect($config['dest_url']->get(array('id' => $record_id)));

                        }

                }

            //--------------------------------------------------
            // Form defaults

                if ($form->initial()) {
                    // $field_name->value_set('My name');
                }

            //--------------------------------------------------
            // Variables

                $this->set('form', $form);

            //--------------------------------------------------
            // JavaScript

                $response = response_get();

                $response->js_add('/a/js/contact.js');
                $response->js_code_add($form->validation_js());

        }

    }

?>

And the HTML:

/app/unit/contact/contact-form.ctp

    <p>Use the form below to contact us:</p>

    <?= $form->html(); ?>

Example usage

For any controller that does not have a related view file, you can simply call:

$unit = unit_add('contact_form', array(
        'dest_url' => url('/contact/thank-you/'),
    ));

Or you can use the unit_get() function:

$unit = unit_get('contact_form', array(
        'dest_url' => url('/contact/thank-you/'),
    ));

$response = response_get();
$response->set('unit', $unit);

Where the view file can print the unit with:

<?= $unit->html(); ?>

The reference to the unit (returned from unit_add() or unit_get()) also allows you to call custom methods on it, and retrieve variables that have been set, e.g.

$unit->search_get();

$name = $unit->get('name');

Config

The protected $config array allows you to define which configuration variables can be passed in (typically from the controller).

Some other examples include:

protected $config = array(
        'id'   => array('type' => 'int'),
        'url1' => array('type' => 'url'),
        'url2' => array('type' => 'url', 'default' => './thank-you/'),
        'url3' => array('type' => 'url', 'default' => NULL),
        'name' => array('type' => 'str', 'default' => 'Unknown'),
        'item' => array('type' => 'obj'),
        'list' => array('default' => array()),
    );

Anything which does not have a 'default' value is required.

If a 'type' is specified, it will convert any non NULL values to that type (int/str/url), or throw an error if is cannot be converted (url/obj).


Authentication

Typically user permissions are checked in the controller.

However the authenticate() method allows you to double check this.

By returning false, the unit will simply call exit_with_error(), alerting you to the problem.

It is probably a good idea to setup a default authenticate() method to ensure you always set this:

/app/library/class/unit.php

class unit extends unit_base {
    protected function authenticate($config) {
        return false;
    }
}

Variables

You can pass variables to the HTML by calling:

$this->set('name', 'value');

Which can be accessed in the HTML with:

<?= html($name); ?>

These variables are not available to the main view file (a unit should be self contained).

But you can still access the response object, and call:

$response = response_get();
$response->js_add('/path/to/file.js');
$response->set('name', 'value');

Sub-unit usage

If you have a unit that in turn needs to use another unit (e.g. a table starting with a search form), then you don't need to do anything different:

$search_form = unit_get('search_form');

This will still return the unit object, and allow you to call methods on it:

$search_form->get('search');
$search_form->search_text_get(); // Custom method

And like any other variable, it can be sent to the ctp file with:

$this->set('search_form', $search_form);

And printed with:

<?= $search_form->html(); ?>

Multiple ctp files

If the unit has many different types of output (HTML), rather than using one large ctp file, you can specify alternatives with:

/app/unit/example/example.php

class example_unit extends unit {

    protected function setup($config) {
        $this->view_name_set('a');
    }

}

Which will then use the ctp file:

/app/unit/example/example-a.ctp