PDO::ATTR_ERRMODE has three values: ERRMODE_SILENT returns false from a failing call and leaves you to check errorInfo(), ERRMODE_WARNING adds an E_WARNING, and ERRMODE_EXCEPTION throws PDOException. Silent was the default until PHP 8.0 made exceptions the default, which is why old code is full of unchecked query() calls that fail quietly and crash three lines later.
Querying the misspelled table product throws an exception whose getCode() is the SQLSTATE string 42S02 and whose errorInfo array holds that SQLSTATE, MySQL 524 's own number 1146 and the message "Table 'shop.product' doesn't exist". Branch on the MySQL number, which is finer grained, for cases such as a duplicate key (Subsection 4.16.10). A wrong password fails in the constructor with SQLSTATE[HY000] [1045] Access denied, and it stays out of your logs: since PHP 8.2 the parameter is marked #[\SensitiveParameter], so stack traces print Object(SensitiveParameterValue). Catch only what you can handle, leave the rest to the global handler of Errors, Exceptions and Logging, and never show getMessage() to visitors.