The Anatomy of an ExpressionEngine Extension

By Erik Reagan

In this post, we’re talking about ExpressionEngine extensions. If you’ve used ExpressionEngine long enough to have a few support threads in the forums, you’ve more than likely seen the word extensions come up.

Add On Merit Banner

Chances are, you’ve also installed an extension or two if you’ve used EE for more than a few weeks.

Many EE users and developers aren’t sure why extensions exist, what they do or what problems they solve. The goal here is to demystify the world of extensions. I’m hoping this will help everyone - from the general user to the new add-on developer - understand why extensions exist, what they do and how they work. While we won’t be building an extension in this article, we will lay a foundation of knowledge about what they are and how they work. Some highlights of today’s article:

  • Extensions in the context of add-ons
    • What are hooks?
  • How do hooks relate to extensions?
  • Extension processing behavior
  • Caveats
    • end_script explanation & use
    • Returning data back to EE
  • Better Debugging Processes

Extensions within the add-on family tree

In order to discuss extensions we need to understand the role they play within EE’s add-on family tree in general. So let’s briefly review the existing add-on types and their roles:

Plugins

  • Runtime add-ons
  • No settings interface
  • No installation required
  • No unique db tables (typically)
  • No language file requirement
  • Can format custom text fields
  • Can provide Template tags
  • Small learning curve

Accessories

  • Only shown in Control Panel (the tabs above the footer)
  • EE handles installation & settings for you
  • Small learning curve

Modules

  • Can have Control Panel interface
  • Requires Language file
  • Must be installed in the Control Panel
  • Can utilize “actions”
  • Can provide Template tags

Fieldtypes

  • Self explanatory
  • Fieldtypes for channel publish form
  • Have corresponding Template tags

Extensions

  • Must be installed in the CP
  • Can use EE’s built-in simple settings builder
  • Requires language file when you have settings
  • Can have unique database tables (but they often don’t)
  • Manipulates data or processing during a given point in a page load
  • Must behave nicely with other extensions
  • Probably the steepest learning curve for EE add-on types
  • Can only be used with existing system (or 3rd party) “hooks”

What are hooks?

Hooks are not unique to ExpressionEngine. In fact, they’re a really important concept in software in general.

A hook is a block of code in the core of a system that allows for other applications to alter the original behavior without having to “hack” the core code. Sometimes this comes in the form of completely re-routing the original behavior to something totally different. Other times it simply makes tiny changes to the behavior and then allows the original application to continue with its process.

Hooks can be seen in many, many software projects. Operating systems like Windows, Linux and OS X all have hooks in them. Popular CMSs like Drupal, WordPress and obviously ExpressionEngine have hooks. Even some games have hooks in the software that make them happen.

The primary benefit to having hooks in a system is the added flexibility for other developers to extend the core functionality of the original product thus making the end result more capable or feature-rich than it was “out of the box.” But with great power, comes great responsibility. Let’s look at how hooks work within the context of EE.

How does a hook relate to an extension?

If we peek at the exp_extensions database table we can get a sense of how hooks relate to extensions. Here is an example of data from that table (I’ve slightly altered the table data for this example):

classmethodhookpriorityversionenabled
Matrix_extchannel_entries_tagdatachannel_entries_tagdata102.2.3.2y
Low_seg2cat_extsessions_endsessions_end12.5.1y
Focus_lab_deployments_extpost_deploy_actionsdeployment_hooks_post_deploy101.0.0y


The data in the table above shows three extensions installed in an EE site. They are “Matrix”, “Low Seg2Cat” and “Focus Lab Deployments”. On any given page load with EE the data above is pulled from the database (we’ll look at that in detail later). So what happens is this:

  1. ExpressionEngine encounters a “hook” in the system
  2. It checks the table above to see if there is a record with the “hook” where the “enabled” column is set to “y” (yes)
  3. If there is, then EE looks at the “class” column to determine which add-on to look in for this extension
  4. If the add-on extension file exists, it is loaded
  5. Within that extension class, the method listed is executed

With those steps in mind it would look something like this with the Low Seg2Cat example:

  1. ExpressionEngine encounters the sessions_end hook in the system;
  2. It checks the already-loaded extensions dataset for an extension using the sessions_end hook which is enabled
  3. EE looks at the class “Low_seg2cat_ext” and knows to look for a folder called third_party/low_seg2cat
  4. If third_party/low_seg2cat/ext.low_seg2cat.php exists, the file is loaded and an object is created;
  5. Finally EE executes the sessions_end method within the ext.low_seg2cat.php file

Extension Processing Behavior

In the current release of ExpressionEngine (2.5.0) there are just over 100 hooks in it and its included modules. That’s 100+ opportunities to bend EE to do exactly what you want without hacking core code. To get started let’s understand how a hook looks in the code. Here is an example pulled from EE’s system/expressionengine/controllers/cp/content_edit.php file:

<code>/* ----------------------------- /* 'delete_entries_start' hook. /*  - Perform actions prior to entry deletion / take over deletion */     $edata = $this->extensions->call('delete_entries_start');     if ($this->extensions->end_script === TRUE) return; /* /* -----------------------------*/ </code>

This is a pretty basic example. You can see from the comments around the PHP that this hook is for performing “actions prior to entry deletion” or to even possibly “take over deletion.” Let’s look at both lines of code.

The call() method

