While callbacks offer a simple approach to wiring logic to a route, they aren’t suited for applications with a high volume of routes. Instead, wire your routes to a controller. In Concrete CMS, a controller is any PHP class.
Autoloading
(Note: in these examples, it’s assumed that you have autoloading set up to load these classes, and they are accessible through PHP. To learn how to enable autoloading of PHP classes in your Concrete packages or application directory, check out the autoloading documentation
Route Example
Let’s take our current_user
route example. How about we move it out of a route, and into a dedicated PHP controller. This is how that’s done:
$router->get(‘/api/current_user’, ‘Application\Api\Controller\UserController::getCurrentUser’);
We now no longer include the logic of retrieving the current user inline with the route definition. Instead, it is housed in a separate class, found at Application\Api\Controller\User
. Furthermore, we have a specific method within this controller class that we run when the route is visited, getCurrentUser
.
What does this controller look like? There isn’t much to it. At application/src/Api/Controller/User.php
, we have:
<?php
namespace Application\Api\Controller;
use Concrete\Core\User\User;
use Symfony\Component\HttpFoundation\JsonResponse;
class UserController
{
public function getCurrentUser()
{
$u = new User();
if ($u->isRegistered()) {
$data = [];
$data[‘user_id’] = $u->getUserID();
$data[‘username’] = $u->getUserName();
return new JsonResponse($data);
} else {
return new JsonResponse([], 500);
}
}
}
That’s it! We have moved our logic into a controller. You don’t need to extend any particular base classes. Just return a valid response object.
Note: Use Dependency Injection
Before we move on, let’s consider the code above, and how it can be improved by using dependency injection. Dependency injection moves any classes that are code is dependent on into the constructor. That means that we can test our code, and provide mock objects to the constructor with a minimum of fuss. Concrete automatically provides classes when you make them available to the class, so you don’t even have to worry about the dependent classes being instantiated and passed. Let’s change the above to use this functionality.
<?php
namespace Application\Api\Controller;
use Concrete\Core\User\User;
class UserController
{
protected $currentUser;
public function __construct(User $currentUser)
$this->currentUser = $currentUser;
}
public function getCurrentUser()
{
if ($this->currentUser->isRegistered() {
// etc…
}
}
}
Yes, this version contains more code, but if we want to test our code (and we should) this will be easier, because we can do things like pass PHP mock objects to the constructor when testing them, in order to determine whether their behavior is proper.
What’s Next
Now that we know how to move our route logic into controllers, let’s example what to do if we need something a little more advanced than a simple HTTP response? Simply returning a JsonResponse is great if we’re making an API, but what if we need to lots of HTML, and perhaps put that HTML into our site’s theme? We can do this with Concrete’s Views.