Since PHP 8.1, pdo_mysql returns integer and floating-point columns as int and float in both prepare modes, and SQL NULL as null. Everything else arrives as a string and needs a deliberate conversion at the edge of your code:
<?php
$pdo = require 'db.php';
$row = $pdo->query('SELECT r.body, r.created_at, p.price, p.attributes FROM reviews r
JOIN products p ON p.id = r.product_id WHERE r.id = 5')->fetch();
var_dump($row['body']); // SQL NULL arrives as null
echo (new DateTimeImmutable($row['created_at']))->format('D j M Y, H:i'), "\n";
echo new BcMath\Number($row['price']) * 3, "\n"; // exact decimal arithmetic
echo json_decode($row['attributes'], flags: JSON_THROW_ON_ERROR)->pages, " pages\n";Output
NULL Wed 20 May 2026, 07:00 126.00 468 pages
BcMath\Number (PHP 8.4, Subsection 4.7.5) keeps money exact where float would not. A DATETIME has no time zone, so store UTC and parse with an explicit DateTimeZone (Subsection 4.7.11). Binding null sends SQL NULL, but body = ? never matches it; write body <=> ? (Subsection 3.3.9). To write JSON, bind json_encode($data, JSON_THROW_ON_ERROR) as a string; MySQL 524 validates it on insert.