Part 2 of the series that teaches you how to unlock the secrets of the Shopp Theme API by mastering product collections.

How the Theme API Really Works – Part 2: Product Collections

Wine Bottles in Window

How the Theme API Really Works – Part 2: Product Collections

In Part 1 we introduced you to the idea of the working object context and how Shopp, like WordPress, handles processing permalink URL requests to automatically load the requested object. Also like WordPress, Shopp gives you template tools to load objects on demand in your template code. We also showed you how the shopp() Theme API gateway function works using context.information dot-notation with one or more options separated by ampersand (&).

Shopp supports several different object contexts in the Theme API including: cart, cartitem, checkout, collection, customer, error, product, purchase, shipping and storefront. You can read up on all of the information tags available for each of them in the Theme API Reference.

In part 1 you also learned how to load up a product on demand using shopp('storefront.product'). Now what about showing a bunch of products instead of just one, like The Loop in WordPress? This introduces a new concept used in Shopp called collections.

Product Collections

In WordPress, posts are published with a date and time. As time goes on they age and are displaced by new posts. Older posts are kept in a grouping that WordPress calls an archive. When you access a category of posts, you access an archive of the old posts assigned to that category.

Products are not normally archived for posterity. You put them on a shelf for customers, and customers buy them. Products are taken off the shelf while new products of the same kind arrive to replace the purchased products. The product lives on and stays published to the website. So instead of an archive, Shopp thinks of groupings of products as a collection of products.

There are three different kinds of built-in product collections for Shopp: smart collections, product categories and product tags.

A smart collection in Shopp is a dynamic selection of products chosen by pre-programmed logic. The logic they use is pre-designed and not easily editable by store owners, but new smart collections can be developed and added to Shopp through custom plugins.

A product category is like a post category, except for products. While you can get at them using WordPress functions, Shopp’s collection APIs are designed with higher performance for ecommerce operations.

A product tag is a collection of products that have been tagged with a specific name and works similar to tag archives for posts.

Both product categories and product tags live as custom taxonomies in WordPress.

All of these fall under the umbrella of product collections. You can show information from a product collection using shopp('collection') tags. Like products, they can be loaded either by requesting the proper collection URL permalink or, by using on demand loading to set the working collection context.

Now that all the abstract explainers are out of the way, let’s look at some real concrete examples.

Loading a Collection

Most people will be working with product categories a lot, so let’s start there. We have a category for some example products named Sauces that uses the slug sauces. Just like products, you can access a collection, in this case a product category, using its permalink URL.

Category URLs follow this format:

http://example.com/shop/category/sauces/

When visiting a category on a working storefront, Shopp will intercept the request and automatically run the necessary database queries, reading in category information and the products assigned to it. This is Shopp’s version of WP_Query running on a page request. When loading a product page, Shopp actually uses the WP_Query result and converts it to a working ShoppProduct object. For product collections, Shopp takes over and runs its own queries for performance reasons that are covered later in this article.

To load a category in a custom location on the website, such as outside of a category page request, you use the shopp('storefront.collection') tag to set the working collection context.

<?php shopp('storefront.collection', 'slug=sauces&load=true'); ?>

If you read part 1 this should look really familiar to you. It’s the same syntax as loading a product, but uses the .collection tag from the storefront rather than the .product tag. The tag above will load the product category with the slug sauces into the working collection context so that shopp('collection') tags will work.

There are two things to note about this tag:

  • The load=true option was used to tell Shopp to load the collection for shopp('collection') tags, not the default behavior of displaying it with the category.php Shopp content template file.
  • It only loads the collection information, meaning it only retrieves the database information of a category or tag itself (like the category name, slug and id). It does not load the products assigned to the collection at this point.

Loading Products

Now that the category you want is loaded into the working context, you’re ready to load the products.

<?php 

// Load the Sauces category into the working collection context
shopp('storefront.collection', 'slug=sauces&load=true'); 

// Determine if the collection has any products
shopp('collection.has-products');

?>

The shopp('collection.has-products') is another special breed of Theme API tags. it does the same sort of thing that shopp('product.found') does for products. In this case, it goes to the database to see if there are products assigned to the Sauces category. Products found assigned to the category are then loaded.

