vijos/openvj

View on GitHub
src/Core/Application.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
/**
 * This file is part of openvj project.
 *
 * Copyright 2013-2015 openvj dev team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace VJ\Core;

use Elastica\Client;
use FastRoute\Dispatcher;
use FastRoute\RouteCollector;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Pimple\Container;
use RandomLib\Factory;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\Translation\Loader\YamlFileLoader;
use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Yaml\Yaml;
use VJ\Core\Event\GenericEvent;
use VJ\Core\Exception\UserException;
use VJ\Core\ExceptionHandler\JsonResponseHandler;
use VJ\Core\Session\MongoDBSessionHandler;
use Whoops\Handler\PlainTextHandler;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Run;

class Application
{
    use SingletonTrait, ContainerTrait, LoggerTrait, EventTrait, RouteTrait, MongoTrait, TranslationTrait;

    public static $container;

    public static $APP_DIRECTORY;
    public static $CACHE_DIRECTORY;
    public static $CONFIG_DIRECTORY;
    public static $LOGS_DIRECTORY;
    public static $TEMPLATES_DIRECTORY;
    public static $TRANSLATION_DIRECTORY;

    public static $resources = [];

    private function __construct()
    {
        self::$container = new Container();

        self::$APP_DIRECTORY = dirname(dirname(__DIR__)) . '/app';
        self::$CACHE_DIRECTORY = self::$APP_DIRECTORY . '/cache';
        self::$CONFIG_DIRECTORY = self::$APP_DIRECTORY . '/config';
        self::$LOGS_DIRECTORY = self::$APP_DIRECTORY . '/logs';
        self::$TEMPLATES_DIRECTORY = self::$APP_DIRECTORY . '/templates';
        self::$TRANSLATION_DIRECTORY = self::$APP_DIRECTORY . '/translation';

        self::initEvent();
        self::initConfig();
        self::initLogger();
        self::initErrHandlers();
        self::initMongoDB();
        self::initRedis();
        self::initES();
        self::initSession();
        self::initRandomGenerator();
        self::initTranslation();
        self::initHttp();
        self::initRouter();
        self::initTemplating();
        self::initService();
    }

    /**
     * 以 dot notation 路径访问配置
     *
     * @param $path
     * @return mixed|null
     */
    public static function getConfig($path)
    {
        $p = explode('.', $path);
        $context = self::get('config');
        foreach ($p as $piece) {
            if (!is_array($context) || !isset($context[$piece])) {
                return null;
            }
            $context = &$context[$piece];
        }
        return $context;
    }

    private static function initEvent()
    {
        self::set('event', function () {
            return new EventDispatcher();
        });
    }

    private static function loadConfigFile($filename, array &$resources)
    {
        $file = self::$CONFIG_DIRECTORY . '/' . $filename;
        $resources[] = new FileResource($file);
        return Yaml::parse(file_get_contents($file));
    }

    private static function initConfig()
    {
        if (file_exists(self::$CONFIG_DIRECTORY . '/config.yml')) {
            // config file exists
            $cachePath = self::$CACHE_DIRECTORY . '/config.php';
            $cache = new ConfigCache($cachePath, true);
            if (!$cache->isFresh()) {
                $resources = [];
                $data = array_merge(
                    self::loadConfigFile('config.yml', $resources),
                    self::loadConfigFile('db.yml', $resources),
                    self::loadConfigFile('routing.yml', $resources),
                    self::loadConfigFile('service.yml', $resources),
                    self::loadConfigFile('permission.yml', $resources)
                );
                $cache->write('<?php return ' . var_export($data, true) . ';', $resources);
            }
            self::$resources = require($cachePath);
        } else {
            // initialize other things
            $resources = [];
            self::$resources = array_merge(
                ['debug' => false],
                self::loadConfigFile('routing.yml', $resources),
                self::loadConfigFile('service.yml', $resources),
                self::loadConfigFile('permission.yml', $resources)
            );
        }
        if (MODE_TEST) {
            $resources = [];
            if (file_exists(self::$CONFIG_DIRECTORY . '/config-test.yml')) {
                self::$resources = array_merge(
                    self::$resources,
                    self::loadConfigFile('config-test.yml', $resources)
                );
            }
            if (file_exists(self::$CONFIG_DIRECTORY . '/db-test.yml')) {
                self::$resources = array_merge(
                    self::$resources,
                    self::loadConfigFile('db-test.yml', $resources)
                );
            }
        }
        self::set('config', self::$resources);
    }

    private static function initLogger()
    {
        self::set('log', function () {
            $logger = new Logger('VJ');
            if (self::get('config')['debug']) {
                $logger->pushHandler(new RotatingFileHandler(
                    self::$LOGS_DIRECTORY . '/debug' . (MODE_TEST ? '-test' : ''),
                    0, Logger::DEBUG
                ));
            }
            $logger->pushHandler(new RotatingFileHandler(
                self::$LOGS_DIRECTORY . '/info' . (MODE_TEST ? '-test' : ''),
                0, Logger::INFO
            ));
            $logger->pushHandler(new RotatingFileHandler(
                self::$LOGS_DIRECTORY . '/error' . (MODE_TEST ? '-test' : ''),
                0, Logger::ERROR
            ));
            return $logger;
        });
    }

    private static function initErrHandlers()
    {
        $whoops = new Run();
        $whoops->pushHandler(new PrettyPageHandler());
        $whoops->pushHandler(new JsonResponseHandler(self::get('config')['debug']));
        $whoops->pushHandler(new PlainTextHandler());
        $whoops->pushHandler(function (\Exception $exception) {
            if (!$exception instanceof UserException) {
                $level = Logger::ERROR;
                if ($exception instanceof \MongoConnectionException) {
                    $level = Logger::ALERT;
                }
                self::log($level, $exception->getMessage(), ['trace' => $exception->getTrace()]);
            }
        });
        $whoops->register();
    }

    private static function initMongoDB()
    {
        self::set('mongo_client', function () {
            $options = [
                'connect' => true,
                'connectTimeoutMS' => self::get('config')['mongodb']['connectTimeoutMS'],
            ];
            if (isset(self::get('config')['mongodb']['username'])) {
                $options['db'] = self::get('config')['mongodb']['db'];
                $options['username'] = self::get('config')['mongodb']['username'];
                $options['password'] = self::get('config')['mongodb']['password'];
            }
            if (isset(self::get('config')['mongodb']['replicaSet'])) {
                $options['replicaSet'] = self::get('config')['mongodb']['replicaSet'];
            }
            $mongoClient = new \MongoClient(self::get('config')['mongodb']['server'], $options);
            return $mongoClient;
        });

        self::set('mongodb', function () {
            $mongoClient = self::get('mongo_client');
            return $mongoClient->selectDB(self::get('config')['mongodb']['db']);
        });
    }

    private static function initRedis()
    {
        self::set('redis', function () {
            $redis = new \Redis();
            $redis->connect(self::get('config')['redis']['host'], self::get('config')['redis']['port']);
            $redis->select(self::get('config')['redis']['db']);
            return $redis;
        });
    }

    private static function initES()
    {
        self::set('es_client', function () {
            return new Client(self::get('config')['elasticsearch']['hosts']);
        });
        self::set('es', function () {
            return self::get('es_client')->getIndex(self::get('config')['elasticsearch']['index']);
        });
    }

    private static function initSession()
    {
        self::set('session_storage', function () {
            return new NativeSessionStorage([
                'name' => self::get('config')['session']['name'],
                'cookie_httponly' => true,
            ], new MongoDBSessionHandler(Application::coll('Session'), (int)self::get('config')['session']['ttl']));
        });
    }

    private static function initRandomGenerator()
    {
        self::set('random_factory', function () {
            return new Factory();
        });
        self::set('random', function () {
            return self::get('random_factory')->getLowStrengthGenerator();
        });
        self::set('random_secure', function () {
            return self::get('random_factory')->getMediumStrengthGenerator();
        });
    }

    private static function initTranslation()
    {
        self::set('i18n', function () {
            $translator = new Translator(self::get('config')['translation']['default'], new MessageSelector());
            $translator->addLoader('yaml', new YamlFileLoader());
            foreach (self::get('config')['translation']['availables'] as $name) {
                $translator->addResource('yaml', self::$TRANSLATION_DIRECTORY . '/' . $name . '.yml', $name);
            }
            return $translator;
        });
    }

    private static function initHttp()
    {
        self::set('request', function () {
            return Request::createFromGlobals();
        });

        self::set('response', function () {
            return new Response();
        });
    }

    private static function initRouter()
    {
        self::set('dispatcher', function () {
            return \FastRoute\cachedDispatcher(function (RouteCollector $r) {
                $router = self::$resources['routing'];
                foreach ($router as $rule) {
                    list($controller, $action) = explode(':', $rule['controller']);
                    foreach ($rule['methods'] as $method) {
                        $routeData = $rule;
                        $routeData['controller'] = strtolower($controller);
                        $routeData['action'] = $action;
                        $routeData['actionName'] = $action . 'Action';
                        $routeData['className'] = '\\VJ\\Controller\\' . ucfirst(strtolower($controller)) . 'Controller';
                        $r->addRoute($method, $rule['path'], $routeData);
                    }
                }
            }, [
                'cacheFile' => self::$CACHE_DIRECTORY . '/route.cache',
                'cacheDisabled' => self::get('config')['debug'],
            ]);
        });
    }

    private static function initTemplating()
    {
        self::set('templating', function () {
            $loader = new \Twig_Loader_Filesystem(self::$TEMPLATES_DIRECTORY);
            $twig = new \Twig_Environment($loader, [
                'cache' => self::$CACHE_DIRECTORY,
                'debug' => self::get('config')['debug'],
            ]);

            $twig->addGlobal('GET', $_GET);
            $twig->addGlobal('POST', $_POST);

            $twig->addFunction(new \Twig_SimpleFunction('trans', function ($id, ...$argv) {
                return Application::get('i18n')->trans($id, $argv);
            }));

            return $twig;
        });
    }

    private static function initService()
    {
        $services = self::$resources['service'];
        foreach ($services as $service_name => $service_config) {
            self::set($service_name, function () use ($service_config) {
                $argv = [];
                if (isset($service_config['arguments'])) {
                    foreach ($service_config['arguments'] as $a) {
                        if (is_string($a) && substr($a, 0, 1) === '@') {
                            $argv[] = self::get(substr($a, 1));
                        } elseif (is_string($a) && substr($a, 0, 1) === '%') {
                            $argv[] = self::getConfig(substr($a, 1));
                        } else {
                            $argv[] = $a;
                        }
                    }
                }
                return new $service_config['class'](...$argv);
            });

            // register event listeners
            if (isset($service_config['events'])) {
                foreach ($service_config['events'] as $event) {
                    self::on($event, function (GenericEvent $event, $eventName) use ($service_name) {
                        self::get($service_name)->onEvent($event, $eventName, ...$event->getArgv());
                    });
                }
            }
        }
    }

}