├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── config ├── env.php ├── errors.php ├── injection │ ├── commandbus.php │ ├── http.php │ ├── oauth.php │ ├── routing.php │ ├── session.php │ └── templates.php └── injector.php ├── example.env ├── example └── README.md ├── phpunit.xml.dist ├── public ├── css │ └── layout.css ├── index.php └── js │ └── ie10-viewport-bug-workaround.js ├── src ├── Authorization.php ├── Domain │ ├── FetchFollowersCommand.php │ ├── FetchFollowersHandler.php │ ├── FetchRepositoriesCommand.php │ ├── FetchRepositoriesHandler.php │ ├── FetchStarsCommand.php │ ├── FetchStarsHandler.php │ ├── FetchTokenCommand.php │ ├── FetchTokenHandler.php │ ├── LoginCommand.php │ ├── LoginHandler.php │ └── LoginState.php ├── Login │ ├── LoginBeginController.php │ ├── LoginCompleteController.php │ ├── LoginController.php │ └── LoginView.php ├── Logout │ ├── LogoutController.php │ └── LogoutView.php └── Profile │ ├── ProfileController.php │ └── ProfileView.php └── templates ├── layout.phtml ├── login.phtml └── profile.phtml /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | composer.lock 3 | /vendor/ 4 | /build/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Woody Gilk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MVC Examples 2 | 3 | Examples for the talk [MVC to ADR](https://github.com/shadowhand/mvc-to-adr-talk): 4 | 5 | - Github login via [oauth2-github](https://github.com/thephpleague/oauth2-github) 6 | - Show a list of your repos 7 | - Show a list of your followers 8 | - Show a list of your stars 9 | 10 | ## Using 11 | 12 | Install all dependencies: 13 | 14 | ```php 15 | composer install 16 | ``` 17 | 18 | [Register a new application](https://github.com/settings/applications/new) with 19 | the following settings: 20 | 21 | - Homepage URL: `http://localhost:8000/` 22 | - Authorization callback URL: `http://localhost:8000/login/complete` 23 | 24 | Then copy the Client ID and Secret into a `.env` file: 25 | 26 | ``` 27 | GITHUB_CLIENT_ID=abc 28 | GITHUB_CLIENT_SECRET=xyz 29 | ``` 30 | 31 | Start a local development server: 32 | 33 | ```php 34 | php -S localhost:8000 -t public public/index.php 35 | ``` 36 | 37 | Open and login. 38 | 39 | ## License 40 | 41 | MIT License. 42 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "project", 3 | "license": "MIT", 4 | "authors": [ 5 | { 6 | "name": "Woody Gilk", 7 | "email": "woody.gilk@gmail.com" 8 | } 9 | ], 10 | "config": { 11 | "sort-packages": true 12 | }, 13 | "minimum-stability": "stable", 14 | "require": { 15 | "php": ">=7.1", 16 | "equip/session": "^2.0", 17 | "filp/whoops": "^2.1", 18 | "http-interop/http-factory-diactoros": "^0.2.0", 19 | "http-interop/response-sender": "^1.0", 20 | "josegonzalez/dotenv": "^3.0", 21 | "league/oauth2-github": "^2.0", 22 | "league/plates": "^3.3", 23 | "league/tactician": "^1.0", 24 | "rdlowrey/auryn": "^1.4", 25 | "shadowhand/cairon": "^0.1.0", 26 | "shadowhand/either-way": "^1.0" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^5.6" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Demo\\": "src/" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /config/env.php: -------------------------------------------------------------------------------- 1 | parse(); 8 | 9 | // We need Github credentials to be defined. 10 | $loader->expect('GITHUB_CLIENT_ID', 'GITHUB_CLIENT_SECRET'); 11 | 12 | // Make variables available to getenv(). 13 | $loader->putEnv(false); 14 | -------------------------------------------------------------------------------- /config/errors.php: -------------------------------------------------------------------------------- 1 | pushHandler(new Whoops\Handler\PrettyPageHandler()); 11 | 12 | // Activate error/exception handling. 13 | $whoops->register(); 14 | -------------------------------------------------------------------------------- /config/injection/commandbus.php: -------------------------------------------------------------------------------- 1 | share(CommandBus::class); 21 | 22 | // Commands are referenced by class name. 23 | $injector->alias(CommandNameExtractor::class, ClassNameExtractor::class); 24 | 25 | // Handlers have a handle() method. 26 | $injector->alias(MethodNameInflector::class, HandleInflector::class); 27 | 28 | // Use an anonymous class to defer creation of command handlers. 29 | $injector->delegate(HandlerLocator::class, function () use ($injector) { 30 | return new class($injector) implements HandlerLocator 31 | { 32 | private $injector; 33 | 34 | public function __construct(Injector $injector) 35 | { 36 | $this->injector = $injector; 37 | } 38 | 39 | public function getHandlerForCommand($command) 40 | { 41 | // FooCommand -> FooHandler 42 | $handler = preg_replace('/Command$/', 'Handler', $command); 43 | if (!class_exists($handler)) { 44 | throw MissingHandlerException::forCommand($command); 45 | } 46 | return $this->injector->make($handler); 47 | } 48 | }; 49 | }); 50 | 51 | // Create command bus middleware by delegation. 52 | $injector->delegate(CommandBus::class, function ( 53 | CommandHandlerMiddleware $handlerMiddleware 54 | ) { 55 | return new CommandBus([ 56 | $handlerMiddleware, 57 | ]); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /config/injection/http.php: -------------------------------------------------------------------------------- 1 | share(ResponseFactoryInterface::class); 31 | $injector->share(ServerRequestFactoryInterface::class); 32 | $injector->share(StreamFactoryInterface::class); 33 | 34 | // Provide implementation for PSR-17. 35 | $injector->alias(ResponseFactoryInterface::class, ResponseFactory::class); 36 | $injector->alias(ServerRequestFactoryInterface::class, ServerRequestFactory::class); 37 | $injector->alias(StreamFactoryInterface::class, StreamFactory::class); 38 | 39 | // ServerRequest can be shared globally. 40 | $injector->share(ServerRequestInterface::class); 41 | 42 | // Use HTTP factory to create the ServerRequest. 43 | $injector->delegate(ServerRequestInterface::class, function (ServerRequestFactoryInterface $factory) { 44 | return $factory->createServerRequest($_SERVER); 45 | }); 46 | 47 | // Use HTTP factory to create Responses. 48 | $injector->delegate(ResponseInterface::class, function (ResponseFactoryInterface $factory) { 49 | return $factory->createResponse(); 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /config/injection/oauth.php: -------------------------------------------------------------------------------- 1 | share(Github::class); 12 | 13 | // Define credentials for Github OAuth. 14 | $injector->define(Github::class, [ 15 | ':options' => [ 16 | 'clientId' => getenv('GITHUB_CLIENT_ID'), 17 | 'clientSecret' => getenv('GITHUB_CLIENT_SECRET'), 18 | ], 19 | ]); 20 | }; 21 | -------------------------------------------------------------------------------- /config/injection/routing.php: -------------------------------------------------------------------------------- 1 | delegate(Dispatcher::class, function () { 16 | // https://github.com/nikic/FastRoute#defining-routes 17 | return simpleDispatcher(function (RouteCollector $r) { 18 | $r->get('/', Profile\ProfileController::class); 19 | 20 | $r->addGroup('/login', function (RouteCollector $r) { 21 | $r->get('', Login\LoginController::class); 22 | $r->post('', Login\LoginBeginController::class); 23 | $r->get('/complete', Login\LoginCompleteController::class); 24 | }); 25 | 26 | $r->post('/logout', Logout\LogoutController::class); 27 | }); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /config/injection/session.php: -------------------------------------------------------------------------------- 1 | share(SessionInterface::class); 15 | 16 | // Use native sessions. 17 | $injector->alias(SessionInterface::class, NativeSession::class); 18 | }; 19 | -------------------------------------------------------------------------------- /config/injection/templates.php: -------------------------------------------------------------------------------- 1 | define(Engine::class, [ 12 | ':directory' => realpath(__DIR__ . '/../../templates'), 13 | ':fileExtension' => 'phtml', 14 | ]); 15 | }; 16 | -------------------------------------------------------------------------------- /config/injector.php: -------------------------------------------------------------------------------- 1 | configure([ 10 | require 'injection/commandbus.php', 11 | require 'injection/http.php', 12 | require 'injection/oauth.php', 13 | require 'injection/routing.php', 14 | require 'injection/session.php', 15 | require 'injection/templates.php', 16 | ]) 17 | ->injector(); 18 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID=abc 2 | GITHUB_CLIENT_SECRET=xyz 3 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Cove Example 2 | 3 | The front controller is located in `public/index.php`. 4 | 5 | All controllers are classes with `__invoke` defined as: 6 | 7 | ```php 8 | public function __invoke(ServerRequestInterface $request): ResponseInterface; 9 | ``` 10 | 11 | ## Local Server 12 | 13 | ```bash 14 | php -S localhost:8000 -t public public/index.php 15 | ``` 16 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./tests 5 | 6 | 7 | 8 | 9 | ./src 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/css/layout.css: -------------------------------------------------------------------------------- 1 | /* Space out content a bit */ 2 | body { 3 | padding-top: 1.5rem; 4 | padding-bottom: 1.5rem; 5 | } 6 | 7 | a[href], 8 | button { 9 | cursor: pointer; 10 | } 11 | 12 | /* Everything but the jumbotron gets side spacing for mobile first views */ 13 | .header, 14 | .marketing, 15 | .footer { 16 | padding-right: 1rem; 17 | padding-left: 1rem; 18 | } 19 | 20 | /* Custom page header */ 21 | .header { 22 | padding-bottom: 1rem; 23 | border-bottom: .05rem solid #e5e5e5; 24 | } 25 | /* Make the masthead heading the same height as the navigation */ 26 | .header h3 { 27 | margin-top: 0; 28 | margin-bottom: 0; 29 | line-height: 3rem; 30 | } 31 | 32 | /* Custom page footer */ 33 | .footer { 34 | padding-top: 1.5rem; 35 | color: #777; 36 | border-top: .05rem solid #e5e5e5; 37 | } 38 | 39 | /* Customize container */ 40 | @media (min-width: 48em) { 41 | .container { 42 | max-width: 66rem; 43 | } 44 | } 45 | .container-narrow > hr { 46 | margin: 2rem 0; 47 | } 48 | 49 | /* Main marketing message and sign up button */ 50 | .jumbotron { 51 | text-align: center; 52 | border-bottom: .05rem solid #e5e5e5; 53 | } 54 | .jumbotron .btn { 55 | padding: .75rem 1.5rem; 56 | font-size: 1.5rem; 57 | } 58 | 59 | /* Supporting marketing content */ 60 | .marketing { 61 | margin: 3rem 0; 62 | } 63 | .marketing p + h4 { 64 | margin-top: 1.5rem; 65 | } 66 | 67 | .followers img { 68 | max-width: 75px; 69 | margin: 0 5px 5px 0; 70 | } 71 | 72 | /* Responsive: Portrait tablets and up */ 73 | @media screen and (min-width: 48em) { 74 | /* Remove the padding we set earlier */ 75 | .header, 76 | .marketing, 77 | .footer { 78 | padding-right: 0; 79 | padding-left: 0; 80 | } 81 | /* Space out the masthead */ 82 | .header { 83 | margin-bottom: 2rem; 84 | } 85 | /* Remove the bottom border on the jumbotron for visual effect */ 86 | .jumbotron { 87 | border-bottom: 0; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | make(Psr\Http\Message\ResponseInterface::class); 28 | $response = $response->withStatus($status); 29 | return $response; 30 | }; 31 | 32 | // Define how valid requests will be transformed to Response. 33 | $successHandler = function (EitherWay\Route $route) use ($injector): Psr\Http\Message\ResponseInterface { 34 | $handler = $injector->make($route->handler()); 35 | $response = $handler($route->request()); 36 | return $response; 37 | }; 38 | 39 | // Route the request and get the error or success. 40 | $response = $injector->execute('EitherWay\dispatch')->either($errorHandler, $successHandler); 41 | 42 | // fin. 43 | Http\Response\send($response); 44 | -------------------------------------------------------------------------------- /public/js/ie10-viewport-bug-workaround.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * IE10 viewport hack for Surface/desktop Windows 8 bug 3 | * Copyright 2014-2017 The Bootstrap Authors 4 | * Copyright 2014-2017 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | */ 7 | 8 | // See the Getting Started docs for more information: 9 | // https://getbootstrap.com/getting-started/#support-ie10-width 10 | 11 | (function () { 12 | 'use strict' 13 | 14 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 15 | var msViewportStyle = document.createElement('style') 16 | msViewportStyle.appendChild( 17 | document.createTextNode( 18 | '@-ms-viewport{width:auto!important}' 19 | ) 20 | ) 21 | document.head.appendChild(msViewportStyle) 22 | } 23 | 24 | }()) 25 | -------------------------------------------------------------------------------- /src/Authorization.php: -------------------------------------------------------------------------------- 1 | session = $session; 28 | $this->responseFactory = $responseFactory; 29 | } 30 | 31 | public function isAuthorized(ServerRequestInterface $request): bool 32 | { 33 | if ($this->session->get('github-token')) { 34 | return true; 35 | } 36 | 37 | return false; 38 | } 39 | 40 | public function redirect(): ResponseInterface 41 | { 42 | return $this->responseFactory 43 | ->createResponse(302) 44 | ->withHeader('Location', '/login'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Domain/FetchFollowersCommand.php: -------------------------------------------------------------------------------- 1 | token; 16 | } 17 | 18 | private $token; 19 | 20 | private function __construct(string $token) 21 | { 22 | $this->token = $token; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Domain/FetchFollowersHandler.php: -------------------------------------------------------------------------------- 1 | github = $github; 18 | } 19 | 20 | public function handle(FetchFollowersCommand $command): array 21 | { 22 | $request = $this->github->getAuthenticatedRequest( 23 | $this->method(), 24 | $this->url(), 25 | $command->token() 26 | ); 27 | 28 | $response = $this->github->getParsedResponse($request); 29 | 30 | return $response; 31 | } 32 | 33 | private function method(): string 34 | { 35 | return 'GET'; 36 | } 37 | 38 | private function url(): string 39 | { 40 | // https://developer.github.com/v3/users/followers/ 41 | return $this->github->apiDomain . '/user/followers'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Domain/FetchRepositoriesCommand.php: -------------------------------------------------------------------------------- 1 | token; 16 | } 17 | 18 | private $token; 19 | 20 | private function __construct(string $token) 21 | { 22 | $this->token = $token; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Domain/FetchRepositoriesHandler.php: -------------------------------------------------------------------------------- 1 | github = $github; 21 | } 22 | 23 | public function handle(FetchRepositoriesCommand $command): array 24 | { 25 | $request = $this->github->getAuthenticatedRequest( 26 | $this->method(), 27 | $this->url(), 28 | $command->token() 29 | ); 30 | 31 | $response = $this->github->getParsedResponse($request); 32 | 33 | return $response; 34 | } 35 | 36 | private function method(): string 37 | { 38 | return 'GET'; 39 | } 40 | 41 | private function url(): string 42 | { 43 | // https://developer.github.com/v3/repos/#list-your-repositories 44 | $params = [ 45 | 'visibility' => 'public', 46 | 'affiliation' => 'owner', 47 | ]; 48 | 49 | return $this->github->apiDomain . '/user/repos?' . $this->buildQueryString($params); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Domain/FetchStarsCommand.php: -------------------------------------------------------------------------------- 1 | token; 16 | } 17 | 18 | private $token; 19 | 20 | private function __construct(string $token) 21 | { 22 | $this->token = $token; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Domain/FetchStarsHandler.php: -------------------------------------------------------------------------------- 1 | github = $github; 18 | } 19 | 20 | public function handle(FetchStarsCommand $command): array 21 | { 22 | $request = $this->github->getAuthenticatedRequest( 23 | $this->method(), 24 | $this->url(), 25 | $command->token() 26 | ); 27 | 28 | $response = $this->github->getParsedResponse($request); 29 | 30 | return $response; 31 | } 32 | 33 | private function method(): string 34 | { 35 | return 'GET'; 36 | } 37 | 38 | private function url(): string 39 | { 40 | // https://developer.github.com/v3/users/:username/starred 41 | return $this->github->apiDomain . '/user/starred'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Domain/FetchTokenCommand.php: -------------------------------------------------------------------------------- 1 | code; 16 | } 17 | 18 | public function state(): string 19 | { 20 | return $this->state; 21 | } 22 | 23 | private $code; 24 | private $state; 25 | 26 | private function __construct(string $code, string $state) 27 | { 28 | $this->code = $code; 29 | $this->state = $state; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Domain/FetchTokenHandler.php: -------------------------------------------------------------------------------- 1 | github = $github; 19 | } 20 | 21 | public function handle(FetchTokenCommand $command): AccessToken 22 | { 23 | // https://github.com/thephpleague/oauth2-github#authorization-code-flow 24 | $params = [ 25 | 'code' => $command->code(), 26 | 'state' => $command->state(), 27 | ]; 28 | 29 | return $this->github->getAccessToken('authorization_code', $params); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Domain/LoginCommand.php: -------------------------------------------------------------------------------- 1 | completeUrl; 16 | } 17 | 18 | public function scopes(): array 19 | { 20 | return ['user', 'repo']; 21 | } 22 | 23 | private $completeUrl; 24 | 25 | private function __construct(string $completeUrl) 26 | { 27 | $this->completeUrl = $completeUrl; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Domain/LoginHandler.php: -------------------------------------------------------------------------------- 1 | github = $github; 18 | } 19 | 20 | public function handle(LoginCommand $command): LoginState 21 | { 22 | // https://github.com/thephpleague/oauth2-github#authorization-code-flow 23 | $url = $this->github->getAuthorizationUrl([ 24 | 'redirect_uri' => $command->completeUrl(), 25 | 'scope' => $command->scopes(), 26 | ]); 27 | 28 | $state = $this->github->getState(); 29 | 30 | return new LoginState($url, $state); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Domain/LoginState.php: -------------------------------------------------------------------------------- 1 | url = $url; 14 | $this->state = $state; 15 | } 16 | 17 | public function state(): string 18 | { 19 | return $this->state; 20 | } 21 | 22 | public function url(): string 23 | { 24 | return $this->url; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Login/LoginBeginController.php: -------------------------------------------------------------------------------- 1 | bus = $bus; 37 | $this->session = $session; 38 | $this->view = $view; 39 | } 40 | 41 | public function __invoke(ServerRequestInterface $request): ResponseInterface 42 | { 43 | $url = $this->loginCompleteUrl($request); 44 | $login = LoginCommand::forUser($url); 45 | $state = $this->bus->handle($login); 46 | 47 | return $this->finish($state); 48 | } 49 | 50 | private function finish(LoginState $login): ResponseInterface 51 | { 52 | $this->session->set('oauth-state', $login->state()); 53 | 54 | return $this->view->redirectTo($login->url()); 55 | } 56 | 57 | private function loginCompleteUrl(ServerRequestInterface $request): string 58 | { 59 | $uri = $request->getUri(); 60 | 61 | return sprintf( 62 | '%s://%s:%s%s/complete', 63 | $uri->getScheme(), 64 | $uri->getHost(), 65 | $uri->getPort(), 66 | $uri->getPath() 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Login/LoginCompleteController.php: -------------------------------------------------------------------------------- 1 | bus = $bus; 36 | $this->session = $session; 37 | $this->view = $view; 38 | } 39 | 40 | public function __invoke(ServerRequestInterface $request): ResponseInterface 41 | { 42 | $code = $this->authCode($request); 43 | $state = $this->authState(); 44 | 45 | if (empty($code) || empty($state)) { 46 | return $this->view->redirectTo('/login'); 47 | } 48 | 49 | $command = FetchTokenCommand::forCode($code, $state); 50 | $token = $this->bus->handle($command); 51 | 52 | return $this->finish($token); 53 | } 54 | 55 | private function finish(AccessToken $token): ResponseInterface 56 | { 57 | $this->session->set('github-token', json_encode($token)); 58 | 59 | return $this->view->redirectTo('/'); 60 | } 61 | 62 | private function authCode(ServerRequestInterface $request): ?string 63 | { 64 | $query = $request->getUri()->getQuery(); 65 | parse_str($query, $params); 66 | 67 | return $params['code'] ?? null; 68 | } 69 | 70 | private function authState(): ?string 71 | { 72 | return $this->session->get('oauth-state'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Login/LoginController.php: -------------------------------------------------------------------------------- 1 | view = $view; 20 | } 21 | 22 | public function __invoke(ServerRequestInterface $request): ResponseInterface 23 | { 24 | return $this->view->showLogin(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Login/LoginView.php: -------------------------------------------------------------------------------- 1 | responseFactory = $responseFactory; 34 | $this->streamFactory = $streamFactory; 35 | $this->templates = $templates; 36 | } 37 | 38 | public function showLogin(): ResponseInterface 39 | { 40 | $content = $this->templates->render('login'); 41 | $body = $this->streamFactory->createStream($content); 42 | return $this->responseFactory->createResponse()->withBody($body); 43 | } 44 | 45 | public function redirectTo(string $target): ResponseInterface 46 | { 47 | return $this->responseFactory->createResponse(302) 48 | ->withHeader('Location', $target); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Logout/LogoutController.php: -------------------------------------------------------------------------------- 1 | view = $view; 27 | $this->session = $session; 28 | } 29 | 30 | public function __invoke(ServerRequestInterface $request): ResponseInterface 31 | { 32 | $this->session->clear(); 33 | 34 | return $this->view->redirectTo('/'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Logout/LogoutView.php: -------------------------------------------------------------------------------- 1 | responseFactory = $responseFactory; 20 | } 21 | 22 | public function redirectTo(string $target): ResponseInterface 23 | { 24 | return $this->responseFactory->createResponse(302) 25 | ->withHeader('Location', $target); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Profile/ProfileController.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 44 | $this->view = $view; 45 | $this->session = $session; 46 | $this->bus = $bus; 47 | } 48 | 49 | public function __invoke(ServerRequestInterface $request): ResponseInterface 50 | { 51 | if (false == $this->auth->isAuthorized($request)) { 52 | return $this->auth->redirect(); 53 | } 54 | 55 | $token = $this->token(); 56 | 57 | $repositories = FetchRepositoriesCommand::forToken($token); 58 | $followers = FetchFollowersCommand::forToken($token); 59 | $stars = FetchStarsCommand::forToken($token); 60 | 61 | return $this->view->render( 62 | $this->bus->handle($repositories), 63 | $this->bus->handle($followers), 64 | $this->bus->handle($stars) 65 | ); 66 | } 67 | 68 | private function token(): string 69 | { 70 | $token = $this->session->get('github-token'); 71 | $token = json_decode($token, true); 72 | 73 | return $token['access_token']; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Profile/ProfileView.php: -------------------------------------------------------------------------------- 1 | responseFactory = $responseFactory; 29 | $this->streamFactory = $streamFactory; 30 | $this->templates = $templates; 31 | } 32 | 33 | public function render(array $repositories, array $followers, array $stars): ResponseInterface 34 | { 35 | $content = $this->templates->render('profile', [ 36 | 'repositories' => $repositories, 37 | 'followers' => $followers, 38 | 'stars' => $stars, 39 | ]); 40 | 41 | $body = $this->streamFactory->createStream($content); 42 | return $this->responseFactory->createResponse()->withBody($body); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /templates/layout.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | My Github Profile 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 34 | 35 |

My Github Profile

36 |
37 | 38 | section('content') ?> 39 | 40 |
41 |

© Woody Gilk 2017

42 |
43 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /templates/login.phtml: -------------------------------------------------------------------------------- 1 | layout('layout') ?> 2 | 3 |
4 |
5 |
6 |

Ready to get started?

7 |

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /templates/profile.phtml: -------------------------------------------------------------------------------- 1 | layout('layout', ['loggedIn' => true]) ?> 2 | 3 |
4 |

MVC to ADR Example

5 |

The source of this application is available on Github.

6 |

Check It Out

7 |
8 | 9 |
10 |
11 |

Public Repos

12 | 17 |
18 | 19 |
20 |

Stars

21 | 26 |
27 | 28 |
29 |

Followers

30 |

31 | 32 | <?= $this->e($user['login']) ?> 36 | 37 |

38 |
39 |
40 | --------------------------------------------------------------------------------