diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..2f13506 --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "damnstupidsimple/core", + "description": "The core package for Damn Stupid Simple framework", + "keywords": ["micro framework","lazy","damn stupid simple"], + "license": "MIT", + "type": "project", + "authors": [{ + "name": "Studio Nexus", + "email": "fariz@studionexus.co", + "homepage": "https://www.studionexus.co", + "role": "Developer" + }], + "config": { + "vendor-dir": "vendor" + }, + "require": { + "php": ">=5.4.0" + }, + "autoload": { + "psr-4": { + "Core\\": "src" + } + } +} \ No newline at end of file diff --git a/src/App.php b/src/App.php new file mode 100644 index 0000000..40f0740 --- /dev/null +++ b/src/App.php @@ -0,0 +1,53 @@ + + * @copyright 2016 Studio Nexus + * @license MIT + * @version Release: 0.1.0 + * @link https://www.studionexus.co/php/damnstupidsimple + */ +namespace Core; + +/** + * The Application Container + * ----------------------------------------------------------------------- + * + * Containers are used for dependency injection, which allows us to reduce + * coupling. It is a rather simple piece of code, but it is powerful. + * + */ +class App { + + /** + * Link a variable or an object to the container + * + * @param mixed $var_name variable name that we want to register + * @param mixed $val the value/array/object + */ + function link($var_name, $val){ + $this->$var_name = $val; + } +} \ No newline at end of file diff --git a/src/Cache.php b/src/Cache.php new file mode 100644 index 0000000..139554f --- /dev/null +++ b/src/Cache.php @@ -0,0 +1,101 @@ + + * @copyright 2016 Studio Nexus + * @license MIT + * @version Release: 0.1.0 + * @link https://www.studionexus.co/php/damnstupidsimple + */ +namespace Core; +use phpFastCache\CacheManager as CacheManager; + +/** + * The Cache Facade + * ----------------------------------------------------------------------- + * + * The Cache Facade configures the Cache Manager and provides access to the + * Cache Manager instance + * + */ +class Cache { + + /** + * The instance of phpFastCache\CacheManager + * @var object + * @access private + * @static + */ + static private $CacheManager = null; + + /** + * The configurations + * @var array + * @access private + * @static + */ + static private $config = null; + + /** + * Create and return the instance of phpFastCache\CacheManager + * + * @return object the instance of the Cache Manager if cache enabled + * in the configuration file (config/cache.php), null + * if disabled + * + * @static + * @see Cache::clean() + * @access public + * @since Method available since Release 0.1.0 + */ + static function init(){ + + if(self::$config === null){ + self::$config = require_once(DSS_PATH.'config/cache.php'); + } + + if(self::$config['enabled'] === true){ + CacheManager::setup(self::$config['settings']); + if(self::$CacheManager === null){ + self::$CacheManager = CacheManager::getInstance(); + } + return self::$CacheManager; + }else{ + return null; + } + } + + /** + * Clear all cached data + * + * @static + * @access public + * @since Method available since Release 0.1.0 + */ + static function clean(){ + CacheManager::clean(); + } + +} diff --git a/src/Database.php b/src/Database.php new file mode 100644 index 0000000..d71a0a2 --- /dev/null +++ b/src/Database.php @@ -0,0 +1,75 @@ + + * @copyright 2016 Studio Nexus + * @license MIT + * @version Release: 0.1.0 + * @link https://www.studionexus.co/php/damnstupidsimple + */ +namespace Core; +use Illuminate\Database\Capsule\Manager as Capsule; + +/** + * The Database Facade + * ----------------------------------------------------------------------- + * + * The Cache Facade configures the Cache Manager and provides access to the + * Cache Manager instance + * + */ +class Database extends Capsule{ + static private $config = null; + static private $capsule; + + /** + * Create and return the instance of Illuminate\Database\Capsule\Manager + * + * @return object the instance of the Capsule if database enabled + * in the configuration file (config/database.php), null + * if disabled + * + * @static + * @access public + * @since Method available since Release 0.1.0 + */ + static function connect(){ + + if(self::$config === null){ + self::$config = include(DSS_PATH.'config/database.php'); + } + + if(self::$config['enabled'] === true){ + $capsule = new Capsule; + $capsule->addConnection(self::$config['settings']); + $capsule->bootEloquent(); + $capsule->setAsGlobal(); + self::$capsule = $capsule; + return self::$capsule; + }else{ + return null; + } + } +} \ No newline at end of file diff --git a/src/Debugger.php b/src/Debugger.php new file mode 100644 index 0000000..ef49db6 --- /dev/null +++ b/src/Debugger.php @@ -0,0 +1,204 @@ + + * @copyright 2016 Studio Nexus + * @license MIT + * @version Release: 0.1.0 + * @link https://www.studionexus.co/php/damnstupidsimple + */ +namespace Core; + +/** + * The Debugger + * ----------------------------------------------------------------------- + * + * Provides the developer with useful messages in case of an exception or + * errors happen. + * + */ +class Debugger { + /** + * Registering the debugger to log exceptions locally or transfer them to + * external services + * + * Depends on the settings in config/env.php: + * + * + 0: Shows "Something went wrong" message ambiguously (handled locally) + * + * + 1: Shows simple error message, file and the line occured (handled + * locally) + * + * + 2: Shows advanced debugging with code snippet, stack frames, and + * envionment details, handled by Flip\Whoops + * + * @static + * @access public + * @since Method available since Release 0.1.0 + */ + static function start(){ + if(getenv('DEBUG') === '0' || getenv('DEBUG') === '1'){ + register_shutdown_function('Core\Debugger::error_handler'); + }else if(getenv('DEBUG') === '2'){ + $whoops = new \Whoops\Run; + $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler); + $whoops->register(); + }else if(getenv('DEBUG') === '-1'){ + + } + } + + /** + * Sets the header of the HTTP request and then display the + * HTTP error codes. + * + * @param string $code The HTTP error code + * @param bool $terminate Terminate the entire script execution + * + * @static + * @see Debugger::set_header(), Debugger::display() + * @access public + * @since Method available since Release 0.1.0 + */ + static function report($code, $terminate = false){ + switch ($code) { + case '404': + self::set_header('404', 'Internal Server Error'); + self::display('simple', '404 Not Found'); + break; + case '500': + self::set_header('500', 'Internal Server Error'); + self::display('simple', 'Something went wrong'); + break; + default: + self::set_header('500', 'Internal Server Error'); + self::display('simple'); + break; + } + + if($terminate){ + die(); + } + } + + /** + * Sets the header of the HTTP request + * + * @static + * @access public + * @since Method available since Release 0.1.0 + */ + static function set_header($code, $error){ + header($_SERVER['SERVER_PROTOCOL']. ''. $code . '' . $error); + } + + /** + * The error handler which is called by register_shutdown_function() + * in event of exceptions, syntax errors, warning and notices. + * + * @static + * @see Debugger::start(), Debugger::display() + * @access public + * @since Method available since Release 0.1.0 + */ + static function error_handler(){ + $error = error_get_last(); + $message = $error['message']; + if($error){ + if(getenv('DEBUG') == 0){ + self::display('simple', 'Something went wrong'); + }else{ + self::display('full', $error); + } + } + } + + /** + * Display error messages + * + * @param string $name error page name + * @param string @message error messages + * + * @static + * @access public + * @since Method available since Release 0.1.0 + */ + static function display($name, $message = ''){ + self::set_header('500', 'Internal Server Error'); + include('errorpage/'. $name .'.php'); + } + + /** + * Reads the configuration file (config/env.php) and and include each of the + * variables (retrieved in a form of associative array) to the Environment + * Variable. + * + * @static + * @access public + * @since Method available since Release 0.1.0 + */ + static function init_env(){ + $var=require_once(DSS_PATH.'config/env.php'); + foreach($var as $v => $a){ + putenv($v.'='.$a); + } + } + + /** + * Calculate a precise time difference. + * + * @param string $start result of microtime() + * @param string $end result of microtime(); if NULL/FALSE/0/'' then it's now + * + * @return flat difference in seconds, calculated with minimum precision loss + * + * @static + * @see Debugger::exec_time() + * @access public + * @since Method available since Release 0.1.0 + */ + static function microtime_diff($start) + { + $duration = microtime(true) - $start; + $hours = (int)($duration/60/60); + $minutes = (int)($duration/60)-$hours*60; + $seconds = $duration-$hours*60*60-$minutes*60; + return number_format((float)$seconds, 5, '.', ''); + } + + /** + * Display execution time (start time - finish time) in human readable form + * (milliseconds). + * + * + * @static + * @see Debugger::microtime_diff() + * @access public + * @since Method available since Release 0.1.0 + */ + static function exec_time(){ + echo ('Request takes '.(self::microtime_diff(DSS_START) * 1000 ) . ' milliseconds'); + } +} \ No newline at end of file diff --git a/src/Router.php b/src/Router.php new file mode 100644 index 0000000..3d3849e --- /dev/null +++ b/src/Router.php @@ -0,0 +1,179 @@ + '[^/]+', + ':num' => '[0-9]+', + ':all' => '.*' + ); + public static $error_callback; + /** + * Defines a route w/ callback and method + */ + public static function __callstatic($method, $params) { + $uri = dirname($_SERVER['PHP_SELF']).'/'.$params[0]; + $callback = $params[1]; + array_push(self::$routes, $uri); + array_push(self::$methods, strtoupper($method)); + array_push(self::$callbacks, $callback); + } + /** + * Defines callback if route is not found + */ + public static function error($callback) { + self::$error_callback = $callback; + } + public static function haltOnMatch($flag = true) { + self::$halts = $flag; + } + /** + * Runs the callback for the given request + */ + public static function dispatch(){ + + $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); + $method = $_SERVER['REQUEST_METHOD']; + $searches = array_keys(static::$patterns); + $replaces = array_values(static::$patterns); + $found_route = false; + + self::$routes = str_replace('//', '/', self::$routes); + // Check if route is defined without regex + if (in_array($uri, self::$routes)) { + $route_pos = array_keys(self::$routes, $uri); + foreach ($route_pos as $route) { + // Using an ANY option to match both GET and POST requests + if (self::$methods[$route] === $method || self::$methods[$route] === 'ANY') { + $found_route = true; + // If route is not an object + if (!is_object(self::$callbacks[$route])) { + // Grab all parts based on a / separator + $parts = explode('/',self::$callbacks[$route]); + // Collect the last index of the array + $last = end($parts); + // Grab the controller name and method call + $segments = explode('@',$last); + // Instanitate controller + $controller = new $segments[0](); + // Call method + $controller->{$segments[1]}(); + if (self::$halts) return; + } else { + // Call closure + call_user_func(self::$callbacks[$route]); + if (self::$halts) return; + } + } + } + } else { + // Check if defined with regex + $pos = 0; + foreach (self::$routes as $route) { + if (strpos($route, ':') !== false) { + $route = str_replace($searches, $replaces, $route); + } + if (preg_match('#^' . $route . '$#', $uri, $matched)) { + if (self::$methods[$pos] === $method || self::$methods[$pos] === 'ANY') { + $found_route = true; + // Remove $matched[0] as [1] is the first parameter. + array_shift($matched); + if (!is_object(self::$callbacks[$pos])) { + // Grab all parts based on a / separator + $parts = explode('/',self::$callbacks[$pos]); + // Collect the last index of the array + $last = end($parts); + // Grab the controller name and method call + $segments = explode('@',$last); + // Instanitate controller + $controller = new $segments[0](); + // Fix multi parameters + if(!method_exists($controller, $segments[1])) { + //"controller and action not found" + Debugger::report(500); + } else { + call_user_func_array(array($controller, $segments[1]), $matched); + } + if (self::$halts) return; + } else { + call_user_func_array(self::$callbacks[$pos], $matched); + if (self::$halts) return; + } + } + } + $pos++; + } + } + // Run the error callback if the route was not found + if ($found_route === false) { + if (!self::$error_callback) { + self::$error_callback = function() { + Debugger::report(404); + }; + } else { + if (is_string(self::$error_callback)) { + self::get($_SERVER['REQUEST_URI'], self::$error_callback); + self::$error_callback = null; + self::dispatch(); + return ; + } + } + call_user_func(self::$error_callback); + } + } + + static function redirect($url, $permanent = false){ + if (headers_sent() === false){ + header('Location: ' . $url, true, ($permanent === true) ? 301 : 302); + } + exit(); + } +} \ No newline at end of file diff --git a/src/Sharer.php b/src/Sharer.php new file mode 100644 index 0000000..38e0c40 --- /dev/null +++ b/src/Sharer.php @@ -0,0 +1,84 @@ + + * @copyright 2016 Studio Nexus + * @license MIT + * @version Release: 0.1.0 + * @link https://www.studionexus.co/php/damnstupidsimple + */ +namespace Core; + +/** + * The Sharer + * ----------------------------------------------------------------------- + * + * Sharer are also used for dependency injection (see Core\App). Sharer + * is used to import variables directly into the current symbol table from + * the array. It is also used for dependency injection to the template + * files. + * + * Simply use extract(Core\Sharer::get()); to import the variables. + * + * @see Core\Sharer::share(), Core\Sharer::get(), Core\App::link() + */ +class Sharer { + /** + * The array of objects / variables. The array key is the variable name, + * while the array values are references to objects / variables. + * + * @var object + * @access private + * @static + */ + static public $store = null; + + /** + * Stores references to variable or object to the static $store + * + * @param string $key the variable name + * @param mixed &$value reference to variable / object + * + * @static + * @access public + * @since Method available since Release 0.1.0 + */ + static function share($key, &$value){ + self::$store[$key] = &$value; + } + + /** + * Get data stored into $store + * + * @return array $store the array of data + * + * @static + * @access public + * @since Method available since Release 0.1.0 + */ + static function get(){ + return self::$store; + } +} \ No newline at end of file diff --git a/src/Viewer.php b/src/Viewer.php new file mode 100644 index 0000000..e31aaf8 --- /dev/null +++ b/src/Viewer.php @@ -0,0 +1,102 @@ + + * @copyright 2016 Studio Nexus + * @license MIT + * @version Release: 0.1.0 + * @link https://www.studionexus.co/php/damnstupidsimple + */ +namespace Core; + +/** + * The Viewer + * ----------------------------------------------------------------------- + * + * Reads and render the template file. Responsible for injecting + * dependencies from both Container and the Core\Sharer + * + */ +class Viewer { + + /** + * Finds, renders and displays a template file. Reports a 404 error in + * case of missing files. + * + * @param string $file file name / path to the file + * @param array &$data array of references to data or objects + * + * @static + * @access public + * @see Viewer::render() + * @since Method available since Release 0.1.0 + */ + static function file($file, array &$data = []){ + // Do you love displaying blank pages? + if($file === 'index' || $file === 'index.php'){ + Debugger::report(404, true); + }else{ + if(file_exists(DSS_PATH.$file)){ + self::render($file, $data); + }else if(file_exists(DSS_PATH.$file.'.php')){ + self::render(DSS_PATH.$file.'.php'); + }else{ + Debugger::report(404, true); + } + } + } + + /** + * Renders a template file. Inject dependencies from the Application + * Container and the Core\Sharer before viewing the file. Also, + * extracts &$data into variables usable from the template files + * + * @param string $file file name / path to the file + * @param array &$data array of data or objects + * + * @static + * @access private + * @since Method available since Release 0.1.0 + */ + static private function render($file, &$data = []){ + // Extract the variable $data passed to the Core\Viewer::file() + extract ($data); + + // We don't want duplicated data + unset($data); + + // Extract data retreived from the Sharer + if(Sharer::get() !== null){ + extract(Sharer::get()); + } + + ob_start(); + include_once($file); + $output = ob_get_contents(); + ob_end_clean(); + echo ($output); + } + +} diff --git a/src/errorpage/full.php b/src/errorpage/full.php new file mode 100644 index 0000000..abf0b78 --- /dev/null +++ b/src/errorpage/full.php @@ -0,0 +1,12 @@ + + + + +
+
+

