FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index mod_harbour Modernizing the Harbour-to-PHP Workflow: The SSOT Request Adapter & Gateway Pattern
Posts: 7020
Joined: Fri Oct 07, 2005 07:07 PM

Modernizing the Harbour-to-PHP Workflow: The SSOT Request Adapter & Gateway Pattern

Posted: Fri Feb 27, 2026 09:47 AM

Hello friends,

The Problem: Why Web Development feels like Chaos to a Harbour Dev

In the Harbour world, we are kings of our environment. When a user opens a mask to edit a customer record, we know exactly who they are, we have an active lock on the DBF, and our variables stay in memory until we explicitly clear them.

In PHP, everything is different. Every time a user clicks "Save," the server "forgets" who the user is. It’s like a new person walking into your office every 5 seconds. To make it worse, data hits you from three different sides:

The URL (GET): ?app=zimmerplan&id=500
The Form (POST): Data from an input field.
The Modern Way (JSON Payload): Hidden data sent by JavaScript (e.g., {"action": "save"}).

The Use Case: Imagine you are writing a "Save Customer" routine. In standard PHP, you’d have to manually check: Is the user logged in? Did the ID come via the URL or the Form? Did they change their IP address since they logged in? If you forget one check in just one file, you have a security hole.

To bring back that "Harbour-like" stability and control, I’ve developed a pattern called the SSOT (Single Source of Truth) Request Adapter.

The Architecture: The "Security Funnel"
Instead of fishing for variables in $GET or $POST, we use a central Gateway (Router) and a Security Funnel (SSOT Adapter). By the time your business logic starts, you receive a single, taxatively defined array called $in.


Why this works for us:

Strict Taxonomy: You know exactly what’s in the box. $in['user']['role'] tells you who is there, and $in['all']['id'] gives you the record ID, regardless of how it was sent.

Fail-Safe Redirection: If the "Security Funnel" detects an issue (expired session, IP change), it automatically shunts the user back to the Login Service (the red path in the schema).

Forensics: The ['meta'] block is a lifesaver for debugging. You can log exactly which browser and IP sent which JSON payload at what microsecond.

Clean Logic: Your actual application code (Step 6) stays 100% clean of security checks. It only runs if the environment is safe.

In Harbour terms, this is like having a pre-processed LPARAMETERS object that is guaranteed to be authenticated before your procedure even begins.

Think of this like a Header File in C or a Library in Harbour. By placing one single require at the very top of your PHP file, you activate the entire security funnel, the session tracking, and the data normalization. You don't just 'start a script'; you 'join the architecture'.

 

<?php
declare(strict_types=1);

/* -------------------------------------------------------------------------
   1. LOADING THE ADAPTER
   We use DOCUMENT_ROOT to ensure the path is always absolute from the base,
   regardless of how deep your script is located in subfolders.
   ------------------------------------------------------------------------- */
require_once $_SERVER['DOCUMENT_ROOT'] . '/shared/log_functions.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/shared/ssot_adapter.php';

/* -------------------------------------------------------------------------
   2. INITIALIZATION (The "Gatekeeper")
   This is where you define: 
   - The target (App name for the Router-Check)
   - The required HTTP method (POST/GET)
   - The line number for the forensic log (__LINE__)
   ------------------------------------------------------------------------- */
$in = initialize_ssot('zimmerplan', 'POST', __LINE__);

/* -------------------------------------------------------------------------
   3. YOUR APPLICATION LOGIC
   From this point forward, you work exclusively with the "Data Basket" $in.
   The heavy lifting of security and sanitization is already done.
   ------------------------------------------------------------------------- */
   
if ($in['all']['action'] === 'save') {
    logline("PROCESS", "Saving room data for ID: " . $in['all']['id'], __LINE__);
    // ... your business logic goes here
}

 

 

Posts: 0
Joined: Wed Aug 26, 2026 09:41 AM

Re: Modernizing the Harbour-to-PHP Workflow: The SSOT Request Adapter & Gateway Pattern

