Part 7 of 10 · MaidsMe Pro

Building Forms the Admin Can Shape, Not Ones Frozen in Code

A maid’s profile needs different fields from a driver’s. Cooking specialities for one, licence class for the other. The naive build hardcodes each profession’s form in PHP, and then every new profession, every new question, is a code change. MaidsMe went the other way, fields live in the database, and the admin shapes the forms. This post is that dynamic field system, the most reusable thing the project produced.

Two tables carry it. One defines fields, one maps them to professions:

// mm_fields: one row per field the platform knows
// id | field_key | label | type | required | options | sort_order
// types: text, number, select, textarea, file, date, email, phone, multiselect

// mm_field_map: which fields belong to which profession, and where they show
// id | profession_id | field_id | show_public | show_register | section_heading

The registration form is then not written, it is rendered, a loop over the mapped fields for the chosen profession, with a renderer per type:

foreach ($this->get_fields_for($profession_id, 'register') as $f) {
    switch ($f->type) {
        case 'select':
            $out .= $this->render_select($f, json_decode($f->options, true));
            break;
        case 'textarea':
            $out .= sprintf(
                '<label>%s</label><textarea name="mm_%s" %s></textarea>',
                esc_html($f->label), esc_attr($f->field_key),
                $f->required ? 'required' : ''
            );
            break;
        default:
            $out .= sprintf(
                '<label>%s</label><input type="%s" name="mm_%s" %s />',
                esc_html($f->label), esc_attr($f->type),
                esc_attr($f->field_key), $f->required ? 'required' : ''
            );
    }
}

Because fields are rows, the admin adds Swimming supervision to nannies or Manual gearbox to drivers from a screen, no deployment, and the same mapping’s show_public flag decides which answers appear on the public profile versus staying admin-only, which quietly extends the privacy architecture, visibility is data too. Submitted answers store as key-value rows against the profile, profile_id, field_id, value, so profiles survive field changes, a renamed label or added field never orphans saved data, it stays mapped by field id.

The honest cost, and it is real, is that flexible schemas move errors from the compiler to the admin. A hardcoded form cannot be misconfigured, a dynamic one can, a required field mapped to no profession, a select with no options. So the field manager validates its own configuration, and the whole definition set exports and imports as JSON, which doubles as a backup and as the way to seed a new site with a proven field setup in one upload.

A few things people ask me about this

Is key-value storage bad for performance? It is heavier to query than columns, and for profile data measured in dozens of fields it is entirely fine. The trade buys schema freedom, reserve fixed columns for the fields every query filters on, like status.

Why store select options as JSON in the field row? Because options belong to the field definition and travel with it, through edits, exports, and imports, without a third table for what is usually a short list.

Next

Fields, mappings, values, inquiries, statuses, the database had become the real design document. The full schema, and the tables I deleted when the direction changed, is the next post.

Leave a Reply

Your email address will not be published. Required fields are marked *