Designing application programming interfaces (APIs) can be a challenging endeavor. Good APIs have simple interfaces that are straightforward and easy to use. Behind this simple interface can be many complex system interactions, and those interactions can really muddy the waters of an otherwise clearly defined endpoint task. Over time, developers may be asked to “tack on” additional business logic for existing endpoints. Then before you know it, a single API call is making interactions with over a dozen systems as part of its main flow.
Wouldn’t it be nice if we could develop a pipeline that’s straightforward but with the capability to add additional tasks later without obscuring the main flow? This article will show you how you can adapt an idea from WordPress, and programming in general, to give your APIs the ability to do more powerful interactions.
What Are Hooks/Actions?
A hook (aka actions/filters) is the name given to events and their related callbacks by the WordPress community. If you have any experience in programming, you might be familiar with callbacks and the publisher–subscriber pattern. During processing, a system may trigger an event which calls zero to many functions subscribed to that event. For instance, in response to loading a page, WordPress makes calls to functions for loading the header, loading a title, listing posts or looking for the right template. These tasks are run without cluttering up the main process of generating a page.
The idea behind hooks is nothing new and wasn’t invented by WordPress. However, WordPress did a great job of implementing them during their server-side page processing lifecycle. This use of hooks, in my opinion, is probably the single greatest feature the platform has. With these hooks, users can write their own functionality — be it plugins or themes — that tie into WordPress and run whatever code you want right when it’s needed. Do you need to alter a header sent to the user? No problem: hook into the wp_headers
event and you can alter the headers as you see fit.
Why Use Hooks in an API?
Hooks are good for many things, including triggering some side tasks, calling out to another system through a PHP cURL command, building an object and putting it into a task queue to be picked up by another system later, sending an email, and more. This can all be done without needing to cloud the main flow of a given endpoint (and possibly forcing a new API version in the process).
If the endpoint is for creating a user, we can focus on creating that user record in the database and along the way just call out to whoever is listening during that process. Maybe after creating the user record, we send out an event that says “anyone listening to this, I just created a user and here’s their info”. Maybe some callback functions have subscribed to the event and are listening or maybe none are. The event doesn’t really care.
With this system, we can have our APIs call out to code that may be written at some later time. We can do this without needing to touch the API endpoint code itself. To demonstrate how this might work, let’s change gears and show the basic mechanism of how we can get this started in a PHP API. Do keep in mind that, while we’re using PHP here, we can actually implement similar logic in web applications using other languages.
Building the Basic Mechanisms
To get started, we’ll need to be able to add a hook/action (which I’ll refer to as “hook” from now on). We’ll also need the ability to remove a hook and lastly trigger a hook. Once we define these mechanisms, we just need to make sure they’re included into the API and then locate places in our API where we might want to call these hooks. Below is one way we might want to set this up.
Here’s hooks.php
:
// Global array which will hold all of our hooks // We will reference this array in each function to add/remove/call our hooks // The code below should also be seen by any callbacks we write for the system later. $hooks = []; // Below are global functions that can be seen from our API code // The add_hook method will allow us to attach a function (callback) to a given event name function add_hook($event_name, $callback) { global $hooks; if ($callback !== null) { if ($callback) { // We can set up multiple callbacks under a single event name $hooks[$event_name][] = $callback; } } } // Super easy to implement, we remove the given hook by its name function remove_hook($event_name) { global $hooks; unset($hooks[$event_name]); } // When we want to trigger our callbacks, we can call this function // with its name and any parameters we want to pass. function do_hook($event_name, ...$params) { global $hooks; if (isset($hooks[$event_name])) { // Loop through all the callbacks on this event name and call them (if defined that is) // As we call each callback, we given it our parameters. foreach ($hooks[$event_name] as $function) { if (function_exists($function)) { call_user_func($function, ...$params); } } } }
Now that we have our hooks.php
file created, we simply need to include it into our API so that these functions can be seen. Once this is done, it’s just a matter of inserting the hooks into our API using do_hook
.
As a simple example, let’s assume we have an API for registering a new user with our system. We may have a REST API endpoint called /addUser
. In the name of simplicity, let’s also assume that the goal here is to simply insert a new user’s name and age into our database’s users
table. Pretty straight forward, right?
// POST endpoint for adding a user (part of a larger API class) public function addUser($name, $age) { if ($this->request->method === 'post') { try { $this->db->insert('users', ['name' => $name, 'age' => $age]); return new Response(200, 'User created successfully!'); } catch (Exception $e) { // Oops, something went wrong. // Do some logging or whatever. } } // If not a POST request, return http status 400 return new Response(400, 'Bad request'); }
The code above is an overly simplistic and generalized view of how we might add a new user. The idea is that, if someone were to call our API’s /addUser
endpoint, they would eventually arrive at this function where the name and age of the user is pulled out of the posted data. We first check to make sure they’re posting (as proper REST rules dictate) and then try to insert the user into the users
table.
Next, if the user has been inserted successfully, we want to call a hook to let any code listening that a user was created (this is similar to raising an event in other languages).
Continue reading Flexible API Design: Create Hooks for Your PHP API Pipeline on SitePoint.