Posted: Sat Aug 29, 2026 10:11 PM

Excellent architectural breakdown, Otto. This SSOT Request Adapter / Gateway Pattern is exactly the kind of discipline that makes PHP behave like a compiled, stateful environment—something Harbour/Clipper developers instinctively crave.

A few observations on why this resonates strongly with the FiveWin/Harbour mindset:

1. It mirrors the LPARAMETERS / PRIVATE contract

In Harbour, a function/procedure declares its contract upfront:

FUNCTION SaveCustomer( nCustId, cName, @cError )

Your $in array is that contract. It turns the chaotic superglobals ($_GET, $_POST, php://input) into a strict, validated function signature. If $in['all']['id'] isn't set or fails type coercion, the "Security Funnel" rejects the request before your business logic loads—just like Harbour would throw Error BASE/1002 Argument error at the call site.

2. The "Meta Block" = AppInfo() / NetErr() on steroids

Harbour devs are used to inspecting runtime context (AppInfo(), OS(), NetErr()). Your ['meta'] block (IP, UA, timestamp, raw payload hash) gives you forensic-grade context for every request. In a multi-tenant FiveWeb/Mod_Harbour deployment, this is gold for auditing and debugging "heisenbugs" that only happen on Client X's network.

3. Fail-Safe Redirection = QUIT / RETURN NIL with side effects

The "Red Path" (auto-shunt to Login Service) is the web equivalent of:

IF !DbRLock()
   Alert("Record locked by another user")
   RETURN .F.
ENDIF

Centralizing this in the Adapter/Router means zero if (!isset($_SESSION['user'])) die(); scattered across 50 entry points.


Practical Harbour-to-PHP Mapping Table

Harbour / FiveWin Concept SSOT Adapter Equivalent
LPARAMETERS / PARAMETERS $in['all'] (merged & coerced GET/POST/JSON)
PRIVATE / MEMVAR (request scope) $in['user'], $in['session'], $in['meta']
DbRLock() / DbRUnlock() guard Adapter middleware (IP check, token nonce, CSRF)
Set(_SET_EXCLUSIVE, .T.) Adapter enforces "single writer" via DB transaction or row version
REQUEST HB_LANG_SELECT() $in['meta']['lang'] / Accept-Language normalization
ErrorBlock({|e| Log(e); QUIT }) Global Exception Handler fed by $in['meta']['trace_id']

One Suggestion: Make the Contract Explicit (PHP 8.1+)

Since you're defining a taxonomy, enforce it with a readonly DTO so your Step 6 code gets IDE autocomplete and static analysis (Psalm/PHPStan):

// Internal/Contracts/RequestInput.php
readonly class RequestInput {
    public function __construct(
        public UserContext   $user,      // authenticated, role, permissions
        public SessionState  $session,   // csrf_token, flash, ttl
        public Payload       $all,       // merged id, action, form fields (typed)
        public Meta          $meta,      // ip, ua, trace_id, timestamp, raw_hash
    ) {}
}

// In your Adapter, after validation:
return new RequestInput(
    user: new UserContext(...),
    session: new SessionState(...),
    all: new Payload(id: (int)$raw['id'], action: $raw['action'], ...),
    meta: new Meta(...)
);

Now your controller is literally:

require_once 'bootstrap.php'; // activates funnel, returns $in (RequestInput)

$customer = Customer::find($in->all->id);
$customer->save($in->all->toArray());

Question for you

Are you using this pattern inside a FiveWin/Mod_Harbour CGI/FastCGI context (where the Harbour process is the PHP handler via mod_harbour/fivecgi), or as a pure PHP layer talking to the same DBF/SQL backend via PDO/ADODB?

The session/locking strategy changes slightly depending on that boundary.


Respuesta generada por Inteligencia Artificial usando el modelo nemotron-3-ultra-free via OpenCode Zen. Puede contener errores; por favor verifiquela antes de aplicar.
AI-generated reply using the nemotron-3-ultra-free model via OpenCode Zen. May contain mistakes; please verify before applying.

Continue the discussion