Formula9/Framework

View on GitHub
F9/Application/AppFactory.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php namespace F9\Application;

/**
 * F9 (Formula 9) Personal PHP Framework
 *
 * Copyright (c) 2010-2016, Greg Truesdell (<odd.greg@gmail.com>)
 * License: MIT (reference: https://opensource.org/licenses/MIT)
 *
 * Acknowledgements:
 *  - The code provided in this file (and in the Framework in general) may include
 * open sourced software licensed for the purpose, refactored code from related
 * packages, or snippets/methods found on sites throughout the internet.
 *  - All originator copyrights remain in force where applicable, as well as their
 *  licenses where obtainable.
 *
 * @package Nine
 * @version 0.4.2
 * @author  Greg Truesdell <odd.greg@gmail.com>
 */

use F9\Application\Application as NineApplication;
use F9\Contracts\FactoryInterface;
use F9\Exceptions\ConfigurationException;
use F9\Exceptions\FeatureNotImplemented;
use Nine\Collections\Config;
use Nine\Collections\GlobalScope;
use Nine\Collections\Paths;
use Nine\Collections\Scope;
use Nine\Containers\Forge;
use Nine\Events\Events;
use Silex\Application as SilexApplication;
use Silex\ExceptionHandler;
use Symfony\Component\Debug\ErrorHandler;

/**
 * __The `AppFactory` class boots the framework environment and constructs the
 * `F9\Application object.`__
 *
 * The `F9\Application` class inherits from the `Silex\Application` class, which itself
 * inherits from the `Pimple\Container` class. In addition, the framework employs the
 * `F9` container object, which inherits from the `Illuminate\Container` class.
 *
 * `AppFactory` ensures that the dual containers are configured properly for the
 * Application. In future versions, `AppFactory` will also create API Applications
 * as well as CLI applications.
 *
 * See: `F9\Application`, `Silex\Application`, `Forge`, `Pimple\Container`
 */
class AppFactory implements FactoryInterface
{
    /** @var bool */
    private static $booted;

    /** @var array - booted environment */
    private static $env = [
        'environment' => 'PRODUCTION',
        'app_key'     => '[set me]',
        'debugging'   => FALSE,
        'testing'     => FALSE,
    ];

    /** @var AppFactory */
    private static $instance;

    /**
     * Given a context value ('app','api','cli'), the `AppFactory::__construct`
     * method builds an execution environment based on the needs of the
     * Application context.
     *
     * This is a `private` method called by the static `make_*` methods.
     *
     */
    private function __construct()
    {
        //static::$context = $context;
        static::$booted = NULL !== static::$booted ?: FALSE;
        static::$instance = $this;

        $this->install_error_handling();
        $this->detect_environment();
    }

    /**
     * **Retrieve the current Application environment.**
     *
     * Default values:
     *
     *      $env = [
     *          'environment' => 'PRODUCTION',
     *          'app_key'     => '[set me]',
     *          'debugging'   => FALSE,
     *          'testing'     => FALSE,
     *      ]
     *
     * @return array
     */
    public static function getEnvironment() : array
    {
        return self::$env;
    }

    /**
     * **Make a new Application instance.**
     *
     * @param array $paths
     *
     * @return Application
     */
    public static function make(array $paths) : Application
    {
        // The Application is, in effect, a singleton...
        // so if the application already exists then simply return it.
        if ((NULL !== static::$instance) and (NULL !== Forge::getApplication())) {
            return Forge::getApplication();
        }

        // cache AppFactory instance.
        static::$instance ?: new static($paths);

        // block anything else from making a new Application.
        static::$booted = TRUE;

        // make the application
        $application = static::$instance->makeApplication($paths);
        Forge::setApplication($application);

        return $application;
    }

    /**
     * **Make an API Application.** (future)
     *
     * @return Application
     * @throws FeatureNotImplemented
     */
    public static function makeAPI()
    {
        throw new FeatureNotImplemented();
    }

    /**
     * **Detect and configure the environment.**
     *
     * @throws ConfigurationException
     */
    private function detect_environment()
    {
        static::$env = [
            'developing' => env('APP_ENV', 'PRODUCTION') !== 'PRODUCTION',
            'app_key'    => env('APP_KEY', '[set me]'),
            'debugging'  => env('DEBUG', FALSE),
            'testing'    => env('TESTING', FALSE),
        ];

        // register this factory
        Forge::set([static::class, 'AppFactory'], $this);
    }

    /**
     * **Install Error and Exception handlers.**
     */
    private function install_error_handling()
    {
        // register the Symfony error handler
        ErrorHandler::register();
        // activate the internal Silex error handler
        new ExceptionHandler(env('DEBUG'));
    }

    /**
     * @param array $paths
     *
     * @return Application
     */
    private function makeApplication(array $paths) : Application
    {
        // this is the Illuminate Container
        $container = Forge::getInstance();

        // we'll start by loading the configuration into the Forge Container
        $container->add([Scope::class, 'context'], function () { return new Scope; });
        $container->add('environment', function () use ($container) { return $container['GlobalScope']; });
        $container->singleton([GlobalScope::class, 'GlobalScope'], $global_scope = new GlobalScope($this));
        $container->singleton([Paths::class, 'Paths'], new Paths($paths));
        $container->singleton([Config::class, 'Config'], $config = Config::createFromFolder(\CONFIG));
        $container->singleton([Events::class, 'Events'], $events = Events::getInstance());

        $container->add('paths', function () use ($container) { return $container['Paths']; });
        $container->add('config', function () use ($container) { return $container['Config']; });

        // the reason we are here
        $app = new NineApplication($container, $config, $events, $global_scope);

        // align the Nine Events object with the Core EventDispatcher (Symfony)
        Events::setEventDispatcher($app['dispatcher']);

        $app['app.context'] = 'app';

        // register the new Application
        $container->singleton([NineApplication::class, 'Application'], $app);

        // synchronize the Application instance with the forge.
        Forge::setApplication($app);

        // additional $app registrations. @formatter:off
        $app['container']       = $container;
        $app['global.scope']    = $global_scope;
        $app['app.factory']     = $this;
        $app['flashbag']        = $app->factory(function () use ($app) { return $app['session']->getFlashBag(); });
        $app['paths']           = $container['Paths'];
        //@formatter:on

        return $app;
    }

}