If it looks strange to load products using has-products, there is an alias you can use that does the same thing: shopp('collection.load-products). If there aren’t any products in the category, you may want to show a message that says “no products were found”, and if you’re really clever, show a search box to allow visitors to search for products instead. When using a test, it’s more natural to use has-products instead:

<?php 

// Load the Sauces category into the working collection context
shopp('storefront.collection', 'slug=sauces&load=true'); 

// Determine if the collection has any products
if ( shopp('collection.has-products') ) {
    // Show the products
} else {
    // No products were found, show a search box instead
    echo "<h1>No products were found in ";
    shopp('collection.name');
    echo "</h1>";
    
    echo "<p>Try a search instead?"'
    shopp('storefront.search-form');
}

?>

The code above will show “No products were found in Sauces” followed by a product search form when no products are available in the category. It’s always a good idea to take care of the exceptions first. The fun stuff happens when products are available and you have information you can display:

<?php 

// Load the Sauces category into the working collection context
shopp('storefront.collection', 'slug=sauces&load=true'); 

// Determine if the collection has any products
if ( shopp('collection.has-products') ) {
    shopp('product.coverimage');
    shopp('product.name');
    shopp('product.price');
}

?>

When products are assigned to the category, the code above will show a product image followed by the name and price for the first product only even though all the products assigned to the Sauces category were loaded. That’s because we haven’t setup The Loop for the collection.

<?php 

// Load the Sauces category into the working collection context
shopp('storefront.collection', 'slug=sauces&load=truex'); 

// Determine if the collection has any products
if ( shopp('collection.has-products') ) {
    
    // Loop through each product in the collection
    while( shopp('collection.products') ) {
        shopp('product.coverimage');
        shopp('product.name');
        shopp('product.price');
    }
}

?>

Now you can use the shopp('product') tags to display whatever information you want about the product (almost). The full description of a product is not loaded in collections to keep memory usage down since they are rarely used there. If you need it though, Shopp allows you to call it with the load option.

Loading Product Data

The Shopp collection query is designed to protect memory consumption as much as possible. At the same time it doesn’t prevent the developer from doing what they want to do. That puts the responsibility on the developer to know what they are doing. So pay attention, this part important.

When loading products it’s helpful to understand how the Shopp collection query system is working behind the scenes, especially on stores that have lots of products or products that are very complex. With the potential to load tons of data, you can easily kill all the memory allocated to PHP on the web server.

When WordPress runs WP_Query to grab posts, it does approximately 4 queries:

  • Get the posts from the database
  • Find how many posts are available in the database
  • Load up all the meta data for the loaded posts
  • Load up all the taxonomy terms for the loaded posts

Shopp’s collection query system does the same thing for products but when using the starter templates only runs 2 queries:

  • Get the products from the database using summary pricing information
  • Load up the cover images for the loaded products

The starter content templates for Shopp collections (category.php) is set to only load the cover images (a single image) for each of products loaded in each page view using:

if ( shopp('collection.has-products', 'load=coverimage') ):

That is really the minimum information needed to display most product collections. There are several types of product meta data that can be loaded in a collection:

  • prices — All of the pricing variants and addons of the product
  • specs — Product specifications and details
  • images — All images assigned to the product
  • coverimage — Load only the first image for each product
  • categories — The assigned categories of the product
  • tags — The tags set for the product
  • meta — All of the other generic to custom product meta data
  • summary— The product pricing summary for a product
  • description — The full product description

Loading all this extra information is easy enough with the load option. Just include one or more of the settings above separated by commas.

shopp('collection.has-products', 'load=prices,specs,images,description')

Knowing Is Half The Battle

Pricing information is kept in a separate, specially designed table for several reasons. For example, did you know WordPress tables have no decimal columns anywhere? While it is possible to keep decimal numbers in the data containers in WordPress tables, it would hurt performance over really large data sets. The CPU utilization needed to convert the strings to real decimal values for doing fast number comparisons like “greater than” and “less than” puts unnecessary burden on the server and adds to the query processing time. All that to say, Shopp doesn’t just add tables because it can, but because there is a marked performance gain in the new design.

These deviations from the “WordPress way” are important to gaining a complete understanding of how to develop performant ecommerce applications with Shopp. Let’s say you don’t load the product specs using the collection.has-products call, but in the product loop your category template displays a list of product specs. This process starts with the product.has-specs test. If specs aren’t available for the product from the collection.has-products operation, Shopp will run a direct query to load specs for each product where they’re missing. If your template shows 25 products that means an extra 25 unnecessary queries. Yes, that’s a bad thing! More queries is more work. That’s why it is important to understand when and how product loading happens in collections.

WordPress, by contrast loads the entire record for each post and all meta records. There isn’t any sophisticated memory or CPU management aside from using cache (when available) to skip the query steps. That is not a criticism of WordPress at all. When loading posts there isn’t as much information to manage (generally) and for the most part you don’t know what information might be used in any given theme. You pretty much need to load it all. This underscores the different application that ecommerce storefronts are and why Shopp uses a different, but similar, strategy for information management.

WordPress puts a lot of trust in the developer not doing anything too crazy with the amount of data that gets loaded. Shopp on the other hand tries to protect performance first, but still allow the developer to do what they want to do. Knowing how to do it right means avoiding performance penalties.

Loading collections of products in Shopp is designed to be efficient for common ecommerce situations. Shopp’s flexibility means you can retool it for your exact needs and knowing how Shopp loads data is the key to keeping things nice and snappy. While you may not appreciate the learning curve introduced by Shopp’s way of doing things, you will appreciate your site’s speed and ability to handle more traffic even with very custom, very complex shopping experiences.

Next we’ll take a tour of the other Shopp object contexts and how they relate to the different content templates used in Shopp. Then the series will finish up with tips and tricks you can use with the Theme API that can make developing templates for Shopp nice and easy.

Avatar of Jonathan Davis

By

Jonathan was born at an early age and began designing and developing shortly after. He is the founder of Ingenesis Limited and Project Lead on the Shopp e-commerce plugin for WordPress. He lives and works in the heart of the midwest US with his family. He fancies himself a designer of code, and is only slightly addicted to coffee.

You must be logged in to post a comment.

© Ingenesis Limited. Shopp™ is a registered trademark of Ingenesis Limited.

Skip to toolbar