iuravic/duktig-skeleton-web-app

Skeleton web application with Duktig MVC micro web framework for PHP 7.1

dev-master 2017-07-07 12:03 UTC

This package is not auto-updated.

Last update: 2022-01-08 13:44:26 UTC


README

Build Status Coverage Status

duktig-skeleton-web-app

This is a skeleton web application made with the Duktig micro MVC web framework written for PHP 7.1, based on iuravic/duktig-core library.

Table of contents

About

The duktig-skeleton-web-app is a starting point for developing your own applications with the Duktig framework. It is founded on the duktig-core package, and it provides all its necessary dependencies. The skeleton application also contains functional examples of the Duktig framework's major features.

duktig-core package

It is advisable to also inspect the duktig-core documentation which describes the purpose and features of the Duktig framework, as well as explains its base elements and functionalities.

Package design

Most of the Duktig's core services are fully decoupled from the duktig-core package. The duktig-core simply describes them by defining its interfaces, and each of them is implemented as a separate package in the form of an adapter towards a readily available open-source project. This kind of approach provides high flexibility, reusability, and generally stands as a good package design.

Dependencies

The duktig-skeleton-web-app composes the full Duktig web application framework by using popular and tested open-source packages. Adapter packages are used simply to adapt the external packages' APIs to the core's interfaces, therefore making them usable by the duktig-core.

The following projects and packages are used here to provide the full functionalities for Duktig framework:

Project Used adapter package
Guzzle HTTP messages duktig-http-factory-guzzle-adapter
Symfony Router duktig-symfony-router-adapter
Symfony Event Dispatcher duktig-symfony-event-dispatcher-adapter
Auryn DI container duktig-auryn-adapter
Monolog /
Middleman middleware dispatcher duktig-middleman-adapter
Twig renderer duktig-twig-adapter

Core services

The services.php file shows how these packages are implemented and configured to provide for the core functionality.

Install

The following command creates a new project via Composer:

$ composer create-project -s dev iuravic/duktig-skeleton-web-app {$PROJECT_PATH}

The Duktig packages' repositories are currently not tagged to corresponding versions, for which I apologize at this moment, but this command will never the less correctly resolve and fetch all the dependencies and create your new project.

Usage and application flow

Let's take a look at a full request-to-response life-cycle, and some of its key elements, in the order in which they come up in the chain of command.

index.php

A typical index.php file would look like this:

<?php
    require __DIR__.'/../vendor/autoload.php';

    $app = (new \Duktig\Core\AppFactory)->make(
        __DIR__.'/../src/Config/config.php',
        \Duktig\Core\App::class
    );

    $app->run();

We see that the app is created by the Duktig\Core\AppFactory by providing the custom configuration file and the application class.

AppFactory

The Duktig\Core\AppFactory creates an instance of the application by:

  • taking the user configuration and merging it with core configuration,
  • instantiating and configuring the DI container through the use of the Duktig\Core\DI\ContainerFactory,
  • resolving the app with its dependencies.

Request processing and the App class

The Duktig\Core\App is the framework's main class and its method run() is the entry point for the request. The framework "runs" the request through the full application stack. It employs HTTP middleware at its core and composes a middleware stack which consists of:

  • application middleware,
  • route middleware,
  • controller responder middleware.

Two kinds of middleware exist in Duktig: the application middleware -- which is used on every request, and the route specific middleware -- which can be assigned to a specific route.

At the end of the middleware stack lies the ControllerResponder middleware. It is in charged of resolving the controller/route handler, and returning the response object from it to the stack.

After finishing processing the response, the framework sends it to the browser and terminates the application business.

Example project functionalities

Within this package several functionalities are implemented as simple show-case examples. These can be looked up as the "how-tos", they can be modified, or simply removed from your project. Basically they should serve to show a quick way around building apps with the Duktig framework.

Following is a quick list of those functionalities.

Core services

The 'Config/services.php' defines and registers all of the core services by using external packages.

Routes

See route configuration Config/routes.php.

Path Route Features
/ 'landing-page' Landing page
/example-controller-action/{myParam} 'example-route-w-controller' URI path params; Route specific middleware
/example-ip-check 'example-ip-check'
/example-callable-handler 'example-route-with-callable-handler' closure-type route handler;

Controllers

See controller directory Controller/.

The DuktigSkeleton\Controller\IndexController features:

  • extension of the BaseController class
  • constructor parameter resolution
  • use of external services
  • response definition
  • template rendering
  • redirection
  • URI path parameters
  • query parameters

Middlewares

The following middlewares are implemented with their corresponding features:

Events

The event EventIPInRange is a simple event object which shows how to:

  • extend the EventAbstract class
  • use external services
  • represent contextual data for its listeners

The EventIPInRange has one listener attached and registered via the Config/events.php config file, the ListenerReportIP, which demponstrates how to:

  • implement the ListenerInterface
  • use external services
  • handle the event object and perform a task

Services

The ExampleIPService is a simple service which implements a few basic features:

  • the ExampleIPService itself is being resolved by the use of the container's automatic provisioning feature (notice that it was not specifically registered in the Config/services.php, since the Auryn implements this feature)
  • dependency injection
  • access to the configuration service and config parameters

Configuration

The following configuration settings are implemented in the skeleton project:

Configuration

Before taking a final step and looking into the configuration, please take a moment to also look the duktig-core's documentation.

Configuration files

Duktig's configuration is contained within the simple '.php' config files inside the Config directory. Your application's Config folder should mirror the contents of the duktig-core's config. The core's and your application's configurations get fully merged at runtime, and all the config values defined in your application overwrite those from the core's. The only exception to this is the services.php file whose content is not overwritten, but merged with your application's services.php. In order to skip the core's services configuration, the config parameter 'skipCoreServices' can be used.

