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.