diff --git a/ActIndex.php b/ActIndex.php new file mode 100644 index 0000000000000000000000000000000000000000..bb3944b49c900926602aeacab36637eb01589b55 --- /dev/null +++ b/ActIndex.php @@ -0,0 +1,34 @@ +<?php + +namespace ExampleApp; + +use AG\WebApp\Action; +use AG\WebApp\Request; +use AG\WebApp\Request\RqGET; +use AG\WebApp\Response; + +/** + * Start page of example app. + * + * @author Alexandr Gorlov <a.gorlov@gmail.com> + */ +class ActIndex implements Action +{ + private $rqGet; + + public function __construct(Request $req = null) + { + $this->rqGet = $req ?? new RqGET(); + } + + public function handle(Response $resp): Response + { + $test = $this->rqGet->param('test') ?? 'nope'; + + return $resp->withBody( + "Hello, World 2!<br>" . + '<a href="/login">login</a><br>' . + '$_GET[test]=' . htmlentities($test) + ); + } +} \ No newline at end of file diff --git a/ActLk.php b/ActLk.php new file mode 100644 index 0000000000000000000000000000000000000000..a77a6979091c6be8eeca10fb2bed01a70b4d310d --- /dev/null +++ b/ActLk.php @@ -0,0 +1,37 @@ +<?php + +namespace ExampleApp; + +use AG\WebApp\Action; +use AG\WebApp\Action\ActRedirect; +use AG\WebApp\Request; +use AG\WebApp\Response; +use AG\WebApp\Session; +use AG\WebApp\Session\AppSession; +use AG\WebApp\AccessDeniedException; + +class ActLk implements Action +{ + private $sess; + private $redirect; + + public function __construct( + Session $sess = null + ) { + $this->sess = $sess ?? new AppSession; + } + + public function handle(Response $resp): Response + { + if (! $this->sess->exists('login')) { + throw new AccessDeniedException('Only Authorized Access'); + } + + // login and password are ok + + return $resp->withBody( + "Hello: " . $this->sess->get('login') . "<br>" . + '<a href="/logout">logout</a>' + ); + } +} diff --git a/ActLogin.php b/ActLogin.php new file mode 100644 index 0000000000000000000000000000000000000000..c0f32547b931090b2d83d634bc17b4f999863ed5 --- /dev/null +++ b/ActLogin.php @@ -0,0 +1,37 @@ +<?php + +namespace ExampleApp; + +use AG\WebApp\Action; +use AG\WebApp\Action\ActRedirect; +use AG\WebApp\Request; +use AG\WebApp\Request\RqGET; +use AG\WebApp\Response; +use AG\WebApp\Session; +use AG\WebApp\Session\AppSession; + +class ActLogin implements Action +{ + private $session; + private $redirect; + + public function __construct( + Session $sess = null, + Action $redirect = null + ) { + $this->session = $sess ?? new AppSession; + $this->redirect = $redirect ?? new ActRedirect('/lk'); + } + + public function handle(Response $resp): Response + { + if (true) { // login and password ok + $this->session->set('login', 'user1'); + return $this->redirect->handle($resp); + } else { + return $this->response->withBody( + "Bad login or password." + ); + } + } +} diff --git a/ActLogout.php b/ActLogout.php new file mode 100644 index 0000000000000000000000000000000000000000..123d55faf8f1fb47efdc10db595596c4b4f06a4d --- /dev/null +++ b/ActLogout.php @@ -0,0 +1,37 @@ +<?php + +namespace ExampleApp; + +use AG\WebApp\Action; +use AG\WebApp\Action\ActRedirect; +use AG\WebApp\Request; +use AG\WebApp\Request\RqGET; +use AG\WebApp\Response; +use AG\WebApp\Session; +use AG\WebApp\Session\AppSession; + +class ActLogout implements Action +{ + private $session; + private $redirect; + + public function __construct( + Session $sess = null, + Action $redirect = null + ) { + $this->session = $sess ?? new AppSession; + $this->redirect = $redirect ?? new ActRedirect('/'); + } + + public function handle(Response $resp): Response + { + if ($this->session->exists('login')) { // login and password ok + $this->session->unset('login'); + return $this->redirect->handle($resp); + } + + return $this->response->withBody( + "You are not logged in." + ); + } +} diff --git a/README.md b/README.md index e98e95c1cd67f45323cfa033e08d65320359a346..42f74404be17c460a56e85ded7bc5443649652ca 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,83 @@ # ObjectMVC PHP Object MVC microframework for web apps + +# Example Application + +index.php: +```php +(new ApplicationStd( + [ + '/' => new ActIndex(), + '/login' => new ActLogin(), + '/logout' => new ActLogout(), + '/lk' => new ActLk(), + ], + new RespStd +))->start(); +``` + +## Actions + +**ActIndex.php** + +```php +class ActIndex implements Action +{ + public function handle(Response $resp): Response + { + return $resp->withBody( + "Hello, World 2!<br>" . + '<a href="/login">login</a>' + ); + } +} +``` + +If we need database or GET params, put it in constructor: + +In this example $_GET['test'] -> with RqGET object + +```php + +class ActIndex implements Action +{ + private $rqGet; + + public function __construct(Request $rqGet = null) + { + $this->rqGet = $rqGet ?? new RqGET(); + } + + public function handle(Response $resp): Response + { + $test = $this->rqGet->param('test') ?? 'nothing'; + + return $resp->withBody( + "Hello, World 2!<br>" . + '<a href="/login">login</a>' . + '$_GET[test]=' . htmlentities($test) + ); + } +} +``` + +GET request, POST request, Database, Config, Environment, Session: +```php +public function __construct( + Request $req = null, + AppPDO $db = null, + Config $config = null, + Env $env = null, + Session $sess = null, + Tpl $tpl + // or anything you need for your Action +) +{ + $this->rqPOST = $req ?? new RqGET(); + $this->rqGET = $req ?? new RqGET(); + $this->db = $db ?? new AppPDO; + $this->config = $config ?? new AppConfig; + $this->env = $env ?? new AppEnv; + $this->tpl = $tpl ?? new AppTpl +} +``` diff --git a/composer.json b/composer.json index 3bad53f725c8afceb4f8612dff4f8e3143cb1c91..d5f58e36c694ca598f1e25aa9f2f862c3d2fdae6 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,34 @@ { + "name": "agorlov/ObjectMVC", + "description": "PHP Object MVC micro framework for web apps", + "type": "library", + "license": "free", + "authors": [ + { + "name": "Alexandr Gorlov", + "email": "a.gorlov@gmail.com" + } + ], "require": { + "php": ">= 7.1" + }, + "require-dev": { + "codeception/codeception": "^2.5.1", "squizlabs/php_codesniffer": "^3.4" - } + }, + + "autoload": { + "psr-4": { + "AG\\WebApp\\": "src", + "ExampleApp\\": "./" + } + }, + "scripts": { + "phpcs": [ + "phpcs --standard=PSR2 --colors src/" + ], + "phpcs-fix": [ + "phpcbf --standard=PSR2 src/" + ] + } } diff --git a/index.php b/index.php index 005ffd666b439cacb55c4c42ebd40814da9026d9..40dd81839a1d4873aa20ad44b50731a0d3c1aa33 100644 --- a/index.php +++ b/index.php @@ -2,378 +2,31 @@ /** - * Object MVC Prototype + * Object MVC Example app * * @author Alexandr Gorlov <a.gorlov@gmail.com> */ -//require __DIR__ . '/vendor/autoload.php'; - -// config -// env -// http-request -// session -// $db - - -interface App -{ - public function start(): void; -} - -class NotFoundException extends Exception -{ -} - -class AccessDeniedException extends Exception -{ -} - - -class ApplicationStd implements App -{ - private $actions; - private $SERVER; - - /** - * Application constructor. - * - * @param Response|array $action - */ - public function __construct(array $actions, Response $response = null, Request $SERVER = null) - { - - $this->SERVER = $SERVER ?? new RqSERVER(); - $this->actions = $actions; - $this->response = $response; // ?? new AppResponse; - } - - public function start(): void - { - try { - $requestUri = $this->SERVER->param('REQUEST_URI'); - - if (! array_key_exists($requestUri, $this->actions)) { - throw new NotFoundException('Page=' . $requestUri . ' not found in actions list!'); - } - - $resp = $this->actions[$requestUri]->handle($this->response); - $resp->print(); - - - //foreach ($this->response->headers() as $header) { - // header($header); - //} - - //if () - - //echo $this->response->body(); - - //$this->response->handle() - } catch (NotFoundException $e) { - header("Status: 404 Not Found"); - echo '404! ' . $e->getMessage(); //$twig->render('404.twig', ['message' => $e->getMessage()]); - } catch (AccessDeniedException $e) { - header("Status: 403 Access denied"); - echo '403! ' . $e->getMessage(); //$twig->render('403.twig', ['message' => $e->getMessage()]); - } catch (Exception $e) { - header("Status: 500 Application Error"); - echo "<h1>Error</h1>"; - echo "<pre>" . $e . "</pre>"; - } - } -} - -interface Request -{ - public function param($param); -} - -interface Response -{ - public function print(): void; - public function withBody(string $body): Response; - public function withHeaders(array $headers): Response; -} - -class RespStd implements Response -{ - private $headers; - private $body; - public function __construct($body = '', $headers = []) - { - $this->body = $body; - $this->headers = $headers; - } - - public function withBody(string $body): Response - { - return new self($body, $this->headers); - } - - public function withHeaders(array $headers): Response - { - return new self($this->body, $headers); - } - - - public function print(): void - { - foreach ($this->headers as $header) { - header($header); - } - - echo $this->body; - } -} - -class RqSERVER implements Request -{ - - private $request; - - public function __construct(array $request = null) - { - $this->request = $request ?? $_SERVER; - } - - public function param($param) - { - return $this->request[$param] ?? null; - } -} - -class RqGET implements Request -{ - private $request; - - public function __construct(array $request = null) - { - $this->request = $request ?? $_GET; - } - - public function param($param) - { - return $this->request[$param] ?? null; - } -} - -class RqPOST implements Request -{ - private $request; - - public function __construct(array $request = null) - { - $this->request = $request ?? $_POST; - } - - public function param($param) - { - return $this->request[$param] ?? null; - } -} - -interface Config -{ - //... -} - -interface Session -{ - public function exists($param): bool; - - public function get($param); - - public function set($param, $value): void; - - public function unset($param): void; -} - -class AppSession implements Session -{ - public function exists($param): bool - { - $this->sessionStart(); - return array_key_exists($param, $_SESSION); - } - - public function get($param) - { - $this->sessionStart(); - return $_SESSION[$param] ?? null; - } - - public function set($param, $value): void - { - $this->sessionStart(); - $_SESSION[$param] = $value; - session_commit(); - } - - public function unset($param): void - { - $this->sessionStart(); - unset($_SESSION[$param]); - session_commit(); - } - - private function sessionStart() - { - if (session_status() == PHP_SESSION_NONE) { - session_start(); - } - } -} - -class AppPDO extends PDO -{ - public function __construct(AppConfig $config) - { -// parent::__construct($dsn, $username, $passwd, $options); - } -} - - -interface Action -{ - public function handle(Response $resp): Response; -} - -class ActIndex implements Action -{ - private $rqGet; - - public function __construct(Request $req = null /*, PDO $db, Config $config, Env $env, Session $sess, Tpl $tpl*/) - { - $this->rqGet = $req ?? new RqGET(); -// $this->db = $db ?? new AppPDO; -// $this->config = $config ?? new AppConfig; -// $this->env = $env ?? new AppEnv; -// $this->session = $sesss ?? new AppSession; -// $this->tpl = $tpl ?? new AppTpl; - } - - public function handle(Response $resp): Response - { - return $resp->withBody("Hello, World 2!"); - } -} - - -class ActRedirect implements Action -{ - private $url; - - public function __construct($url) - { - $this->url = $url; - } - - public function handle(Response $resp): Response - { - return $resp - ->withBody('Redirect: ' . $this->url) - ->withHeaders([ 'Location: ' . $this->url ]); - } -} - - -class ActLogin implements Action -{ - private $session; - private $redirect; - - public function __construct( - Session $sess = null, - Action $redirect = null // Request $req = null , PDO $db, Config $config, Env $env, Session $sess, Tpl $tpl - ) { - $this->session = $sess ?? new AppSession; - $this->redirect = $redirect ?? new ActRedirect('/lk'); -// $this->rqGet = $req ?? new RqGET(); -// $this->db = $db ?? new AppPDO; -// $this->config = $config ?? new AppConfig; -// $this->env = $env ?? new AppEnv; -// $this->tpl = $tpl ?? new AppTpl; - } - - public function handle(Response $resp): Response - { - if (true) { // login and password ok - $this->session->set('login', 'user1'); - return $this->redirect->handle($resp); - } else { - return $this->response->withBody( - "Bad login or password." - ); - } - } -} - - -class ActLogout implements Action -{ - private $session; - private $redirect; - - public function __construct( - Session $sess = null, - Action $redirect = null // Request $req = null , PDO $db, Config $config, Env $env, Session $sess, Tpl $tpl - ) { - $this->session = $sess ?? new AppSession; - $this->redirect = $redirect ?? new ActRedirect('/'); -// $this->rqGet = $req ?? new RqGET(); -// $this->db = $db ?? new AppPDO; -// $this->config = $config ?? new AppConfig; -// $this->env = $env ?? new AppEnv; -// $this->tpl = $tpl ?? new AppTpl; - } - - public function handle(Response $resp): Response - { - if ($this->session->exists('login')) { // login and password ok - $this->session->unset('login'); - return $this->redirect->handle($resp); - } - - return $this->response->withBody( - "You are not logged in." - ); - } -} - - - -class ActLk implements Action -{ - private $sess; - private $redirect; - - public function __construct( - Session $sess = null - //Action $redirect = null // Request $req = null , PDO $db, Config $config, Env $env, Session $sess, Tpl $tpl - ) { - $this->sess = $sess ?? new AppSession; - //$this->redirect = $redirect ?? new ActRedirect('/'); -// $this->rqGet = $req ?? new RqGET(); -// $this->db = $db ?? new AppPDO; -// $this->config = $config ?? new AppConfig; -// $this->env = $env ?? new AppEnv; -// $this->tpl = $tpl ?? new AppTpl; - } - - public function handle(Response $resp): Response - { - if (! $this->sess->exists('login')) { // login and password ok - throw new AccessDeniedException('Only Authorized Access'); - } - - return $resp->withBody( - "Hello: " . $this->sess->get('login') - ); - } -} - - +require_once './vendor/autoload.php'; + +use AG\WebApp\App; +use AG\WebApp\AccessDeniedException; +use AG\WebApp\NotFoundException; +use AG\WebApp\ApplicationStd; +use AG\WebApp\Request; +use AG\WebApp\Request\RqGET; +use AG\WebApp\Request\RqPOST; +use AG\WebApp\Response; +use AG\WebApp\Response\RespStd; +use AG\WebApp\Action; +use AG\WebApp\Action\ActRedirect; +use AG\WebApp\Session; +use AG\WebApp\Session\AppSession; + +use ExampleApp\ActIndex; +use ExampleApp\ActLogin; +use ExampleApp\ActLogout; +use ExampleApp\ActLk; (new ApplicationStd( [ @@ -382,12 +35,5 @@ class ActLk implements Action '/logout' => new ActLogout(), '/lk' => new ActLk(), ], - new RespStd // AppResponse - /* - new class() implements Request { - public function param($param) { - return '/login'; - } - } - */ + new RespStd ))->start(); diff --git a/src/AccessDeniedException.php b/src/AccessDeniedException.php new file mode 100644 index 0000000000000000000000000000000000000000..e88de677d13691745bda10cc85dddbc8668a283b --- /dev/null +++ b/src/AccessDeniedException.php @@ -0,0 +1,8 @@ +<?php +namespace AG\WebApp; + +use Exception; + +class AccessDeniedException extends Exception +{ +} diff --git a/src/Action.php b/src/Action.php new file mode 100644 index 0000000000000000000000000000000000000000..d995027ba809c3f38518f3312c87792c76a5efa8 --- /dev/null +++ b/src/Action.php @@ -0,0 +1,9 @@ +<?php +namespace AG\WebApp; + +//use AG\Response; + +interface Action +{ + public function handle(Response $resp): Response; +} diff --git a/src/Action/ActRedirect.php b/src/Action/ActRedirect.php new file mode 100644 index 0000000000000000000000000000000000000000..80b6e8fc7b7e743047b2c4e579def77753b3e3a0 --- /dev/null +++ b/src/Action/ActRedirect.php @@ -0,0 +1,23 @@ +<?php + +namespace AG\WebApp\Action; + +use AG\WebApp\Action; +use AG\WebApp\Response; + +class ActRedirect implements Action +{ + private $url; + + public function __construct($url) + { + $this->url = $url; + } + + public function handle(Response $resp): Response + { + return $resp + ->withBody('Redirect: ' . $this->url) + ->withHeaders([ 'Location: ' . $this->url ]); + } +} diff --git a/src/App.php b/src/App.php new file mode 100644 index 0000000000000000000000000000000000000000..93245472f8fc8b3f57189ad8b488be18db84f356 --- /dev/null +++ b/src/App.php @@ -0,0 +1,7 @@ +<?php +namespace AG\WebApp; + +interface App +{ + public function start(): void; +} diff --git a/src/AppPDO.php b/src/AppPDO.php new file mode 100644 index 0000000000000000000000000000000000000000..f23f6b45c66e0e4797a633dc4b323565b7c94b79 --- /dev/null +++ b/src/AppPDO.php @@ -0,0 +1,11 @@ +<?php + +namespace AG\WebApp; + +class AppPDO extends PDO +{ + public function __construct(AppConfig $config) + { +// parent::__construct($dsn, $username, $passwd, $options); + } +} \ No newline at end of file diff --git a/src/ApplicationStd.php b/src/ApplicationStd.php new file mode 100644 index 0000000000000000000000000000000000000000..d3e4bd833399610ea28b14fac68a34ff47f17743 --- /dev/null +++ b/src/ApplicationStd.php @@ -0,0 +1,49 @@ +<?php + +namespace AG\WebApp; + +use Exception; +use AG\WebApp\Request\RqSERVER; + +class ApplicationStd implements App +{ + private $actions; + private $SERVER; + + /** + * Application constructor. + * + * @param Response|array $action + */ + public function __construct(array $actions, Response $response = null, Request $SERVER = null) + { + + $this->SERVER = $SERVER ?? new RqSERVER(); + $this->actions = $actions; + $this->response = $response; // ?? new AppResponse; + } + + public function start(): void + { + try { + $requestUri = $this->SERVER->param('REQUEST_URI'); + + if (! array_key_exists($requestUri, $this->actions)) { + throw new NotFoundException('Page=' . $requestUri . ' not found in actions list!'); + } + + $resp = $this->actions[$requestUri]->handle($this->response); + $resp->print(); + } catch (NotFoundException $e) { + header("Status: 404 Not Found"); + echo '404! ' . $e->getMessage(); + } catch (AccessDeniedException $e) { + header("Status: 403 Access denied"); + echo '403! ' . $e->getMessage(); + } catch (Exception $e) { + header("Status: 500 Application Error"); + echo "<h1>Error</h1>"; + echo "<pre>" . $e . "</pre>"; + } + } +} diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 0000000000000000000000000000000000000000..a89017f33078828e647bf2aa30e0e924557cba8d --- /dev/null +++ b/src/Config.php @@ -0,0 +1,8 @@ +<?php + +namespace AG\WebApp; + +interface Config +{ + //... +} diff --git a/src/NotFoundException.php b/src/NotFoundException.php new file mode 100644 index 0000000000000000000000000000000000000000..f5c42c1f9faec92cd00108f398194bdd0f3f3a70 --- /dev/null +++ b/src/NotFoundException.php @@ -0,0 +1,8 @@ +<?php +namespace AG\WebApp; + +use Exception; + +class NotFoundException extends Exception +{ +} diff --git a/src/Request.php b/src/Request.php new file mode 100644 index 0000000000000000000000000000000000000000..dbc3de511de430f09b0c0a7b3a68a88c4994ffbc --- /dev/null +++ b/src/Request.php @@ -0,0 +1,8 @@ +<?php + +namespace AG\WebApp; + +interface Request +{ + public function param($param); +} diff --git a/src/Request/RqGET.php b/src/Request/RqGET.php new file mode 100644 index 0000000000000000000000000000000000000000..81dfec6226b0624fc5ac5ccb6be696015a6327fc --- /dev/null +++ b/src/Request/RqGET.php @@ -0,0 +1,19 @@ +<?php +namespace AG\WebApp\Request; + +use AG\WebApp\Request; + +class RqGET implements Request +{ + private $request; + + public function __construct(array $request = null) + { + $this->request = $request ?? $_GET; + } + + public function param($param) + { + return $this->request[$param] ?? null; + } +} diff --git a/src/Request/RqPOST.php b/src/Request/RqPOST.php new file mode 100644 index 0000000000000000000000000000000000000000..d5388ecfa912a29c609a3b9569058fe8bb029ba1 --- /dev/null +++ b/src/Request/RqPOST.php @@ -0,0 +1,19 @@ +<?php +namespace AG\WebApp\Request; + +use AG\WebApp\Request; + +class RqPOST implements Request +{ + private $request; + + public function __construct(array $request = null) + { + $this->request = $request ?? $_POST; + } + + public function param($param) + { + return $this->request[$param] ?? null; + } +} diff --git a/src/Request/RqSERVER.php b/src/Request/RqSERVER.php new file mode 100644 index 0000000000000000000000000000000000000000..59929c069f5d26020e5fe1d98972edc8840d15e8 --- /dev/null +++ b/src/Request/RqSERVER.php @@ -0,0 +1,21 @@ +<?php + +namespace AG\WebApp\Request; + +use AG\WebApp\Request; + +class RqSERVER implements Request +{ + + private $request; + + public function __construct(array $request = null) + { + $this->request = $request ?? $_SERVER; + } + + public function param($param) + { + return $this->request[$param] ?? null; + } +} diff --git a/src/Response.php b/src/Response.php new file mode 100644 index 0000000000000000000000000000000000000000..c7aab0dd08c21dadf01ab76f5b8233d51f982830 --- /dev/null +++ b/src/Response.php @@ -0,0 +1,9 @@ +<?php +namespace AG\WebApp; + +interface Response +{ + public function print(): void; + public function withBody(string $body): Response; + public function withHeaders(array $headers): Response; +} diff --git a/src/Response/RespStd.php b/src/Response/RespStd.php new file mode 100644 index 0000000000000000000000000000000000000000..df369b64f7eb4f2e913fcb9ef937c9c55dae9843 --- /dev/null +++ b/src/Response/RespStd.php @@ -0,0 +1,35 @@ +<?php +namespace AG\WebApp\Response; + +use AG\WebApp\Response; + +class RespStd implements Response +{ + private $headers; + private $body; + public function __construct($body = '', $headers = []) + { + $this->body = $body; + $this->headers = $headers; + } + + public function withBody(string $body): Response + { + return new self($body, $this->headers); + } + + public function withHeaders(array $headers): Response + { + return new self($this->body, $headers); + } + + + public function print(): void + { + foreach ($this->headers as $header) { + header($header); + } + + echo $this->body; + } +} diff --git a/src/Session.php b/src/Session.php new file mode 100644 index 0000000000000000000000000000000000000000..ab60aea14b9e4d280de71393239ba8dfc9a6663b --- /dev/null +++ b/src/Session.php @@ -0,0 +1,14 @@ +<?php + +namespace AG\WebApp; + +interface Session +{ + public function exists($param): bool; + + public function get($param); + + public function set($param, $value): void; + + public function unset($param): void; +} diff --git a/src/Session/AppSession.php b/src/Session/AppSession.php new file mode 100644 index 0000000000000000000000000000000000000000..a2311c9999c1f0c0dbc361a0f2098d19d672f30c --- /dev/null +++ b/src/Session/AppSession.php @@ -0,0 +1,44 @@ +<?php + +namespace AG\WebApp\Session; + +use AG\WebApp\Session; + +/** + * @todo rename to SessionStd ? + */ +class AppSession implements Session +{ + public function exists($param): bool + { + $this->sessionStart(); + return array_key_exists($param, $_SESSION); + } + + public function get($param) + { + $this->sessionStart(); + return $_SESSION[$param] ?? null; + } + + public function set($param, $value): void + { + $this->sessionStart(); + $_SESSION[$param] = $value; + session_commit(); + } + + public function unset($param): void + { + $this->sessionStart(); + unset($_SESSION[$param]); + session_commit(); + } + + private function sessionStart() + { + if (session_status() == PHP_SESSION_NONE) { + session_start(); + } + } +}