X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=framework%2Fbootstrap%2Fclass_FrameworkBootstrap.php;h=fb7d036546afdf63443efd202aa74f3fb5244cc3;hb=52671aae16bf9a48ca95af707b96a5d74fa64b29;hp=6f53a01501e2f74867e64e6d7b05b7464b8ebf0a;hpb=6acaeb0b08448f701ca13f50aec3ee89ba6ab948;p=core.git diff --git a/framework/bootstrap/class_FrameworkBootstrap.php b/framework/bootstrap/class_FrameworkBootstrap.php index 6f53a015..fb7d0365 100644 --- a/framework/bootstrap/class_FrameworkBootstrap.php +++ b/framework/bootstrap/class_FrameworkBootstrap.php @@ -1,16 +1,35 @@ * @version 0.0.0 - * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2016 Core Developer Team + * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2017 Core Developer Team * @license GNU GPL 3.0 or any newer version * @link http://www.ship-simu.org * @@ -28,6 +47,52 @@ use CoreFramework\EntryPoint\ApplicationEntryPoint; * along with this program. If not, see . */ final class FrameworkBootstrap { + + /** + * Detected server address + */ + private static $serverAddress = NULL; + + /** + * Instance of a Requestable class + */ + private static $requestInstance = NULL; + + /** + * Instance of a Responseable class + */ + private static $responseInstance = NULL; + + /** + * Instance of a FrameworkConfiguration class + */ + private static $configurationInstance = NULL; + + /* + * Includes applications may have. They will be tried in the given order, + * some will become soon deprecated. + */ + private static $configAppIncludes = array( + // The ApplicationHelper class (required) + 'class_ApplicationHelper' => 'required', + // Some debugging stuff (optional but can be committed) + 'debug' => 'optional', + // Application's exception handler (optional but can be committed) + 'exceptions' => 'optional', + // Application's configuration file (committed, non-local specific) + 'config' => 'required', + // Local configuration file (optional, not committed, listed in .gitignore) + 'config-local' => 'optional', + // Application data (deprecated) + 'data' => 'deprecated', + // Application loader (deprecated) + 'loader' => 'deprecated', + // Application initializer (deprecated) + 'init' => 'deprecated', + // Application starter (deprecated) + 'starter' => 'deprecated', + ); + /** * Private constructor, no instance is needed from this class as only * static methods exist. @@ -36,6 +101,146 @@ final class FrameworkBootstrap { // Prevent making instances from this "utilities" class } + /** + * Some "getter" for a configuration instance, making sure, it is unique + * + * @return $configurationInstance An instance of a FrameworkConfiguration class + */ + public static function getConfigurationInstance () { + // Is the instance there? + if (is_null(self::$configurationInstance)) { + // Init new instance + self::$configurationInstance = new FrameworkConfiguration(); + } // END - if + + // Return it + return self::$configurationInstance; + } + + /** + * Getter for request instance + * + * @return $requestInstance An instance of a Requestable class + */ + public static function getRequestInstance () { + return self::$requestInstance; + } + + /** + * Getter for response instance + * + * @return $responseInstance An instance of a Responseable class + */ + public static function getResponseInstance () { + return self::$responseInstance; + } + + /** + * "Getter" to get response/request type from analysis of the system. + * + * @return $requestType Analyzed request type + */ + public static function getRequestTypeFromSystem () { + // Default is console + $requestType = 'console'; + + // Is 'HTTP_HOST' set? + if (isset($_SERVER['HTTP_HOST'])) { + // Then it is a HTML response/request. + $requestType = 'html'; + } // END - if + + // Return it + return $requestType; + } + + /** + * Checks whether the given file/path is in open_basedir(). This does not + * gurantee that the file is actually readable and/or writeable. If you need + * such gurantee then please use isReadableFile() instead. + * + * @param $fileInstance An instance of a SplFileInfo class + * @return $isReachable Whether it is within open_basedir() + */ + public static function isReachableFilePath (SplFileInfo $fileInstance) { + // Is not reachable by default + $isReachable = false; + + // Get open_basedir parameter + $openBaseDir = trim(ini_get('open_basedir')); + + // Is it set? + if (!empty($openBaseDir)) { + // Check all entries + foreach (explode(PATH_SEPARATOR, $openBaseDir) as $dir) { + // Check on existence + if (substr($fileInstance->getPathname(), 0, strlen($dir)) == $dir) { + // Is reachable + $isReachable = true; + + // Abort lookup as it has been found in open_basedir + break; + } // END - if + } // END - foreach + } else { + // If open_basedir is not set, all is allowed + $isReachable = true; + } + + // Return status + return $isReachable; + } + + /** + * Checks whether the give file is within open_basedir() (done by + * isReachableFilePath()), is actually a file and is readable. + * + * @param $fileInstance An instance of a SplFileInfo class + * @return $isReadable Whether the file is readable (and therefor exists) + */ + public static function isReadableFile (SplFileInfo $fileInstance) { + // Default is not readable + $isReadable = false; + + // Check if it is a file and readable + $isReadable = ( + ( + self::isReachableFilePath($fileInstance) + ) && ( + $fileInstance->isFile() + ) && ( + $fileInstance->isReadable() + ) + ); + + // Return status + return $isReadable; + } + + /** + * Loads given include file + * + * @param $fileInstance An instance of a SplFileInfo class + * @return void + * @throws InvalidArgumentException If file was not found or not readable or deprecated + */ + public static function loadInclude (SplFileInfo $fileInstance) { + // Trace message + //* NOISY-DEBUG: */ printf('[%s:%d]: fileInstance=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $fileInstance); + + // Should be there ... + if (!self::isReadableFile($fileInstance)) { + // Abort here + throw new InvalidArgumentException(sprintf('Cannot find fileInstance.pathname=%s.', $fileInstance->getPathname())); + } // END - if + + // Load it + require $fileInstance->getPathname(); + + // Trace message + //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__); + } + /** * Does the actual bootstrap * @@ -43,27 +248,432 @@ final class FrameworkBootstrap { */ public static function doBootstrap () { // Load basic include files to continue bootstrapping - require ApplicationEntryPoint::detectFrameworkPath() . 'main/interfaces/class_FrameworkInterface.php'; - require ApplicationEntryPoint::detectFrameworkPath() . 'main/interfaces/registry/class_Registerable.php'; - require ApplicationEntryPoint::detectFrameworkPath() . 'config/class_FrameworkConfiguration.php'; + self::loadInclude(new SplFileInfo(sprintf('%smain%sinterfaces%sclass_FrameworkInterface.php', ApplicationEntryPoint::detectFrameworkPath(), DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR))); + self::loadInclude(new SplFileInfo(sprintf('%smain%sclasses%sclass_BaseFrameworkSystem.php', ApplicationEntryPoint::detectFrameworkPath(), DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR))); + self::loadInclude(new SplFileInfo(sprintf('%smain%sinterfaces%sregistry%sclass_Registerable.php', ApplicationEntryPoint::detectFrameworkPath(), DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR))); + self::loadInclude(new SplFileInfo(sprintf('%sconfig%sclass_FrameworkConfiguration.php', ApplicationEntryPoint::detectFrameworkPath(), DIRECTORY_SEPARATOR))); + + // Load global configuration + self::loadInclude(new SplFileInfo(sprintf('%s%s', ApplicationEntryPoint::detectFrameworkPath(), 'config-global.php'))); + } + + /** + * Initializes the framework by scanning for all framework-relevant + * classes, interfaces and exception. Then determine the request type and + * initialize a Requestable instance which will then contain all request + * parameter, also from CLI. Next step is to validate the application + * (very basic). + * + * @return void + */ + public static function initFramework () { + /** + * 1) Load class loader and scan framework classes, interfaces and + * exceptions. + */ + self::scanFrameworkClasses(); + + /* + * 2) Determine the request type, console or web and store request and + * response here. This also initializes the request instance will + * all given parameters (see doc-tag for possible sources of + * parameters). + */ + self::determineRequestType(); + + /* + * 3) Now, that there are all request parameters being available, check + * if 'app' is supplied. If it is not found, abort execution, if + * found, continue below with next step. + */ + self::validateApplicationParameter(); + } + + /** + * Initializes the detected application. This may fail if required files + * are not found in the application's base path (not to be confused with + * 'application_base_path' which only points to /some/foo/application/. + * + * @return void + */ + public static function prepareApplication () { + // Configuration entry 'detected_app_name' must be set, get it here, including full path + $application = self::getConfigurationInstance()->getConfigEntry('detected_app_name'); + $fullPath = self::getConfigurationInstance()->getConfigEntry('detected_full_app_path'); + + /* + * Now check and load all files, found deprecated files will throw a + * warning at the user. + */ + foreach (self::$configAppIncludes as $fileName => $status) { + // Construct file instance + $fileInstance = new SplFileInfo(sprintf('%s%s.php', $fullPath, $fileName)); + + // Determine if this file is wanted/readable/deprecated + if (($status == 'required') && (!self::isReadableFile($fileInstance))) { + // Nope, required file cannot be found/read from + ApplicationEntryPoint::exitApplication(sprintf('Application "%s" does not have required file "%s.php". Please add it.', $application, $fileInstance->getBasename())); + } elseif (($fileInstance->isFile()) && (!$fileInstance->isReadable())) { + // Found, not readable file + ApplicationEntryPoint::exitApplication(sprintf('File "%s.php" from application "%s" cannot be read. Please fix CHMOD.', $fileInstance->getBasename(), $application)); + } elseif (($status != 'required') && (!self::isReadableFile($fileInstance))) { + // Not found but optional/deprecated file, skip it + continue; + } + + // Is the file deprecated? + if ($status == 'deprecated') { + // Issue warning + trigger_error(sprintf('Deprecated file "%s.php" found, will not load it to avoid problems. Please remove it from your application "%s" to avoid this warning.', $fileName, $application), E_USER_WARNING); + + // Skip loading deprecated file + continue; + } // END - if + + // Load it + self::loadInclude($fileInstance); + } // END - foreach + + // Scan for application's classes, exceptions and interfaces + ClassLoader::scanApplicationClasses(); + } + + /** + * Starts a fully initialized application, the class ApplicationHelper must + * be loaded at this point. + * + * @return void + */ + public static function startApplication () { + // Configuration entry 'detected_app_name' must be set, get it here + $application = self::getConfigurationInstance()->getConfigEntry('detected_app_name'); + + // Is there an application helper instance? + $applicationInstance = call_user_func_array( + array( + 'Org\Mxchange\CoreFramework\Helper\Application\ApplicationHelper', 'getSelfInstance' + ), array() + ); - // Load main configuration - require ApplicationEntryPoint::detectFrameworkPath() . 'config.inc.php'; + // Some sanity checks + if ((empty($applicationInstance)) || (is_null($applicationInstance))) { + // Something went wrong! + ApplicationEntryPoint::exitApplication(sprintf('[Main:] The application %s could not be launched because the helper class %s is not loaded.', + $application, + 'Org\Mxchange\CoreFramework\Helper\Application\ApplicationHelper' + )); + } elseif (!is_object($applicationInstance)) { + // No object! + ApplicationEntryPoint::exitApplication(sprintf('[Main:] The application %s could not be launched because 'app' is not an object (%s).', + $application, + gettype($applicationInstance) + )); + } elseif (!($applicationInstance instanceof ManageableApplication)) { + // Missing interface + ApplicationEntryPoint::exitApplication(sprintf('[Main:] The application %s could not be launched because 'app' is lacking required interface ManageableApplication.', + $application + )); + } + + // Set it in registry + Registry::getRegistry()->addInstance('app', $applicationInstance); + + // Now call all methods in one go + foreach (array('setupApplicationData', 'initApplication', 'launchApplication') as $methodName) { + // Debug message + //* NOISY-DEBUG: */ printf('[%s:%d]: Calling methodName=%s ...' . PHP_EOL, __METHOD__, __LINE__, $methodName); + + // Call method + call_user_func(array($applicationInstance, $methodName)); + } // END - foreach + } + + /** + * Initializes database instance, no need to double-call this method + * + * @return void + */ + public static function initDatabaseInstance () { + // Get application instance + $applicationInstance = ApplicationHelper::getSelfInstance(); + + // Is the database instance already set? + if ($applicationInstance instanceof DatabaseConnector) { + // Yes, then abort here + throw new BadMethodCallException('Method called twice.'); + } // END - if + + // Initialize database layer + $databaseInstance = ObjectFactory::createObjectByConfiguredName(self::getConfigurationInstance()->getConfigEntry('database_type') . '_class'); + + // Prepare database instance + $connectionInstance = DatabaseConnection::createDatabaseConnection(DebugMiddleware::getSelfInstance(), $databaseInstance); + + // Set it in application helper + $applicationInstance->setDatabaseInstance($connectionInstance); + } + + /** + * Detects the server address (SERVER_ADDR) and set it in configuration + * + * @return $serverAddress The detected server address + * @throws UnknownHostnameException If SERVER_NAME cannot be resolved to an IP address + * @todo Have to check some more entries from $_SERVER here + */ + public static function detectServerAddress () { + // Is the entry set? + if (!isset(self::$serverAddress)) { + // Is it set in $_SERVER? + if (!empty($_SERVER['SERVER_ADDR'])) { + // Set it from $_SERVER + self::$serverAddress = $_SERVER['SERVER_ADDR']; + } elseif (isset($_SERVER['SERVER_NAME'])) { + // Resolve IP address + $serverIp = ConsoleTools::resolveIpAddress($_SERVER['SERVER_NAME']); + + // Is it valid? + if ($serverIp === false) { + /* + * Why is gethostbyname() returning the host name and not + * false as many other PHP functions are doing? ;-( + */ + throw new UnknownHostnameException(sprintf('Cannot resolve "%s" to an IP address. Please fix your setup.', $_SERVER['SERVER_NAME'])); + } // END - if + + // Al fine, set it + self::$serverAddress = $serverIp; + } else { + // Run auto-detecting through console tools lib + self::$serverAddress = ConsoleTools::acquireSelfIpAddress(); + } + } // END - if + + // Return it + return self::$serverAddress; + } + + /** + * Setter for default time zone (must be correct!) + * + * @param $timezone The timezone string (e.g. Europe/Berlin) + * @return $success If timezone was accepted + * @throws NullPointerException If $timezone is NULL + * @throws InvalidArgumentException If $timezone is empty + */ + public static function setDefaultTimezone ($timezone) { + // Is it null? + if (is_null($timezone)) { + // Throw NPE + throw new NullPointerException(NULL, BaseFrameworkSystem::EXCEPTION_IS_NULL_POINTER); + } elseif (!is_string($timezone)) { + // Is not a string + throw new InvalidArgumentException(sprintf('timezone[]=%s is not a string', gettype($timezone))); + } elseif ((is_string($timezone)) && (empty($timezone))) { + // Entry is empty + throw new InvalidArgumentException('timezone is empty'); + } + + // Default success + $success = FALSE; + + /* + * Set desired time zone to prevent date() and related functions to + * issue an E_WARNING. + */ + $success = date_default_timezone_set($timezone); + + // Return status + return $success; + } + + /** + * Checks whether HTTPS is set in $_SERVER + * + * @return $isset Whether HTTPS is set + * @todo Test more fields + */ + public static function isHttpSecured () { + return (isset($_SERVER['HTTPS'])); + } + + /** + * Dectect and return the base URL for all URLs and forms + * + * @return $baseUrl Detected base URL + */ + public static function detectBaseUrl () { + // Initialize the URL + $protocol = 'http'; + + // Do we have HTTPS? + if (self::isHttpSecured()) { + // Add the >s< for HTTPS + $protocol = 's'; + } // END - if + + // Construct the full URL and secure it against CSRF attacks + $baseUrl = sprintf('%s://%s%s', $protocol, self::detectDomain(), self::detectScriptPath()); + + // Return the URL + return $baseUrl; + } + + /** + * Detect safely and return the full domain where this script is installed + * + * @return $fullDomain The detected full domain + */ + public static function detectDomain () { + // Full domain is localnet.invalid by default + $fullDomain = 'localnet.invalid'; + + // Is the server name there? + if (isset($_SERVER['SERVER_NAME'])) { + // Detect the full domain + $fullDomain = htmlentities(strip_tags($_SERVER['SERVER_NAME']), ENT_QUOTES); + } // END - if + + // Return it + return $fullDomain; + } + + /** + * Detect safely the script path without trailing slash which is the glue + * between "http://your-domain.invalid/" and "script-name.php" + * + * @return $scriptPath The script path extracted from $_SERVER['SCRIPT_NAME'] + */ + public static function detectScriptPath () { + // Default is empty + $scriptPath = ''; + + // Is the scriptname set? + if (isset($_SERVER['SCRIPT_NAME'])) { + // Get dirname from it and replace back-slashes with slashes for lame OSes... + $scriptPath = str_replace("\\", '/', dirname($_SERVER['SCRIPT_NAME'])); + } // END - if + + // Return it + return $scriptPath; + } + + /** + * 1) Loads class scanner and scans all framework's classes and interfaces. + * This method also registers the class loader's method autoLoad() for the + * SPL auto-load feature. Remember that you can register additional methods + * (not functions, please) for other libraries. + * + * Yes, I know about Composer, but I like to keep my class loader around. + * You can always use mine as long as your classes have a namespace + * according naming-convention: Vendor\Project\Group[\SubGroup] + * + * @return void + */ + private static function scanFrameworkClasses () { + // Include the class loader function + require self::getConfigurationInstance()->getConfigEntry('framework_base_path') . 'loader/class_ClassLoader.php'; + + // Register auto-load function with the SPL + spl_autoload_register('Org\Mxchange\CoreFramework\Loader\ClassLoader::autoLoad'); + + // Scan for all framework classes, exceptions and interfaces + ClassLoader::scanFrameworkClasses(); + } + + /** + * 2) Determines request/response type and stores the created + * request/response instances in this object for later usage. + * + * @return void + */ + private static function determineRequestType () { + // Determine request type + $request = self::getRequestTypeFromSystem(); + $requestType = self::getRequestTypeFromSystem(); + + // Create a new request object + $requestInstance = ObjectFactory::createObjectByName(sprintf('Org\Mxchange\CoreFramework\Request\%sRequest', BaseFrameworkSystem::convertToClassName($request))); + + // Remember request instance here + self::setRequestInstance($requestInstance); + + // Do we have another response? + if ($requestInstance->isRequestElementSet('request')) { + // Then use it + $request = strtolower($requestInstance->getRequestElement('request')); + $requestType = $request; + } // END - if + + // ... and a new response object + $responseClass = sprintf('Org\Mxchange\CoreFramework\Response\%sResponse', BaseFrameworkSystem::convertToClassName($request)); + $responseInstance = ObjectFactory::createObjectByName($responseClass); + + // Remember response instance here + self::setResponseInstance($responseInstance); } /** - * Checks all parameters, like $_GET, $_POST, $_COOKIE and also "emulated" - * from command-line. Accepted CLI parameters are in following forms: + * 3) Validate parameter 'app' if it is set and the application is there. * - * --foo=bar - parameter 'foo' gets value "bar" (string) - * --enable-foo=true - parameter 'enableFoo' gets value true (boolean) - * --baz=123 - parameter 'baz' get value 123 (interger) + * @return void + */ + private static function validateApplicationParameter () { + // Is the parameter set? + if (!self::getRequestInstance()->isRequestElementSet('app')) { + /* + * Don't continue here, the application 'selector' is no longer + * supported and only existed as an idea to select the proper + * application (by user). + */ + ApplicationEntryPoint::exitApplication('No application specified. Please provide a parameter "app" and retry.'); + } // END - if + + // Get it for local usage + $application = self::getRequestInstance()->getRequestElement('app'); + + // Secure it, by keeping out tags + $application = htmlentities(strip_tags($application), ENT_QUOTES); + + // Secure it a little more with a reg.exp. + $application = preg_replace('/([^a-z0-9_-])+/i', '', $application); + + // Construct FQPN (Full-Qualified Path Name) for ApplicationHelper class + $applicationPath = sprintf( + '%s%s%s', + self::getConfigurationInstance()->getConfigEntry('application_base_path'), + $application, + DIRECTORY_SEPARATOR + ); + + // Full path for application + // Is the path there? This secures a bit the parameter (from untrusted source). + if ((!is_dir($applicationPath)) || (!is_readable($applicationPath))) { + // Not found or not readable + ApplicationEntryPoint::exitApplication(sprintf('Application "%s" not found.', $application)); + } // END - if + + // Set the detected application's name and full path for later usage + self::getConfigurationInstance()->setConfigEntry('detected_full_app_path', $applicationPath); + self::getConfigurationInstance()->setConfigEntry('detected_app_name' , $application); + } + /** + * Setter for request instance * - * The same also works without 2 dashes as it was possible before: + * @param $requestInstance An instance of a Requestable class + * @return void + */ + private static function setRequestInstance (Requestable $requestInstance) { + self::$requestInstance = $requestInstance; + } + + /** + * Setter for response instance * - * app=tests - launches application 'tests' (parameter 'app' gets string value "tests") + * @param $responseInstance An instance of a Responseable class + * @return void */ - public static function checkParameters () { + private static function setResponseInstance (Responseable $responseInstance) { + self::$responseInstance = $responseInstance; } }