I suggest the following:
$categories = get_the_category();
$category_ids = wp_list_pluck( $categories, 'term_id' );
$args = [
'numberposts' => 4,
'category__in' => $category_ids,
];
$related_posts = get_posts( $args );
$related_posts = wp_list_filter( $related_posts, [ 'ID' => get_queried_oject_id() ], 'NOT' );
if ( count( $related_posts > 3 ) ) {
array_pop( $related_posts );
}
global $post;
foreach ( $related_posts as $post ) {
setup_postdata( $post );
// Output posts.
}
wp_reset_postdata();
Note the following points about the code:
- I use
wp_list_pluck()
to get the category IDs in a single line. - I don’t use
post__not_in
. This has known performance issues. Instead I query for 1 more post than we need so that we can remove the current post later if it appears in the results. This will be faster than usingpost__not_in
. - I use
get_posts()
instead ofWP_Query
. This is because it has more appropriate defaults for secondary queries (such as settingignore_sticky_posts
andno_found_rows
totrue
. - Once we have results I use
wp_list_filter()
to remove the current post, if it appears in the results. I do this by usingget_queried_oject_id()
to get the ID of the current post. If it doesn’t appear in the results then I usearray_pop()
to remove the last post to get back down to 3 posts.