Application Lifecycle

Entry point

We use the Front controller pattern to capture all incoming web and console requests as well. The public/index.php it’s a requests handler which runs the application.

Aside from launching the handler also defines the working environment (there are two possible modes: dev and prod) which are responsible for displaying and catching errors (see the Error handling chapter). The quick example of public/index.php:

<?php

    // sourced from: public/index.php

    $applicationEnv = getenv('APPLICATION_ENV') ?: 'dev';
    ...

    require_once 'vendor/autoload.php';
    require_once 'error-handler.php';
    require_once 'application-env/'.$applicationEnv.'.php';

    ...

When all initialization steps are finished the handler runs the application using the following way:

<?php

    // sourced from: public/index.php

    $application = new Application\Application(
        new Application\Bootstrapper(
            new Application\BootstrapperUtils(getcwd()),
            $isProdEnv
        ),
        $isCliContext,
        require_once 'modules.php'
    );

    echo $application->run();

Bootstrapping

Bellow we check a look the details of bootstrapping process which contains of 8 steps.

1. Loading modules configs

The skeleton follows a modular structure, it means each module provides it’s own config file which may include (listeners, routes, factories, etc) for managing the application’s state.

<?php
    // sourced from: src/Application/Application.php

    $configsArray = $this->bootstrapper->loadModulesConfigs(
        $this->registeredModules
    );

The list of all defined modules ($this->registeredModules) is stored in the root’s modules.php file:

<?php

    // sourced from: modules.php

    return [
        'Base',
        'User',
        ...
    ];

Generally speaking the application collects all modules configs and merges they in a one global config. Example of a config file:

<?php

    // sourced from: src/Module/Base/config.php

    return [
        'site' => [
            'name' => 'Test site'
        ],
        'modules_root' => dirname(__DIR__),
        'view'            => [
            'base_layout_path'   => 'layout/base',
            'template_extension' => 'phtml',
        ],
        'service_manager' => require_once 'config/service-manager.php',
        'listeners'       => require_once 'config/listeners.php',
        ...
    ];

2. Init service manager

The service manager layer is responsible for registering any kind of services (controllers, listeners, utils, view helpers, etc). It looks like a big registry where you can get any service using factories (view more details).

<?php

    // sourced from: src/Application/Application.php

    $serviceManager = $this->bootstrapper->initServiceManager(
        $configsArray
    );

Services definitions are stored in config files:

<?php

    // sourced from: src/Module/Base/config/service-manager.php

    return [
        'shared' => [ // means we need only singletons
            // application listener
            Base\EventListener\Application\AfterCallingControllerViewInitListener::class => Base\EventListener\Application\Factory\AfterCallingControllerViewInitListenerFactory::class,
            ...

            // controller
            Base\Controller\NotFoundController::class                                    => InvokableFactory::class,
            ...
        ],
        'discrete' => [ // means we always need a new class instance
            // utils
            Base\Utils\ViewHelperUtils::class                                            => Base\Utils\Factory\ViewHelperUtilsFactory::class,
            ...
        ]
    ];

The config structure it’s a simple map with service names and its factories (classes which are responsible for creating those).

PS: To not to make modules main config to big we split it on a few small parts, example:

<?php

    // sourced from: src/Module/Base/config.php

    return [
        'site' => [
            'name' => 'Test site'
        ],
        ...
        // both service manager and listeners configs are stored separately
        'service_manager' => require_once 'config/service-manager.php',
        'listeners'       => require_once 'config/listeners.php',
    ];

So it’s a good practice which you also should follow.

3. Init event manager

We use the event manager to make a communication among the different parts of application (view more details), for instance we may notify listeners about an action or even ask provide us with some data.

<?php

    // sourced from: src/Application/Application.php

    $this->bootstrapper->initEventManager(
        $serviceManager->get(EventManager::class),
        $configsArray
    );

Listeners definitions also are stored in config files:

<?php

    // sourced from: src/Module/Base/config/listeners.php

    return [
        // application
        [
            'event'    => EventManager\ControllerEvent::EVENT_BEFORE_CALLING_CONTROLLER,
            'listener' => EventListener\Application\BeforeCallingControllerCorsListener::class,
            'priority' => -1000,
        ],
        ...
        // view helper
        [
            'event'    => View::EVENT_CALL_VIEW_HELPER.'config',
            'listener' => EventListener\ViewHelper\ViewHelperConfigListener::class,
        ],
        ...
    ];