Error:

+

In file: , line

+
+ +
+ diff --git a/src/errorpage/simple.php b/src/errorpage/simple.php new file mode 100644 index 0000000..88cce16 --- /dev/null +++ b/src/errorpage/simple.php @@ -0,0 +1,10 @@ + + + + +
+
+

Error:

+
+
+ \ No newline at end of file diff --git a/src/errorpage/style.css b/src/errorpage/style.css new file mode 100644 index 0000000..abad92e --- /dev/null +++ b/src/errorpage/style.css @@ -0,0 +1,34 @@ +.dss_main { + padding: 18px; + width: 100%; +} + +.dss_error { + z-index: 2; + max-width: 960px; + min-width: 600px; + margin: 0px auto; + left: 0; + right: 0; +} + +.dss_error > h1 { + border: 1px solid #dadada; + overflow: hidden; + background: #f7f7f7; + font-size: 19px; + font-weight: 100; + margin: 0; + padding: 15px; + +} + +.dss_error > p { + border: 1px solid #dadada; + overflow: hidden; + background: #fff; + font-weight: 100; + margin-top: 7px; + padding: 10px 15px; + font-size: 14px; +} \ No newline at end of file diff --git a/tests/AppTest.php b/tests/AppTest.php new file mode 100644 index 0000000..19e2c19 --- /dev/null +++ b/tests/AppTest.php @@ -0,0 +1,38 @@ +link('database', Core\Database::connect()); + $app->link('cachemanager', Core\Cache::init()); + $app->link('success', true); + Core\Sharer::share('app', $app); + $test = Core\Sharer::get(); + if($test['app']->success == true){ + $this->assertTrue(true); + }else{ + $this->assertTrue(false); + } + $this->assertTrue(true); + return 'first'; + } + + /** + * @depends testProducerFirst + */ + public function testConsumer() + { + $this->assertEquals( + ['first'], + func_get_args() + ); + } +} +?> \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..c26965b --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,7 @@ + \ No newline at end of file