Concrete5

Meet concrete5's REST API and New Routing Component

concrete5 is not slim. It is, by design, a monolithic, feature-packed application, controlling every aspect of the website where it runs. This approach enables us to deliver the best editing experience of any open source content management system, because we’re hooked into the theme, the backend, the JavaScript and CSS assets, and more.

While great for enabling features like in-context editing and theme customization, this approach is limiting in other ways, and hampers our ability to work with sites built with non-PHP technologies. And as concrete5 grows as both a data management platform – with features like Express Data Objects in version 8 – it makes less sense that this data would be tied so tightly to our platform, with no easy way to share it with other web applications.

In version 9, we’re going to fix that.

REST API

In concrete5 version 9 (coming this summer), we’re including a full-featured REST API, enabled via a simple configuration setting. Built on industry standards like Guzzle HTTP Services and Oauth2 authentication, this API will dramatically improve how you get data into and out of your concrete5 site from external sources.

Routing

One of the reasons this improvement is possible is due to a completely redesigned and rebuilt Routing component. While concrete5 has employed the Symfony Routing Component since version 5.7, the syntax for adding routes was archaic and not very developer-friendly. With version 9’s routing component taking inspiration from PHP frameworks like Laravel, it will become much easier to define and work with routes in your concrete5 packages and applications. Here are some examples of the new router in action.

You can easily make a route respond with all HTTP verbs:

$router->all('/render/', 'Block::render');

or just at a particular one:

$router->get('/ccm/calendar/dialogs/event/versions', '\Concrete\Controller\Dialog\Event\Versions::view');

You can name a route and set restrictions using a fluent syntax:

$router->get('/ccm/calendar/view_event/{bID}/{occurrence_id}', '\Concrete\Controller\Dialog\Frontend\Event::view')  
->setName('view_event_occurrence')  
->setRequirements(['occurrence_id' => '[0-9]+']);`

and group routes together in order to apply complex rules to them:

$router->post('/a-fun-test', 'ConcreteTestsCoreRoutingTestControllertest');  
$router->buildGroup()  
->setPrefix('/api/v1')  
->routes(function($groupRouter)   
    $groupRouter->get('/hello-world', 'Concrete\Tests\Core\Routing\TestController::hello');  
    $groupRouter->get('/status', 'Concrete\Tests\Core\Routing\TestController::status');  
    $groupRouter->get('/user/:user', 'Concrete\Tests\Core\Routing\TestController::getUserDetails')  
        ->setName('user_details');  
    return $groupRouter;  

You can even ensure that all routes in a particular group are run through a particular PHP script, called a middleware, every time they’re accessed:

$router->buildGroup()  
->setPrefix('/ccm/api/v1')  
->addMiddleware(ProjectorMiddleware::class)  
->addMiddleware(APIAuthenticatorMiddleware::class)  
->routes('api/system.php')  
->routes('api/site.php');

With version 9, routing will be a whole lot more fun, and much less of a chore.

Consuming the REST API

One of the big reasons behind REST’s success as an API approach is its usability. concrete5’s REST API will work easily with all standard API tools, with the common commands responding at URLs like http://www.somesite.com/index.php/ccm/api/v1/system/status. Any framework that can send an HTTP request to your site will be able to get nicely structured JSON data back.

If you’re querying a site’s API from another concrete5 site, however, we have a consumer library that will make things even easier. You don’t have to monkey around with an HTTP library like Guzzle or Zend HTTP; instead, version 9 will have an API factory class, and helper functions to get data back. Here’s an example of a concrete5 site querying www.mysite.com, which is another concrete5, for its system status object.

$factory = new \Concrete\Core\API\APIFactory();
$api = $factory->create(new \Concrete\Core\API\UrlProvider('http://www.mysite.com'));
$result = $api->system()->getSystemInformation();  
if ($result) 
    $json = $result->toArray();
}

Developing for the REST API

In addition to upgrading our routing component, we’ve put a lot of effort into making sure that the experience of developing for the REST API is as nice as possible. We’ve integrated Fractal, which is a presentation layer for JSON data derived from complex objects. If your REST API route returns an object, you can make that object implement Concrete\Core\API\Resource\TransformableInterface, and your object will automatically be transformed to JSON and sent with the proper JSON response to the requester. For example, here’s the entirety of the /ccm/api/v1/system/status route:

use Concrete\Core\System\Info;
$router->get('/system/info', function()   
    return new Info();  
);  

Since the info object implements the interface above, it’s automatically transformed to JSON and sent in the proper format. Additionally, if this object is ever referenced by other objects in the API, it will be transformed there properly as well.

Extending the REST API

It’s easy for packages to extend the REST API as well. Simply create some routes and some guzzle service descriptions, and you can add new top level methods to the API, like this one:

$result = $api->export()->exportUser(['id' => 20]);

More information on this is coming soon, but you can see examples of it in GitHub now.

Going Forward

We’re very excited about the version 9 API, but we have a lot more work to do! The components are there but the API itself is almost completely empty. We need assistance with the following:

  • Currently only the ClientCredentials authentication method works with the API, but we’d love to get JWT working.
  • We need methods for reading from common concrete5 objects like pages, users and express objects.
  • We need methods for creating Express objects
  • We need methods for creating Page objects

Want to see the code and help out? Check out the release/8.4.0 branch.

Please get in touch in Slack, or in GitHub: https://github.com/concrete5/concrete5/issues/4708 or hit me up directly on Twitter. Please let us know what you think! We want this API to be the one you like to use the best.

Loading Conversation