├── .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 |
37 |
38 | = $this->section('content') ?>
39 |
40 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/templates/login.phtml:
--------------------------------------------------------------------------------
1 | layout('layout') ?>
2 |
3 |
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 |
27 |
28 |
29 |
Followers
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------