PHP / Editorial solution guide
Prepared SQL, corrupted text, and lost session state
Distinguish database binding, encoding, and session-lifecycle failures before weakening safeguards. Use an owned SQLite fixture and synthetic session evidence rather than configured application storage.
Read-only knowledge base · No public comments
Recognize the symptoms
A prepared query rejects parameters, Arabic text returns corrupted, or a session change disappears on the next request. Similar visible symptoms can originate in different layers: SQL structure, driver behavior, connection encoding, cookie scope, handler storage, early output, or writing after a session was closed.
Understand the likely causes
- Common mistakes include binding a column name as data, mismatched placeholder counts, quoting an already bound value, assuming every driver returns identical types, converting text from a guessed encoding, and treating session_write_close as if later edits still persist. A CLI storage check cannot establish browser cookie delivery.
1. Reduce SQL to a value-binding reproducer
Inspect the exact fixed SQL structure, placeholder names, parameter keys, and driver. Bind values only; choose identifiers and sort directions from a program-owned allowlist. Enable an explicit exception mode in the isolated test and preserve safe error categories without logging private parameter values. The in-memory example verifies quote-containing UTF-8 data round-trips through one SQLite PDO connection. It does not validate a configured server, another engine’s encoding settings, or concurrency. Do not replace prepared statements with interpolation to make a parameter error disappear.
<?php
declare(strict_types=1);
$db = new PDO("sqlite::memory:", null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec("CREATE TABLE names (value TEXT NOT NULL)");
$write = $db->prepare("INSERT INTO names (value) VALUES (:value)");
$expected = "أمل's course";
$write->execute(["value" => $expected]);
$actual = $db->query("SELECT value FROM names")->fetchColumn();
echo $actual, PHP_EOL;
echo $actual === $expected ? "same bytes
" : "changed bytes
";
أمل's course
same bytes
The strict comparison checks the same returned bytes. No real DSN, credential, or application table is used.
2. Find the first encoding disagreement
Trace one synthetic Arabic value through input decoding, domain storage, connection settings, retrieval, response headers, and rendering. Compare small byte-level evidence at boundaries rather than repeatedly converting until the display looks plausible. A correct database value can still be displayed under the wrong response charset, and already corrupted stored text cannot be repaired reliably by declaring UTF-8 later. Inspect the intended engine’s documented connection encoding instead of assuming the SQLite demonstration proves it. Preserve original evidence privately and avoid logging real names for convenience.
3. Separate cookie transport from server persistence
For session loss, check whether the response could set the cookie, whether the next request sends the expected scoped cookie, and whether the intended handler can read its own stored state. Inspect HTTPS and Secure, host/path scope, SameSite context, expiration, early output, and the actual handler configuration. Never paste session identifiers into public diagnostics. Do not disable security flags or reuse a real account session to make a test pass. Use synthetic clients and owned temporary storage for a narrow lifecycle test, then verify the actual browser boundary separately.
4. Repair write ownership and locking
Complete required session changes before session_write_close, then stop treating later in-memory edits as automatically durable. Release a handler lock before unrelated long work when the application contract allows, but do not reopen and overwrite state without considering concurrent requests. For database transactions, verify the final invariant after a controlled failure rather than only seeing rollBack called. Keep session state separate from durable business records. A session counter surviving close/reopen is useful local evidence, while cookie policy, distributed storage, authentication, and authorization remain separate integration responsibilities.
Verify the fix
Verify quoted and Arabic text round-trips, intended parameter rejection, and rollback invariants with synthetic data on the target driver. For sessions, verify server write/read and browser cookie scope separately, including close timing and an expired state. Record runtime, driver, handler, encoding, and tested boundary; do not claim another environment was validated.
Keep it from recurring
- Keep SQL structure allowlisted, values bound, UTF-8 policy end-to-end, and session lifecycle explicit. Use private diagnostic categories and owned fixtures. Maintain separate tests for database, HTTP, and session boundaries so one passing local example cannot conceal an untested transport or storage assumption.