Learn

Organization

Constructed to let you build amazing things.

Statamic’s add-on system is built upon a number of objects that will let you create each of the aspects of your add-on. We’ll get more into the specifics of each object in another section, but first it’s best to understand the architecture of how these objects work together.

First, each of your add-on aspects will extend an object specific to that aspect:

  • A plugin will extend Plugin
  • A fieldtype will extend Fieldtype
  • Hooks will extend Hooks
  • An API will extend API
  • The Core will extend Core
  • Tasks will extend Tasks

Each of these aspect objects (Plugin, Fieldtype, Hooks, API, Core and Tasks) extends the AddOn object. This AddOn object is at the top of the chain and holds a set of helper methods and objects for use in your add-on. These helpers are available in all aspects of your add-on.

The Helpers

The helper methods and objects allow all of your add-on’s aspects to work closely together. These helpers let you share variables and data throughout your add-on, allowing you to have complete control over your add-on, all within the comfort of your add-on’s namespace.

Note: the most important thing to remember when using helpers is that all aspects share the same data. Data stored in one aspect can be retrieved in another. This is where the magic happens.

Message Logging

There will be times when you’ll want to record messages for the site’s owner to see and understand, whether those are strictly informative, or more-pressing error messages. The $this->log object will help you with this.

$this->log is a reference to a contextual logging object. That’s a fancy way of saying that $this->log is an object that you can use to create messages that automatically show themselves as coming from your add-on’s namespace. This will let site owners know where a logged message has come from so that if they do need help, they’ll know where to go to get it.

To write an error message to the log originating from your add-on, simply use this helper’s error method:

$this->log->error("Something has gone pear-shaped.");

In the log, this message will record itself as coming from your add-on (and will also say which which aspect within your add-on was responsible). For example, if your plugin is the object that has caused this message to be written, the What Caused This column will show users plugin : your-add-on-name. This will be helpful when it comes to debugging your add-on’s users’ errors later on should it come to that.

There are five log methods available, each of which correspond to the severity-level of message you intend to log:

$this->log->debug("This is a debug message.");
$this->log->info("This is an info message.");
$this->log->warn("This is a warn message.");
$this->log->error("This is an error message.");
$this->log->fatal("This is a fatal message.");

Scripts, Stylesheets & Assets

Your add-on may require including JavaScript, loading CSS, or linking to assets that you’ve included with your aspect files. These helper objects will help you find these and work with these files.

Folder Structure

Each of these file-finding helpers has a set list of locations that they’ll look for files. The first place they’ll look is on the root-level of your add-on’s folder. This makes including one or two quick files easy without having to deal with the tedium of folders. If the file it’s looking for isn’t found there, it will look into the appropriate folder as laid out below:

your-add-on/
   css/
   js/
   assets/

We recommend using this folder structure as a best practice for your helper files anyway, but if you want to use the included helper objects (which, again, is recommended), you’ll need to follow this naming convention.

CSS

We’ve included three helper methods for accessing and creating CSS for your add-on, each of which are found on the $this->css helper object. To get the path of one of your CSS files, you would do the following:

$css_file = $this->css->get("my-css.css");

This will set the full webpath of my-css.css to $css_file. You should use this helper to locate your CSS files because of the difference in settings from site-to-site. This method is guaranteed to find your files.

If you want to get the full HTML to include a stylesheet, you can use the link method, like so:

$html = $this->css->link("my-css.css");

This will return an HTML <link> tag for use with your add-on. This method can also accept an array of stylesheets, so if you have a couple of different stylesheets to include, it’s even easier.

Finally, if you want to create some inline CSS, you can do that with the inline method:

$style = $this->css->inline("h1 { font-weight: bold; }");

Statamic will return your passed-in CSS wrapped in a simple HTML style tag.

JavaScript

The JavaScript helper is similar to the CSS helper, except that it handles JavaScript instead of stylesheets. That makes sense. To get the full web-path to a script in your add-on, do the following:

$js_file = $this->js->get("my-script.js");

To get the full HTML tag for including your script, you would use:

$html = $this->js->link("my-script.js");

And again, this method can also accept an array for including multiple scripts. This is probably more helpful here than it will be with CSS.

Of course, we wanted to also let you write inline JS, so we provide you with:

