The answer is eager loading or cache or both.
Eager loading
Have a look to this pseudo-code:
$ids = get_ids_from_a_db_table();
foreach ( $ids as $id ) {
$row = get_row_from_foreign_table_using( $id );
echo "Row title for the ID: $id is $row->title";
}
If the number of $ids is n than this simple code runs n+1 queries, the first to load the ids, then one for each id.
You code is even worse, it run 2 queries for every id, one to get attachment post object, one to get attachemnt url (is WordPress, not you, really)
So you run 2n+1 queries…
Eager loading, referred to a db query, is the way you solve the n+1 (or 2n+1) queries problem by querying in only one db request all the data you need (normally is done using proper JOIN + WHERE )
$data = get_data_from_joined_db_tables();
foreach ( $data as $row ) {
echo "Row title for the ID: $row->id is $row->title";
}
How do that in WordPress? You need two things:
- add proper join and where clauses to merge the post meta table
- filter the fields returned by
WP_Queryto include meta table fields
Both can be done using query filters, here a class extending WP_Query that do that:
class Thumb_Query extends WP_Query {
protected static $args = array( 'nopaging' => TRUE );
public function __construct( $args="" ) {
if ( empty( $args ) ) $args = self::$args; // defaults
parent::__construct( $args );
}
public function get_posts() {
add_filter('posts_clauses', array( __CLASS__, 'thumb_filters') );
$results = parent::get_posts();
remove_filter('posts_clauses', array( __CLASS__, 'thumb_filters') );
return $this->parse_images( $results );
}
public static function thumb_filters( $pieces ) {
$meta = $GLOBALS['wpdb']->postmeta;
$posts = $GLOBALS['wpdb']->posts;
$pieces['fields'] .= ", thumbs.meta_value as thumb_id";
$pieces['fields'] .= ", imgs.post_title as thumb_title";
$pieces['fields'] .= ", imgdata.meta_value as thumb_file";
$pieces['join'] .= "INNER JOIN {$meta} thumbs ON ({$posts}.ID = thumbs.post_id)";
$pieces['join'] .= " INNER JOIN {$posts} imgs ON (thumbs.meta_value = imgs.ID)";
$pieces['join'] .= " INNER JOIN {$meta} imgdata ON (imgs.ID = imgdata.post_id)";
$where = " AND ( thumbs.meta_key = '_thumbnail_id' AND ";
$where .= " CAST( thumbs.meta_value AS SIGNED ) > 0 )";
$where .= " AND ( imgdata.meta_key = '_wp_attached_file' )";
$pieces['where'] .= $where;
$pieces['groupby'] = " {$posts}.ID";
return $pieces;
}
protected function parse_images( $rows ) {
$exts = array('jpg', 'jpeg', 'gif', 'png');
foreach ( $rows as $i => $row ) {
$urls = wp_extract_urls( $row->post_content );
$img = FALSE;
while ( ! $img && ! empty($urls) ) {
$url = array_shift($urls);
$ext = strtolower ( pathinfo( $url, PATHINFO_EXTENSION ) );
if ( in_array( $ext, $exts ) ) $img = $url;
}
$rows[$i]->thumb_link = $img ? $img : '#';
}
}
}
This class extends WP_Query, so accepts same things of its parent but add some filters to change the eager loading thumnail post id, thumnail post name and thumbnail file path.
So if your query returns 50 posts, to display thumbnails you run 101 (2n+1) queries, my class you runs only 1 query.
In addition before output the results, the class parse all the rows, and extract first image url from post content and remove the filter added.
How to use
Use the standard WP_Query arguments:
$args = array(
'post_type' => 'portfolio',
'category_name' => 'featured',
'posts_per_page' => 50
);
But use Thumb_Query instead of WP_Query
$portfolio = new Thumb_Query( $args );
Then loop and output:
if ( $portfolio->have_posts() ) {
$u = wp_upload_dir();
global $post;
foreach( $portfolio->posts as $post ) {
setup_postdata( $post );
$f="<div class="oneCell"><a href="%s" title="%s">";
$f .= '<img class="lazy" alt="%s" width="189" src="%s" data-original="%s"/>';
$f .= '</a></div>';
$title = esc_attr( get_the_title() );
printf( $f,
esc_url( $post->thumb_link ),
$title, $title,
esc_url( get_template_directory_uri() . "https://wordpress.stackexchange.com/images/grey.png" ),
esc_url( trailingslashit($u['baseurl']) . $post->thumb_file )
);
}
wp_reset_postdata();
}
Inside the loop you have access to all standard post properties, but every post has additional 4 properties:
$post->thumb_idthe thumbnail attachment id$post->thumb_titlethe thumbnail attachment title$post->thumb_filethe thumbnail file relative to uploads folder, something like'/2014/04/myfile.jpg'$post->thumb_linkthe full url of the first image in post content or ‘#’ if no image found