Making Data Globally Available in Statamic

Using data from collections and globals in Statamic templates is easy and convenient. But sometimes you might also have an external data source that needs to be passed into your views and where it doesn't make sense to pull it into Statamic via Runway or other means.

In one recent example I worked on there was a bunch of geographical data (provinces, cities, etc.) that needed to be available everywhere on the site to allow for search forms to be placed freely on different pages.

After doing a bit of research I came up with three possible solutions.

Note that in these examples I'm fetching data via a sample API Facade. In the real app this also implements a caching layer to keep page request times at a reasonable level instead of calling the external API on each page load.

Option 1: Custom Antlers Tags

Probably the easiest and most straight-forward solution is using what Statamic offers anyways: Custom Tags.

These are often used to modify or gather data, so we can do just that. Simply run php please make:tag SampleTag to quickly generate the class, adjust the handle if desired, and add the data fetching logic to the index() method.

app/Tags/SampleTag.php
<?php namespace App\Tags; use Statamic\Tags\Tags; use App\Facades\SampleApi; class SampleTag extends Tags { protected static $handle = 'sampletag'; public function index() { $sampleData = SampleApi::getSampleData(); return $sampleData; } }
A custom tag fetching and returning our sample data.

Now that the tag is created we can simply use it in any view. Just add a tag pair (opening and closing) and use the data in between like any other view data.

resources/views/sample.antlers.html
{{ sampletag }} // the data returned from the tag is avaiilable here {{ /sampletag }}
Using the tag to output our data in a view.

A minor downside to the tag approach is the fact that using the tag multiple times will execute the function just as many times. So if you're accessing the data in a lot of different places you might get some performance issues if you do complex operations or fetch data without proper caching.

Option 2: Statamic View Models

Unsurprisingly, Statamic also comes with a native way of adding additional data to your views. As the ViewModel docs explain, Statamic will run a ViewModel's data() method right before displaying the view and inject any data returned into it.

This is a very powerful way of pre-processing your data before showing it to the user, but nothing is stopping us from simply injecting additional data instead.

To do so, all we have to do is create a ViewModel with a data() method that returns our required data.

app/ViewModels/SampleData.php
<?php namespace App\ViewModels; use Statamic\View\ViewModel; use App\Facades\SampleApi; class SampleData extends ViewModel { public function data(): array { $sampleData = SampleApi::getSampleData(); return [ 'sample_data' => $sampleData ]; } }
Gathering the required data in a very simple ViewModel class.

Now navigate to the collection definition where you want to add this data (e.g. Pages, Posts) and inject the new class. After this, any view connected to that collection will have our additional information available just like all other data.

content/collections/pages.yaml
title: Pages [...] inject: view_model: App\ViewModels\SampleData
Injecting the newly created ViewModel into a Statamic collection.

This assignment to a specific collection is great for the use case of data manipulation, but doesn't work well in our example case. While we could assign it to all collections, there are still some cases (like custom routes or error pages) where the data would be missing.

Option 3: Laravel View Composers

The last approach isn't using anything Statamic related but a function provided by the underlying framework. Laravel's View Composers aim to solve the same issue as Statamic's View Models, that is to inject additional or modified data into a view before it is rendered.

Accordingly, the resulting class looks fairly similar to the previous approach. The main difference is that here the compose() method does the magic right on the View model, so it doesn't need to return the data it collected.

app/ViewComposers/SampleInformation.php
<?php namespace App\ViewComposers; use Illuminate\View\View; use App\Facades\SampleApi; class SampleInformation { public function compose(View $view): void { $sampleData = SampleApi::getSampleData(); } }
Gathering our sample data using a simple ViewComposer.

Registering this class works a bit different as well, since Laravel doesn't know anything about Statamic's collections. Instead we use a Service Provider to load the View Composer by adding it to the boot() method.

It's possible to limit the execution to specific views by changing the first parameter. In this case we wanted the data in any and all views, so we applied it to the wildcard '*'.

app/Providers/SampleServiceProvider.php
<?php namespace App\Providers; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; class SampleServiceProvider extends ServiceProvider { [...] public function boot(): void { View::composer('*', GlobalInmovilla::class); } }

Binding the View Composer to all views gives us truly global access to our new data set, no matter what kind of route we are on. The downside: the data is fetched on every page load, even if the view in question doesn't use the data at all.

Decision Time

To be honest with you, all three of these options would have worked for the site in question. In the end I implemented the third option, mainly because this project already had a bunch of custom tags and I wanted to keep things as clean as possible for the client.

More Posts