From 0ca0c23cee3c63ad19e719ed300c3d3db2d4569b Mon Sep 17 00:00:00 2001 From: sheychen Date: Sat, 10 Jun 2017 17:22:33 +0200 Subject: [PATCH] First commit --- LICENSE | 21 +++++ README.md | 2 + composer.json | 15 ++++ src/App.php | 84 ++++++++++++++++++++ src/Controller.php | 5 ++ src/HttpException.php | 16 ++++ src/NeedApp.php | 9 +++ src/Path.php | 133 +++++++++++++++++++++++++++++++ src/Route.php | 176 ++++++++++++++++++++++++++++++++++++++++++ src/Router.php | 81 +++++++++++++++++++ 10 files changed, 542 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 src/App.php create mode 100644 src/Controller.php create mode 100644 src/HttpException.php create mode 100644 src/NeedApp.php create mode 100644 src/Path.php create mode 100644 src/Route.php create mode 100644 src/Router.php diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..59d50c5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 sheychen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..dbc62be --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# krutush +Pretty Simple PHP Framework diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ec7137b --- /dev/null +++ b/composer.json @@ -0,0 +1,15 @@ +{ + "name": "krutush/krutush", + "license": "MIT", + "authors": [ + { + "name": "sheychen", + "email": "contact@clementbois.fr" + } + ], + "autoload": { + "psr-4": { + "Krutush\\": "src/" + } + } +} \ No newline at end of file diff --git a/src/App.php b/src/App.php new file mode 100644 index 0000000..b8269ad --- /dev/null +++ b/src/App.php @@ -0,0 +1,84 @@ +router; + } + + /** + * Config storage + * @var array + */ + protected $config = array( + 'namespace' => '', + 'controller' => 'Controller', + 'debug' => true + ); + + /** + * Needed config values + * @var array + */ + protected static $configMap = array( + 'namespace' => true, + 'controller' => false, + 'debug' => false + ); + + public function __construct(array $data = array()){ + foreach(static::$configMap as $var => $needed){ + if(isset($data['app'][$var])){ + $this->config[$var] = $data['app'][$var]; + }else{ + if($needed) + trigger_error('app.'.$var.' must be define in your app'); + } + } + + //TODO error handler + //if($this->config['debug']) + + if(isset($data['path'])){ + Path::sets($data['path']); + }else{ + trigger_error('path.root must be define in your app'); + } + + $this->router = new Router(isset($data['router']) ? $data['router'] : Path::get('config').'/Routes.php'); + } + + public function run($uri = null, array $filters = array()){ + $route = $this->router->run(($uri ?: $_SERVER['REQUEST_URI']), $filters); + + if(!isset($route)) + $this->error(new HttpException(404, 'Not Found')); + + try{ + $route->call($this, $this->config['namespace'], $this->config['controller']); + }catch(HttpException $e){ + if(!$this->config['debug']) + $e = new HttpException($e->getHttpCode(), 'Server internal error'); + $this->error($e); + }catch(\Exception $e){ + $this->error(new HttpException(500, $this->config['debug'] ? $e->getMessage() : 'Server internal error')); + } + } + + public function error(HttpException $e){ + http_response_code($e->getHttpCode()); + echo $e->getMessage(); + exit; + } +} \ No newline at end of file diff --git a/src/Controller.php b/src/Controller.php new file mode 100644 index 0000000..83c711e --- /dev/null +++ b/src/Controller.php @@ -0,0 +1,5 @@ +httpCode = $httpCode; + parent::__construct($message); + } + + public function getHttpCode(){ + return $this->httpCode; + } +} \ No newline at end of file diff --git a/src/NeedApp.php b/src/NeedApp.php new file mode 100644 index 0000000..83b6709 --- /dev/null +++ b/src/NeedApp.php @@ -0,0 +1,9 @@ +app = $app; } +} \ No newline at end of file diff --git a/src/Path.php b/src/Path.php new file mode 100644 index 0000000..da704ec --- /dev/null +++ b/src/Path.php @@ -0,0 +1,133 @@ + $value){ + static::set($key, $value); + } + } + + public static function get(string $key): string{ + $key = strtoupper($key); + switch($key){ + case 'ROOT': + if(isset(static::$paths['ROOT'])) + return static::$paths['ROOT']; + + case 'WWW': + case 'WEB': + case 'PUBLIC': + if(isset(static::$paths['WWW'])) + return static::$paths['WWW']; + if(static::$strict == false) + return static::get('ROOT').'/public'; + + case 'SRC': + if(isset(static::$paths['SRC'])) + return static::$paths['SRC']; + if(static::$strict == false) + return static::get('ROOT').'/src'; + + case 'CFG': + case 'CONF': + case 'CONFIG'; + if(isset(static::$paths['CFG'])) + return static::$paths['CFG']; + if(static::$strict == false) + return static::get('SRC').'/Config'; + + case 'TPL': + case 'TEMPLATE': + if(isset(static::$paths['TPL'])) + return static::$paths['TPL']; + if(static::$strict == false) + return static::get('SRC').'/Template'; + + case 'TMP': + case 'CACHE': + if(isset(static::$paths['TPL'])) + return static::$paths['TPL']; + if(static::$strict == false) + return static::get('SRC').'/Cache'; + + default: + trigger_error('Get Unknown key : '.$key); + return ''; + } + } + + public static function isset(string $key): bool{ + $key = strtoupper($key); + switch($key){ + case 'ROOT': + return isset(static::$paths['ROOT']); + + case 'WWW': + case 'WEB': + case 'PUBLIC': + return isset(static::$paths['WWW']) || + (static::$strict == false && static::isset('ROOT')); + + case 'SRC': + return isset(static::$paths['SRC']) || + (static::$strict == false && static::isset('ROOT')); + + case 'CFG': + case 'CONF': + case 'CONFIG'; + return isset(static::$paths['CFG']) || + (static::$strict == false && static::isset('SRC')); + + case 'TPL': + case 'TEMPLATE': + return isset(static::$paths['TPL']) || + (static::$strict == false && static::isset('SRC')); + + default: + return false; + } + } + + public static function set(string $key, string $value): bool{ + $key = strtoupper($key); + switch($key){ + case 'ROOT': + static::$paths['ROOT'] = $value; + return true; + + case 'WWW': + case 'WEB': + case 'PUBLIC': + static::$paths['WWW'] = $value; + return true; + + case 'SRC': + static::$paths['SRC'] = $value; + return true; + + case 'CFG': + case 'CONF': + case 'CONFIG'; + static::$paths['CFG'] = $value; + return true; + + case 'TPL': + case 'TEMPLATE': + static::$paths['TPL'] = $value; + return true; + + default: + trigger_error('Set Unknown key : '.$key); + return false; + } + } + + public static function setStrict(bool $value = true){ + static::$strict = $value; + } +} \ No newline at end of file diff --git a/src/Route.php b/src/Route.php new file mode 100644 index 0000000..09cdd32 --- /dev/null +++ b/src/Route.php @@ -0,0 +1,176 @@ +filters + * @var array + */ + private static $defaultFilters = array(); + + /** + * Register a new Route + * @param string $path Url from Root + * @param callable|string $callable Function to run or 'Controller#function' + */ + public function __construct(string $path, $callable){ + $this->path = trim($path, '/'); + $this->callable = $callable; + $this->filters = static::$defaultFilters; + } + + /** + * My name is [...] + * @param string $name + * @return Route + */ + public function name(string $name): Route{ + $this->name = $name; + return $this; + } + + /** + * Add a filter + * @param string $name + * @param array $values + * @return Route + */ + public function filter(string $name, array $values): Route{ + $this->filters[$name] = $values; + return $this; + } + + /** + * Params match regex + * @param string $param + * @param string $regex + * @return Route + */ + public function with(string $param, string $regex): Route{ + $this->params[$param] = str_replace('(', '(?:', $regex); + return $this; + } + + /** + * Check url + * @param string $url + * @param array $filters + * @return bool + */ + public function match(string $url, array $filters = array()): bool{ + //Check filters + foreach($filters as $key => $value){ + if(!isset($this->filters[$key])) + return false; + + if(!in_array($value, $this->filters[$key])) + return false; + } + + //Check url + $url = trim($url, '/'); + $path = preg_replace_callback('#:([\w]+)#', array($this, 'paramMatch'), $this->path); + $regex = "#^$path$#i"; + if(!preg_match($regex, $url, $matches)) + return false; + + array_shift($matches); + $this->matches = $matches; + return true; + } + + private function paramMatch($match){ + if(isset($this->params[$match[1]])){ + return '(' . $this->params[$match[1]] . ')'; + } + return '([^/]+)'; + } + + /** + * Build Url with params + * @param array $params + * @return string + */ + public function getUrl(array $params = array()): string{ + $path = $this->path; + foreach($params as $k => $v){ + $path = str_replace(":$k", $v, $path); + } + return $path; + } + + /** + * Check name + * @param string $name + * @return bool + */ + public function matchName(string $name): bool{ + return $this->name == $name; + } + + /** + * Run Route + * @param App $app + * @param string $namespace + * @param string $prefix + */ + public function call(App $app, string $namespace, string $prefix = "Controller"){ + //is a Controller + if(is_string($this->callable)){ + $params = explode('#', $this->callable); + $params[0] = ((isset($namespace) && strpos($params[0], '\\') === false) ? ($namespace.$params[0].$prefix) : $params[0]); + if(!class_exists($params[0])) + throw new HttpException(404, 'Class Not Found : '.$params[0]); + $controller = new $params[0]($app); + if(!method_exists($controller,$params[1])) + throw new HttpException(404, 'Function Not Found : '.$params[0].'::'.$params[1]); + return call_user_func_array(array($controller, $params[1]), $this->matches); + } else { + return call_user_func_array($this->callable, $this->matches); + } + } + + /** + * Set default filters + * @param array $array + * @return void + */ + public static function filters(array $array = array()){ + static::$defaultFilters = $array; + } +} \ No newline at end of file diff --git a/src/Router.php b/src/Router.php new file mode 100644 index 0000000..bd34d4d --- /dev/null +++ b/src/Router.php @@ -0,0 +1,81 @@ +routes[] = $route; + return $route; + } + + /** + * Get (first) Route matching url and filters + * + * @param string $url + * @param array $filters + * @return Route|null + */ + public function run(string $url, array $filters):?Route{ + foreach($this->routes as $route){ + if($route->match($url, $filters)) + return $route; + } + return null; + } + + /** + * Get (first) Route by name + * + * @param string $name + * @return Route|null + */ + public function get(string $name):?Route{ + foreach($this->routes as $route){ + if($route->matchName($name)) + return $route; + } + return null; + } + + /** + * Redirect helper + * + * @param string $url + * @param bool $stop + * @return void + */ + public static function redirect(string $url, bool $stop = true){ + header('Location: '.$url); + if($stop) + exit(); + } +} \ No newline at end of file