1010/**
1111 * Core class used to organize comments as instantiated objects with defined members.
1212 *
13+ * The `@property-read` fields below are not stored on the comment. They are proxied to the
14+ * comment's post by {@see WP_Comment::__get()}, and are null when the comment is not attached
15+ * to a post or when that post no longer exists.
16+ *
1317 * @since 4.4.0
18+ *
19+ * @property-read numeric-string|''|null $post_author
20+ * @property-read string|null $post_date
21+ * @property-read string|null $post_date_gmt
22+ * @property-read string|null $post_content
23+ * @property-read string|null $post_title
24+ * @property-read string|null $post_excerpt
25+ * @property-read non-empty-string|null $post_status
26+ * @property-read non-empty-string|null $comment_status
27+ * @property-read non-empty-string|null $ping_status
28+ * @property-read string|null $post_name
29+ * @property-read string|null $to_ping
30+ * @property-read string|null $pinged
31+ * @property-read string|null $post_modified
32+ * @property-read string|null $post_modified_gmt
33+ * @property-read string|null $post_content_filtered
34+ * @property-read non-negative-int|null $post_parent
35+ * @property-read string|null $guid
36+ * @property-read int|null $menu_order
37+ * @property-read non-empty-string|null $post_type
38+ * @property-read string|null $post_mime_type
39+ * @property-read numeric-string|null $comment_count
40+ *
41+ * @phpstan-type Data_Array array{
42+ * comment_ID: numeric-string,
43+ * comment_post_ID: numeric-string,
44+ * comment_author: string,
45+ * comment_author_email: string,
46+ * comment_author_url: string,
47+ * comment_author_IP: string,
48+ * comment_date: non-empty-string,
49+ * comment_date_gmt: non-empty-string,
50+ * comment_content: string,
51+ * comment_karma: numeric-string,
52+ * comment_approved: non-empty-string,
53+ * comment_agent: string,
54+ * comment_type: string,
55+ * comment_parent: numeric-string,
56+ * user_id: numeric-string,
57+ * ...
58+ * }
1459 */
1560#[AllowDynamicProperties]
1661final class WP_Comment {
@@ -74,6 +119,7 @@ final class WP_Comment {
74119 *
75120 * @since 4.4.0
76121 * @var string
122+ * @phpstan-var non-empty-string
77123 */
78124 public $ comment_date = '0000-00-00 00:00:00 ' ;
79125
@@ -82,6 +128,7 @@ final class WP_Comment {
82128 *
83129 * @since 4.4.0
84130 * @var string
131+ * @phpstan-var non-empty-string
85132 */
86133 public $ comment_date_gmt = '0000-00-00 00:00:00 ' ;
87134
@@ -107,8 +154,12 @@ final class WP_Comment {
107154 /**
108155 * Comment approval status.
109156 *
157+ * The values used in core are '0' (unapproved), '1' (approved), 'spam', 'trash',
158+ * and 'post-trashed' (set for every comment on a post that is moved to the trash).
159+ *
110160 * @since 4.4.0
111161 * @var string
162+ * @phpstan-var non-empty-string
112163 */
113164 public $ comment_approved = '1 ' ;
114165
@@ -123,6 +174,13 @@ final class WP_Comment {
123174 /**
124175 * Comment type.
125176 *
177+ * The values used in core are 'comment', 'pingback', 'trackback', and 'note'. Custom
178+ * comment types are possible.
179+ *
180+ * Comments created before 5.5.0 may store an empty string rather than 'comment', so this
181+ * cannot be relied upon to be non-empty. {@see get_comment_type()} normalizes that case
182+ * when reading.
183+ *
126184 * @since 4.4.0
127185 * @since 5.5.0 Default value changed to `comment`.
128186 * @var string
@@ -154,8 +212,16 @@ final class WP_Comment {
154212 /**
155213 * Comment children.
156214 *
215+ * Mapping of comment ID to WP_Comment object, as populated by the default
216+ * `hierarchical => 'threaded'` argument of get_children(). Note that if a
217+ * caller passes a `hierarchical` value of 'flat' or `false` to
218+ * get_children(), a sequentially-keyed array of WP_Comment objects (also
219+ * including all descendants, in the 'flat' case) is stored here instead.
220+ *
221+ * Null until populated by {@see WP_Comment::get_children()}.
222+ *
157223 * @since 4.4.0
158- * @var array
224+ * @var array<int, WP_Comment>|null
159225 */
160226 protected $ children ;
161227
@@ -171,7 +237,8 @@ final class WP_Comment {
171237 * Post fields.
172238 *
173239 * @since 4.4.0
174- * @var array
240+ * @var string[]
241+ * @phpstan-var list<non-empty-string>
175242 */
176243 protected $ post_fields = array ( 'post_author ' , 'post_date ' , 'post_date_gmt ' , 'post_content ' , 'post_title ' , 'post_excerpt ' , 'post_status ' , 'comment_status ' , 'ping_status ' , 'post_name ' , 'to_ping ' , 'pinged ' , 'post_modified ' , 'post_modified_gmt ' , 'post_content_filtered ' , 'post_parent ' , 'guid ' , 'menu_order ' , 'post_type ' , 'post_mime_type ' , 'comment_count ' );
177244
@@ -183,6 +250,7 @@ final class WP_Comment {
183250 * @global wpdb $wpdb WordPress database abstraction object.
184251 *
185252 * @param int $id Comment ID.
253+ * @phpstan-param int|numeric-string $id
186254 * @return WP_Comment|false Comment object, otherwise false.
187255 */
188256 public static function get_instance ( $ id ) {
@@ -195,7 +263,8 @@ public static function get_instance( $id ) {
195263
196264 $ _comment = wp_cache_get ( $ comment_id , 'comment ' );
197265
198- if ( ! $ _comment ) {
266+ if ( ! is_object ( $ _comment ) ) {
267+ /** @var object{ comment_ID: string, comment_post_ID: string, comment_author: string, comment_author_email: string, comment_author_url: string, comment_author_IP: string, comment_date: string, comment_date_gmt: string, comment_content: string, comment_karma: string, comment_approved: string, comment_agent: string, comment_type: string, comment_parent: string, user_id: string }|null $_comment */
199268 $ _comment = $ wpdb ->get_row ( $ wpdb ->prepare ( "SELECT * FROM $ wpdb ->comments WHERE comment_ID = %d LIMIT 1 " , $ comment_id ) );
200269
201270 if ( ! $ _comment ) {
@@ -228,19 +297,28 @@ public function __construct( $comment ) {
228297 *
229298 * @since 4.4.0
230299 *
231- * @return array Object as array.
300+ * @return array<string, mixed> Object as array.
301+ * @phpstan-return Data_Array
232302 */
233- public function to_array () {
234- return get_object_vars ( $ this );
303+ public function to_array (): array {
304+ /** @var Data_Array $comment */
305+ $ comment = get_object_vars ( $ this );
306+ return $ comment ;
235307 }
236308
237309 /**
238310 * Gets the children of a comment.
239311 *
240312 * @since 4.4.0
313+ * @since 7.1.0 A `count` or `fields` query now returns its result directly rather than
314+ * erroneously storing it in the comment's children cache.
241315 *
242316 * @param array $args {
243- * Array of arguments used to pass to get_comments() and determine format.
317+ * Array of arguments used to pass to {@see get_comments()} and determine format.
318+ * Any other argument accepted by {@see WP_Comment_Query::__construct()} may also be passed, and is
319+ * forwarded to `get_comments()`. Note that `parent` is always overridden with this comment's ID.
320+ * A `$count` or `$fields` query returns the direct children only, and does not populate the
321+ * comment's cached children, since that cache holds `WP_Comment` objects.
244322 *
245323 * @type string $format Return value format. 'tree' for a hierarchical tree, 'flat' for a flattened array.
246324 * Default 'tree'.
@@ -267,8 +345,42 @@ public function to_array() {
267345 * the value of $meta_key, and the array keys of
268346 * `$meta_query`. Also accepts false, an empty array, or
269347 * 'none' to disable `ORDER BY` clause.
348+ * @type string $fields Which fields to return. Accepts 'ids' for comment IDs, or an
349+ * empty string for full `WP_Comment` objects. Default empty.
350+ * @type bool $count Whether to return a comment count rather than comments.
351+ * Default false.
352+ * @type string $type Limit results to comments of a given type, such as 'comment',
353+ * 'pingback', 'trackback', or 'note'. Accepts 'all' for every
354+ * type. Default empty.
355+ * @type int $number Maximum number of comments to retrieve. Default empty (no limit).
356+ * @type int $post_id Limit results to comments on a given post. Default 0.
357+ * @type string $order How to order retrieved comments. Accepts 'ASC' or 'DESC'.
358+ * Default 'DESC'.
270359 * }
271- * @return WP_Comment[] Array of `WP_Comment` objects.
360+ * @return WP_Comment[]|int[]|int Array of `WP_Comment` objects, an array of comment IDs when
361+ * `$fields` is 'ids', or the number of children when `$count`
362+ * is true.
363+ *
364+ * @phpstan-param array{
365+ * format?: 'tree'|'flat',
366+ * status?: 'hold'|'approve'|'all'|string,
367+ * hierarchical?: 'threaded'|'flat'|false,
368+ * orderby?: string|string[]|false,
369+ * fields?: 'ids'|'',
370+ * count?: bool,
371+ * type?: string,
372+ * number?: int,
373+ * post_id?: int,
374+ * order?: 'ASC'|'DESC',
375+ * ...
376+ * } $args
377+ * @phpstan-return (
378+ * $args is array{ count: true, ... } ? non-negative-int : (
379+ * $args is array{ fields: 'ids', ... } ? non-negative-int[] : (
380+ * $args is array{ format: 'flat', ... } ? list<WP_Comment> : array<int, WP_Comment>
381+ * )
382+ * )
383+ * )
272384 */
273385 public function get_children ( $ args = array () ) {
274386 $ defaults = array (
@@ -278,9 +390,29 @@ public function get_children( $args = array() ) {
278390 'orderby ' => '' ,
279391 );
280392
393+ /** @var array{ format: 'tree'|'flat', status: string, hierarchical: 'threaded'|'flat'|false, orderby: string|string[]|false, fields?: 'ids'|'', count?: bool, type?: string, number?: int, post_id?: int, order?: 'ASC'|'DESC', ... } $_args */
281394 $ _args = wp_parse_args ( $ args , $ defaults );
282395 $ _args ['parent ' ] = $ this ->comment_ID ;
283396
397+ /*
398+ * A 'count' or 'ids' query returns an integer or a list of comment IDs rather than
399+ * WP_Comment objects. Neither may be written to the children cache, which holds
400+ * WP_Comment objects and is read back by add_child(), get_child(), and the 'flat'
401+ * format below. Return the result directly and leave the cache untouched. The two
402+ * branches must stay separate: each is narrowed independently, and `count` is only
403+ * safe to overwrite in the 'ids' branch.
404+ */
405+ if ( ! empty ( $ _args ['count ' ] ) ) {
406+ return get_comments ( $ _args );
407+ } elseif ( isset ( $ _args ['fields ' ] ) && 'ids ' === $ _args ['fields ' ] ) {
408+ $ _args ['count ' ] = false ; // For static analysis of the conditional return type.
409+ return get_comments ( $ _args );
410+ }
411+
412+ // Only WP_Comment objects are returned past this point. Stated positively for static analysis.
413+ $ _args ['count ' ] = false ;
414+ $ _args ['fields ' ] = '' ;
415+
284416 if ( is_null ( $ this ->children ) ) {
285417 if ( $ this ->populated_children ) {
286418 $ this ->children = array ();
@@ -315,8 +447,8 @@ public function get_children( $args = array() ) {
315447 *
316448 * @param WP_Comment $child Child comment.
317449 */
318- public function add_child ( WP_Comment $ child ) {
319- $ this ->children [ $ child ->comment_ID ] = $ child ;
450+ public function add_child ( WP_Comment $ child ): void {
451+ $ this ->children [ ( int ) $ child ->comment_ID ] = $ child ;
320452 }
321453
322454 /**
@@ -341,7 +473,7 @@ public function get_child( $child_id ) {
341473 *
342474 * @param bool $set Whether the comment's children have already been populated.
343475 */
344- public function populated_children ( $ set ) {
476+ public function populated_children ( $ set ): void {
345477 $ this ->populated_children = (bool ) $ set ;
346478 }
347479
@@ -351,14 +483,15 @@ public function populated_children( $set ) {
351483 * If `$name` matches a post field, the comment post will be loaded and the post's value checked.
352484 *
353485 * @since 4.4.0
486+ * @since 7.1.0 Returns false instead of causing a fatal error when the comment's post cannot be found.
354487 *
355488 * @param string $name Property to check if set.
356489 * @return bool Whether the property is set.
357490 */
358491 public function __isset ( $ name ) {
359492 if ( in_array ( $ name , $ this ->post_fields , true ) && 0 !== (int ) $ this ->comment_post_ID ) {
360- $ post = get_post ( $ this ->comment_post_ID );
361- return property_exists ( $ post , $ name );
493+ $ post = get_post ( ( int ) $ this ->comment_post_ID );
494+ return $ post && property_exists ( $ post , $ name );
362495 }
363496
364497 return false ;
@@ -370,14 +503,20 @@ public function __isset( $name ) {
370503 * If `$name` matches a post field, the comment post will be loaded and the post's value returned.
371504 *
372505 * @since 4.4.0
506+ * @since 7.1.0 Returns null instead of the global post's field when the comment is not attached to
507+ * a post, and no longer raises a warning when the comment's post cannot be found.
373508 *
374509 * @param string $name Property name.
375510 * @return mixed
376511 */
377512 public function __get ( $ name ) {
378- if ( in_array ( $ name , $ this ->post_fields , true ) ) {
379- $ post = get_post ( $ this ->comment_post_ID );
513+ if ( in_array ( $ name , $ this ->post_fields , true ) && 0 !== (int ) $ this ->comment_post_ID ) {
514+ $ post = get_post ( (int ) $ this ->comment_post_ID );
515+ if ( ! $ post ) {
516+ return null ;
517+ }
380518 return $ post ->$ name ;
381519 }
520+ return null ;
382521 }
383522}
0 commit comments