├── tests ├── temp │ └── .gitignore ├── DI │ ├── default.neon │ ├── settings.neon │ ├── configurators.neon │ ├── lazyConfigurators.neon │ └── SlimExtensionTest.phpt ├── bootstrap.php ├── LazyConfiguratorMock.php ├── ConfiguratorMock.php └── LazyMiddleware.php ├── .gitignore ├── phpstan.neon ├── src ├── Application │ ├── ApplicationConfigurator.php │ ├── ChainApplicationConfigurator.php │ └── ApplicationFactory.php ├── Container │ ├── ContainerException.php │ ├── ServiceNotFoundException.php │ └── ContainerAdapter.php ├── Http │ └── DefaultResponseFactory.php └── DI │ └── SlimExtension.php ├── .travis.yml ├── composer.json ├── LICENSE.md └── README.md /tests/temp/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | composer.lock 4 | -------------------------------------------------------------------------------- /tests/DI/default.neon: -------------------------------------------------------------------------------- 1 | extensions: 2 | slim: Oops\SlimNetteBridge\DI\SlimExtension(%debugMode%) 3 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | excludes_analyse: 3 | - %rootDir%/../../../tests/temp/**/* 4 | 5 | ignoreErrors: 6 | - '#Constant TEMP_DIR not found#' 7 | -------------------------------------------------------------------------------- /tests/DI/settings.neon: -------------------------------------------------------------------------------- 1 | extensions: 2 | slim: Oops\SlimNetteBridge\DI\SlimExtension(%debugMode%) 3 | 4 | slim: 5 | settings: 6 | addContentLengthHeader: false 7 | -------------------------------------------------------------------------------- /tests/DI/configurators.neon: -------------------------------------------------------------------------------- 1 | extensions: 2 | slim: Oops\SlimNetteBridge\DI\SlimExtension(%debugMode%) 3 | 4 | slim: 5 | configurators: 6 | - OopsTests\SlimNetteBridge\ConfiguratorMock 7 | -------------------------------------------------------------------------------- /tests/DI/lazyConfigurators.neon: -------------------------------------------------------------------------------- 1 | extensions: 2 | slim: Oops\SlimNetteBridge\DI\SlimExtension(%debugMode%) 3 | 4 | slim: 5 | configurators: 6 | - OopsTests\SlimNetteBridge\LazyConfiguratorMock 7 | 8 | services: 9 | - OopsTests\SlimNetteBridge\LazyMiddleware 10 | -------------------------------------------------------------------------------- /src/Application/ApplicationConfigurator.php: -------------------------------------------------------------------------------- 1 | get('/whoami', LazyMiddleware::class); 18 | $application->post('/whoami', LazyMiddleware::class . ':post'); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/Http/DefaultResponseFactory.php: -------------------------------------------------------------------------------- 1 | 'text/html; charset=UTF-8']); 20 | $response = new Response(200, $headers); 21 | 22 | return $response->withProtocolVersion($protocolVersion); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/ConfiguratorMock.php: -------------------------------------------------------------------------------- 1 | get('/whoami', function (Http\Message\RequestInterface $request, Http\Message\ResponseInterface $response, array $args): Http\Message\ResponseInterface { 18 | return $response->withStatus(418, "I'm a teapot"); 19 | }); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /tests/LazyMiddleware.php: -------------------------------------------------------------------------------- 1 | withStatus(418, "I'm a teapot"); 16 | } 17 | 18 | 19 | public function post(Http\Message\RequestInterface $request, Http\Message\ResponseInterface $response, array $args): Http\Message\ResponseInterface 20 | { 21 | return $response->withStatus(405, "Don't you try this on me"); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | language: php 4 | 5 | php: 6 | - 7.1 7 | - 7.2 8 | - 7.3 9 | 10 | before_install: 11 | - travis_retry composer self-update 12 | 13 | install: 14 | - travis_retry composer install --no-interaction --prefer-source 15 | - travis_retry composer global require "jakub-onderka/php-parallel-lint:^0.9.0" "phpstan/phpstan-shim:^0.9.0" 16 | 17 | before_script: 18 | - $HOME/.composer/vendor/bin/parallel-lint -e php,phpt --exclude vendor . 19 | - $HOME/.composer/vendor/bin/phpstan.phar analyze --no-progress --no-interaction -c phpstan.neon -l 7 src tests 20 | 21 | script: 22 | - vendor/bin/tester tests 23 | 24 | cache: 25 | directories: 26 | - $HOME/.composer/cache 27 | -------------------------------------------------------------------------------- /src/Application/ChainApplicationConfigurator.php: -------------------------------------------------------------------------------- 1 | configurators[] = $configurator; 22 | } 23 | 24 | 25 | public function configureApplication(App $application): void 26 | { 27 | foreach ($this->configurators as $configurator) { 28 | $configurator->configureApplication($application); 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/Application/ApplicationFactory.php: -------------------------------------------------------------------------------- 1 | container = $container; 28 | $this->configurator = $configurator; 29 | } 30 | 31 | 32 | public function createApplication(): App 33 | { 34 | $app = new App($this->container); 35 | $this->configurator->configureApplication($app); 36 | return $app; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oops/slim-nette-bridge", 3 | "description": "Slim Framework bridge for Nette DI.", 4 | "keywords": ["nette", "di", "slim"], 5 | "type": "library", 6 | "license": "BSD-3-Clause", 7 | "authors": [ 8 | { 9 | "name": "Jiří Pudil", 10 | "email": "me@jiripudil.cz", 11 | "homepage": "https://jiripudil.cz" 12 | } 13 | ], 14 | "support": { 15 | "email": "me@jiripudil.cz", 16 | "issues": "https://github.com/o2ps/SlimNetteBridge/issues" 17 | }, 18 | "require": { 19 | "php": ">= 7.1.0", 20 | "nette/di": "^3.0", 21 | "nette/utils": "^3.0", 22 | "psr/container": "1.0", 23 | "slim/slim": "^3.0" 24 | }, 25 | "require-dev": { 26 | "nette/tester": "^2.2", 27 | "nette/bootstrap": "^3.0" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "Oops\\SlimNetteBridge\\": "src/" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "OopsTests\\SlimNetteBridge\\": "tests/" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Container/ContainerAdapter.php: -------------------------------------------------------------------------------- 1 | prefix = $prefix; 29 | $this->container = $container; 30 | } 31 | 32 | 33 | public function get($id) 34 | { 35 | try { 36 | return \class_exists($id) 37 | ? $this->container->getByType($id) 38 | : $this->container->getService($this->prefix($id)); 39 | 40 | } catch (MissingServiceException $exception) { 41 | throw new ServiceNotFoundException($exception->getMessage(), $exception->getCode(), $exception); 42 | 43 | } catch (\Exception $exception) { 44 | throw new ContainerException($exception->getMessage(), $exception->getCode(), $exception); 45 | } 46 | } 47 | 48 | 49 | public function has($id) 50 | { 51 | return \class_exists($id) 52 | ? (bool) $this->container->getByType($id, FALSE) 53 | : $this->container->hasService($this->prefix($id)); 54 | } 55 | 56 | 57 | private function prefix(string $id): string 58 | { 59 | return $this->prefix . '.' . $id; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Jiří Pudil 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Oops/SlimNetteBridge 2 | 3 | [![Build Status](https://img.shields.io/travis/o2ps/SlimNetteBridge.svg)](https://travis-ci.org/o2ps/SlimNetteBridge) 4 | [![Downloads this Month](https://img.shields.io/packagist/dm/oops/slim-nette-bridge.svg)](https://packagist.org/packages/oops/slim-nette-bridge) 5 | [![Latest stable](https://img.shields.io/packagist/v/oops/slim-nette-bridge.svg)](https://packagist.org/packages/oops/slim-nette-bridge) 6 | 7 | This package helps you quickly build a [Slim Framework](https://www.slimframework.com) application, utilizing the power of [Nette DI container](https://github.com/nette/di). 8 | 9 | 10 | ## THIS PACKAGE IS NO LONGER MAINTAINED! 11 | 12 | As suggested in [#6](https://github.com/o2ps/SlimNetteBridge/issues/6), you can use [slimapi/slimapi](https://github.com/slimapi/slimapi) instead. 13 | 14 | 15 | ## Installation and requirements 16 | 17 | ```bash 18 | $ composer require oops/slim-nette-bridge 19 | ``` 20 | 21 | Oops/SlimNetteBridge requires PHP >= 7.1. 22 | 23 | 24 | ## Usage 25 | 26 | Register the extension in your config file. 27 | 28 | ```yaml 29 | extensions: 30 | slim: Oops\SlimNetteBridge\DI\SlimExtension(%debugMode%) 31 | ``` 32 | 33 | Then configure it: 34 | 35 | ```yaml 36 | slim: 37 | settings: 38 | addContentLengthHeader: false 39 | configurators: 40 | - App\MyConfigurator 41 | ``` 42 | 43 | - `settings` section can be used to override Slim's [default settings](https://www.slimframework.com/docs/objects/application.html#slim-default-settings); 44 | - `configurators` is a list of `ApplicationConfigurator` implementations which, in the same order as defined in the list, can add routes and middlewares to the instance of `Slim\App`. 45 | 46 | Once you have configured the bridge, you can create a simple `index.php` script in your document root, using [`nette/bootstrap`](https://github.com/nette/bootstrap) to build the container: 47 | 48 | ```php 49 | setTempDirectory(__DIR__ . '/path/to/temp'); 57 | $configurator->addConfig(__DIR__ . '/path/to/config.neon'); 58 | $container = $configurator->createContainer(); 59 | 60 | // run the configured Slim application 61 | $container->getByType(Slim\App::class)->run(); 62 | ``` 63 | 64 | Don't forget to configure your web server to pass the incoming requests to the `index.php` script. 65 | -------------------------------------------------------------------------------- /src/DI/SlimExtension.php: -------------------------------------------------------------------------------- 1 | [ 25 | 'httpVersion' => '1.1', 26 | 'responseChunkSize' => 4096, 27 | 'outputBuffering' => 'append', 28 | 'determineRouteBeforeAppMiddleware' => FALSE, 29 | 'displayErrorDetails' => NULL, 30 | 'addContentLengthHeader' => TRUE, 31 | 'routerCacheFile' => FALSE, 32 | ], 33 | 'configurators' => [], 34 | ]; 35 | 36 | 37 | public function __construct(bool $debugMode) 38 | { 39 | $this->defaults['settings']['displayErrorDetails'] = $debugMode; 40 | } 41 | 42 | 43 | public function loadConfiguration() 44 | { 45 | $builder = $this->getContainerBuilder(); 46 | $config = $this->validateConfig($this->defaults); 47 | 48 | $containerAdapter = $builder->addDefinition($this->prefix('containerAdapter')) 49 | ->setType(ContainerInterface::class) 50 | ->setFactory(ContainerAdapter::class, [$this->name]) 51 | ->setAutowired(FALSE); 52 | 53 | $chainConfigurator = $builder->addDefinition($this->prefix('configurator')) 54 | ->setType(ChainApplicationConfigurator::class) 55 | ->setAutowired(FALSE); 56 | 57 | foreach ($config['configurators'] as $configurator) { 58 | if ( ! ($configurator instanceof Statement)) { 59 | $configurator = new Statement($configurator); 60 | } 61 | 62 | $chainConfigurator->addSetup('addConfigurator', [$configurator]); 63 | } 64 | 65 | $builder->addDefinition($this->prefix('applicationFactory')) 66 | ->setFactory(ApplicationFactory::class, [$containerAdapter, $chainConfigurator]) 67 | ->setAutowired(FALSE); 68 | 69 | $builder->addDefinition($this->prefix('application')) 70 | ->setType(Slim\App::class) 71 | ->setFactory($this->prefix('@applicationFactory::createApplication')); 72 | 73 | /** 74 | * SERVICES REQUIRED BY SLIM FRAMEWORK 75 | * {@see Slim\DefaultServicesProvider} 76 | */ 77 | 78 | $builder->addDefinition($this->prefix('settings')) 79 | ->setType(ArrayHash::class) 80 | ->setFactory(ArrayHash::class . '::from', [$config['settings']]) 81 | ->setAutowired(FALSE); 82 | 83 | $builder->addDefinition($this->prefix('environment')) 84 | ->setType(Slim\Interfaces\Http\EnvironmentInterface::class) 85 | ->setFactory(Slim\Http\Environment::class, [new PhpLiteral('$_SERVER')]) 86 | ->setAutowired(FALSE); 87 | 88 | $builder->addDefinition($this->prefix('request')) 89 | ->setType(Http\Message\ServerRequestInterface::class) 90 | ->setFactory(Slim\Http\Request::class . '::createFromEnvironment', [$this->prefix('@environment')]) 91 | ->setAutowired(FALSE); 92 | 93 | $builder->addDefinition($this->prefix('response')) 94 | ->setType(Http\Message\ResponseInterface::class) 95 | ->setFactory(DefaultResponseFactory::class . '::createResponse', [$config['settings']['httpVersion']]) 96 | ->setAutowired(FALSE); 97 | 98 | $builder->addDefinition($this->prefix('router')) 99 | ->setType(Slim\Interfaces\RouterInterface::class) 100 | ->setFactory(Slim\Router::class) 101 | ->addSetup('setCacheFile', [$config['settings']['routerCacheFile']]) 102 | ->addSetup('setContainer', [$containerAdapter]) 103 | ->setAutowired(FALSE); 104 | 105 | $builder->addDefinition($this->prefix('foundHandler')) 106 | ->setType(Slim\Interfaces\InvocationStrategyInterface::class) 107 | ->setFactory(Slim\Handlers\Strategies\RequestResponse::class) 108 | ->setAutowired(FALSE); 109 | 110 | $builder->addDefinition($this->prefix('phpErrorHandler')) 111 | ->setFactory(Slim\Handlers\PhpError::class, [$config['settings']['displayErrorDetails']]) 112 | ->setAutowired(FALSE); 113 | 114 | $builder->addDefinition($this->prefix('errorHandler')) 115 | ->setFactory(Slim\Handlers\Error::class, [$config['settings']['displayErrorDetails']]) 116 | ->setAutowired(FALSE); 117 | 118 | $builder->addDefinition($this->prefix('notFoundHandler')) 119 | ->setType(Slim\Handlers\NotFound::class) 120 | ->setAutowired(FALSE); 121 | 122 | $builder->addDefinition($this->prefix('notAllowedHandler')) 123 | ->setType(Slim\Handlers\NotAllowed::class) 124 | ->setAutowired(FALSE); 125 | 126 | $builder->addDefinition($this->prefix('callableResolver')) 127 | ->setType(Slim\Interfaces\CallableResolverInterface::class) 128 | ->setFactory(Slim\CallableResolver::class, [$containerAdapter]) 129 | ->setAutowired(FALSE); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /tests/DI/SlimExtensionTest.phpt: -------------------------------------------------------------------------------- 1 | createContainer('default'); 29 | 30 | Assert::type(ArrayHash::class, $container->getService('slim.settings')); 31 | Assert::type(Slim\Interfaces\Http\EnvironmentInterface::class, $container->getService('slim.environment')); 32 | Assert::type(Http\Message\RequestInterface::class, $container->getService('slim.request')); 33 | Assert::type(Http\Message\ResponseInterface::class, $container->getService('slim.response')); 34 | Assert::type(Slim\Router::class, $container->getService('slim.router')); 35 | Assert::type(Slim\Handlers\Strategies\RequestResponse::class, $container->getService('slim.foundHandler')); 36 | Assert::type(Slim\Handlers\PhpError::class, $container->getService('slim.phpErrorHandler')); 37 | Assert::type(Slim\Handlers\Error::class, $container->getService('slim.errorHandler')); 38 | Assert::type(Slim\Handlers\NotFound::class, $container->getService('slim.notFoundHandler')); 39 | Assert::type(Slim\Handlers\NotAllowed::class, $container->getService('slim.notAllowedHandler')); 40 | Assert::type(Slim\CallableResolver::class, $container->getService('slim.callableResolver')); 41 | } 42 | 43 | 44 | public function testContainerAdapter(): void 45 | { 46 | $container = $this->createContainer('default'); 47 | $containerAdapter = $container->getService('slim.containerAdapter'); 48 | 49 | Assert::type(ContainerInterface::class, $containerAdapter); 50 | Assert::same($container->getService('slim.settings'), $containerAdapter->get('settings')); 51 | Assert::same($container->getService('slim.environment'), $containerAdapter->get('environment')); 52 | Assert::same($container->getService('slim.request'), $containerAdapter->get('request')); 53 | Assert::same($container->getService('slim.response'), $containerAdapter->get('response')); 54 | Assert::same($container->getService('slim.router'), $containerAdapter->get('router')); 55 | Assert::same($container->getService('slim.foundHandler'), $containerAdapter->get('foundHandler')); 56 | Assert::same($container->getService('slim.phpErrorHandler'), $containerAdapter->get('phpErrorHandler')); 57 | Assert::same($container->getService('slim.errorHandler'), $containerAdapter->get('errorHandler')); 58 | Assert::same($container->getService('slim.notFoundHandler'), $containerAdapter->get('notFoundHandler')); 59 | Assert::same($container->getService('slim.notAllowedHandler'), $containerAdapter->get('notAllowedHandler')); 60 | Assert::same($container->getService('slim.callableResolver'), $containerAdapter->get('callableResolver')); 61 | } 62 | 63 | 64 | public function testSettings(): void 65 | { 66 | $container = $this->createContainer('settings'); 67 | 68 | /** @var ArrayHash $settings */ 69 | $settings = $container->getService('slim.settings'); 70 | Assert::same('1.1', $settings['httpVersion']); 71 | Assert::false($settings['addContentLengthHeader']); 72 | } 73 | 74 | 75 | public function testConfigurators(): void 76 | { 77 | $container = $this->createContainer('configurators'); 78 | 79 | $request = new Slim\Http\Request( 80 | 'GET', 81 | new Slim\Http\Uri('http', 'example.com', NULL, '/whoami'), 82 | new Slim\Http\Headers(), 83 | [], 84 | [], 85 | new Slim\Http\Stream(\fopen('php://input', 'r')) 86 | ); 87 | $this->assertRequest($container, $request, 418, "I'm a teapot"); 88 | } 89 | 90 | 91 | public function testLazyConfigurators(): void 92 | { 93 | $container = $this->createContainer('lazyConfigurators'); 94 | 95 | $request = new Slim\Http\Request( 96 | 'GET', 97 | new Slim\Http\Uri('http', 'example.com', NULL, '/whoami'), 98 | new Slim\Http\Headers(), 99 | [], 100 | [], 101 | new Slim\Http\Stream(\fopen('php://input', 'r')) 102 | ); 103 | $this->assertRequest($container, $request, 418, "I'm a teapot"); 104 | 105 | $request = new Slim\Http\Request( 106 | 'POST', 107 | new Slim\Http\Uri('http', 'example.com', NULL, '/whoami'), 108 | new Slim\Http\Headers(), 109 | [], 110 | [], 111 | new Slim\Http\Stream(\fopen('php://input', 'r')) 112 | ); 113 | $this->assertRequest($container, $request, 405, "Don't you try this on me"); 114 | } 115 | 116 | 117 | private function createContainer(string $configFile): Container 118 | { 119 | $configurator = new Configurator(); 120 | $configurator->setTempDirectory(\dirname(\TEMP_DIR)); 121 | $configurator->addConfig(__DIR__ . '/' . $configFile . '.neon'); 122 | 123 | return $configurator->createContainer(); 124 | } 125 | 126 | 127 | private function assertRequest(Container $container, Slim\Http\Request $request, int $statusCode, string $statusReason): void 128 | { 129 | /** @var Slim\App $app */ 130 | $app = $container->getByType(Slim\App::class); 131 | 132 | $processedResponse = $app->process($request, new Slim\Http\Response()); 133 | Assert::same($statusCode, $processedResponse->getStatusCode()); 134 | Assert::same($statusReason, $processedResponse->getReasonPhrase()); 135 | } 136 | 137 | } 138 | 139 | 140 | (new SlimExtensionTest())->run(); 141 | --------------------------------------------------------------------------------