Skip to content

Commit f0f8511

Browse files
authored
Slim v4 integration (#10)
1 parent f37852d commit f0f8511

File tree

7 files changed

+137
-153
lines changed

7 files changed

+137
-153
lines changed

CHANGELOG-2.0.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Changelog
2+
All notable changes to this project will be documented in this file.
3+
4+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6+
7+
## [2.0.0] - 2020-11-27
8+
### Added
9+
- Slim v4 support.
10+
### Removed
11+
- Drop Slim v3 support. If you use Slim v3, please use the previous version from library.

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Build Status](https://img.shields.io/github/workflow/status/DoclerLabs/codeception-slim-module/CI?label=build&style=flat-square)](https://github.com/DoclerLabs/codeception-slim-module/actions?query=workflow%3ACI)
44
[![PHPStan Level](https://img.shields.io/badge/PHPStan-level%208-brightgreen.svg?style=flat-square)](https://img.shields.io/badge/PHPStan-level%208-brightgreen.svg)
55

6-
This module allows you to run functional tests inside [Slim 3 Microframework](http://www.slimframework.com/docs/v3/) without HTTP calls,
6+
This module allows you to run functional tests inside [Slim 4 Microframework](http://www.slimframework.com/docs/v4/) without HTTP calls,
77
so tests will be much faster and debug could be easier.
88

99
Inspiration comes from [herloct/codeception-slim-module](https://github.com/herloct/codeception-slim-module) library.
@@ -12,7 +12,7 @@ Inspiration comes from [herloct/codeception-slim-module](https://github.com/herl
1212

1313
### Minimal requirements
1414
- php: `^7.2`
15-
- slim/slim: `^3.1`
15+
- slim/slim: `^4.2`
1616
- codeception/codeception: `^4.0`
1717

1818
If you don't know Codeception, please check [Quickstart Guide](https://codeception.com/quickstart) first.
@@ -24,16 +24,24 @@ you can add codeception-slim-module with a single composer command.
2424
composer require --dev docler-labs/codeception-slim-module
2525
```
2626

27+
If you use Slim v3, please use the previous version from library:
28+
29+
```shell
30+
composer require --dev docler-labs/codeception-slim-module "^1.0"
31+
```
32+
2733
### Configuration
2834

2935
**Example (`test/suite/functional.suite.yml`)**
3036
```yaml
3137
modules:
3238
enabled:
33-
- DoclerLabs\CodeceptionSlimModule\Module\Slim:
34-
application: path/to/application.php
3539
- REST:
3640
depends: DoclerLabs\CodeceptionSlimModule\Module\Slim
41+
42+
config:
43+
DoclerLabs\CodeceptionSlimModule\Module\Slim:
44+
application: path/to/application.php
3745
```
3846
3947
The `application` property is a relative path to file which returns your `Slim\App` instance.
@@ -42,9 +50,9 @@ Here is the minimum `application.php` content:
4250
```php
4351
require __DIR__ . '/vendor/autoload.php';
4452
45-
use Slim\App;
53+
use Slim\Factory\AppFactory;
4654
47-
$app = new App();
55+
$app = AppFactory::create();
4856
4957
// Add routes and middlewares here.
5058

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"php": "^7.2",
1313
"codeception/codeception": "^4.0",
1414
"codeception/lib-innerbrowser": "^1.0",
15-
"slim/slim": "^3.1"
15+
"slim/psr7": "^1.1",
16+
"slim/slim": "^4.2"
1617
},
1718
"require-dev": {
1819
"ext-json": "*",

src/Lib/Connector/Slim.php renamed to src/Lib/Connector/SlimPsr7.php

Lines changed: 50 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,18 @@
55
namespace DoclerLabs\CodeceptionSlimModule\Lib\Connector;
66

77
use Psr\Http\Message\UploadedFileInterface;
8-
use RuntimeException;
98
use Slim\App;
10-
use Slim\Http\Cookies;
11-
use Slim\Http\Environment;
12-
use Slim\Http\Headers;
13-
use Slim\Http\Request;
14-
use Slim\Http\RequestBody;
15-
use Slim\Http\Response;
16-
use Slim\Http\Stream;
17-
use Slim\Http\UploadedFile;
18-
use Slim\Http\Uri;
9+
use Slim\Psr7\Cookies;
10+
use Slim\Psr7\Factory\StreamFactory;
11+
use Slim\Psr7\Factory\UriFactory;
12+
use Slim\Psr7\Headers;
13+
use Slim\Psr7\Request;
14+
use Slim\Psr7\UploadedFile;
1915
use Symfony\Component\BrowserKit\AbstractBrowser;
2016
use Symfony\Component\BrowserKit\Request as BrowserKitRequest;
2117
use Symfony\Component\BrowserKit\Response as BrowserKitResponse;
2218

23-
class Slim extends AbstractBrowser
19+
class SlimPsr7 extends AbstractBrowser
2420
{
2521
/** @var App */
2622
private $app;
@@ -37,17 +33,8 @@ public function setApp(App $app): void
3733
*/
3834
protected function doRequest($request): BrowserKitResponse
3935
{
40-
$slimRequest = $this->convertRequest($request);
41-
42-
$stream = fopen('php://temp', 'wb+');
43-
if ($stream === false) {
44-
throw new RuntimeException('Could not open `php://temp` stream.');
45-
}
46-
47-
$headers = new Headers(['Content-Type' => 'text/html; charset=UTF-8']);
48-
$body = new Stream($stream);
49-
$slimResponse = new Response(200, $headers, $body);
50-
$slimResponse = $this->app->process($slimRequest, $slimResponse);
36+
$slimRequest = $this->convertRequest($request);
37+
$slimResponse = $this->app->handle($slimRequest);
5138

5239
return new BrowserKitResponse(
5340
(string)$slimResponse->getBody(),
@@ -58,33 +45,20 @@ protected function doRequest($request): BrowserKitResponse
5845

5946
private function convertRequest(BrowserKitRequest $request): Request
6047
{
61-
$environment = Environment::mock($request->getServer());
62-
$uri = Uri::createFromString($request->getUri());
63-
$headers = Headers::createFromEnvironment($environment);
64-
$cookieHeader = $headers->get('Cookie', []);
65-
$cookies = Cookies::parseHeader($cookieHeader[0] ?? '');
66-
67-
$slimRequest = Request::createFromEnvironment($environment);
68-
$slimRequest = $slimRequest
69-
->withMethod($request->getMethod())
70-
->withUri($uri)
71-
->withUploadedFiles($this->convertFiles($request->getFiles()))
72-
->withCookieParams($cookies);
73-
74-
foreach ($headers->keys() as $key) {
75-
$slimRequest = $slimRequest->withHeader($key, $headers->get($key));
76-
}
48+
$server = $request->getServer();
49+
$method = $request->getMethod();
50+
$content = (string)$request->getContent();
7751

78-
$requestContent = $request->getContent();
79-
if ($requestContent !== null) {
80-
$body = new RequestBody();
81-
$body->write($requestContent);
52+
$uri = (new UriFactory())->createUri($request->getUri());
53+
$headers = $this->convertToHeaders($server);
54+
$cookies = Cookies::parseHeader($headers->getHeader('Cookie', []));
55+
$body = (new StreamFactory())->createStream($content);
56+
$uploadedFiles = $this->convertFiles($request->getFiles());
8257

83-
$slimRequest = $slimRequest->withBody($body);
84-
}
58+
$slimRequest = new Request($method, $uri, $headers, $cookies, $server, $body, $uploadedFiles);
8559

8660
$parsed = [];
87-
if ($request->getMethod() !== 'GET') {
61+
if ($method !== 'GET') {
8862
$parsed = $request->getParameters();
8963
}
9064

@@ -96,6 +70,37 @@ private function convertRequest(BrowserKitRequest $request): Request
9670
return $slimRequest;
9771
}
9872

73+
/**
74+
* Collect headers from server variables and transform to proper header names.
75+
*
76+
* @param array $serverVariables List of server variables.
77+
*
78+
* @return Headers
79+
*/
80+
private function convertToHeaders(array $serverVariables): Headers
81+
{
82+
$headers = [];
83+
foreach ($serverVariables as $key => $value) {
84+
// Replace underscores to dashes.
85+
$headerName = str_replace('_', '-', $key);
86+
87+
// Transform the first characters to uppercase of each word, other characters are lowercased.
88+
$headerName = implode('-', array_map('ucfirst', explode('-', strtolower($headerName))));
89+
90+
// Decode if there are html entities in the header name.
91+
$headerName = html_entity_decode($headerName, ENT_NOQUOTES);
92+
93+
// Collect headers from server variables and cut "Http-" prefix.
94+
if (strpos($headerName, 'Http-') === 0) {
95+
$headerName = substr($headerName, 5);
96+
97+
$headers[$headerName] = $value;
98+
}
99+
}
100+
101+
return new Headers($headers, $serverVariables);
102+
}
103+
99104
/**
100105
* Convert uploaded file list to UploadedFile instances.
101106
*

src/Module/Slim.php

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,28 @@
55
namespace DoclerLabs\CodeceptionSlimModule\Module;
66

77
use Codeception\Configuration;
8+
use Codeception\Exception\ConfigurationException;
89
use Codeception\Exception\ModuleConfigException;
910
use Codeception\Lib\Framework;
1011
use Codeception\TestInterface;
11-
use DoclerLabs\CodeceptionSlimModule\Lib\Connector\Slim as SlimConnector;
12+
use DoclerLabs\CodeceptionSlimModule\Lib\Connector\SlimPsr7;
1213
use Slim\App;
1314

1415
/**
1516
* This module uses Slim App to emulate requests and test response.
1617
*
1718
* ## Configuration
1819
*
19-
* ### Slim 3.x
20+
* ### Slim 4.x
2021
*
21-
* * application: 'app/bootstrap.php' - relative path to file which bootstrap and returns your `Slim\App` instance.
22+
* * application - Relative path to file which bootstrap and returns your `Slim\App` instance.
2223
*
2324
* #### Example (`test/suite/functional.suite.yml`)
2425
* ```yaml
2526
* modules:
26-
* enabled:
27-
* - DoclerLabs\CodeceptionSlimModule\Module\Slim:
28-
* application: 'app/bootstrap.php'
27+
* config:
28+
* DoclerLabs\CodeceptionSlimModule\Module\Slim:
29+
* application: 'app/bootstrap.php'
2930
* ```
3031
*
3132
* ## Public Properties
@@ -38,10 +39,12 @@
3839
* actor: FunctionalTester
3940
* modules:
4041
* enabled:
41-
* - DoclerLabs\CodeceptionSlimModule\Module\Slim:
42-
* application: 'app/bootstrap.php'
4342
* - REST:
4443
* depends: DoclerLabs\CodeceptionSlimModule\Module\Slim
44+
*
45+
* config:
46+
* DoclerLabs\CodeceptionSlimModule\Module\Slim:
47+
* application: 'app/bootstrap.php'
4548
* ```
4649
*/
4750
class Slim extends Framework
@@ -57,25 +60,38 @@ class Slim extends Framework
5760

5861
public function _initialize(): void
5962
{
60-
$this->applicationPath = Configuration::projectDir() . $this->config['application'];
61-
62-
if (!file_exists($this->applicationPath)) {
63+
$applicationPath = Configuration::projectDir() . $this->config['application'];
64+
if (!is_readable($applicationPath)) {
6365
throw new ModuleConfigException(
6466
static::class,
65-
"\nApplication file doesn't exist.\n"
66-
. 'Please, check path for php file: ' . $this->applicationPath
67+
"Application file does not exist or is not readable.\nPlease, check path for php file: `$applicationPath`"
6768
);
6869
}
6970

71+
$this->applicationPath = $applicationPath;
72+
7073
parent::_initialize();
7174
}
7275

7376
public function _before(TestInterface $test): void
7477
{
78+
/* @noinspection PhpIncludeInspection */
7579
$this->app = require $this->applicationPath;
7680

77-
$this->client = new SlimConnector();
78-
$this->client->setApp($this->app);
81+
// Check if app instance is ready.
82+
if (!$this->app instanceof App) {
83+
throw new ConfigurationException(
84+
sprintf(
85+
"Unable to bootstrap slim application.\n Application file must return with `%s` instance.",
86+
App::class
87+
)
88+
);
89+
}
90+
91+
$connector = new SlimPsr7();
92+
$connector->setApp($this->app);
93+
94+
$this->client = $connector;
7995

8096
parent::_before($test);
8197
}

0 commit comments

Comments
 (0)