Commit 7f1e8cb37b1 for php

commit 7f1e8cb37b1e6da736070d5501b06d2d1763ef12
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Tue Sep 29 11:38:23 2026 -0400

    ext/pdo: Throw a ValueError from bindColumn() for an unknown column (#23835)

    A column name that is not in the result set is a programming error, so
    report it the way the method already reports an empty name or an index
    below one, rather than through PDO::ATTR_ERRMODE. The equivalent parameter
    failure in bindParam() and bindValue() stays an ERRMODE error: its site in
    rewrite_name_to_position() is only reachable once execute() has populated
    bound_param_map, never from the bind methods themselves.

    Closes GH-23835

diff --git a/UPGRADING b/UPGRADING
index 5d551005042..27dc06f12d2 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -19,6 +19,11 @@ PHP 8.7 UPGRADE NOTES
 1. Backward Incompatible Changes
 ========================================

+- PDO:
+  . PDOStatement::bindColumn() now throws a ValueError when the column name is
+    not present in the result set. It previously reported the condition
+    through PDO::ATTR_ERRMODE and returned false.
+
 - Standard:
   . The number of filters that can be chained in a php://filter URL is limited
     to 16 by default. Set the stream context option max_filter_count to change
diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c
index 1e9e1052d28..9774b197b7d 100644
--- a/ext/pdo/pdo_stmt.c
+++ b/ext/pdo/pdo_stmt.c
@@ -295,12 +295,7 @@ static bool really_register_bound_param(struct pdo_bound_param_data *param, pdo_
 		/* if you prepare and then execute passing an array of params keyed by names,
 		 * then this will trigger, and we don't want that */
 		if (param->paramno == -1) {
-			/* Should this always be an Error? */
-			char *tmp;
-			/* TODO Error? */
-			spprintf(&tmp, 0, "Did not find column name '%s' in the defined columns; it will not be bound", ZSTR_VAL(param->name));
-			pdo_raise_impl_error(stmt->dbh, stmt, "HY000", tmp);
-			efree(tmp);
+			zend_argument_value_error(1, "must refer to a column present in the result set, \"%s\" given", ZSTR_VAL(param->name));
 			return false;
 		}
 	}
diff --git a/ext/pdo/tests/pdo_bindcolumn_unknown_column.phpt b/ext/pdo/tests/pdo_bindcolumn_unknown_column.phpt
index 44e85ec07c3..3e3ba30e8c4 100644
--- a/ext/pdo/tests/pdo_bindcolumn_unknown_column.phpt
+++ b/ext/pdo/tests/pdo_bindcolumn_unknown_column.phpt
@@ -1,5 +1,5 @@
 --TEST--
-PDO: bindColumn() must fail for a column name that is not in the result set
+PDO: bindColumn() must throw for a column name that is not in the result set
 --EXTENSIONS--
 pdo
 --SKIPIF--
@@ -17,16 +17,20 @@
 $db = PDOTest::factory();
 $db->exec('CREATE TABLE pdo_bindcolumn_unknown_column (name varchar(255))');

-$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
-$stmt = $db->query('SELECT name FROM pdo_bindcolumn_unknown_column');
-var_dump(@$stmt->bindColumn('nosuchcolumn', $var));
-
-$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-try {
-    $stmt->bindColumn('nosuchcolumn', $var);
-} catch (PDOException $e) {
-    echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+// The error mode must not affect a ValueError.
+foreach ([PDO::ERRMODE_SILENT, PDO::ERRMODE_WARNING, PDO::ERRMODE_EXCEPTION] as $mode) {
+    $db->setAttribute(PDO::ATTR_ERRMODE, $mode);
+    $stmt = $db->query('SELECT name FROM pdo_bindcolumn_unknown_column');
+    try {
+        $stmt->bindColumn('nosuchcolumn', $var);
+    } catch (ValueError $e) {
+        echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+    }
 }
+
+// A column that does exist still binds.
+$stmt = $db->query('SELECT name FROM pdo_bindcolumn_unknown_column');
+var_dump($stmt->bindColumn('name', $var));
 ?>
 --CLEAN--
 <?php
@@ -37,5 +41,7 @@
 $db->exec('DROP TABLE pdo_bindcolumn_unknown_column');
 ?>
 --EXPECT--
-bool(false)
-PDOException: SQLSTATE[HY000]: General error: Did not find column name 'nosuchcolumn' in the defined columns; it will not be bound
+ValueError: PDOStatement::bindColumn(): Argument #1 ($column) must refer to a column present in the result set, "nosuchcolumn" given
+ValueError: PDOStatement::bindColumn(): Argument #1 ($column) must refer to a column present in the result set, "nosuchcolumn" given
+ValueError: PDOStatement::bindColumn(): Argument #1 ($column) must refer to a column present in the result set, "nosuchcolumn" given
+bool(true)
diff --git a/ext/pdo_sqlite/tests/pdo_driver_options_weakref.phpt b/ext/pdo_sqlite/tests/pdo_driver_options_weakref.phpt
index 16dc9a3c466..15cafa656e7 100644
--- a/ext/pdo_sqlite/tests/pdo_driver_options_weakref.phpt
+++ b/ext/pdo_sqlite/tests/pdo_driver_options_weakref.phpt
@@ -19,17 +19,20 @@ class Tracked {}

 $stmt = $db->prepare('SELECT ? AS value');
 $stmt->execute();
-$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
 $value = null;
 $driverOptions = [new Tracked()];
 $weakReference = WeakReference::create($driverOptions[0]);
-var_dump(@$stmt->bindColumn('missing', $value, PDO::PARAM_STR, 0, $driverOptions));
-unset($driverOptions);
+try {
+    $stmt->bindColumn('missing', $value, PDO::PARAM_STR, 0, $driverOptions);
+} catch (ValueError $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+unset($e, $driverOptions);
 var_dump($weakReference->get());
 unset($value, $stmt);
 ?>
 --EXPECT--
 bool(true)
 NULL
-bool(false)
+ValueError: PDOStatement::bindColumn(): Argument #1 ($column) must refer to a column present in the result set, "missing" given
 NULL