Adventure Documentation

4. Data

  1. How do we Access the Shop Data
  2. Collection
  3. Entity
  4. Smarty Functions

How do we Access the Shop Data

All data in the webshop is available to the template through controllers.

These controllers effectively work as a layer that exposes data in a structure which makes it easy to work with in the template. Each feature generally has its own controller, ex. if you need to work with blog, you would most likely need the BlogController, BlogCommentController and the BlogCategoryController.

The most common way to work with controllers is through the collection and entity functions.

Collection

To generate collections of entities in the shop, we use the collection function. The collection function is a convenience function which will generate a collection of entities using a controller. To get a list of all blog posts we would do the following.

{collection assign='blogPostCollection' controller='blog'}
{$blogPosts = $blogPostCollection->getData()}

The BlogController also accepts other parameters making it easy to fetch blog post for specific use cases.

To get all the blog post for a specific page, we provide a pageId to the collection function.

To handle use cases where there are a lot of blog posts, the BlogController also supports pagination which can also be used to further reduce the amount of blog posts returned.

{collection assign='blogPostCollection' controller='blog' pageId=2 page=1 pageSize=4}
{$blogPosts = $blogPostCollection->getData()}

The full list of parameters accepted by the BlogController can be found in the controller documentation.

/**
* @var id int - Get a specific Blog entity
* @var categoryId int - Get entities from a specific BlogCategory only (primary: cat)
* @var year int (default: null) - Get entities from a specific year with format 'YYYY' (primary: y)
* @var month int (default: null) - Get entities from a specific month with format: 'MM' (primary: m)
* @var page int (default: 1) - Pagination: Start page (primary: page)
* @var pageSize int (default: 0) - Pagination: Amount of entities per page (primary: pageSize)
* @var search string - Get entities with a search query (searches: Title, Text)
* @var pageId int (default: current page) - Get entities from a specific Page
* @var languageIso string (default: current language) - Get entities from a specific Language
*/

Entity

The {entity} function works exactly like the {collection} function, the only difference is that it will always return a single entity instead of a collection of entities.

{entity assign='blogPost' controller='blog' id=1}

Smarty Functions

In order to better separate the business logic from the presentation code, we have created a set of Smarty functions that can be used to access the data in the webshop.

What are functions?

Functions are based on smarty's original concept of {function}, but with an added functionality to {return} data if needed.

Functions are used for non-visual utilities that can share logic between views, components and templates. They also help improve separation of concern, which makes the code easier to read and maintain.

Functions are great to conceal data access logic, like {controller}, {collection} and {entity} calls from components.

Example function

Below is an example of a function definition. The function always returns a variable with the featured product image, while taking both productId and variantId into account:

{function getFeaturedProductImage
    productId=0
    variantId=0
    return=null
}
    {$productImage = null}

    {if $productId && $variantId}
        {entity assign="productImage" controller="files" type="variant" variantId=$variantId productId=$productId}
    {/if}

    {if !$productImage && $productId}
        {entity assign="productImage" controller="files" type="product" productId=$productId}
    {/if}

    {return value=$productImage}
{/function}

Use {call} to execute a function outside the file and add a reference with {include scope='parent' file='/path/to/file.tpl'|resolve}

Notice that the |resolve modifier is used after the path of the extended file. This modifier enables the system to make use of the optimized pre-compiled template files in case the parent template file has not been customized.

{* --------------------------------------
    Functions
--------------------------------------- *}
{include scope='parent' file='src/functions/product.tpl'|resolve}

{* --------------------------------------
    Logic
--------------------------------------- *}
{call getFeaturedProductImage
    productId=1
    variantId=5
    return="productImage"
}

{* Product image variable ready for use *}
{$productImage}

Limitations

However, due to the nature of Smarty functions and how they work with the Smarty engine, there are some limitations to be aware of when using them.

Limitation 1 - Behavior of {return}

{return} functionality works differently than one would typically expect from a function.

{return name="someName" value="someValue"} is similar/a shorthand to {assign name="someName" value="someValue" scope="parent"} The {return} function will find the "return" argument of the function if "name" is not set.

In other words, {return} does not "end" the function call. It simply ensures that the result inside the function can be read via the call outside of the function. If a function has multiple {return}, make sure to write code around this limitation, to avoid unexpected behavior.

The following code shows an example of this unexpected behavior:

{function doubleReturnFunction
    someBoolean=false
    return=null
}
    {if $someBoolean}
        {return value="Yes!"}
    {/if}

    {return value="No!"}
{/function}

If we call this function with someBoolean=true , we would normally expect the return value to be "Yes!". However, since {return} doesn't end the function call, the second {return} will overwrite the value to be "No!" instead.

To avoid this, we need to make sure that all {return}s have conditions tied to them, even if they are placed at the end of the function. A quick fix for the example above could be:

{if $someBoolean}
    {return value="Yes!"}
{else}
    {return value="No!"}
{/if}

Limitation 2 - Smarty inheritance and scopes

When including function files, Smarty will see this as merging the content of the function file with the content of the origin file.

This means, that anything defined before the {call} will be accessible inside the function being called. This could be imports, props or variables defined further up in the logic section. But since a function should work independently of any file it's called in, we need to be extra careful of this behavior.

Imagine we have the following function:

{function functionWithDependency
    return=null
}
    {$someTranslation = $i18n->get('translation')}

    {return value=$someTranslation}
{/function}

If we call the function like this, the function would work and return the translation:

{$i18n = $Platform->import("i18n")}

{call functionWithDependency
    return="someTranslation"
}

However, if we call it like this, we will get an error:

{* No import *}

{call functionWithDependency
    return="someTranslation"
}

This behavior could lead us to believe the function works fine, until we try to use the function in a different file.

A good rule of thumb to avoid this, is to import everything needed in a function, inside the function declariation. Any other external value needed, should be parsed via function parameters.

{function functionWithDependency
    return=null
}
    {* Imports *}
    {$i18n = $Platform->import("i18n")}

    {* Logic *}
    {$someTranslation = $i18n->get('translation')}

    {return value=$someTranslation}
{/function}

The Smarty functions luckily have their own scope, so we don't have to worry about data carrying over the opposite way. Any variables defined or changed inside a function, will not affect the file that calls the function. (Unless we explicitly return it)

Search results