$js = $this->js->inline('document.write("Don't use document.write.");');

This simply wraps your inline JavaScript in a <script> tag for convenience.

Assets

The assets helper will let you locate pretty much any other file that you’ve packaged with your add-on. We’ve named this folder assets so that you didn’t feel that you were limited to simple images. If you want to include other types of files, feel free to do that.

This helper object only has one method:

$img = $this->assets->get('handsome-man.jpg');

This, like the others, will look at the root-level of your add-on for handsome-man.jpg, and will then look inside of the assets folder in your add-on’s root-level folder (if it exists) for your file. If it finds it, you’ll get back the full path. Otherwise, you’ll get back an empty string and a message will log an error that the file couldn’t be found.

Something to keep in mind: you can sub-divide your assets folder further if you’d like, so if for example your add-on folder structure looked like this:

your-add-on/
   assets/
      img/
         handsome-man.jpg

You can find this with a call of:

$img = $this->assets->get('img/handsome-man.jpg');

Session Variables

Session variables allow you to store temporary data that follows a user around from page to page. This data will be erased once the user’s session ends. (Read up about sessions on PHP’s site to learn more about what will end a user’s session.)

As an add-on developer, you don’t have to worry about starting or ending sessions — they’ll be there and ready for you when you want them. By using the $this->session helper object, you can check for, create, read, and delete variables in the user’s session that are nicely namespaced for just your add-on.

As an example, below is how you would create a has_flannel_shirt session variable:

$this->session->set('has_flannel_shirt', true);

This variable is set and will follow the user around until either you overwrite it, delete it, or their session ends.

Want to check to see if a user has a flannel shirt? Simply do the following:

$this->session->get('has_flannel_shirt');

You don’t need to worry about checking to see if a session variable already exists before looking up its value. Statamic will return null if the session variable you’re looking for hasn’t yet been set. That being said, you can check to see if a session variable is set like so:

$this->session->exists('has_flannel_shirt');

When you want to delete one of your session variables, you can do so with the delete method:

$this->session->delete('has_flannel_shirt');

This will unset the variable so that if you were to get it again, you’ll simply have null returned.

Finally, we’ve created a method to completely remove all of the session variables you’ve set:

$this->session->destroy();

This will only affect your namespace, so you don’t have to worry about removing other people’s hard work if they’ve been using sessions too.

And remember, all aspects of your add-on can use these methods, and they all share the same data sources. This means that you could set has_flannel_shirt on one of your pages, and have a hook that destroys the session when a user logs out, for example. The value of has_flanel_shirt is the same to your plugins as it is to your fieldtypes, hooks, and tasks. Simple and convenient.

Your Data Cache

Where session variables are a short-term data storage technique, your add-on’s data cache is a long-term solution. The $this->cache helper object lets you create, maintain, manipulate, and delete files with your add-on’s namespaced cache folder. This folder is intended for long-term data storage, and is not available publicly via a URL.

This helper object has a lot of methods that let you do quite a bit of file manipulation without too much effort.

First, let’s look at storing data to a file. Let’s say that you want to store a timestamp of the last time that your add-on did some action. We can store that into a data file for safe keeping like so:

$this->cache->put('last-check', $timestamp);

This will save the value of $timestamp to the last-check file in your object’s long-term data store.

Remember that technically, this is just creating a last-check file. You can use an extension if that makes you feel more comfortable, something like last-check.txt if you’d like, but you don’t have to. You’ll be using this helper’s methods to read and write to these files, so you shouldn’t ever have to worry about file extensions if you don’t want to. Like most things in Statamic, we leave it up to you.

When it’s time to read the timestamp of that last-check, you’ll do this:

$timestamp = $this->cache->get('last-check');

This will set the contents of your last-check file to $timestamp for use in your add-on. Again, don’t worry about checking to see if this file exists, Statamic will return null if no file was found (and actually, you can pass it a second parameter that will be returned if the file can’t be found). Also again, you can check to see if a given file exists in your data cache with the following:

$this->cache->exists('last-check');

Not necessary in most circumstances, but helpful in some.

Sometimes you won’t want to just write data to a file, but you’ll want to add to what’s already there. We’ve given you two methods to do just that:

$this->cache->append('my-file', 'FIN');
$this->cache->prepend('my-file', 'But I digress… ');

Easy enough: append appends, prepend prepends.

Because you’re interacting with files, we’ve created a couple of methods to help you do filesystem-like things. First, listAll lets you see what’s in your cache.

$files = $this->cache->listAll();

This returns an array of all files in your cache.

If you wish to move a file in your cache, that can be done with the move method:

$this->cache->move('last-check', 'new-last-check');

In this example, the last-check file becomes new-last-check. Of course, if you don’t want to just move, you can use this method’s sister method copy:

$this->cache->copy('last-check', 'also-last-check');

This example will copy the contents of last-check into the also-last-check file, so that you end up with two files in your data cache.

Subfolders

You don’t have to keep all of your cache files in one area. When you name your files with put, move, or copy, you can include subfolders as well:

$this->cache->move('last-check', 'old/last-check');

This will create an old folder within your cache’s data storage, and will move the contents of last-check into a last-check folder within the old folder. Neat, right? And you can create as many subfolders as you’d like. Any parameter that accepts a filename can accept a subfolder path like in the example above, and it can be as many levels deep as you’d like.

And that includes listAll. If you only want to see the contents of a given folder, pass that to listAll:

$old_files = $this->cache->listAll('old/');

Perhaps you want to move a file to the old folder once it hasn’t been touched for more than a set period of time. This can be done with the help of getAge:

if ($this->cache->getAge('last-check') >= 60) {
   $this->cache->move('last-check', 'old/last-check');
}

The above example will move last-check into the old folder if it hasn’t been touched for more than 60 seconds.

Deleting Files

There are a couple of different ways that you can delete files from your add-on’s data cache. First, there’s the straight-forward file deletion:

$this->cache->delete('last-check');

This will delete the last-check file if it exists. Next, there’s the bit-more-brash destroy method that will delete entire folders:

$this->cache->destroy();

The above example will delete all files within your cache. If you want to just destroy one particular folder (and everything within it), you can do that with:

$this->cache->destroy('old');

There goes the old folder.

Rather than making you check getAge to purge older files, we’ve also added two methods to help you do this faster. First, you can delete all files that haven’t been modified since before a given date:

$this->cache->purgeFromBefore("December 25, 2012");

Second, you can delete all files that haven’t been modified in a given number of seconds:

$this->cache->purgeOlderThan("60");

By default, both of these methods will act on the root folder, but both take a second parameter to let you specify which folder to purge from.

We Love YAML

You may be thinking that these methods are all well-and-good, but storing one value per file is going to get cumbersome rather quickly. Fear not! We’ve got you covered.

There are two additional methods to help you store large amounts of organized data into files for use later on. Let’s say that your add-on has a task that collects your tweets from the Twitter API. You want to store this information for use later on by a plugin so that you can get those tweets onto your page.

For this example, let’s assume that your task has packaged up tweets into a PHP array called $tweets. To store this data as YAML, use the putYAML method.

$this->cache->putYAML('tweets', $tweets);

This method will convert $tweets — a PHP array — into YAML before it stores it to the file. If you were to look at this file, it will be plain-old YAML.

Next, your plugin will need to use this data to get it into a template. Since all aspects within an add-on share the same data storage, that’s a simple matter of grabbing it with getYAML:

$tweets = $this->cache->getYAML('tweets');

This method will read the contents of your tweets file and will run it through the YAML parser, returning a PHP array into $tweets. And that’s that. Simple complex data storage that’s also easily human-readable when stored.

Core

The Core is one of the aspects of your add-on.

All other aspects in your add-on (plugin, fieldtype, hooks, API, tasks) have access to your add-on’s Core object via $this->core. This is a simple reference to the Core object itself.

This allows you put all of your add-on’s processing methods (as in, any method your add-on needs to process information to do what it’s supposed to do) in a Core object and access it through the $this->core helper — this will keep your code DRY. It’s very possible that a something in a Plugin, Hook and API will all do the same thing. You can write it once in your Core file and be done with it.

The Core object itself doesn’t get a $this->core helper, because it would reference itself, and inside that would be a reference to itself — the dreaded infinite loop. Anyway, a method within a Core that needs access to another method in that same object will be using $this->, so you’re covered.

Tasks

While not all Statamic installations are guaranteed to support Tasks, that doesn’t mean that using a Tasks file is a bad idea.

Previously, Tasks were used for 2 purposes:

  • A way to keep your processing methods in one place and your code DRY.
  • For background processing (cron jobs)

The Core was introduced to alleviate the multi-purpose nature of Tasks. Now Tasks are intended for background processing only, however still work exactly as they did prior to the introduction of Cores.

We recommend that any object using Tasks should have a back-up plan in case an installation can’t use Tasks automatically — including your add-on’s Tasks object allows you easy-access to its methods inside for triggering at other times, whether that’s at run-time or through hooks

Much like the Core object, the Tasks object also doesn’t get a $this->tasks helper, because it would be self-referencial and cause an infinite loop. However, you can still access its own methods using $this->.

This article was last updated on September 13th, 2016. Find an error? Please let us know!