Currently, Element API is hard coded to throw a 404 exception for single element endpoints, if the element criteria returns no results. There's no way to prevent this exception, add "fallback" criteria or to display an error message.
Note that the 404 exception will only be thrown if the first
parameter is set to true
, though. If it isn't, Element API will just return an empty dataset.
Obviously, omitting the first
parameter is not a real solution to your issue. You still have no way to set a custom error message, and it would also mean that all single element endpoints would return arrays, as well as paging metadata etc, instead of a "flat" JSON object.
On that note, my opinion is that the canonical answer to this question should probably be to fork the Element API plugin and/or add a feature request.
However, if you don't want to go down that road, here's a possible workaround. It's not pretty :)
Basically, what you'll need to do is to catch the HttpException
, in order to return a custom payload.
To do this, you'll need to write a custom plugin. Important: make sure that the string returned by your plugin's getName()
method comes before "Element API", alphabetically. The reason for this is that you need to make sure that Craft loads your plugin before the Element API plugin (Craft loads plugins alphabetically, by name). I'll pretend you opt for the name "Awesome Plugin", which would work nicely.
Your plugin will need a controller, so create one called "AwesomeController" or the like. This controller should have a method called actionGetElements
, taking the same arguments as the ElementApiController's actionGetElements
method.
The only purpose of your controller is to "wrap" the ElementApiController, in order to catch the 404 HttpException fired by Element API, and echo a custom error message. Here's how that could look:
<?php
namespace Craft;
class AwesomeController extends BaseController
{
protected $allowAnonymous = true;
public function actionGetElements($configFactory = null, array $config = null)
{
try {
// Fire up the Element API controller
craft()->runController('elementApi/getElements');
} catch (HttpException $e) {
switch ($e->statusCode) {
case 404 :
// It's a 404. Output a custom error message
JsonHelper::sendJsonHeaders();
echo JsonHelper::encode(array(
'error' => 'No element found',
));
// End the request
craft()->end();
}
}
}
}
Obviously, the controller will do nothing by itself – the final thing you need to do is to "hijack" the Element API routes; making them point to your controller instead of the ElementApiController. To achieve this, you'll need to create a method called registerSiteRoutes()
in your plugin's primary class. Inside this method, you'll basically want to pull all the routes from the Element API plugin, changing their action
value to awesomePlugin/getElements
.
This is why it's important to make sure that your plugin is loaded by Craft before the Element API plugin: when an existing route is added to Craft, it'll be ignored. This means that for every endpoint in /config/elementapi.php
, you can trick Craft into loading your custom controller instead of ElementApiController!:
Here's how that registerSiteRoutes()
method could look:
public function registerSiteRoutes()
{
$routes = [];
// Get the Element API plugin
$elementApiPlugin = craft()->plugins->getPlugin('elementApi');
if (!$elementApiPlugin) {
return $routes;
}
// Get the Element API routes
$elementApiRoutes = $elementApiPlugin->registerSiteRoutes();
// Set action property
foreach ($elementApiRoutes as $endpoint => $config) {
$config['action'] = 'awesomePlugin/getElements';
$routes[$endpoint] = $config;
}
return $routes;
}
All in all – not exactly an elegant solution; but if the 404's are a dealbreaker to you, its at least a working workaround that doesn't require you to tamper with the Element API source, and the performance footprint should be minimal, too (no redundant database queries).