The configuration service

The configuration service is used to access the configuration parameters and values, that is the contents of the config.php file. It implements a simple API given by the Duktig\Core\Config\ConfigInterface.

It can be accessed via dependency injection, where it is type-hinted as the ConfigInterface, which will then have the Duktig\Core\Config\Config service resolved in its place.

The configuration service is a shared service, meaning that its instantiation will not return a blank instance, but an already configured value object.

Implementing duktig-core's requirements

The duktig-core package's has requirements which must be implemented and provided in order to create a full-functioning application environment. Those requirements need to be implemented, and registered with the container. The duktig-skeleton-web-app package already implements all those requirements; they are packaged separately (see chapter dependencies) and already resolved as the project's requirements.

Registering services

Services are registered in the Config/services.php file in your app folder. This file must return a closure which gets the container as it's argument, and returns it after configuring it:

<?php
return function($container) {
    // ...
    return $container;
};

Middleware

Duktig uses the "single-pass" PSR-15 compatible middleware and its dispatching system. Two different middleware types exist within the framework.

Application middleware

The application middleware is one which is run with every request. It is the app-wide middleware, and is defined in the Config/middleware.php configuration file in your app's folder. The next example shows how two application middlewares can be assigned:

<?php
return [
    \MyProject\Middleware\ExampleAppMiddleware1::class,
    \MyProject\Middleware\ExampleAppMiddleware2::class,
];

Route middleware

The route middleware is assigned to a specific route and is only run when that route is resolved. The route middleware is defined in your app's Config/routes.php file by using the 'middlewares' route config parameter. This brief excerpt shows how one route specific middleware can be assigned to an 'example-route' route:

<?php
return [
    'example-route' => [
        // ...
        'middlewares' => [
            \MyProject\Middleware\ExampleRouteMiddleware::class,
        ],
    ],
];

Events

Events and listeners can be registered either by using the configuration or programmatically. Even though both mechanisms are available, it is a general recommendation to use configuration to define events and their listeners rather than the programmatic method since the separation of configuration and code generally results in better factoring of the code. However this programming practice should be evaluated on a case-to-case basis.

Registration via the config file

To register events and their listeners, the Config/events.php file in your app's directory is used. The following example shows how two events can be registered with a listener each:

<?php
return [
    \MyProject\Event\EventIPInRange::class => [
        \MyProject\Event\ListenerReportIP::class,
    ],
    'custom-event-name' => [
        function($event) {
            // ...
            $event->getName();
        },
    ],
];

The first event exists as a standalone class and uses its fully qualified class name as the event's name. Its listener ListenerReportIP is defined as a standalone class as well. All such events with a class of their own must extend the Duktig\Core\Event\EventAbstract class. And all such listeners must implement the Duktig\Core\Event\ListenerInterface. Both the event and the listener are resolved by the container, and have their constructor dependencies injected.

The second event is defined by a custom name, and has one closure-type listener assigned to it. The closure-type listener can be assigned to any event, be it a standalone class or an event with a custom name. This listener can take only one argument, the event, and does not get resolved by the container.

In case the event is determined only by its unique name, and holds no specific contextual information for its listeners, it can be dispatched and instantiatiated as the special Duktig\Core\Event\EventSimple class. The EventSimple class creates an event on-the-fly requiring only its unique name.

Programatic configuration

To attach listeners programatically, we use the event dispatcher's API defined by the Duktig\Core\Event\Dispatcher\EventDispatcherInterface. The following example demonstrates registering the same two events and listeners as in the previous example where the configuration file was used:

$eventDispatcher->addListener(
    \MyProject\Event\EventIPInRange::class,
    \MyProject\Event\ListenerReportIP::class
);
$eventDispatcher->addListener(
    'custom-event-name',
    function($event) {
        // ...
    }
);

Custom listeners can be added to the core events in the same way, therefore tapping access to the framework's internal check-points.

Routes

Routes are defined in the Config/route.php file. Since Duktig's route model is heavily influenced by the Symfony's route model, it's elements match it quite closely.

Here is an example of a route which takes an URI path parameter called myParam. The parameter gets passed as an argument to the IndexController::exampleAction. In the following example the route gets an ExampleRouteMiddleware assigned to it.

<?php
return [
    'example-route-w-controller' => [
        'path' => '/example/{myParam}',
        'params_requirements' => ['myParam' => '.*'],
        'handler' => \MyProject\Controller\IndexController::class,
        'handler_method' => 'exampleAction',
        'methods' => ['GET'],
        'middlewares' => [\MyProject\Middleware\ExampleRouteMiddleware::class],
    ],
];

Let's take a look at another example with a route that gets an optional trailing slash at the end, and which uses a closure-type handler. This kind of route handler also gets resolved by the container and it's arguments will be resolved and injected:

<?php
return [
    'example-route-with-callable-handler' => [
        'path' => '/example-callable-handler{trailingSlash}',
        'params_requirements' => ['trailingSlash' => '/?'],
        'handler' => function (\Interop\Http\Factory\ResponseFactoryInterface $responseFactory) {
            $response = $responseFactory->createResponse();
            $response->getBody()->write('Response set by a callable route handler');
            return $response;
        },
        'handler_method' => null,
        'methods' => ['GET'],
    ]
];

The full list of route configuration parameters can be found in the ´duktig-core´'s Config/routes.php file.

Tests

This package achieves a high code coverage percentage using the PHPUnit and Mockery. To run its tests at the command line, install the package as a new project with full dev requirements, and within the project directory run the command:

$ vendor/bin/phpunit -c phpunit.xml.dist

This will also generate a coverage report in the coverage directory within the project directory.

These tests cover the project's functional elements, while all the othe packages that are used within it are fully unit tested separately.