docs: add design-rationale commentary to core support classes
Enriches class-level docblocks and inline comments across the shared
utility classes with the "why" behind design decisions that aren't
obvious from reading the code alone:
- Cache two-tier rationale, atomic-write semantics, failure modes
- Curl single-use-per-instance rationale, default option choices
- Log wrapper rationale, redaction expectations for callers
- Database auto-migration philosophy, schema-versioning approach
- ServerResource flat-array rationale, interfaces[0]-only limit called
out for future maintainers, unit-conversion map
- ConfigureService why a sibling of ModuleFunctions, catalogue caching
policy, cp-in-constructor reasoning
Pure documentation — no code changes, all files remain lint-clean and
Pint-formatted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,11 +3,47 @@
|
||||
namespace WHMCS\Module\Server\VirtFusionDirect;
|
||||
|
||||
/**
|
||||
* Two-tier cache: uses Redis when the ext-redis extension is available, with an atomic
|
||||
* filesystem fallback stored in the system temp directory.
|
||||
* Two-tier cache: Redis when ext-redis is available, atomic filesystem fallback otherwise.
|
||||
*
|
||||
* WHY TWO TIERS
|
||||
* -------------
|
||||
* The module is deployed to every kind of WHMCS install — shared hosting, dedicated
|
||||
* VPS, bare-metal. Requiring Redis would exclude the long tail of smaller operators
|
||||
* who never installed the extension. But operators who DO have Redis get a huge
|
||||
* performance win for cross-request caching (PowerDNS zone lists, OS template
|
||||
* listings, traffic stats), so we opportunistically use it when present.
|
||||
*
|
||||
* The fallback is filesystem-based, using the OS temp directory. Writes are atomic
|
||||
* via the classic tmp-file + rename pattern so a process crash mid-write can never
|
||||
* corrupt an existing cache entry for another concurrent reader.
|
||||
*
|
||||
* EXPIRY SEMANTICS
|
||||
* ----------------
|
||||
* Redis: native SETEX — the key auto-expires on the server side.
|
||||
* Filesystem: we store a JSON envelope {expires, data} and check expiry on read,
|
||||
* deleting stale entries lazily. This means a cache with lots of expired entries
|
||||
* will slowly accumulate files until accessed — acceptable for the module's scale
|
||||
* (tens-to-hundreds of keys per install) but worth noting if someone ports this
|
||||
* to a higher-volume context.
|
||||
*
|
||||
* NAMESPACE
|
||||
* ---------
|
||||
* Every key is prefixed with "vfd:" to avoid collisions with anything else that
|
||||
* shares the Redis instance. Nested keys add their own sub-prefix (e.g.
|
||||
* "pdns:zones:<hash>" for PowerDNS zone lists) for semantic clarity in the logs.
|
||||
*
|
||||
* FAILURE MODES
|
||||
* -------------
|
||||
* Redis unreachable: we set $redisAvailable = false on first failure, which
|
||||
* permanently disables Redis for the rest of this PHP process (subsequent calls
|
||||
* skip straight to the file cache). Prevents paying reconnect overhead on every
|
||||
* miss when Redis is down.
|
||||
* File cache write fails: silently skipped. Cache is best-effort; a failed SET
|
||||
* just means the next GET will re-fetch from the authoritative source.
|
||||
*/
|
||||
class Cache
|
||||
{
|
||||
/** Module-global key prefix — keeps us out of Redis key collisions on shared installs. */
|
||||
const PREFIX = 'vfd:';
|
||||
|
||||
/** @var \Redis|null */
|
||||
@@ -150,12 +186,18 @@ class Cache
|
||||
}
|
||||
}
|
||||
|
||||
// File cache fallback with atomic write (race condition safe)
|
||||
// File cache fallback with atomic write.
|
||||
// Writing to a temp file + rename ensures that readers either see the
|
||||
// complete previous entry or the complete new entry — never a truncated
|
||||
// or partially-written file. getmypid() suffix lets concurrent PHP
|
||||
// processes write to the same key without stomping each other's temp files.
|
||||
$path = self::filePath($key);
|
||||
$tmp = $path . '.' . getmypid() . '.tmp';
|
||||
$entry = json_encode(['expires' => time() + $ttl, 'data' => $value]);
|
||||
|
||||
if (@file_put_contents($tmp, $entry, LOCK_EX) !== false) {
|
||||
// rename() is atomic on POSIX when source and target are on the same
|
||||
// filesystem (which they always are here — both in sys_get_temp_dir).
|
||||
@rename($tmp, $path);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user