├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── config ├── application.config.php ├── commands.php ├── routes.php └── services.php ├── data └── .gitignore ├── example ├── Action │ ├── Home.php │ └── HomeFactory.php ├── Infrastructure │ ├── CommandHandlerFactory │ │ └── RegisterUserHandlerFactory.php │ ├── Container │ │ └── Zf2InteropContainer.php │ └── Repository │ │ └── FileStorageUserCollection.php └── Model │ └── User │ ├── Email.php │ ├── Exception │ ├── Email.php │ ├── ErrorCode.php │ └── UserName.php │ ├── RegisterUser.php │ ├── RegisterUserHandler.php │ ├── User.php │ └── UserCollection.php ├── public ├── .htaccess └── index.php └── src └── Middleware ├── CommandMiddleware.php └── CommandMiddlewareFactory.php /.gitignore: -------------------------------------------------------------------------------- 1 | nbproject 2 | ._* 3 | .~lock.* 4 | .buildpath 5 | .DS_Store 6 | .idea 7 | .project 8 | .settings 9 | composer.lock 10 | vendor 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015, prooph software GmbH 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the prooph software GmbH nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NoMVC for PHP 2 | No View, No Controller, Just a Model and a Bus 3 | 4 | ## Experimental Package 5 | 6 | The name of the package describes its goal in two simple words. It's obvious that we derived it from NoSql. And like NoSql 7 | we don't really want to say NO MVC but instead NOT ONLY MVC. 8 | 9 | ## What is the problem with MVC? 10 | 11 | There is no problem with MVC. It works great for server-side applications which need to validate forms, process user input and 12 | generate dynamic html pages. But what if our backend only consists of a model and a web API be it REST or RPC? 13 | Of course we already have lightweight alternatives for full stack web frameworks, but in this repository we want to try another approach. 14 | 15 | ## HTTP-Server + Message Bus + Model 16 | 17 | Zend has released a new PSR-7 HTTP Message implementation called [zend-diactoros](https://github.com/zendframework/zend-diactoros). 18 | The lib also ships with a Node.js like HTTP Server. That will be our front controller. The idea is now to translate 19 | incoming HTTP messages into command, event or query messages and dispatch them via [prooph/service-bus](https://github.com/prooph/service-bus). 20 | It is a proof of concept to find out how well both tools work together and if it is possible to put them in a [reactphp event loop](https://github.com/reactphp/event-loop). 21 | The second concept we want to try out is to define a web API with this new [HTTPApiDoc definition](https://github.com/Philiagus/httpapidoc). 22 | The result should be a swagger like documentation of the API and additionally a request object generator on client-side and a message object 23 | generator on server-side. We hope that thus two tools will simplify the communication set up on both ends so that you can focus on user experience 24 | client-side and consistent business logic server-side. 25 | 26 | If you want to join the experiment, just drop us an email or watch the project! We appreciate any help and input. 27 | 28 | ## Installation 29 | `git clone` the repository and run `composer install`. 30 | 31 | ## Run the http example 32 | `cd` into the root of the cloned repository. 33 | Start the internal PHP web server with the command `php -S 0.0.0.0:8000 -t public` and send a `http POST request` to 34 | `http://localhost:8000/api/commands/RegisterUser` with following request body 35 | ```json 36 | { 37 | "name" : "Jane Doe", 38 | "email" : "doe@acme.com" 39 | } 40 | ``` 41 | 42 | If everything was set up correctly you should get a `202 Accepted` response without a response body. 43 | You can verify that your user was registered by looking into the file `data/users.json`. 44 | 45 | ## Help 46 | 47 | If you need any help or one of the examples doesn't work feel free to open an [issue](https://github.com/prooph/no-mvc/issues). 48 | Same if you have a suggestion or want to give feedback. 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prooph/no-mvc", 3 | "description": "NoMVC for PHP", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "prooph", 7 | "no-mvc", 8 | "messaging" 9 | ], 10 | "homepage": "https://github.com/prooph/no-mvc", 11 | "repositories": [ 12 | { 13 | "type": "vcs", 14 | "url": "https://github.com/ezimuel/zend-stratigility-dispatch" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=5.5", 19 | "prooph/proophessor": "~0.5", 20 | "zendframework/zend-diactoros" : "~1.0", 21 | "zendframework/zend-stratigility": "~1.0", 22 | "zendframework/zend-stratigility-dispatch": "dev-master", 23 | "zendframework/zend-servicemanager" : "~2.4" 24 | }, 25 | "require-dev" : { 26 | "phpspec/phpspec": "~2.0" 27 | }, 28 | "bin-dir" : "./bin", 29 | "autoload": { 30 | "psr-4": { 31 | "Prooph\\NoMvc\\": "src", 32 | "ProophExample\\NoMvc\\": "example" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /config/application.config.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 8:26 PM 10 | */ 11 | return [ 12 | 'proophessor' => [ 13 | 'command_bus' => [ 14 | 'prooph.psb.command_router', 15 | 'prooph.psb.service_locator_proxy', 16 | 'prooph.psb.handle_command_invoke_strategy', 17 | ], 18 | 'command_router_map' => [ 19 | \ProophExample\NoMvc\Model\User\RegisterUser::class => \ProophExample\NoMvc\Model\User\RegisterUserHandler::class, 20 | ], 21 | ] 22 | ]; -------------------------------------------------------------------------------- /config/commands.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 7:24 PM 10 | */ 11 | return [ 12 | '/api/commands/RegisterUser' => \ProophExample\NoMvc\Model\User\RegisterUser::class . '::fromPayload', 13 | ]; -------------------------------------------------------------------------------- /config/routes.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 7:24 PM 10 | */ 11 | return [ 12 | 'routes' => [ 13 | 'home' => [ 14 | 'url' => '/', 15 | 'action' => 'ProophExample\NoMvc\Action\HomeFactory::factory', 16 | ], 17 | 'commands' => [ 18 | 'url' => '/api/commands/RegisterUser', 19 | 'action' => \Prooph\NoMvc\Middleware\CommandMiddleware::class, 20 | ] 21 | ] 22 | ]; -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 7:24 PM 10 | */ 11 | return [ 12 | 'invokables' => [ 13 | 'prooph.psb.handle_command_invoke_strategy' => \Prooph\ServiceBus\InvokeStrategy\HandleCommandStrategy::class, 14 | ], 15 | 'factories' => [ 16 | \Prooph\NoMvc\Middleware\CommandMiddleware::class => \Prooph\NoMvc\Middleware\CommandMiddlewareFactory::class, 17 | \ProophExample\NoMvc\Model\User\RegisterUserHandler::class => \ProophExample\NoMvc\Infrastructure\CommandHandlerFactory\RegisterUserHandlerFactory::class, 18 | 'proophessor.command_bus' => \Prooph\Proophessor\ServiceBus\CommandBusFactory::class, 19 | 'prooph.psb.command_router' => \Prooph\Proophessor\ServiceBus\CommandRouterFactory::class, 20 | 'prooph.psb.service_locator_proxy' => \Prooph\Proophessor\ServiceBus\ServiceLocatorProxyFactory::class, 21 | ] 22 | ]; -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /example/Action/Home.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 5:58 PM 10 | */ 11 | namespace ProophExample\NoMvc\Action; 12 | 13 | use Psr\Http\Message\ResponseInterface; 14 | use Psr\Http\Message\ServerRequestInterface; 15 | 16 | final class Home 17 | { 18 | public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) 19 | { 20 | $response->getBody()->write('Prooph NoMVC Example'); 21 | 22 | return $response; 23 | } 24 | } -------------------------------------------------------------------------------- /example/Action/HomeFactory.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 6:01 PM 10 | */ 11 | namespace ProophExample\NoMvc\Action; 12 | 13 | use Psr\Http\Message\ResponseInterface; 14 | use Psr\Http\Message\ServerRequestInterface; 15 | 16 | final class HomeFactory 17 | { 18 | public static function factory(ServerRequestInterface $request, ResponseInterface $response, callable $next) 19 | { 20 | $home = new Home(); 21 | return $home($request, $response, $next); 22 | } 23 | } -------------------------------------------------------------------------------- /example/Infrastructure/CommandHandlerFactory/RegisterUserHandlerFactory.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 9:38 PM 10 | */ 11 | namespace ProophExample\NoMvc\Infrastructure\CommandHandlerFactory; 12 | 13 | 14 | use ProophExample\NoMvc\Infrastructure\Repository\FileStorageUserCollection; 15 | use ProophExample\NoMvc\Model\User\RegisterUserHandler; 16 | use Zend\ServiceManager\FactoryInterface; 17 | use Zend\ServiceManager\ServiceLocatorInterface; 18 | 19 | final class RegisterUserHandlerFactory implements FactoryInterface 20 | { 21 | 22 | /** 23 | * Create service 24 | * 25 | * @param ServiceLocatorInterface $serviceLocator 26 | * @return mixed 27 | */ 28 | public function createService(ServiceLocatorInterface $serviceLocator) 29 | { 30 | return new RegisterUserHandler(new FileStorageUserCollection()); 31 | } 32 | } -------------------------------------------------------------------------------- /example/Infrastructure/Container/Zf2InteropContainer.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 7:22 PM 10 | */ 11 | namespace ProophExample\NoMvc\Infrastructure\Container; 12 | 13 | use Interop\Container\ContainerInterface; 14 | use Zend\ServiceManager\ServiceManager; 15 | 16 | class Zf2InteropContainer extends ServiceManager implements ContainerInterface 17 | { 18 | 19 | } -------------------------------------------------------------------------------- /example/Infrastructure/Repository/FileStorageUserCollection.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 9:30 PM 10 | */ 11 | namespace ProophExample\NoMvc\Infrastructure\Repository; 12 | 13 | use ProophExample\NoMvc\Model\User\User; 14 | use ProophExample\NoMvc\Model\User\UserCollection; 15 | 16 | final class FileStorageUserCollection implements UserCollection 17 | { 18 | private $folder = 'data'; 19 | 20 | private $fileName = 'users.json'; 21 | 22 | private $file; 23 | 24 | /** 25 | * @var [] 26 | */ 27 | private $users; 28 | 29 | public function __construct() 30 | { 31 | if (! is_writable($this->folder)) { 32 | throw new \Exception("Folder {$this->folder} is not writable"); 33 | } 34 | 35 | $this->file = $this->folder . DIRECTORY_SEPARATOR . $this->fileName; 36 | 37 | if (! file_exists($this->file)) { 38 | $this->users = []; 39 | return; 40 | } 41 | 42 | if (! is_readable($this->file)) { 43 | throw new \Exception("File {$this->file} is not readable"); 44 | } 45 | 46 | $this->users = json_decode(file_get_contents($this->file), true); 47 | } 48 | 49 | public function add(User $user) 50 | { 51 | $this->users[] = [ 52 | 'name' => $user->name(), 53 | 'email' => $user->email()->toString() 54 | ]; 55 | 56 | file_put_contents($this->file, json_encode($this->users)); 57 | } 58 | } -------------------------------------------------------------------------------- /example/Model/User/Email.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 9:14 PM 10 | */ 11 | namespace ProophExample\NoMvc\Model\User; 12 | 13 | 14 | final class Email 15 | { 16 | /** 17 | * @var string 18 | */ 19 | private $email; 20 | 21 | public static function fromString($email) 22 | { 23 | if (! is_string($email)) { 24 | throw Exception\Email::isMissing(); 25 | } 26 | 27 | if (! filter_var($email, FILTER_VALIDATE_EMAIL)) { 28 | throw Exception\Email::isNotValid(); 29 | } 30 | 31 | return new self($email); 32 | } 33 | 34 | private function __construct($email) 35 | { 36 | $this->email = $email; 37 | } 38 | 39 | /** 40 | * @return string 41 | */ 42 | public function toString() 43 | { 44 | return $this->email; 45 | } 46 | } -------------------------------------------------------------------------------- /example/Model/User/Exception/Email.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 10:02 PM 10 | */ 11 | namespace ProophExample\NoMvc\Model\User\Exception; 12 | 13 | 14 | final class Email extends \InvalidArgumentException 15 | { 16 | /** 17 | * @return UserName 18 | */ 19 | public static function isMissing() 20 | { 21 | return new self('Email is missing', ErrorCode::INVALID_ARGUMENT); 22 | } 23 | 24 | /** 25 | * @return Email 26 | */ 27 | public static function isNotValid() 28 | { 29 | return new self('Email is not valid', ErrorCode::INVALID_ARGUMENT); 30 | } 31 | } -------------------------------------------------------------------------------- /example/Model/User/Exception/ErrorCode.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 9:59 PM 10 | */ 11 | namespace ProophExample\NoMvc\Model\User\Exception; 12 | 13 | 14 | final class ErrorCode 15 | { 16 | const ENTITY_NOT_FOUND = 404; 17 | const INVALID_ARGUMENT = 422; 18 | } -------------------------------------------------------------------------------- /example/Model/User/Exception/UserName.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 9:55 PM 10 | */ 11 | namespace ProophExample\NoMvc\Model\User\Exception; 12 | 13 | 14 | use Prooph\NoMvc\Model\Definition; 15 | 16 | final class UserName extends \InvalidArgumentException 17 | { 18 | /** 19 | * @return UserName 20 | */ 21 | public static function isMissing() 22 | { 23 | return new self('User name is missing', ErrorCode::INVALID_ARGUMENT); 24 | } 25 | 26 | /** 27 | * @return UserName 28 | */ 29 | public static function isNotValid() 30 | { 31 | return new self('User name is not valid', ErrorCode::INVALID_ARGUMENT); 32 | } 33 | } -------------------------------------------------------------------------------- /example/Model/User/RegisterUser.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 9:54 PM 10 | */ 11 | namespace ProophExample\NoMvc\Model\User; 12 | 13 | 14 | use Prooph\Common\Messaging\Command; 15 | use ProophExample\NoMvc\Model\User\Exception\UserName; 16 | 17 | final class RegisterUser extends Command 18 | { 19 | /** 20 | * @param array $payload 21 | * @return RegisterUser 22 | * @throws Exception\UserName 23 | */ 24 | public static function fromPayload(array $payload) 25 | { 26 | if (! isset($payload['name'])) { 27 | throw UserName::isMissing(); 28 | } 29 | 30 | if (! isset($payload['email'])) { 31 | throw Exception\Email::isMissing(); 32 | } 33 | 34 | return new self(__CLASS__, $payload); 35 | } 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function userName() 41 | { 42 | return (string)$this->payload['name']; 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function email() 49 | { 50 | return (string)$this->payload['email']; 51 | } 52 | } -------------------------------------------------------------------------------- /example/Model/User/RegisterUserHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 9:07 PM 10 | */ 11 | namespace ProophExample\NoMvc\Model\User; 12 | 13 | 14 | final class RegisterUserHandler 15 | { 16 | /** 17 | * @var UserCollection 18 | */ 19 | private $userCollection; 20 | 21 | public function __construct(UserCollection $userCollection) 22 | { 23 | $this->userCollection = $userCollection; 24 | } 25 | 26 | public function handle(RegisterUser $command) 27 | { 28 | $user = User::registerWithData($command->userName(), Email::fromString($command->email())); 29 | 30 | $this->userCollection->add($user); 31 | } 32 | } -------------------------------------------------------------------------------- /example/Model/User/User.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 9:14 PM 10 | */ 11 | namespace ProophExample\NoMvc\Model\User; 12 | 13 | use ProophExample\NoMvc\Model\User\Exception\UserName; 14 | 15 | final class User 16 | { 17 | /** 18 | * @var string 19 | */ 20 | private $name; 21 | 22 | /** 23 | * @var Email 24 | */ 25 | private $email; 26 | 27 | /** 28 | * @param string $name 29 | * @param Email $email 30 | * @return User 31 | */ 32 | public static function registerWithData($name, Email $email) 33 | { 34 | return new self($name, $email); 35 | } 36 | 37 | private function __construct($name, Email $email) 38 | { 39 | $this->setName($name); 40 | $this->setEmail($email); 41 | } 42 | 43 | /** 44 | * @return string 45 | */ 46 | public function name() 47 | { 48 | return $this->name; 49 | } 50 | 51 | /** 52 | * @return Email 53 | */ 54 | public function email() 55 | { 56 | return $this->email; 57 | } 58 | 59 | private function setEmail(Email $email) 60 | { 61 | $this->email = $email; 62 | } 63 | 64 | private function setName($name) 65 | { 66 | if (! is_string($name) || empty($name)) { 67 | throw UserName::isNotValid(); 68 | } 69 | 70 | $this->name = $name; 71 | } 72 | } -------------------------------------------------------------------------------- /example/Model/User/UserCollection.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 9:26 PM 10 | */ 11 | namespace ProophExample\NoMvc\Model\User; 12 | 13 | 14 | interface UserCollection 15 | { 16 | public function add(User $user); 17 | } -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | # The following rule tells Apache that if the requested filename 3 | # exists, simply serve it. 4 | RewriteCond %{REQUEST_FILENAME} -s [OR] 5 | RewriteCond %{REQUEST_FILENAME} -l [OR] 6 | RewriteCond %{REQUEST_FILENAME} -d 7 | RewriteRule ^.*$ - [NC,L] 8 | # The following rewrites all other queries to index.php. The 9 | # condition ensures that if you are using Apache aliases to do 10 | # mass virtual hosting, the base path will be prepended to 11 | # allow proper resolution of the index.php file; it will work 12 | # in non-aliased environments as well, providing a safe, one-size 13 | # fits all solution. 14 | RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$ 15 | RewriteRule ^(.*) - [E=BASE:%1] 16 | RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L] 17 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 5/28/15 - 11:26 PM 10 | */ 11 | 12 | /** 13 | * This makes our life easier when dealing with paths. Everything is relative 14 | * to the application root now. 15 | */ 16 | chdir(dirname(__DIR__)); 17 | 18 | // Setup autoloading 19 | require 'vendor/autoload.php'; 20 | 21 | 22 | $smConfig = new \Zend\ServiceManager\Config(require 'config/services.php'); 23 | $serviceManager = new \ProophExample\NoMvc\Infrastructure\Container\Zf2InteropContainer($smConfig); 24 | 25 | $serviceManager->setService('config', require('config/application.config.php')); 26 | 27 | $app = new \Zend\Stratigility\MiddlewarePipe(); 28 | 29 | $dispatcher = \Zend\Stratigility\Dispatch\MiddlewareDispatch::factory(require 'config/routes.php'); 30 | 31 | $dispatcher->setContainer($serviceManager); 32 | 33 | $app->pipe('/', $dispatcher); 34 | 35 | // Run the server! 36 | $server = \Zend\Diactoros\Server::createServer( 37 | $app, 38 | $_SERVER, 39 | $_GET, 40 | $_POST, 41 | $_COOKIE, 42 | $_FILES 43 | ); 44 | 45 | $server->listen(); 46 | -------------------------------------------------------------------------------- /src/Middleware/CommandMiddleware.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 7:31 PM 10 | */ 11 | namespace Prooph\NoMvc\Middleware; 12 | 13 | use Prooph\ServiceBus\CommandBus; 14 | use Prooph\ServiceBus\Exception\CommandDispatchException; 15 | use Psr\Http\Message\RequestInterface; 16 | use Psr\Http\Message\ResponseInterface; 17 | 18 | class CommandMiddleware 19 | { 20 | /** 21 | * @var \Prooph\ServiceBus\CommandBus 22 | */ 23 | private $commandBus; 24 | 25 | /** 26 | * @var [] 27 | */ 28 | private $commands; 29 | 30 | public function __construct(CommandBus $commandBus, array $commands) 31 | { 32 | $this->commandBus = $commandBus; 33 | $this->commands = $commands; 34 | } 35 | 36 | public function __invoke(RequestInterface $request, ResponseInterface $response, callable $next) 37 | { 38 | $path = $request->getUri()->getPath(); 39 | 40 | if (! isset($this->commands[$path])) { 41 | return $next($request, $response); 42 | } 43 | 44 | if ($request->getMethod() !== 'POST') { 45 | return $response->withStatus(405); 46 | } 47 | 48 | try { 49 | 50 | $payload = $this->getPayloadFromRequest($request); 51 | 52 | $command = $this->commands[$path]; 53 | 54 | if (! is_callable($command)) { 55 | throw new \RuntimeException("Command associated with the path $path is not callable"); 56 | } 57 | 58 | $command = call_user_func($command, $payload); 59 | 60 | $this->commandBus->dispatch($command); 61 | 62 | return $response->withStatus(202); 63 | } catch (CommandDispatchException $dispatchException) { 64 | $e = $dispatchException->getFailedCommandDispatch()->getException(); 65 | 66 | return $this->populateError($response, $e); 67 | } catch (\Exception $e) { 68 | return $this->populateError($response, $e); 69 | } 70 | } 71 | 72 | /** 73 | * Get request payload from request object. 74 | * 75 | * @todo check $request->getHeaderLine('content-type') ?? 76 | * 77 | * @param RequestInterface $request 78 | * @return array 79 | * 80 | * @throws \Exception 81 | */ 82 | private function getPayloadFromRequest($request) 83 | { 84 | $payload = json_decode($request->getBody(), true); 85 | 86 | switch (json_last_error()) { 87 | case JSON_ERROR_DEPTH: 88 | throw new \Exception('Invalid JSON, maximum stack depth exceeded.', 400); 89 | case JSON_ERROR_UTF8: 90 | throw new \Exception('Malformed UTF-8 characters, possibly incorrectly encoded.', 400); 91 | case JSON_ERROR_SYNTAX: 92 | case JSON_ERROR_CTRL_CHAR: 93 | case JSON_ERROR_STATE_MISMATCH: 94 | throw new \Exception('Invalid JSON.', 400); 95 | } 96 | 97 | return is_null($payload) ? [] : $payload; 98 | } 99 | 100 | /** 101 | * @param \Exception $e 102 | * @return int 103 | */ 104 | private function getStatusErrorCodeFromException(\Exception $e) 105 | { 106 | $code = $e->getCode(); 107 | 108 | if ($code >= 400 or $code < 500) { 109 | return $code; 110 | } 111 | 112 | return 500; 113 | } 114 | 115 | /** 116 | * @param ResponseInterface $response 117 | * @param \Exception $e 118 | * @return ResponseInterface 119 | */ 120 | private function populateError(ResponseInterface $response, \Exception $e) 121 | { 122 | $response = $response->withStatus($this->getStatusErrorCodeFromException($e)); 123 | $response->getBody()->write(json_encode(['message' => $e->getMessage()])); 124 | return $response; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Middleware/CommandMiddlewareFactory.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Date: 7/5/15 - 8:23 PM 10 | */ 11 | namespace Prooph\NoMvc\Middleware; 12 | 13 | use Zend\ServiceManager\FactoryInterface; 14 | use Zend\ServiceManager\ServiceLocatorInterface; 15 | 16 | class CommandMiddlewareFactory implements FactoryInterface 17 | { 18 | /** 19 | * Create service 20 | * 21 | * @param ServiceLocatorInterface $serviceLocator 22 | * @return mixed 23 | */ 24 | public function createService(ServiceLocatorInterface $serviceLocator) 25 | { 26 | return new CommandMiddleware($serviceLocator->get('proophessor.command_bus'), require('config/commands.php')); 27 | } 28 | } --------------------------------------------------------------------------------