Opened 4 weeks ago
Last modified 33 hours ago
#65261 reviewing defect (bug)
Docs: Clarify return value semantics of wpdb::get_results()
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | 7.1 | Priority: | normal |
| Severity: | trivial | Version: | |
| Component: | Database | Keywords: | has-patch |
| Focuses: | docs | Cc: |
Description
The source docblock for wpdb::get_results() in src/wp-includes/class-wpdb.php
currently only documents the return type as a bare union:
@return array|object|null Database query results.
It does not state *when* each branch of that union is returned. The
user-facing documentation on developer.wordpress.org(https://developer.wordpress.org/reference/classes/wpdb/get_results/) is correct
and more specific:
If no matching rows are found or if there is a database error, the
return value will be an empty array. If the$querystring is empty
or you pass an invalidoutput_type, NULL will be returned.
That distinction is not derivable from the docblock and is easy to get
wrong when reading the source — query() calls flush() (which sets
last_result = array()) before any error-producing branch can run, so
SQL-level failures (bad syntax, missing table, lost connection that does
not reconnect) return array(), not null. The only paths that return
null from get_results() are:
- A falsy
$queryargument (early return at the top of the method). - An unrecognised
$outputconstant (final fallthrough at the bottom).
Sister methods are similarly under-documented. wpdb::get_row() does
note "or null on failure" but conflates two different "failure" modes
(falsy $query vs. invalid $output). wpdb::get_col() does not
mention failure at all.
This ticket proposes lifting the devhub wording into the source
docblock so that the inline documentation matches what is published on
developer.wordpress.org, and so that consumers of the docblock (IDEs,
static analysis, generated reference) get the same accuracy as the
manually-edited devhub page.
The patch is a docblock-only change to one method. No behaviour change.
Change History (7)
This ticket was mentioned in PR #11855 on WordPress/wordpress-develop by @apermo.
4 weeks ago
#1
- Keywords has-patch added
@westonruter commented on PR #11855:
4 weeks ago
#2
Can we go the extra mile and provide a @phpstan-return conditional types for the branches? For this part, for example:
If the
$querystring is empty or you pass an invalidoutput_type, NULL will be returned.
I think this would be:
( $query is null|''|false|0 ) ? null : ( $output_type is 'ARRAY_A'|'ARRAY_N'|'OBJECT_K'|'OBJECT' ) ? ( ... ) : null
Where ... contains the branches for the valid parameters.
#3
@
4 weeks ago
- Component changed from General to Database
- Milestone changed from Awaiting Review to 7.1
- Owner set to westonruter
- Status changed from new to reviewing
@apermo commented on PR #11855:
3 weeks ago
#4
Yeah, makes sense.
A couple of small things while drafting it:
- The $query param is typed string in the docblock but the default is null, so the honest type is
string|null(whichget_row()already uses). The cleanest way to express the falsy branch is$queryis non-falsy-string, which covers'','0', andnullwithout enumerating them. - The
$outputconstants are literal strings (OBJECT,OBJECT_K,ARRAY_A,ARRAY_N), so literal matching works directly. - The case-insensitive back-compat path (
strtoupper( $output ) === OBJECT) is hard to express in PHPStan without going into regex territory. I would lean toward modeling the documented contract and letting that path fold into the else branch. Anyone relying on the back-compat is using undocumented behavior anyway.
Here is what I have so far:
@phpstan-param string|null $query
@phpstan-return (
$query is non-falsy-string
? (
$output is 'OBJECT'|'OBJECT_K'
? array<int|string, stdClass>
: (
$output is 'ARRAY_A'
? array<int, array<string, mixed>>
: (
$output is 'ARRAY_N'
? array<int, list<mixed>>
: null
)
)
)
: null
)
While looking at this I noticed something else. The current @return array|object|null overstates the type. get_results() never actually returns a bare stdClass. Every branch returns either an array of stdClass rows, an array of arrays, or null. The object in the union looks like a copy-paste leftover from get_row() (which does return one stdClass). I would want to fix that to @return array|null in the same patch.
Two questions:
- Do you want this in #65261, or should I split it into a follow-up so this PR stays narrowly scoped on the descriptive text? Either is fine with me.
- The same kind of imprecise @return and missing conditional types is almost certainly there on
get_row(),get_col(), andget_var()too. If you are up for a broader sweep, I am happy to take that on as well. Would you prefer this as a follow-up ticket or as part of this PR?
@westonruter commented on PR #11855:
2 weeks ago
#5
- Do you want this in #65261, or should I split it into a follow-up so this PR stays narrowly scoped on the descriptive text? Either is fine with me.
I think it's preferable to fix up PHPStan issues in the course of making other related changes. It reduces the amount of work to make commits.
2\. The same kind of imprecise
@returnand missing conditional types is almost certainly there onget_row(),get_col(), andget_var()too. If you are up for a broader sweep, I am happy to take that on as well. Would you prefer this as a follow-up ticket or as part of this PR?
Yeah, I think a PR that fixes up the return semantics of all methods on WPDB would be good. So if anything, the ticket scope can be changed.
@apermo commented on PR #11855:
3 days ago
#6
Pushed four follow-up commits, one per method.
get_results(): PHPStan conditional +@return array|null+@param string|null(cdd144afb8)get_row(): PHPStan conditional (31989de97c)get_col(): PHPStanlist<string|null>element type (72a1952155)get_var(): clarified that null is also returned when the matched value is an empty string, per Trac #30257 (f89caa9bfa)
Verified with composer phpstan -- src/wp-includes/class-wpdb.php: no new errors, the two preexisting ones (lines 1923/1924, flush() assigning null to non-nullable col_info/last_query) are unrelated and show up on trunk without my changes too.
PR title and description are updated to reflect the broader scope. The Trac ticket still says get_results() only in its title and description. Happy to add a comment over there summarising the scope change, or to update the title directly. Whichever way works for you.
Take a look whenever.
@apermo commented on PR #11855:
33 hours ago
#7
Pushed four more commits after running the diff through a fresh independent review.
522405ae95:@phpstan-paramnarrowing for$outputonget_row()andget_results(). Pins to the documented constants and lets PHPStan catch typos / lowercase. No core callers use the lowercase form, so no baseline work needed.ba0a402929: splitOBJECTvsOBJECT_Kinget_results()(list<stdClass>vsarray<array-key, stdClass>, they have different shapes), and switchARRAY_A/ARRAY_Nouter types tolist<...>to match the$new_array[]population.fad48753c8: hoisted theget_var()empty-string-vs-failure explanation out of@returnand into the description block. The@returnline is now one sentence, matching WP-core style.9655035b38: dropped the blank line between@returnand@phpstan-tags so it matcheswp_parse_url()'s formatting inhttp.php.
PHPStan verified again: no new errors. The two preexisting ones in flush() still show up on trunk without my changes.
PR description updated to reflect all nine commits. Take another look whenever.
The source docblock for
wpdb::get_results()previously documented only the union return typearray|object|null, with no indication of when each branch is returned. The user-facing reference on developer.wordpress.org already states this correctly:This is the behaviour of the implementation:
wpdb::query()callswpdb::flush()(which sets$last_result = array()) before any error-producing branch can run, so SQL-level failures (bad syntax, missing table, lost connection that does not reconnect) returnarray(), notnull. The only paths that returnnullfromget_results()are a falsy$queryargument and an unrecognized$outputconstant.This patch lifts the developer.wordpress.org wording into the inline docblock so that consumers of the docblock — IDEs, static analysis, generated reference — get the same accuracy as the manually edited reference page.
Docblock only. No behaviour change.
## Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4.7
Used for: Source-code trace of
wpdb::get_results()/query()/flush()failure paths, comparison of the inline docblock against the developer.wordpress.org reference, drafting of the revised docblock wording, and drafting of this PR description and the Trac ticket. All output was reviewed by me; I take responsibility for the change.