From 9177a466c2ce786114365d44e3c6fe32bf58f361 Mon Sep 17 00:00:00 2001 From: Carlos Amaral Date: Tue, 3 Jan 2017 16:37:25 -0200 Subject: [PATCH] 1.0.1-beta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mapeamento de Parâmetros e Dispatch --- .gitignore | 12 +++ CHANGELOG.md | 11 +++ README.md | 4 +- app/example/config.php | 59 ------------ .../controller/maincontroller.class.php | 5 +- app/example/routes.php | 70 +++++++++++++++ base/core/application.class.php | 89 +++++-------------- base/core/config.class.php | 2 +- base/core/controller.class.php | 46 ++++++++++ base/core/dispatch.class.php | 86 ++++++++++++++++-- base/core/exception.class.php | 4 +- base/core/request.class.php | 42 +++++++-- base/core/routes/matcher.class.php | 13 ++- base/core/routes/route.class.php | 43 ++++++--- base/core/routes/router.class.php | 2 +- base/core/service.class.php | 7 ++ 16 files changed, 335 insertions(+), 160 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 app/example/routes.php create mode 100644 base/core/service.class.php diff --git a/.gitignore b/.gitignore index 46e56cb..b57ae1f 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,15 @@ Temporary Items .idea/misc.xml .idea/modules.xml .idea/workspace.xml +/.idea/codeStyleSettings.xml +/.idea/encodings.xml +/.idea/markdown-navigator.xml +/.idea/misc.xml +/.idea/modules.xml +/.idea/vcs.xml +/.idea/workspace.xml +/.idea/copyright +/.idea/jsLinters +/.idea/markdown-navigator +/.idea/base.iml +/.idea/blade.xml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..165b66e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# 1.0.1-beta + +Mapeado os parâmetros da rota, com os parâmetros da _action_: + + /post/:date/:slug + function postagens($slug, $date) { ... } + + :date -> $date + :slug -> $slug + +Passado a execução da Rota para a classe \Core\Dispatch \ No newline at end of file diff --git a/README.md b/README.md index 2251f48..c811e25 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Base – 1.0.0-beta +# Base – 1.0.1-beta Base é uma "base" para construção de soluções em MVC com PHP. Construi o _framework_ para uso próprio, porém resolvi disponibilizar @@ -48,7 +48,7 @@ English ------------------------------------------------------------------------ -# Base – 1.0.0-alpha.5.1.2 +# Base – 1.0.1-beta Base is a "base" for building solutions in MVC with PHP. Build the _framework_ for their own use, but decided to make available diff --git a/app/example/config.php b/app/example/config.php index 81b1f47..d867f5c 100644 --- a/app/example/config.php +++ b/app/example/config.php @@ -1,6 +1,5 @@ 'http://localhost/Base/public_html/', 'name' => 'example', -]); - -// Seta a rota de erro 404 -Router::notfound([ - 'controller' => 'Error', - 'action' => 'index' -]); - - -// Setá a rota padrão, home/index -Router::main([ - 'controller' => 'Main', - 'action' => 'index' -]); - -// Apenas replicando a rota padrão para ser acessada através do endereço /index -Router::route('/index', Router::main()); - -// Rota para o formulário de LOGIN, acessível apenas via GET -Router::get('/entrar', 'entrar', [ - 'controller' => 'Login', - 'action' => 'index' -]); - -// Replicando a rota /entrar para /login. Permitira acesso pelo mesmo Method que a rota /entrar (GET) -Router::route('/login', 'login.form', Router::GetByName('entrar')->_clone()); - -// Rota para realizar o login, acessível apenas via POST -Router::post('/login', 'login', [ - 'controller' => 'Login', - 'action' => 'login' -]); - - -// Rota: GET /post/ -Router::get('/post/:id',[ - 'controller' => 'Main', - 'action' => 'teste' -])->params([ - 'id' => '\d+' -]); - -// Rota: GET /post/ -Router::get('/post/:slug',[ - 'controller' => 'Main', - 'action' => 'action' -])->params([ - 'slug' => '[a-zA-Z0-9\-_]+' -]); - - -// Rota: GET /post// -Router::get('/post/:date/:slug', [ - 'controller' => 'Main', - 'action' => 'pdate' -])->params([ - 'date' => '[0-9]{2}-[0-9]{2}-[0-9]{4}', - 'slug' => '[a-zA-Z0-9\-_]+' ]); \ No newline at end of file diff --git a/app/example/controller/maincontroller.class.php b/app/example/controller/maincontroller.class.php index 460a889..a39bdb9 100644 --- a/app/example/controller/maincontroller.class.php +++ b/app/example/controller/maincontroller.class.php @@ -1,5 +1,6 @@ load->view('pages/index'); } - public function teste($id) { - return 'ID: '.$id; + public function teste($id, $nome = 'Fulano') { + return "Olá $nome, o ID é {$id}"; } diff --git a/app/example/routes.php b/app/example/routes.php new file mode 100644 index 0000000..50de1c2 --- /dev/null +++ b/app/example/routes.php @@ -0,0 +1,70 @@ + 'Error', + 'action' => 'index' +]); + + +// Setá a rota padrão, home/index +Router::main([ + 'controller' => 'Main', + 'action' => 'index' +]); + +// Apenas replicando a rota padrão para ser acessada através do endereço /index +Router::route('/index', Router::main()); + +// Rota para o formulário de LOGIN, acessível apenas via GET +Router::get('/entrar', 'entrar', [ + 'controller' => 'Login', + 'action' => 'index' +]); + +// Replicando a rota /entrar para /login. Permitira acesso pelo mesmo Method que a rota /entrar (GET) +Router::route('/login', 'login.form', Router::GetByName('entrar')->_clone()); + +// Rota para realizar o login, acessível apenas via POST +Router::post('/login', 'login', [ + 'controller' => 'Login', + 'action' => 'login' +]); + + +// Rota: GET /post/ +Router::get('/post/:id',[ + 'controller' => 'Main', + 'action' => 'teste' +])->params([ + 'id' => '\d+' +]); + +// Rota: GET /post/ +Router::get('/post/:nome/:id',[ + 'controller' => 'Main', + 'action' => 'teste' +])->params([ + 'id' => '\d+', + 'nome' => '\w+' +]); + +// Rota: GET /post/ +Router::get('/post/:slug',[ + 'controller' => 'Main', + 'action' => 'action' +])->params([ + 'slug' => '[a-zA-Z0-9\-_]+' +]); + + +// Rota: GET /post// +Router::get('/post/:date/:slug', [ + 'controller' => 'Main', + 'action' => 'pdate' +])->params([ + 'date' => '[0-9]{2}-[0-9]{2}-[0-9]{4}', + 'slug' => '[a-zA-Z0-9\-_]+' +]); \ No newline at end of file diff --git a/base/core/application.class.php b/base/core/application.class.php index 1718f83..7f9c1a9 100644 --- a/base/core/application.class.php +++ b/base/core/application.class.php @@ -3,6 +3,7 @@ use \Core\Request; use \Core\Config; +use \Core\Dispatch; use \Core\Routes\Router; use \Core\Exception\InvalidApplicationException; /** @@ -12,92 +13,42 @@ class Application { + private static $config; + public static function RUN($application) { try { - $config = Config::SetApplication($application); + self::$config = Config::SetApplication($application); - if (is_readable($config->dir . 'config.php')) - require_once $config->dir . 'config.php'; + self::loadConfigs(); } catch (InvalidApplicationException $e) { echo self::Error($e->getMessage()); return FALSE; } - $request = Request::getInstance(); - - // Router em implementação - $router = Router::getInstance(); - - $route = $router->GetByRequest(); - - if ($config->onlyroutes && !$route) { - $route = Router::notfound(); - } - - if ($route) { - $request->controller = $route->controller; - $request->action = $route->action; - $request->params = (object) $route->attributes; - } else { - $request->parseRoute(); - } - - $class = "\\Controller\\{$request->controller}Controller"; - - // Retorno caso configuração $outputreturn do controller seja true - - $output = ''; - try { - - if (!class_exists($class) ) - throw new Exception("A URL {$request->uri} é inválida."); - - $app = New $class(); - - } catch (Exception $e) { - echo self::Error( $e->getMessage() ); - return FALSE; - } - - - if ( !empty($request->post['mvc:model']) ) { - - $model = '\Model\\' . array_remove($request->post, 'mvc:model') . 'Model'; - - try { - $param = New $model($request->post); - - if ($param) - $param = [$param]; - - } catch (Exception $e) { - $app->setOutput($app->index()); - $app->output(); - return FALSE; - } - } else if (!!$route && count($route->attributes) > 0) - $param = $route->attributes; - else if ( empty($request->lost) && !is_numeric($request->lost) ) - $param = NULL; - else - $param = [$request->lost]; - - try { - $output = $app->execute($param); - } catch (Exception $e) { + Dispatch::fire(); + + + } catch (\Exception $e) { echo self::Error($e->getMessage()); return FALSE; } - if ($app->outputreturn) - $app->setOutput( $output ); + return TRUE; + } + + private static function loadConfigs() { + $configFiles = ['config.php', 'routes.php', 'constants.php']; - $app->output(); + foreach ($configFiles as $configFile) + self::loadConfigFile($configFile); + } - return TRUE; + private static function loadConfigFile($file) { + if (is_readable(self::$config->dir . $file)) + require_once self::$config->dir . $file; } diff --git a/base/core/config.class.php b/base/core/config.class.php index 88b4694..b2158a0 100644 --- a/base/core/config.class.php +++ b/base/core/config.class.php @@ -283,7 +283,7 @@ public function url($value) { if (empty($value)) throw new \InvalidArgumentException('Não é possível setar a propriedade "url" para um valor vazio.'); - $this->url = $value; + $this->url = strtolower($value); } /** diff --git a/base/core/controller.class.php b/base/core/controller.class.php index 69a58b0..5f699b2 100644 --- a/base/core/controller.class.php +++ b/base/core/controller.class.php @@ -49,6 +49,11 @@ class Controller { */ protected $model; + /** + * @var array + */ + protected $services = []; + /** * @var Config */ @@ -79,6 +84,47 @@ function __construct(Request $request = NULL) { Model::$controller = $this; } + /** + * @param string $name + * @return Service + */ + protected function service($name, $args = []) { + if (isset($this->services[ strtolower($name) ])) return $this->services[ strtolower($name) ]; + + try { + $service = $this->loadClass($name, "\\Services\\", 'Service', $args); + } catch (\Exception $e) { + throw new \InvalidArgumentException('O serviço informado não existe.'); + } + return $this->services[ strtolower($name) ] = $service; + } + + /** + * @param string $name + * @return mixed + */ + protected function handle($name) { + $data = !empty($this->request->post[$name]) ? $this->request->post[$name] : []; + return $this->loadClass($name, "\\Model\\", "Model", $data); + } + + /** + * @param string $name + * @param string $namespace + * @param string $sufix + * @param array $args + * @return mixed + */ + private function loadClass($name, $namespace = "\\", $sufix = "", $args = []) { + $class = $namespace . $name . $sufix; + + if (!class_exists($class)) + throw new \InvalidArgumentException('A classe informada não existe'); + + return new $class(...$args); + } + + /** * Executa o controlador. Em breve será papel do Dispatch * @param mixed $param diff --git a/base/core/dispatch.class.php b/base/core/dispatch.class.php index 9a4ca54..7d16a70 100644 --- a/base/core/dispatch.class.php +++ b/base/core/dispatch.class.php @@ -1,8 +1,10 @@ getControllerName(); + + if (!class_exists($class) ) + throw new Exception('A URL '.self::$request->uri.' é inválida.'); + + self::$app = New $class(); + + if (!!self::$route && count(self::$route->attributes) > 0) { + $ref = new \ReflectionMethod(self::$app, self::$route->action); + + $parameters = $ref->getParameters(); + + foreach ($parameters as $parameter) { + + if (!isset(self::$route->attributes[$parameter->getName()])) { + + if (!$parameter->isOptional()) + throw new InvalidPropertyException('A propriedade "'.$parameter->getName().'" não está declarada na rota.'); + + self::$params[$parameter->getPosition()] = $parameter->getDefaultValue(); + continue; + } + - if (is_null($route)) - $route = $router->GetByRequest(); + self::$params[$parameter->getPosition()] = self::$route->attributes[$parameter->getName()]; - return call_user_func_array([$route->getController(), $route->action], $route->attributes); + } + } } + private static function execute() { + try { + self::$output = call_user_func_array([self::$app, self::$route->action], self::$params); + } catch (\Exception $e) { + throw $e; + } + } + + private static function rprint() { + if (self::$app->outputreturn) + self::$app->setOutput( self::$output ); + + self::$app->output(); + } + + + + private static function initialize() { + self::$request = Request::getInstance(); + self::$config = Config::getInstance(); + self::$router = Router::getInstance(); + } + + private static function findRoute() { + self::$route = self::$router->match(); + if (self::$config->onlyroutes && !self::$route) { + self::$route = Router::notfound(); + } + + if (!self::$route) + self::$route = self::$request->getRouteByRequest(); + } + + } \ No newline at end of file diff --git a/base/core/exception.class.php b/base/core/exception.class.php index 341e13a..36336a8 100644 --- a/base/core/exception.class.php +++ b/base/core/exception.class.php @@ -2,7 +2,9 @@ namespace Core; +use \Exception as BaseException; + /** * Exception */ -class Exception extends \Exception {} \ No newline at end of file +class Exception extends BaseException {} \ No newline at end of file diff --git a/base/core/request.class.php b/base/core/request.class.php index 7a75567..5f8fe62 100644 --- a/base/core/request.class.php +++ b/base/core/request.class.php @@ -9,6 +9,17 @@ */ class Request { + const METHOD_HEAD = 'HEAD'; + const METHOD_GET = 'GET'; + const METHOD_POST = 'POST'; + const METHOD_PUT = 'PUT'; + const METHOD_PATCH = 'PATCH'; + const METHOD_DELETE = 'DELETE'; + const METHOD_PURGE = 'PURGE'; + const METHOD_OPTIONS = 'OPTIONS'; + const METHOD_TRACE = 'TRACE'; + const METHOD_CONNECT = 'CONNECT'; + /** * @var Request */ @@ -128,6 +139,11 @@ class Request { */ public $uri; + /** + * @var string + */ + public $queryString; + /** * Construtor * @return void @@ -150,10 +166,15 @@ function __construct() { if (!empty($_SERVER['SCRIPT_URI'])) $this->url = $_SERVER['SCRIPT_URI']; else - $this->url = $_SERVER['REQUEST_SCHEME'] . '://' .$_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']; + + $this->url = strtolower($_SERVER['REQUEST_SCHEME'] . '://' .$_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']); $this->uri = '/' . trim(!empty($_GET['URI']) ? $_GET['URI'] : str_replace(Config::getInstance()->url, '', $this->url), '/'); + $this->queryString = strpos($this->uri, '?') > -1 ? substr($this->uri, strpos($this->uri, '?')+1) : null; + + $this->uri = strpos($this->uri, '?') > -1 ? substr($this->uri, 0, strpos($this->uri, '?')) : $this->uri; + self::$instance = $this; } @@ -178,17 +199,13 @@ public function getAction() { * Realiza o parser da requisição * @return void * - * OBS: Deprecated + * @deprecated */ public function parseRoute(){ $this->params = new \stdClass(); $uri = $this->uri; - if (strpos($uri, '?') > -1) { - $uri = substr($uri, 0, strpos($uri, '?')); - } - $uri = explode('/', trim($uri, '/')); $this->controller = count($uri) > 0 ? array_shift($uri) : 'Main'; @@ -208,7 +225,20 @@ public function parseRoute(){ $this->lost = $key; } + } + + public function getRouteByRequest() { + $this->parseRoute(); + + $r = \Core\Routes\Router::get($this->uri, 'request_runtime_route', [ + 'controller' => $this->controller, + 'action' => $this->action + ]); + + if ($this->params) + $r->params( (array) $this->params )->attributes( (array) $this->params ); + return $r; } diff --git a/base/core/routes/matcher.class.php b/base/core/routes/matcher.class.php index f434ac6..d643c55 100644 --- a/base/core/routes/matcher.class.php +++ b/base/core/routes/matcher.class.php @@ -66,7 +66,7 @@ public function applyRules(Request $request, Route $route, $name, $path) { public function routeMatched(Route $route, $name, $path) { - $this->log .= "{$path} MATCHED ON {$name}" . PHP_EOL; + $this->log($name, $path, 'SUCCESS'); $this->matchedRoute = $route; return $route; } @@ -80,8 +80,15 @@ protected function ruleFailed($request, $route, $name, $path, $rule, $score) { $this->failedScore = $score; } - $this->log .= "{$path} FAILED {$ruleClass} ON {$name}" . PHP_EOL; - + $this->log($name, $path, 'FAIL', $ruleClass); return FALSE; } + + private function log($name, $path, $status, $rule = '') { + $this->log .= "{ + target: {$name} + path: {$path} + status: {$status} + rule: {$rule}" . PHP_EOL . '}'.PHP_EOL; + } } \ No newline at end of file diff --git a/base/core/routes/route.class.php b/base/core/routes/route.class.php index c11eea8..0ab74ab 100644 --- a/base/core/routes/route.class.php +++ b/base/core/routes/route.class.php @@ -1,5 +1,7 @@ setImutableProperty('controller', $controller); + if (!$this->checkControllerIsValid()) + throw new InvalidPropertyException('O controller informado é inválido.'); return $this; } - /** * * Seta o action @@ -334,7 +338,7 @@ public function lang($lang) { /** * - * Seta o controller + * Seta o host * * @param string $controller * @@ -399,17 +403,36 @@ private function setImutableProperty($prop, $value) { } /** - * + * * Retorna o controller da rota - * + * * @return Controller - * + * */ public function getController() { - $controller = "\Controller\{$this->controller}Controller"; + $controller = $this->getControllerName(); return New $controller(); } + /** + * + * Retorna o nome do controller da rota + * + * @return string + * + */ + public function getControllerName($namespace = TRUE) { + if (!$namespace) return $this->controller; + return "\\Controller\\{$this->controller}Controller"; + } + + /** + * @return mixed + */ + public function checkControllerIsValid() { + return class_exists($this->getControllerName()); + } + /** * * Retorna o valor de uma propriedade diff --git a/base/core/routes/router.class.php b/base/core/routes/router.class.php index 6dc6a0b..77981b7 100644 --- a/base/core/routes/router.class.php +++ b/base/core/routes/router.class.php @@ -272,7 +272,7 @@ public static function GetByName($name) { return NULL; } - public function GetByRequest() { + public function match() { $matcher = $this->matcher; $route = $matcher(self::$request, self::$routes); diff --git a/base/core/service.class.php b/base/core/service.class.php new file mode 100644 index 0000000..149c85f --- /dev/null +++ b/base/core/service.class.php @@ -0,0 +1,7 @@ +