Trongate PHP Framework Docs
Introduction
Quick Start
Basic Concepts
Understanding Routing
Intercepting Requests
Module Fundamentals
Database Operations
Templates
Helpers
Form Handling
Form Validation
Working With Files
Image Manipulation
Working With Dates & Times
Language Control
Security
Tips And Best Practices

The Create/Update Pattern

Building forms for creating and editing records is a universal requirement in web applications. Trongate v2 encourages an elegant pattern that handles both operations with a single form, single set of validation rules, and minimal code duplication.

Why This Pattern Works

  • DRY (Don't Repeat Yourself) - One form, one view, one set of validation rules
  • Simple Logic - A single conditional determines data source
  • Maintainable - Change fields in one place, not two
  • User-Friendly - Consistent interface for creation and editing

The Three Components

Component Purpose Key Responsibility
create() Display the form Decide whether to load data from POST or database
submit() Process submission Validate, save, redirect on success or redisplay on failure
Model helpers Data preparation get_data_from_post() and get_data_from_db()

Complete Working Example

Here's a complete Members module demonstrating the pattern:

The Controller (Members.php)

PHP
<?php
class Members extends Trongate {

    /**
     * Display a list of members.
     */
    public function manage(): void {
        $this->trongate_security->make_sure_allowed();

        $data = [
            'members' => $this->model->fetch_members(),
            'view_module' => 'members',
            'view_file' => 'manage'
        ];

        $this->templates->admin($data);
    }

    /**
     * Display the create or update member form.
     */
    public function create(): void {
        $this->trongate_security->make_sure_allowed();

        $update_id = segment(3, 'int');
        $submit = post('submit');

        // Load data from POST if creating new OR if validation failed
        // Otherwise load from database for editing
        if (($update_id === 0) || ($submit === 'Submit')) {
            $data = $this->model->get_data_from_post();
        } else {
            $data = $this->model->get_data_from_db($update_id);
        }

        $data['headline'] = ($update_id === 0) ? 'Create Member' : 'Update Member';
        $data['update_id'] = $update_id;
        $data['form_location'] = str_replace('/create', '/submit', current_url());
        $data['view_module'] = 'members';
        $data['view_file'] = 'create';

        $this->templates->admin($data);
    }

    /**
     * Handle form submission for creating or updating a member.
     */
    public function submit(): void {
        $this->trongate_security->make_sure_allowed();

        // Validate the submitted data
        $this->validation->set_rules('username', 'username', 'required|min_length[3]|max_length[30]');
        $this->validation->set_rules('email_address', 'email address', 'required|valid_email');

        $result = $this->validation->run();

        if ($result === true) {
            // Validation passed - save data and redirect
            $data = $this->model->get_data_from_post();
            $update_id = segment(3, 'int');

            if ($update_id === 0) {
                $this->db->insert($data, 'members');
                set_flashdata('The new member was successfully created.');
            } else {
                $this->db->update($update_id, $data, 'members');
                set_flashdata('The member was successfully updated.');
            }

            redirect('members/manage');
        } else {
            // Validation failed - redisplay form with errors
            $this->create();
        }
    }

    /**
     * Display the delete confirmation screen.
     */
    public function confirm_delete(): void {
        $this->trongate_security->make_sure_allowed();

        $update_id = segment(3, 'int');
        $this->model->get_data_from_db($update_id);

        $data = [
            'form_location' => str_replace('/confirm_delete', '/submit_confirm_delete', current_url()),
            'update_id' => $update_id,
            'view_module' => 'members',
            'view_file' => 'confirm_delete'
        ];

        $this->templates->admin($data);
    }

    /**
     * Handle confirmed deletion of a member.
     */
    public function submit_confirm_delete(): void {
        $this->trongate_security->make_sure_allowed();

        $update_id = (int) post('update_id', true);
        $this->db->delete($update_id, 'members');
        set_flashdata('The member record was successfully deleted.');
        redirect('members/manage');
    }
}

The Model (Members_model.php)

PHP
<?php
class Members_model extends Model {

    /**
     * Retrieve and sanitise member data from POST.
     * 
     * Used for both saving to database and redisplaying form after validation errors.
     * 
     * @return array
     */
    public function get_data_from_post(): array {
        $data = [
            'username' => post('username', true),
            'email_address' => post('email_address', true),
            'active' => (int) (bool) post('active', true)
        ];

        return $data;
    }

    /**
     * Retrieve a single member record from the database.
     * 
     * Used when editing an existing member.
     * 
     * @param int $update_id
     * @return array
     */
    public function get_data_from_db(int $update_id): array {
        $record_obj = $this->db->get_where($update_id, 'members');

        if ($record_obj === false) {
            http_response_code(404);
            echo 'Member not found';
            die();
        }

        $member = (array) $record_obj;
        return $member;
    }

    /**
     * Fetch all members and append a human-readable status.
     * 
     * @return array
     */
    public function fetch_members(): array {
        $members = $this->db->get('id', 'members');

        foreach ($members as $key => $member) {
            $active = (int) $member->active;
            $members[$key]->status = ($active === 1) ? 'active' : 'inactive';
        }

        return $members;
    }
}

The View (create.php)

View File
<h1><?= $headline ?></h1>
<div class="card">
    <div class="card-heading">Member Details</div>
    <div class="card-body">
        <?php
        echo form_open($form_location, array('class' => 'highlight-errors'));
        
        echo form_label('Username');
        echo validation_errors('username');
        echo form_input('username', $username, array('placeholder' => 'Username...', 'autocomplete' => 'off'));

        echo form_label('Email Address');
        echo validation_errors('email_address');
        echo form_email('email_address', $email_address, array('placeholder' => '[email protected]'));

        echo '<label>';
        echo form_checkbox('active', 1, $active);
        echo ' Active member';
        echo '</label>';

        echo '<div class="text-center">';
        echo anchor('members/manage', 'Cancel', array('class' => 'button alt'));

        if ($update_id > 0) {
            echo anchor('members/confirm_delete/'.$update_id, 'Delete Member', array('class' => 'button danger'));
        }

        echo form_submit('submit', 'Submit');
        echo '</div>';

        echo form_close();
        ?>
    </div>
</div>

How It Works: The Four Scenarios

The key to this pattern is the conditional in create():

PHP
if (($update_id === 0) || ($submit === 'Submit')) {
    $data = $this->model->get_data_from_post();
} else {
    $data = $this->model->get_data_from_db($update_id);
}

Translation: "If we're creating a new record OR if the form was just submitted (validation error), get data from POST. Otherwise, we're editing an existing record, so get data from the database."

Scenario 1: Creating a New Record

URL/members/create
Condition$update_id === 0 is true
Data Sourceget_data_from_post() returns empty strings
ResultEmpty form displays

Scenario 2: Editing an Existing Record

URL/members/create/5
Condition$submit is empty (GET request)
Data Sourceget_data_from_db(5)
ResultForm displays with existing data

Scenario 3: Validation Error

TriggerUser submits form with invalid data
Flowsubmit() calls create() on failure
Condition$submit === 'Submit' is true
Data Sourceget_data_from_post() with submitted values
ResultForm redisplays with user's values and error messages

Scenario 4: Successful Submission

TriggerUser submits valid form
ValidationPasses in submit()
DatabaseInsert or update via get_data_from_post()
Feedbackset_flashdata() stores success message
ResultRedirect to manage page (PRG pattern)

Validation Flow

Validation happens in submit(), not create():

  1. User submits form to submit()
  2. defines validation requirements
  3. executes validation
  4. If valid: Save data, then invoke , followed by
  5. If invalid: Errors stored in session, create() gets called to redisplay form

Key Point: The create() method never validates. It only decides where to get data from (POST or database) and displays the form. Validation is exclusively the responsibility of submit().

Checkbox Handling

Checkboxes require special attention because unchecked boxes submit nothing. The following model code demonstrates a robust and consistent way to handle checkboxes:

PHP
public function get_data_from_post(): array {
    $data = [
        'username' => post('username', true),
        'email_address' => post('email_address', true),
        'active' => (int) (bool) post('active', true)  // 0 or 1
    ];
    return $data;
}

This conversion works for both:

  • Database storage: Stores 0 or 1 in the database
  • Form redisplay: Returns 0 or 1, which form_checkbox() interprets correctly

The Post-Redirect-Get (PRG) Pattern

After successful submission, Trongate uses PRG to prevent duplicate submissions:

Step Action Result
POSTForm submits to submit()Data received
Validaterun() checks dataPass or fail
SaveDatabase insert or updateRecord stored
Flashdataset_flashdata()Message stored in session
Redirectredirect('members/manage')Browser receives 302
GETBrowser requests manage pageSuccess message displays

If the user refreshes, they see the manage page (GET) - no resubmission warning.

URL Structure

URLMethodAction
/members/managemanage()List all records
/members/createcreate()New record form (empty)
/members/create/5create()Edit record form (ID 5)
/members/submitsubmit()Create new record
/members/submit/5submit()Update record ID 5
/members/confirm_delete/5confirm_delete()Confirm delete ID 5
  • Two model methods only: get_data_from_post() and get_data_from_db() handle all data loading
  • Single validation location: Always validate in submit(), never in create()
  • Consistent checkbox handling: Use (int) (bool) post('field', true) for all checkboxes
  • Flashdata for success: Always use set_flashdata() before redirecting
  • Always redirect after POST: Prevents duplicate submissions on refresh
  • URL conventions: Use /create for forms, /submit for processing

We're continually improving the Trongate documentation. If anything is incorrect, unclear, incomplete, or could be better, we'd genuinely appreciate your input.

Share your thoughts in the Documentation Feedback.

Leave Feedback About This Page