It’s a list of named events and their handlers. Optionally you may setup a listener’s priority to manage their calling order.

4. Init config service

To make raw collected modules configs available in the application we need to register them as a service.

<?php

    // sourced from: src/Application/Application.php

    $this->bootstrapper->initConfigsService(
        $serviceManager->get(EventManager::class),
        $serviceManager->get(ConfigService::class),
        $configsArray
    );

Whenever you need an access to that configs you may inject the config service into you class and get access to any config value:

<?php

    // sourced from: src/Module/Base/EventListener/Application/AfterCallingControllerViewInitListener.php

    // a factory
    return new AfterCallingControllerViewInitListener(
        $serviceManager->get(ConfigService::class),
        ...
    );

    ...

    // somewhere inside the AfterCallingControllerViewInitListener
    $configValue = $this->configService->getConfig('config_key');
    ...

The final collected list of configs maybe modified by listeners in the Event manager. Read more at: Configs events

5. Init routes

On this step application collects and registers routes which are used in the navigation.

<?php

    // sourced from: src/Application/Application.php

    $this->bootstrapper->initRoutes(
        $serviceManager->get(EventManager::class),
        $serviceManager->get(Router::class),
        $serviceManager->get(ConfigService::class),
        $this->isCliContext // auto detect the current context
    );

For the performance reason application collects only routes related to the current context. Context may be either console or http|http_api. Routes definitions are stored in config files:

<?php

    // sourced from: src/Module/User/config/routes.php

    return [
        'http'     => [
            [
                'request'     => '/users',
                'controller'  => Controller\UserController::class,
                'action_list' => [
                    Request::METHOD_GET  => 'list',
                    Request::METHOD_POST => 'create',
                ],
            ],
        ],
        'http_api' => [
              [
                'request'     => '/api/v1/users',
                'controller'  => Controller\UserApiController::class,
                'action_list' => [
                    Request::METHOD_GET  => 'list',
                    Request::METHOD_POST => 'create',
                ],
            ],
        ],
        'console'  => [
            [
                'request'     => 'user list',
                'controller'  => Controller\UserCliController::class,
                'action_list' => 'list',
            ],
        ],
    ];

We split the http and http api routes due to different error handling strategy. For example when the 404 error occurred we display a normal 404 page but for the api routes whe display json response.

The routes registration process maybe changed by listeners. For instance you can add a new route or delete some of existing ones using different criteria. Read more at: Route events

6. Init router

The router’s main job is to find a matched route inside registered routes using a request query or throw an exception if it cannot be found.

<?php

    // sourced from: src/Application/Application.php

    $route = $this->bootstrapper->initRouter(
        $serviceManager->get(EventManager::class),
        $serviceManager->get(Router::class)
    );

Using listeners in this case you can manipulate of searching a matched route or catch the Exception when route is not found and show a 404 page as an example. Read more at: Router events

7. Init controller

When a Route is found we are able to call an associated controller’s method and get a response.

<?php

    // sourced from: src/Application/Application.php

    $response = $this->bootstrapper->initController(
        $serviceManager->get(EventManager::class),
        $serviceManager->get($route->getController()),
        $serviceManager->get(Http\Request::class),
        $serviceManager->get(Http\AbstractResponse::class),
        $route
    );

Like in all the previous examples here you also is available to control the execution flow using listeners. For example before execute a method we may check a user’s role or even gzip the received response after the execution, you are free to implement anything you want. Read more at: Controller events

8. Init response

The latest step in the life cycle process. The received response from the controller from the previous step is triggering to listeners, then it displays in a browser or in the console.

<?php

    // sourced from: src/Application/Application.php

    $responseText = $this->bootstrapper->initResponse(
        $serviceManager->get(EventManager::class),
        $response,
        $route->getController(),
        $route->getMatchedAction()
    );

So it’s a good place to process the response. For instance you may wrap received response with your custom content. For example you may show a profiler information. Read more at: Response events