The first line of code in our example is:

$edata = $this->extensions->call('delete_entries_start');

A variable called $edata is being created and its value is whatever gets returned from $this->extensions->call('delete_entries_start'). So what does $this->extensions->call() do? The best way to find out is to look at the code itself. This blog format doesn’t lend itself well to that, so instead I’ll walk through some high points of the extensions library (found in system/expressionengine/libraries/Extensions.php).

The extensions library is very bare-bones so it’s easy to walk through what it does. There are four methods if you include the constructor. (If you’re not familiar with what a constructor is, for the sake of this article, we’ll just note that the constructor method is automatically called / executed on each newly created object.) The __construct method in this class does something very important to us, which I alluded to earlier. There’s a simple query to the database that pulls in all active extensions and their corresponding hooks and methods. For reference, the query is:

SELECT DISTINCT ee.* FROM exp_extensions ee WHERE enabled = 'y' ORDER BY hook, priority ASC, class

From this query we get a full list of all extensions to be easily referenced later in the class.

Next we have two methods that are closely related. There is call() and universal_call(). The call() method is what you’ll see most often through the code and it does some magic and then just runs the data through the universal_call() method. This is where the called extension file is actually loaded and executed. Finally, there’s a simple utility method called active_hook() which is just used for conditional checks before running an extension in the code.

Caveat #1: end_script

The second line of code in our example is:

if ($this->extensions->end_script === TRUE) return;

This brings up caveat number one for today. Any extension being called by ExpressionEngine has the ability to say to EE, “Hey, I want to be the last thing processed in this particular method.” If the extension in use does this, the method that the hook is within will immediately cease running and return its current state.

Now, if you don’t know much about Object Oriented PHP this may not mean much to you. If that’s the case, do understand that this could cause EE to not perform some of its built-in actions. In the case of our example it means that the extension would completely override the “delete” behavior. Remember the comments in the PHP earlier?

“Perform actions prior to entry deletion / take over deletion”

It’s the “take over deletion” part that end_script would cause. If the 3rd party developer doesn’t set end_script to TRUE then EE will still process its full “delete” behavior. You’re probably starting to see how many possibilities exist when multiple extensions are at play within a given ExpressionEngine website. Here’s the explanation directly from the EE docs:

“Many extension hooks exist for the express purpose of totally controlling a page or script in the Control Panel. They are meant for redesigning the appearance of a form or perhaps usurping a script for processing form data. In those instances you want your extension to be the last thing called for that extension hook so that nothing else is processed after that point. The $this->extensions->end_script exists solely for that purpose. If you set this value to TRUE, then once your extension is done being processed the execution of the hook is finished, as is the script that the extension hook is contained within.”

The next caveat is a doozy though. Let’s look at it.

Caveat #2: Extensions not playing well with others

If we go back to the example above you’ll see that in the first line of code EE creates a variable called “edata”. Any time you see this you can likely assume that the data returned from the extension’s method won’t be used anywhere. However, there are some hooks that actually use the data returned form the extension. In these cases it is imperative that the 3rd party developer actually return the data back to EE. This, again, gets a little deeper into PHP than I would like for this article so I won’t detail the “how” of this.

There is one additional detail to note regarding this behavior though. The extensions library has another property called last_call that exists to help multiple extensions using the same hook to play well with one another. This variable holds the “most recent value” of the hook data. Here is the example straight from the EE docs:

“Say, there is a hook for formatting text and an extension before yours is called. That extension will be returning the text formatted in its own way, but then your extension is called with the original text details being sent. In such an instance of data being returned and possible prior extensions, there is a variable available to retrieve that already formatted text: $this->extensions->last_call. This variable will return whatever the last extension returned to this hook.”

Using this knowledge for debugging

Extensions don’t have many moving parts out of the box, but as you add more and more 3rd party add-ons into the mix they can really complicate a given EE page load. Because of this, one of the first things that an EllisLab support staff member might ask you to do when debugging an EE problem is to “disable extensions.” There is a really simple 1-click process to turn off all extensions in EE and this effectively removes all 3rd party modifications to the core behavior.

You can take that approach one step further when trying to debug problems on your own sites. If you have a particular page that isn’t working as expected, or even a form submission in the Control Panel that isn’t producing the expected results, you can look through the code to see what hooks might be used on that page load and then go directly into your exp_extensions table and disable each extension one-by-one to find the culprit. This comes in handy a lot for the Focus Lab dev team.

If you take some time to really understand how these hooks work within EE and also be conscious of which add-ons use which hooks you can really put this knowledge to use and cut down on your debugging time. You can also cut down on dependency for EllisLab support for some of these issues.

My hope is that the information here can help you better plan, build and debug your ExpressionEngine sites.

Resources and Links:

Here are a few links and tidbits to help you continue forth with your extensions and hook education.

Information on and examples of hooks:

Articles about EE extensions and hooks:

Other helpful resources:

Searching for hooks in ExpressionEngine:

If you want to find all hooks in EE you can use a regular expression search in your preferred IDE or text editor. The reason it needs to be a regular expression is because EE has two methods to call a hook. Here’s the regex pattern:

->(universal_)?call(

Let’s Talk!

What has your experience been with extensions? Have they always seemed like black magic to you? Have you ever beat your head against a wall trying to understand them or debug a problem with one? Let’s talk in the comments below.