Controllers¶
Controllers it’s mediator between all incoming requests and your business logic. So please keep they simple as possible, don’t store any complex logic inside controllers, rather keep you logic in services.
All new controllers must extend the AbstractController to get a few helpful methods.
Json Response¶
When you need to return a json based response from your services you may use the method jsonResponse as in example below:
<?php
// sourced from: src/Module/User/Controller/UserApiController.php
namespace Tiny\Skeleton\Module\User\Controller;
use Tiny\Http\AbstractResponse;
class UserApiController extends AbstractUserController
{
/**
* @param AbstractResponse $response
*/
public function list(AbstractResponse $response)
{
$this->jsonResponse($response, $this->userService->getAllUsers());
}
}
The method jsonResponse it’s just a wrapper for the Response object.
<?php
// sourced from: src/Module/Base/Controller/AbstractController.php
protected function jsonResponse(
AbstractResponse $response,
array $variables = [],
int $code = AbstractResponse::RESPONSE_OK
): AbstractResponse {
$response->setResponse(json_encode($variables))
->setCode($code)
->setResponseType(
AbstractResponse::RESPONSE_TYPE_JSON
);
return $response;
}
View Response¶
if you need to return an html based response you may use the viewResponse method;
<?php
// sourced from: src/Module/User/Controller/UserController.php
use Tiny\Http\AbstractResponse;
class UserController extends AbstractUserController
{
/**
* @param AbstractResponse $response
*/
public function list(AbstractResponse $response)
{
$this->viewResponse($response, [ // passing a variable list in a template
'users' => $this->userService->getAllUsers()
]);
// don't forget to create a template at: view/UserController/list.phtml
}
}
// sourced from: src/Module/User/view/UserController/list.phtml
<ul>
<?php foreach ($this->users as $user): ?>
<li>
<?= $user['name'] ?>
</li>
<?php endforeach ?>
</ul>
The method viewResponse it’s just a wrapper for the Response object.
<?php
// sourced from: src/Module/Base/Controller/AbstractController.php
protected function viewResponse(
AbstractResponse $response,
array $variables = [],
int $code = AbstractResponse::RESPONSE_OK
): AbstractResponse {
$response->setResponse(new View($variables))
->setCode($code)
->setResponseType(
AbstractResponse::RESPONSE_TYPE_HTML
);
return $response;
}
You may ask which html template is used in this case (I don’t see anything related with a path), but the magic is hidden under the hood.
Generally speaking the application subscribes to the ControllerEvent::EVENT_AFTER_CALLING_CONTROLLER
event and generates a template path dynamically it means you don’t need to provide it manually every time (but you still can do that).
Below the some peace of code from AfterCallingControllerViewInitListener which is in charge of generating path.
<?php
// sourced from: src/Module/Base/EventListener/Application/AfterCallingControllerViewInitListener.php
// make sure we received an instance of "View" object
if ($controllerResponse instanceof View) {
// set both layout and template path (if they are missing)
// the path generator uses the mask: "view/[ControllerName/ControllerAction"
if (!$controllerResponse->getTemplatePath()) {
$controllerResponse->setTemplatePath(
$this->getTemplatePath($event->getParams()['route'])
);
}
// by default we setup a layout which is defined in configs "base_layout_path"
if (!$controllerResponse->getLayoutPath()) {
// get the View's configs
$viewConfig = $this->configService->getConfig('view', []);
$controllerResponse->setLayoutPath(
$this->viewHelperUtils->getTemplatePath(
$viewConfig['base_layout_path'],
'Base'
)
);
}
$controllerResponse->setEventManager($this->eventManager);
// return the modified response
$response->setResponse(
$controllerResponse
);
// replace the data in event
$event->setData($response);
}
To know more about the View read the chapter
To know more about the Application events read the chapter
Custom Response¶
Some time you need a custom response based on you needs. In that case you can modify the Response object directly in controllers.
Let say we need to return an XML based response:
<?php
use Tiny\Http\AbstractResponse;
class UserXmlController extends AbstractUserController
{
/**
* @param AbstractResponse $response
*/
public function list(AbstractResponse $response)
{
$user = [
'name' => 'tester',
'country' => 'USA',
];
// convert the array to an xml string
$xml = new SimpleXMLElement('<root/>');
array_walk_recursive($user, [$xml, 'addChild']);
$response->setResponse($xml->asXML())
->setCode(200)
->setResponseType('text/xml');
return $response;
}
}
Exceptions¶
Sometimes we need to show special pages like 404 (Not found), 401 (Unauthorized), etc
within our controllers. So how can we achieve that? We can easily trigger an Exception.
Then we may subscribe (using the Event manager and the application's lifecycle) to that Exception and show a special page.
Let’s check a look how we handle the 404 page in the app
(using the similar way you can implement handling of any kind of errors):
Let’s suppose we have a controller:
<?php
namespace Tiny\Skeleton\Module\User\Controller;
use Tiny\Http\AbstractResponse;
use Application/Exception/Request/NotFoundException.php;
class UserApiController extends AbstractUserController
{
/**
* @param AbstractResponse $response
*/
public function list(AbstractResponse $response)
{
$users = $this->userService->getAllUsers();
if (!$users) {
// we need to show the "404" page (because we don't have users)
throw new NotFoundException();
}
$this->jsonResponse($response, $users);
}
}
Now lets create a file: NotFoundException.php in the Application/Exception/Request/ folder
<?php
namespace Tiny\Skeleton\Application\Exception\Request;
use Exception;
class NotFoundException extends Exception implements ExceptionInterface
{
}
Now we only to need capture that Exception and show a page. Let’s create a listener for that:
<?php
// sourced from: Application/Exception/Request/NotFoundException.php
namespace Tiny\Skeleton\Module\Base\EventListener\Application;
use Tiny\EventManager\EventManager;
use Tiny\Http\AbstractResponse;
use Tiny\Router\Route;
use Tiny\Skeleton\Application\EventManager\ControllerEvent;
use Tiny\Skeleton\Application\Exception\Request\NotFoundException;
use Tiny\Skeleton\Module\Base\Utils\ViewHelperUtils;
use Tiny\View\View;
class ControllerExceptionNotFoundListener
extends AbstractControllerExceptionListener
{
/**
* @var AbstractResponse
*/
private AbstractResponse $response;
/**
* @var EventManager
*/
private EventManager $eventManager;
/**
* @var ViewHelperUtils
*/
private ViewHelperUtils $viewHelperUtils;
/**
* ControllerExceptionNotFoundListener constructor.
*
* @param AbstractResponse $response
* @param EventManager $eventManager
* @param ViewHelperUtils $viewHelperUtils
*/
public function __construct(
AbstractResponse $response,
EventManager $eventManager,
ViewHelperUtils $viewHelperUtils
) {
$this->response = $response;
$this->eventManager = $eventManager;
$this->viewHelperUtils = $viewHelperUtils;
}
/**
* @param ControllerEvent $event
*/
public function __invoke(ControllerEvent $event)
{
$eventParams = $event->getParams();
$exception = $eventParams['exception'] ?? null;
/** @var Route $route */
$route = $eventParams['route'] ?? null;
// we handle only "NotFoundException" exception (all other will be skipped)
if ($exception && $route && $exception instanceof NotFoundException) {
$errorMessage = $exception->getMessage() ?: 'Not found';
// we either show a json based "404" response (for the api context) or html based "404" page
if ($this->isJsonErrorResponse($route->getContext())) {
$this->jsonErrorResponse(
$this->response,
$errorMessage,
AbstractResponse::RESPONSE_NOT_FOUND
);
} else {
$this->viewErrorResponse(
$this->response,
$this->getView($errorMessage),
AbstractResponse::RESPONSE_NOT_FOUND
);
}
$event->setData($this->response);
}
}
/**
* @param string $errorMessage
*
* @return View
*/
private function getView(string $errorMessage): View
{
$view = new View(
[
'message' => $errorMessage,
]
);
// set path to the "404" page and its layout
$view->setTemplatePath(
$this->viewHelperUtils->getTemplatePath(
'NotFoundController/index', 'Base'
)
)
->setLayoutPath(
$this->viewHelperUtils->getTemplatePath(
'layout/base', 'Base'
)
)
->setEventManager($this->eventManager);
return $view;
}
}
And we need to register our listener in the configs:
<?php
// sourced from: src/Module/Base/config/listeners.php
use Tiny\Skeleton\Application\EventManager;
use Tiny\Skeleton\Module\Base\EventListener;
return [
// application
...
[
// whenever we have an exception in controllers we call our listener to check the error type
'event' => EventManager\ControllerEvent::EVENT_CONTROLLER_EXCEPTION,
'listener' => EventListener\Application\ControllerExceptionNotFoundListener::class,
],
...
];
To read more about the app’s lifecycle
PS: By default all uncaught errors will be displayed in the 500 error page.