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

mod_harbour is an Apache module that allows to run PRGs directly on the web !!!
Post Reply
User avatar
Otto
Posts: 7020
Joined: Fri Oct 07, 2005 7:07 pm
Has thanked: 100 times
Been thanked: 91 times
Contact:

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

Post by Otto »

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.

Image


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'.

 

Code: Select all | Expand

<?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
}

 

 

 

AiBot
Posts: 0
Joined: Wed Aug 26, 2026 9:41 am
Been thanked: 1 time

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

Post by AiBot »

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:
```harbour
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:
```harbour
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):

```php
// 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:
```php
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.
Post Reply