FF/readme.md
2019-06-21 10:51:21 +02:00

8.9 KiB

Fast Forward

by Marco Stoll


Introduction - What is Fast Forward?

Fast Forward (in short FF) is a generic application template for building web and/or command line applications fast and easy. It addresses the common tasks and provides configurable and/or extendable default implementations for you to use.

Currently FF is composed of the following features:

  1. Services and the Service Factory
  2. Events and the Event Broker

More features will follow (see the Road Map section below).

A Warning before you start

FF is highly opinionated and depends on a bunch of conventions! So be sure to consult the documentation before deciding to develop your application based on FF.
But if you do FF ensures a minimal amount of setup, letting you concentrate your efforts on your business logic instead of some framework configuration.

Dependencies

  • Fast Forward Family

    FF makes heavy usage of the Fast Forward Family components, a collection of independent components providing generic implementations (like data structures of design patterns) used by many of FF's features.

Installation

via Composer

manual Installation

Bootstrapping

Bootstrap a web application

Bootstrap a command line application

Services and the Service Factory

Services - a definition

From the FF perspective a service is a singular component (mostly class) that provide needed functionality as part of certain domain. Common attributes of services should be

  • Make minimal assumptions of the surrounding runtime environment.
  • Be stateless.
  • Be unit testable.

Writing services

[CONVENTION] Service classes extend FF\Services\AbstractService.
[CONVENTION] Services must be located in your project's MyProject\Services namespace (or any sub namespace thereof) to be found by the ServicesFactory.

Example: Basic service implementation

namespace MyProject\Services;

use FF\Services\AbstractService;

class MyService extends AbstractService
{

}

FF services can be configured if needed. The ServicesFactory will initialize a service's instance with available config options. But it will be your responsibility to validate that any given options will meet your service's requirements.

Example: Configurable service implementation

namespace MyProject\Services;

use FF\Services\AbstractService;

class MyConfigurableService extends AbstractService
{
    /**
     * {@inheritDoc}
     */
    protected function validateOptions(array $options, array &$errors): bool
    {
        // place you option validation logic here
        // example
        if (!isset($options['some-option'])) {
            $errors[] = 'missing options [some-options]';
            return false;
        }
        
        return true;
    }
}    

Using the Service Factory

FF's service factory is based on the AbstractSingletonFactory of the FF-Factories component. Be sure to consult component's documentation for further information about using FF factories.

To retrieve a service from the factory you have to know thw service's class identifier. These class identifiers are composed of the service's class name prefixed by any sub namespace relative to FF\Services (for built-in services) or MyProject\Services (for you custom services).

For convenience reasons there is a shorthand class SF that lets you retrieve one or more services with minimal effort.

Example: Getting a single service

use FF\Factories\SF;
use MyProject\Services\MyService;

/** @var MyService $myService */
$myService = SF::i()->get('MyService'); // finds MyProject\Services\MyService

Example: Getting multiple services at once

use FF\Factories\SF;
    use FF\Services\Events\EventBroker;
use MyProject\Services\MyService;

/** @var EventBroker $eventBroker */
/** @var MyService $myService */
list ($eventBroker, $myService) = SF::i()->get('Events\EventBroker', 'MyService');

Extending built-in FF Services

The ServicesFactory uses a FF\Factories\NamespaceClassLocator locator to find services definitions bases on a class identifier. To archive this, it searches the list of registered base namespace for any suitable service class in the given order.

This feature lets you sub class and replace FF's built-in service implementations easily by just following the naming conventions and registering the ServiceFactory as shown in the Bootstrapping section of this document.

Example: Extend/Replace a built-in service

namespace MyProject\Services\Runtime;

use FF\Services\Runtime\ExceptionHandler as FFExceptionHandler;

class ExceptionHandler extends FFExceptionHandler
{
    // place your custom logic here
}

###############

// when ever some component refers to the 'Runtime\ExceptionHandler' service via the ServicesFactory
// an instance of your service extension will be used instead of the built-in service

/** @var MyProject\Services\Runtime\ExceptionHandler $exceptionHandler */
$exceptionHandler = SF::get('Runtime\ExceptionHandler');

Events and the Event Broker

This feature provides a basic observer/observable pattern implementation. The key class is the EventBroker. Any other class may act as an observable by firing events using the EventBroker's api. Other classes my act as observers by subscribing to specific types of events and being notified by the EventBroker in each time this type of event was fired.

Firing Events

If an observable wants to notify potential observers of notable changes it simply fires a suitable event using the EventBroker's api.

Example: Fire an event

use FF\Factories\SF;
use FF\Services\Events\EventBroker;

class MyExceptionHandler
{
    /**
     * Generic exception handler callback
     *
     * @param \Throwable $e
     * @see http://php.net/set_exception_handler
     */
    public function onException(\Throwable $e)
    {
        try {
            /** @var EventBroker $eventBroker */
            $eventBroker = SF::i()->get('Events\EventBroker');
            $eventBroker->fire('Runtime\Exception', $e);
        } catch (\Exception $e) {
            // do not handle exceptions thrown while
            // processing the on-exception event
        }
    }
}

The fire method of the EventBroker uses the EventsFactory to instantiate the actual event object passing any additional argument provided to the event class's constructor.

Subscribing to Events

A valid event handling method must meet the following requirements:

  • must be public
  • must not be static or abstract
  • accept exactly one argument: the event classes instance

Example: Subscribe to an event

use FF\Events;
use FF\Factories\SF;
use FF\Services\Events\EventBroker;    

class MyErrorObserver
{
    /**
     * Event handling callback
     *
     * @param Runtime\Error $event
    public function onRuntimeError(Runtime\Error $event)
    {
        // handle the Error event
        var_dump(
            $event->getErrNo(), 
            $event->getErrMsg(), 
            $event->getErrFile(),
            $event->getErrLine()
        );
    }
}

// subscribe to the Runtime\Error event
/** @var EventBroker $eventBroker */
$eventBroker = SF::i()->get('Events\EventBroker');
$eventBroker->subscribe([new MyErrorObserver, 'onRuntimeError'], 'Runtime\Error');

The subscription is bases on the class identifier of the event class. This is exactly the same string to use by the observable when firing the event.

Defining Custom Events

[CONVENTION] Event classes extend FF\Events\AbstractEvent.
[CONVENTION] Events must be located in your project's MyProject\Events namespace (or any sub namespace thereof) to be found by the EventsFactory.

Example: A custom event

namespace MyProject\Events;

use FF\Events\AbstractEvent;

/**
 * This event's class identifier would just be 'Logoff'
 */
class Logoff extends AbstractEvent
{
    /**
     * Define constructor arguments (the event data) to meet your needs.
     */
    public function __construct($eventData)
    {
        var_dump($eventData);
    }
}

Road map

  • Runtime
  • Controllers
  • Sessions
  • Security
  • CLI
  • ORM
  • Bootstrapping