Field Class Architecture

Field Class Architecture

lh-profile-page.php began as a single "god class" (LH_profile_page_plugin) holding every field's rendering, validation, and storage logic. Starting with the phone field (v2.03) and continuing through v2.11, each field was extracted into its own dedicated class, following a consistent pattern:

The extraction pattern

For each field:

  1. A new includes/lh-profile-{field}-field-class.php file holds the real, self-contained rendering/validation/storage methods as a static class (LH_Profile_{Field}_Field).
  2. A back-compat proxy shim stays on LH_profile_page_plugin for every extracted public method, delegating to the new class. This exists because the original 2.03 phone extraction assumed no external plugin called these methods directly – that assumption was wrong (ppt-lh-membership-extender called render_phone_input() directly) and fatalled the site the moment 2.03 shipped. Every subsequent extraction leaves a shim regardless of whether an external caller is known, on the same "don't assume, leave the shim" principle.
  3. register_meta() registration, where the field has a real validation rule worth enforcing (see Database Schema doc for which fields this applies to).

Shim-call diagnostic logging

Since the gender/birthdate/address/name extractions (v2.09+), every shim proxy calls a shared maybe_log_external_shim_call() helper as its first line. This inspects the immediate caller's file via debug_backtrace(): calls originating from within lh-profile-page.php itself (expected, pre-extraction internal usage) are silently ignored; calls from any other file are logged at warning level via the plugin's write_log() convention, naming the exact method, file, and line.

This exists to turn "we assume nothing external calls this" into "we know nothing external calls this" – the only way to eventually make shim removal an evidence-based decision rather than a guess. Search LH Debug Log Viewer for "Deprecated shim" to find any external callers the logging has caught. One confirmed find this way: ppt-lh-membership-extender.php was calling render_phone_input(), render_gender_input(), and handle_phone_update() directly – all three were repointed to call LH_Profile_Phone_Field/LH_Profile_Gender_Field directly (site plugin, updated to v1.01).

Extracted field classes

FieldClassFile
Phone (+ secondary)LH_Profile_Phone_Fieldlh-profile-phone-field-class.php
GenderLH_Profile_Gender_Fieldlh-profile-gender-field-class.php
BirthdateLH_Profile_Birthdate_Fieldlh-profile-birthdate-field-class.php
Street addressLH_Profile_Street_Address_Fieldlh-profile-street-address-field-class.php
TownLH_Profile_Town_Fieldlh-profile-town-field-class.php
PostcodeLH_Profile_Postcode_Fieldlh-profile-postcode-field-class.php
State/provinceLH_Profile_State_Province_Fieldlh-profile-state-province-field-class.php
CountryLH_Profile_Country_Fieldlh-profile-country-field-class.php
First nameLH_Profile_First_Name_Fieldlh-profile-first-name-field-class.php
Last nameLH_Profile_Last_Name_Fieldlh-profile-last-name-field-class.php
Display nameLH_Profile_Display_Name_Fieldlh-profile-display-name-field-class.php

Address fields are five separate classes (not one shared class) deliberately – matching the one-class-per-field precedent, and because postcode specifically has real per-country format validation worth adding later (a self-contained future change if it stays in its own file).

What was NOT extracted

The multi-field orchestrator methods – post_handler(), maybe_do_user_update(), new_user_update(), save_backend_profile_profile_fields(), maybe_add_details_when_csv_is_uploaded() – remain in the god class. Each reimplements "for each field: check nonce, check if already set, write if empty" for a different trigger context (frontend POST, backend admin save, CRM enquiry, group invite, CSV import). They're close to duplicates of each other and are the single biggest remaining opportunity to shrink the god class, but consolidating them is a real behaviour-preserving refactor across many call sites, not a quick extraction – flagged as future work, not attempted.

Emails (lh_profile-emails) was also deliberately left unextracted – it's slated for removal, its functionality being superseded by lh-user-identity.

Name field label methods (render_firstname_label(), render_lastname_label(), render_display_name_label()) were extracted then removed entirely (v2.12) once confirmed to have no caller anywhere, live or dead, beyond commented-out code.