├── .travis.yml
├── README.md
├── app.phpsgi
├── bin
└── funk
├── composer.json
├── phpunit.xml
├── phpunit.xml.dist
├── src
├── App
│ ├── MuxApp.php
│ └── MuxAppTest.php
├── Buffer
│ └── SAPIInputBuffer.php
├── Compositor.php
├── CompositorTest.php
├── Console.php
├── Environment.php
├── EnvironmentTest.php
├── Middleware
│ ├── CORSMiddleware.php
│ ├── ContentNegotiationMiddleware.php
│ ├── ContentNegotiationMiddlewareTest.php
│ ├── GeocoderMiddleware.php
│ ├── GeocoderMiddlewareTest.php
│ ├── HeadMiddleware.php
│ ├── MiddlewareTest.php
│ ├── TryCatchMiddleware.php
│ ├── XHProfMiddleware.php
│ ├── XHProfMiddlewareTest.php
│ └── XHTTPMiddleware.php
├── Request.php
├── Responder
│ ├── SAPIResponder.php
│ └── SAPIResponderTest.php
├── Server
│ ├── BaseServer.php
│ ├── EventHttpServer.php
│ └── StreamSocketServer.php
└── Testing
│ └── TestUtils.php
└── tests
├── bootstrap.php
├── controllers.php
└── travis-setup.sh
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - '7.0'
4 | - '7.1'
5 | - hhvm
6 | matrix:
7 | fast_finish: true
8 | allow_failures:
9 | - php: hhvm
10 | install:
11 | - composer require "satooshi/php-coveralls" "^1" --dev --no-update
12 | - composer install
13 | script:
14 | - phpunit -c phpunit.xml.dist
15 | after_success:
16 | - php vendor/bin/coveralls -v
17 | cache:
18 | apt: true
19 | directories:
20 | - vendor
21 | notifications:
22 | email:
23 | on_success: change
24 | on_failure: change
25 | slack:
26 | secure: z/QbNbfOj8oHvJWjviKe3oU/g2DDqvqHv/Fb8ONIJuqJt0fv/mmy8+QWWFp2mXrF+c6Ncn9W77spLWMCDlAdXNvm9Sf61KVVO0PlFuA48V3AnfbyCF/1txZ1OpxKkVCzzOsmq6yVef2eHgXtZFdk+uGaLs4R1AaL33urOrFZT4zXF8wE2rKd4davdtimP30++hJEO3JUrInQkv0AmrC/A66y1G/hPLpVu3Xf+sYDvgnGoXsTKBcrvcWQeB7684hKHAPLwHLiLe1Tnbsn6h8zdF+HZuTLfNTE/FsesTnZ5zGvDc3wSFBBd3Fu6xceUhQdRoTPfan8o5JlYAIqKRgsPLvpv9xK0AdWTxy7RRDKuwhsNHvOtwBK14HE+XHUv7lL6CYiQi9NN/UOA89Tqox9wEruEY8ab15JtW+2d5lN6ZKkloTFujoVazxedpjX2IC5ahzKfGjxrChUHJRP9QJcRWFVbh3gWux+VYVSBZaJZ91ACK0pPvvR0xd2a7zlKT/X4cUfPv5BLRB4t1NVFW3lnxuMhdXmdPh5mOuxpbBaudA7wrYqiyXv6ZGD7AyRnvejC8afx+Yzu411Zy2iXh9MP0X+ZReryvdfhEB6jk/YQRs3Cn7ouvSQQu0LTDfTX9oorfYJHkQkaB0PJJku3Srm08Oi1k/vxviN86sJyNxRjrA=
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Funk
2 |
3 | [](https://travis-ci.org/phpsgi/Funk)
4 | [](https://coveralls.io/r/phpsgi/Funk)
5 | [](https://packagist.org/packages/phpsgi/funk)
6 | [](https://packagist.org/packages/phpsgi/funk)
7 | [](https://packagist.org/packages/phpsgi/funk)
8 | [](https://packagist.org/packages/phpsgi/funk)
9 | [](https://packagist.org/packages/phpsgi/funk)
10 | [](https://packagist.org/packages/phpsgi/funk)
11 | [](https://gitter.im/phpsgi/funk?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
12 | [](README.md)
13 |
14 | Funk is an implementation of PHPSGI. It supports HTTP servers implemented with PHP SAPI (Apache2 `mod_php`, `php-fpm`, `fastcgi`), therefore you can integrate your application with Funk and switch to different HTTP server implementation.
15 |
16 | PHPSGI and Funk aims to provide lightweight HTTP interfaces, middlewares for
17 | web frameworks. It's a bit different from the PSR-7 spec. PHPSGI focuses on the core data structure instead of
18 | forcing components to implement the interface requirements.
19 |
20 | ## Components
21 |
22 | - HTTP server (with event extension or `socket_select`)
23 | - SAPI support (php-fpm, apache2 php handler servers)
24 | - Middlewares
25 | - Middleware Compositor
26 | - A Simple Mux Builder (integrated with Pux)
27 |
28 |
29 | ### Environment
30 |
31 | ```php
32 | // This creates $env array from $_SERVER, $_REQUEST, $_POST, $_GET ...
33 | $env = Environment::createFromGlobals();
34 | ```
35 |
36 | ### Application
37 |
38 | ```php
39 | $app = function(array & $environment, array $response) {
40 | return [ 200, [ 'Content-Type' => 'text/plain' ], 'Hello World' ];
41 | };
42 | ```
43 |
44 |
45 | ### Responder
46 |
47 | #### SAPIResponder
48 |
49 | You can integrate your application with SAPIResponder to support Apache2 php handler / php-fpm / fastcgi.
50 |
51 | ```php
52 | use Funk\Responder\SAPIResponder;
53 |
54 | $fd = fopen('php://output', 'w');
55 | $responder = new SAPIResponder($fd);
56 | $responder->respond([ 200, [ 'Content-Type: text/plain' ], 'Hello World' ]);
57 | fclose($fd);
58 | ```
59 |
60 |
61 | ```php
62 | use Funk\Responder\SAPIResponder;
63 |
64 | $env = Environment::createFromGlobals();
65 | $app = function(array & $environment, array $response) {
66 | return [ 200, [ 'Content-Type' => 'text/plain' ], 'Hello World' ];
67 | };
68 | $fd = fopen('php://output', 'w');
69 | $responder = new SAPIResponder($fd);
70 | $responder->respond($app($env, []));
71 | fclose($fd);
72 | ```
73 |
74 |
75 |
76 | ### Middleware
77 |
78 | - `Funk\Middleware\ContentNegotiationMiddleware`
79 | - `Funk\Middleware\CORSMiddleware`
80 | - `Funk\Middleware\GeocoderMiddleware`
81 | - `Funk\Middleware\HeadMiddleware`
82 | - `Funk\Middleware\TryCatchMiddleware`
83 | - `Funk\Middleware\XHProfMiddleware`
84 | - `Funk\Middleware\XHTTPMiddleware`
85 |
86 |
87 | ```php
88 | use Funk\Environment;
89 | use Funk\Middleware\TryCacheMiddleware;
90 |
91 | $app = function(array $environment, array $response) {
92 | return [ 200, ['Content-Type' => 'text/html' ], 'Hello World' ];
93 | };
94 | $middleware = new TryCatchMiddleware($app);
95 |
96 |
97 | $env = Environment::createFromGlobals();
98 | $response = $middleware($env, [200, [], []]);
99 | ```
100 |
101 |
102 |
103 | ## Contributing
104 |
105 | ### Testing XHProf Middleware
106 |
107 |
108 | Define your XHPROF_ROOT in your `phpunit.xml`, you can copy `phpunit.xml.dist` to `phpunit.xml`,
109 | for example:
110 |
111 | ```xml
112 |
113 |
114 |
115 | ```
116 |
117 |
--------------------------------------------------------------------------------
/app.phpsgi:
--------------------------------------------------------------------------------
1 | runWithTry($argv);
13 | exit($ret);
14 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phpsgi/funk",
3 | "description": "An implementation of PHPSGI middleware specification",
4 | "homepage": "https://github.com/phpsgi/Funk",
5 | "keywords": [
6 | "http",
7 | "middleware",
8 | "server",
9 | "phpsgi"
10 | ],
11 | "authors": [
12 | {
13 | "name": "c9s",
14 | "email": "cornelius.howl@gmail.com"
15 | }
16 | ],
17 | "require": {
18 | "universal/universal": "2.0.x-dev",
19 | "phpsgi/phpsgi": "@dev"
20 | },
21 | "license": "MIT",
22 | "suggest": {
23 | "corneltek/pux": "2.0.x-dev",
24 | "corneltek/cliframework": "4.0.x-dev"
25 | },
26 | "require-dev": {
27 | "corneltek/pux": "2.0.x-dev",
28 | "willdurand/negotiation": "^1.4",
29 | "willdurand/geocoder": "^3.1"
30 | },
31 | "autoload": {
32 | "psr-4": { "Funk\\": "src/" }
33 | },
34 | "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } }
35 | }
36 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | src
13 |
14 |
15 |
16 | src/Middleware
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | src
9 |
10 |
11 | src/Middleware
12 |
13 |
14 |
15 |
16 |
17 | src
18 |
19 | src
20 |
21 |
22 |
23 |
24 |
25 |
26 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/App/MuxApp.php:
--------------------------------------------------------------------------------
1 | mux = $mux ?: new Mux;
16 | }
17 |
18 | /**
19 | * Mount app on a specific path
20 | *
21 | * @param string $path
22 | * @param App $app
23 | * @return MuxApp
24 | */
25 | public function mount($path, App $app)
26 | {
27 | $this->mux->any($path, $app);
28 | return $this;
29 | }
30 |
31 | public function getMux()
32 | {
33 | return $this->mux;
34 | }
35 |
36 | public function call(array & $environment, array $response)
37 | {
38 | if ($route = $this->mux->dispatch($environment['PATH_INFO'])) {
39 | $path = $route[1];
40 | $app = $route[2];
41 |
42 | // Save the original PATH_INFO in ORIG_PATH_INFO
43 | // Note that some SAPI implementation will save
44 | // use the ORIG_PATH_INFO (not noticed yet)
45 | if (!isset($environment['ORIG_PATH_INFO'])) {
46 | $environment['ORIG_PATH_INFO'] = $environment['PATH_INFO'];
47 | }
48 | $environment['PATH_INFO'] = substr($environment['PATH_INFO'], strlen($path));
49 |
50 | // If callback object complies the App call prorotype
51 | if ($app instanceof App || $app instanceof Middleware) {
52 |
53 | return $app($environment, $response);
54 |
55 | } else {
56 |
57 | return RouteExecutor::execute($route);
58 |
59 | }
60 | }
61 | return $response;
62 | }
63 |
64 | public function __invoke(array $environment, array $response)
65 | {
66 | return $this->call($environment, $response);
67 | }
68 |
69 | static public function mountWithUrlMap(array $map)
70 | {
71 | $mux = new Mux;
72 | foreach ($map as $path => $app) {
73 | if ($app instanceof Compositor) {
74 | $app = $app->wrap();
75 | }
76 | $mux->any($path, $app);
77 | }
78 | return new self($mux);
79 | }
80 |
81 |
82 | }
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/src/App/MuxAppTest.php:
--------------------------------------------------------------------------------
1 | ['ProductController', 'fooAction'],
17 | "/bar" => ['ProductController', 'barAction'],
18 | ]);
19 | $this->assertNotNull($app);
20 | $this->assertInstanceOf(MuxApp::class,$app);
21 | $this->assertInstanceOf(App::class, $app, 'Must be an instanceof PHPSGI App');
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Buffer/SAPIInputBuffer.php:
--------------------------------------------------------------------------------
1 | fd = fopen('php://input', 'r');
13 | }
14 |
15 | public function read($bytes)
16 | {
17 | return fread($this->fd, $bytes);
18 | }
19 |
20 | public function __destruct()
21 | {
22 | fclose($this->fd);
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/Compositor.php:
--------------------------------------------------------------------------------
1 | app = $app;
31 | }
32 |
33 | public function enable($appClass)
34 | {
35 | if ($appClass instanceof Closure) {
36 | $this->stacks[] = $appClass;
37 | } else {
38 | $args = func_get_args();
39 | array_shift($args);
40 | $this->stacks[] = [$appClass, $args];
41 | }
42 | return $this;
43 | }
44 |
45 | public function app($app)
46 | {
47 | $this->app = $app;
48 | return $this;
49 | }
50 |
51 | public function wrap()
52 | {
53 | $app = $this->app;
54 |
55 | for ($i = count($this->stacks) - 1; $i > 0; $i--) {
56 | $stack = $this->stacks[$i];
57 |
58 | // middleware closure
59 | if (is_callable($stack)) {
60 | $app = $stack($app);
61 | } else {
62 | list($appClass, $args) = $stack;
63 | $refClass = new ReflectionClass($appClass);
64 | array_unshift($args, $app);
65 | $app = $refClass->newInstanceArgs($args);
66 | }
67 | }
68 |
69 | return $app;
70 | }
71 |
72 | public function call(array & $environment, array $response)
73 | {
74 | if ($app = $this->wrappedApp) {
75 | return $app($environment, $response);
76 | }
77 | $this->wrappedApp = $app = $this->wrap();
78 | return $app($environment, $response);
79 | }
80 |
81 | public function __invoke(array & $environment, array $response)
82 | {
83 | return $this->call($environment, $response);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/CompositorTest.php:
--------------------------------------------------------------------------------
1 | enable(TryCatchMiddleware::class, [ 'throw' => true ]);
19 | $compositor->enable(function($app) {
20 | return function(array & $environment, array $response) use ($app) {
21 | $environment['middleware.app'] = true;
22 | return $app($environment, $response);
23 | };
24 | });
25 |
26 | $compositor->app(function(array & $environment, array $response) {
27 | $request = RouteRequest::createFromEnv($environment);
28 | if ($request->pathStartWith('/foo')) {
29 |
30 | }
31 |
32 | $response[0] = 200;
33 | return $response;
34 | });
35 |
36 | $env = TestUtils::createEnv('GET', '/foo/bar');
37 | $response = $compositor($env, []);
38 | $this->assertNotEmpty($response);
39 | }
40 |
41 | public function testCompositorWithRecursiveUrlMap()
42 | {
43 | $appcomp = new Compositor;
44 | $appcomp->app(MuxApp::mountWithUrlMap([
45 | "/hack" => new Compositor(MuxApp::mountWithUrlMap([
46 | "/foo" => ['ProductController', 'fooAction'],
47 | "/bar" => ['ProductController', 'barAction'],
48 | ])),
49 | ]));
50 | $app = $appcomp->wrap();
51 |
52 | $env = TestUtils::createEnv('GET', '/hack/foo');
53 | $response = $app($env, []);
54 | $this->assertNotEmpty($response);
55 | $this->assertEquals('foo',$response);
56 | }
57 |
58 | public function testCompositorWithUrlMap()
59 | {
60 | $compositor = new Compositor;
61 | $compositor->app(MuxApp::mountWithUrlMap([
62 | "/foo" => ['ProductController', 'fooAction'],
63 | "/bar" => ['ProductController', 'barAction'],
64 | ]));
65 | $app = $compositor->wrap();
66 | $this->assertInstanceOf('Funk\\App\\MuxApp', $app,
67 | 'When there is only one app and no middleware, the returned type should be just MuxApp');
68 | $env = TestUtils::createEnv('GET', '/foo');
69 | $response = $app($env, []);
70 | $this->assertNotEmpty($response);
71 | $this->assertEquals('foo',$response);
72 | }
73 |
74 |
75 |
76 | public function testCompositor()
77 | {
78 | $compositor = new Compositor;
79 | $compositor->enable(TryCatchMiddleware::class, [ 'throw' => true ]);
80 | $compositor->enable(function($app) {
81 | return function(array & $environment, array $response) use ($app) {
82 | $environment['middleware.app'] = true;
83 | return $app($environment, $response);
84 | };
85 | });
86 |
87 | // TODO
88 | // $compositor->mount('/foo', function() { });
89 |
90 | $compositor->app(function(array & $environment, array $response) {
91 | $request = RouteRequest::createFromEnv($environment);
92 |
93 | // $mux = new Mux;
94 |
95 | if ($request->pathStartWith('/foo')) {
96 |
97 | }
98 |
99 | $response[0] = 200;
100 | return $response;
101 | });
102 | $app = $compositor->wrap();
103 |
104 | $env = TestUtils::createEnv('GET', '/foo');
105 | $res = [ ];
106 | $res = $app($env, $res);
107 | $this->assertNotEmpty($res);
108 | $this->assertEquals(200, $res[0]);
109 | }
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/src/Console.php:
--------------------------------------------------------------------------------
1 | 'text/plain' ], 'Hello World' ];
40 | };
41 | }
42 |
43 | if (extension_loaded('event')) {
44 |
45 | $logger->info("Found 'event' extension, enabling EventHttpServer server.");
46 | $server = new EventHttpServer($app);
47 |
48 | } else {
49 |
50 | $logger->info("Falling back to StreamSocketServer server.");
51 | $server = new StreamSocketServer($app);
52 | }
53 | return $server->listen();
54 | }
55 |
56 | }
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/Environment.php:
--------------------------------------------------------------------------------
1 | assertNotEmpty($env);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Middleware/CORSMiddleware.php:
--------------------------------------------------------------------------------
1 | options = $this->normalizeOptions($options);
11 | }
12 | private function normalizeOptions(array $options = array())
13 | {
14 | $options += array(
15 | 'allowedOrigins' => array(),
16 | 'supportsCredentials' => false,
17 | 'allowedHeaders' => array(),
18 | 'exposedHeaders' => array(),
19 | 'allowedMethods' => array(),
20 | 'maxAge' => 0,
21 | );
22 | // normalize array('*') to true
23 | if (in_array('*', $options['allowedOrigins'])) {
24 | $options['allowedOrigins'] = true;
25 | }
26 | if (in_array('*', $options['allowedHeaders'])) {
27 | $options['allowedHeaders'] = true;
28 | } else {
29 | $options['allowedHeaders'] = array_map('strtolower', $options['allowedHeaders']);
30 | }
31 | if (in_array('*', $options['allowedMethods'])) {
32 | $options['allowedMethods'] = true;
33 | } else {
34 | $options['allowedMethods'] = array_map('strtoupper', $options['allowedMethods']);
35 | }
36 | return $options;
37 | }
38 | public function isActualRequestAllowed(array $environment)
39 | {
40 | return $this->checkOrigin($environment);
41 | }
42 |
43 | public function isCorsRequest(array $environment)
44 | {
45 | return isset($environment['HTTP_ORIGIN']);
46 | }
47 |
48 | public function isPreflightRequest(array $environment)
49 | {
50 | // if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
51 | return $this->isCorsRequest($environment)
52 | && $environment['REQUEST_METHOD'] == 'OPTIONS'
53 | && isset($environment['HTTP_ACCESS_CONTROL_REQUEST_METHOD']);
54 | }
55 |
56 | public function addActualRequestHeaders(array $environment, array $response)
57 | {
58 | if (! $this->checkOrigin($environment)) {
59 | return $response;
60 | }
61 | $response[1][] = [ 'Access-Control-Allow-Origin' => $request->headers->get('Origin') ];
62 | /*
63 | if (! $response->headers->has('Vary')) {
64 | $response->headers->set('Vary', 'Origin');
65 | } else {
66 | $response->headers->set('Vary', $response->headers->get('Vary') . ', Origin');
67 | }
68 | */
69 | if ($this->options['supportsCredentials']) {
70 | $response[1][] = ['Access-Control-Allow-Credentials' => 'true'];
71 | }
72 | if ($this->options['exposedHeaders']) {
73 | $response[1][] = ['Access-Control-Expose-Headers' => implode(', ', $this->options['exposedHeaders'])];
74 | }
75 | return $response;
76 | }
77 | public function handlePreflightRequest(array $environment)
78 | {
79 | if (true !== $check = $this->checkPreflightRequestConditions($environment)) {
80 | return $check;
81 | }
82 | return $this->buildPreflightCheckResponse($environment);
83 | }
84 |
85 | private function buildPreflightCheckResponse(array $environment)
86 | {
87 | $response = [200,[],[]];
88 | if ($this->options['supportsCredentials']) {
89 | $response[1][] = array('Access-Control-Allow-Credentials' => 'true');
90 | }
91 | $response[1][] = array('Access-Control-Allow-Origin' => $request->headers->get('Origin'));
92 |
93 | if ($this->options['maxAge']) {
94 | $response[1][] = array('Access-Control-Max-Age' => $this->options['maxAge']);
95 | }
96 |
97 | $allowMethods = $this->options['allowedMethods'] === true
98 | ? strtoupper($environment['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])
99 | : implode(', ', $this->options['allowedMethods']);
100 | $response->headers->set('Access-Control-Allow-Methods', $allowMethods);
101 | $allowHeaders = $this->options['allowedHeaders'] === true
102 | ? strtoupper($environment['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])
103 | : implode(', ', $this->options['allowedHeaders']);
104 |
105 | $response[1][] = array('Access-Control-Allow-Headers' => $allowHeaders);
106 | return $response;
107 | }
108 |
109 |
110 | private function checkPreflightRequestConditions(array $environment)
111 | {
112 | if ( ! $this->checkOrigin($environment)) {
113 | return $this->createBadRequestResponse(403, 'Origin not allowed');
114 | }
115 | if ( ! $this->checkMethod($environment)) {
116 | return $this->createBadRequestResponse(405, 'Method not allowed');
117 | }
118 | $requestHeaders = array();
119 | // if allowedHeaders has been set to true ('*' allow all flag) just skip this check
120 | if ($this_>options['allowedHeaders'] !== true && isset($environment['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
121 | $headers = strtolower($environment['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
122 | $requestHeaders = explode(',', $headers);
123 | foreach ($requestHeaders as $header) {
124 | if (! in_array(trim($header), $this->options['allowedHeaders'])) {
125 | return $this->createBadRequestResponse(403, 'Header not allowed');
126 | }
127 | }
128 | }
129 | return true;
130 | }
131 |
132 | private function createBadRequestResponse($code, $reason = '')
133 | {
134 | return [ $code, [], $reason];
135 | }
136 |
137 | private function checkOrigin(array $environment)
138 | {
139 | if ($this->options['allowedOrigins'] === true) {
140 | // allow all '*' flag
141 | return true;
142 | }
143 | $origin = $environment['HTTP_ORIGIN'];
144 | return in_array($origin, $this->options['allowedOrigins']);
145 | }
146 |
147 | private function checkMethod(array $environment) {
148 | if ($this->options['allowedMethods'] === true) {
149 | // allow all '*' flag
150 | return true;
151 | }
152 | $requestMethod = strtoupper($environment['HTTP_ACCESS_CONTROL_REQUEST_METHOD']);
153 | return in_array($requestMethod, $this->options['allowedMethods']);
154 | }
155 | }
156 |
157 |
158 | class CORSMiddleware extends Middleware
159 | {
160 | private $cors;
161 |
162 | private $defaultOptions = array(
163 | 'allowedHeaders' => array(),
164 | 'allowedMethods' => array(),
165 | 'allowedOrigins' => array(),
166 | 'exposedHeaders' => false,
167 | 'maxAge' => false,
168 | 'supportsCredentials' => false,
169 | );
170 |
171 | public function __construct($app, array $options = array())
172 | {
173 | parent::__construct($app);
174 | $this->cors = new CORSService(array_merge($this->defaultOptions, $options));
175 | }
176 |
177 | public function call(array & $environment, array $response)
178 | {
179 | if (! $this->cors->isCorsRequest($environment)) {
180 | return parent::call($environment, $response);
181 | }
182 |
183 | if ($this->cors->isPreflightRequest($environment)) {
184 | return $this->cors->handlePreflightRequest($environment);
185 | }
186 | if ( ! $this->cors->isActualRequestAllowed($environment)) {
187 | return [403, [], 'Not allowed'];
188 | }
189 | $response = parent::call($environment, $response);
190 | return $this->cors->addActualRequestHeaders($environment, $response);
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/Middleware/ContentNegotiationMiddleware.php:
--------------------------------------------------------------------------------
1 | negotiator = $negotiator ?: new Negotiator();
15 | }
16 |
17 | public function call(array & $environment, array $response)
18 | {
19 | $accept = isset($environment['HTTP_ACCEPT']) ? $environment['HTTP_ACCEPT'] : '';
20 | $priorities = isset($environment['negotiation.priorities']) ? $environment['negotiation.priorities'] : array();
21 | $environment['request.best_format'] = $this->negotiator->getBest($accept, $priorities);
22 | return parent::call($environment, $response);
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/Middleware/ContentNegotiationMiddlewareTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('text/html', $environment['request.best_format']->getValue());
16 | };
17 |
18 | $env = TestUtils::createEnv('GET', '/');
19 | $env['HTTP_ACCEPT'] = 'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8';
20 | $env['negotiation.priorities'] = array('text/html', 'application/json');
21 | $m = new ContentNegotiationMiddleware($app, new Negotiator);
22 | $response = $m->call($env, []);
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/Middleware/GeocoderMiddleware.php:
--------------------------------------------------------------------------------
1 | geocoder = $geocoder ?: $this->createDefaultGeocoder();
17 | }
18 |
19 |
20 | public function createDefaultGeocoder()
21 | {
22 | $adapter = new CurlHttpAdapter();
23 | $geocoder = new FreeGeoIp($adapter);
24 | return $geocoder;
25 | }
26 |
27 |
28 | public function call(array & $environment, array $response)
29 | {
30 | if (isset($environment['REMOTE_ADDR'])) {
31 | $results = $this->geocoder->geocode($environment['REMOTE_ADDR']);
32 | if ($countryCode = $results->get(0)->getCountryCode()) {
33 | $environment['geoip.country_code'] = $countryCode;
34 | }
35 | }
36 | $n = $this->next;
37 | return $n($environment, $response);
38 | }
39 | }
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/Middleware/GeocoderMiddlewareTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('US', $env['geoip.country_code']);
22 | return $res;
23 | };
24 |
25 |
26 |
27 | // $adapter = new CurlHttpAdapter([ 'CURLOPT_CONNECTTIMEOUT' => 10000 ]);
28 | $adapter = new FileGetContentsHttpAdapter();
29 | $geocoder = new FreeGeoIp($adapter);
30 | $middleware = new GeocoderMiddleware($app, $geocoder);
31 | $env = TestUtils::createEnv('GET', '/');
32 | $env['REMOTE_ADDR'] = '173.194.72.113';
33 | $middleware($env, []);
34 |
35 | } catch (\Ivory\HttpAdapter\HttpAdapterException $e) {
36 | // This allowes connection timeout failture
37 |
38 | }
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/src/Middleware/HeadMiddleware.php:
--------------------------------------------------------------------------------
1 | assertNotEmpty($response);
27 | }
28 | }
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/Middleware/TryCatchMiddleware.php:
--------------------------------------------------------------------------------
1 | options = $options;
14 | }
15 |
16 |
17 | public function call(array & $environment, array $response)
18 | {
19 | try {
20 | return parent::call($environment, $response);
21 | } catch (Exception $e) {
22 | if (isset($this->options['throw'])) {
23 | throw $e;
24 | }
25 | }
26 | return $response;
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/src/Middleware/XHProfMiddleware.php:
--------------------------------------------------------------------------------
1 | options = array_merge([
17 | 'flags' => XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY,
18 | 'prefix' => 'pux_',
19 | 'output_dir' => ini_get('xhprof.output_dir') ?: '/tmp',
20 | 'root' => null,
21 | ],$options);
22 |
23 | if (!$this->options['root']) {
24 | throw new LogicException("xhprof root is not defined.");
25 | }
26 | include_once $this->options['root'] . "/xhprof_lib/utils/xhprof_lib.php";
27 | include_once $this->options['root'] . "/xhprof_lib/utils/xhprof_runs.php";
28 | }
29 |
30 | public function getLastRunId()
31 | {
32 | return $this->runId;
33 | }
34 |
35 | public function call(array & $environment, array $response)
36 | {
37 | $namespace = $this->options['prefix'];
38 | if (isset($environment['PATH_INFO'])) {
39 | $namespace .= $environment['PATH_INFO'];
40 | } else if (isset($environment['REQUEST_URI'])) {
41 | $namespace .= $environment['REQUEST_URI'];
42 | }
43 | $namespace = preg_replace('#[^\w]+#','_', $namespace);
44 |
45 | xhprof_enable($this->options['flags']);
46 | $response = parent::call($environment, $response);
47 | $profile = xhprof_disable();
48 |
49 |
50 | $runs = new XHProfRuns_Default($this->options['output_dir']);
51 | $this->runId = $runs->save_run($profile, $namespace);
52 | return $response;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Middleware/XHProfMiddlewareTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped("'XHPROF_ROOT' environment variable is unset.");
16 | }
17 |
18 | $testing = $this;
19 | $app = function(array & $environment, array $response) use ($testing) {
20 | $cnt = 0;
21 | for ($i = 0 ; $i < 10000; $i++) {
22 | $cnt++;
23 | }
24 | return [200, [], ['Hell Yeah']];
25 | };
26 |
27 | $env = TestUtils::createEnv('GET', '/');
28 | $m = new XHProfMiddleware($app, [
29 | 'flags' => XHPROF_FLAGS_NO_BUILTINS | XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY,
30 | 'root' => getenv('XHPROF_ROOT'),
31 | 'prefix' => 'pux_testing_',
32 | ]);
33 | $response = $m($env, []);
34 | $this->assertNotEmpty($response);
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/src/Middleware/XHTTPMiddleware.php:
--------------------------------------------------------------------------------
1 | requestMethod = $requestMethod;
38 | $this->path = $path;
39 |
40 | // Note: It's not neccessary to call parent::__construct because we
41 | // don't depend on superglobal variables.
42 | }
43 |
44 | /**
45 | * Return the current path for route dispatching.
46 | * the path can be PATH_INFO or REQUEST_URI (depends on what the user gives)
47 | *
48 | * @return string path
49 | */
50 | public function getPath()
51 | {
52 | return $this->path;
53 | }
54 |
55 | /**
56 | * Return the request method
57 | *
58 | * @return string request method.
59 | */
60 | public function getRequestMethod()
61 | {
62 | return $this->requestMethod;
63 | }
64 |
65 | /**
66 | * @param contraints[]
67 | *
68 | * @return boolean true on match
69 | */
70 | public function matchConstraints(array $constraints)
71 | {
72 | foreach ($constraints as $constraint) {
73 | $result = true;
74 | if (isset($constraints['host_match'])) {
75 | $result = $result && $this->hostMatch($constraints['host_match']);
76 | }
77 |
78 | if (isset($constraints['host'])) {
79 | $result = $result && $this->hostEqual($constraints['host']);
80 | }
81 |
82 | if (isset($constraints['request_method'])) {
83 | $result = $result && $this->requestMethodEqual($constraints['request_method']);
84 | }
85 |
86 | if (isset($constraints['path_match'])) {
87 | $result = $result && $this->pathMatch($constraints['path_match']);
88 | }
89 |
90 | if (isset($constraints['path'])) {
91 | $result = $result && $this->pathEqual($constraints['path']);
92 | }
93 |
94 | // If it matches all constraints, we simply return true and skip other constraints
95 | if ($result) {
96 | return true;
97 | }
98 | // try next one
99 | }
100 |
101 | return false;
102 | }
103 |
104 | public function queryStringMatch($pattern, array &$matches = array())
105 | {
106 | return preg_match($pattern, $this->serverParameters['QUERY_STRING'], $matches) !== false;
107 | }
108 |
109 | public function portEqual($port)
110 | {
111 | if (isset($this->serverParameters['SERVER_PORT'])) {
112 | return intval($this->serverParameters['SERVER_PORT']) == intval($port);
113 | }
114 | }
115 |
116 | /**
117 | * Check if the request host is in the list of host.
118 | *
119 | * @param array $hosts
120 | *
121 | * @return bool
122 | */
123 | public function isOneOfHosts(array $hosts)
124 | {
125 | foreach ($hosts as $host) {
126 | if ($this->matchHost($host)) {
127 | return true;
128 | }
129 | }
130 |
131 | return false;
132 | }
133 |
134 | public function pathLike($path)
135 | {
136 | $pattern = '#'.preg_quote($path, '#').'#i';
137 |
138 | return preg_match($pattern, $this->path) !== false;
139 | }
140 |
141 | public function pathMatch($pattern, array &$matches = array())
142 | {
143 | return preg_match($pattern, $this->path, $matches) !== false;
144 | }
145 |
146 | public function pathEqual($path)
147 | {
148 | return strcasecmp($path, $this->path) === 0;
149 | }
150 |
151 | public function pathContain($path)
152 | {
153 | return strpos($this->path, $path) !== false;
154 | }
155 |
156 | public function pathStartWith($path)
157 | {
158 | return strpos($this->path, $path) === 0;
159 | }
160 |
161 | public function pathEndWith($suffix)
162 | {
163 | $p = strrpos($this->path, $suffix);
164 |
165 | return ($p == strlen($this->path) - strlen($suffix));
166 | }
167 |
168 | public function hostMatch($host, array &$matches = array())
169 | {
170 | if (isset($this->serverParameters['HTTP_HOST'])) {
171 | return preg_match($host, $this->serverParameters['HTTP_HOST'], $matches) !== false;
172 | }
173 | // the HTTP HOST is not defined.
174 | return false;
175 | }
176 |
177 | public function hostEqual($host)
178 | {
179 | if (isset($this->serverParameters['HTTP_HOST'])) {
180 | return strcasecmp($this->serverParameters['HTTP_HOST'], $host) === 0;
181 | }
182 |
183 | return false;
184 | }
185 |
186 | /**
187 | * requestMethodEqual does not use PCRE pattern to match request method.
188 | *
189 | * @param string $requestMethod
190 | */
191 | public function requestMethodEqual($requestMethod)
192 | {
193 | return strcasecmp($this->requestMethod, $requestMethod) === 0;
194 | }
195 |
196 | /**
197 | * A helper function for creating request object based on request method and request uri.
198 | *
199 | * @param string $method
200 | * @param string $path
201 | * @param array $headers The headers will be built on $_SERVER if the argument is null.
202 | *
203 | * @return RouteRequest
204 | */
205 | public static function create($method, $path, array $env = array())
206 | {
207 | $request = new self($method, $path);
208 |
209 | if (function_exists('getallheaders')) {
210 | $request->headers = getallheaders();
211 | } else {
212 | // TODO: filter array keys by their prefix, consider adding an extension function for this.
213 | $request->headers = self::createHeadersFromServerGlobal($env);
214 | }
215 |
216 | if (isset($env['_SERVER'])) {
217 | $request->serverParameters = $env['_SERVER'];
218 | } else {
219 | $request->serverParameters = $env;
220 | }
221 | $request->parameters = isset($env['_REQUEST']) ? $env['_REQUEST'] : array();
222 | $request->queryParameters = isset($env['_GET']) ? $env['_GET'] : array();
223 | $request->bodyParameters = isset($env['_POST']) ? $env['_POST'] : array();
224 | $request->cookieParameters = isset($env['_COOKIE']) ? $env['_COOKIE'] : array();
225 | $request->sessionParameters = isset($env['_SESSION']) ? $env['_SESSION'] : array();
226 |
227 | return $request;
228 | }
229 |
230 | /**
231 | * Create request object from global variables.
232 | *
233 | * @param array $env
234 | * @return RouteRequest
235 | */
236 | public static function createFromEnv(array $env)
237 | {
238 | // cache
239 | if (isset($env['__request_object'])) {
240 | return $env['__request_object'];
241 | }
242 |
243 | if (isset($env['PATH_INFO'])) {
244 | $path = $env['PATH_INFO'];
245 | } elseif (isset($env['REQUEST_URI'])) {
246 | $path = $env['REQUEST_URI'];
247 | } elseif (isset($env['_SERVER']['PATH_INFO'])) {
248 | $path = $env['_SERVER']['PATH_INFO'];
249 | } elseif (isset($env['_SERVER']['REQUEST_URI'])) {
250 | $path = $env['_SERVER']['REQUEST_URI'];
251 | } else {
252 | // XXX: check path or throw exception
253 | $path = '/';
254 | }
255 |
256 | $requestMethod = 'GET';
257 | if (isset($env['REQUEST_METHOD'])) {
258 | $requestMethod = $env['REQUEST_METHOD'];
259 | } else if (isset($env['_SERVER']['REQUEST_METHOD'])) { // compatibility for superglobal
260 | $requestMethod = $env['_SERVER']['REQUEST_METHOD'];
261 | }
262 |
263 | // create request object with request method and path,
264 | // we can assign other parameters later.
265 | $request = new self($requestMethod, $path);
266 | if (function_exists('getallheaders')) {
267 | $request->headers = getallheaders();
268 | } else {
269 | // TODO: filter array keys by their prefix, consider adding an extension function for this.
270 | $request->headers = self::createHeadersFromServerGlobal($env);
271 | }
272 |
273 | if (isset($env['_SERVER'])) {
274 | $request->serverParameters = $env['_SERVER'];
275 | } else {
276 | $request->serverParameters = $env;
277 | }
278 | $request->parameters = $env['_REQUEST'];
279 | $request->queryParameters = $env['_GET'];
280 | $request->bodyParameters = $env['_POST'];
281 | $request->cookieParameters = $env['_COOKIE'];
282 | $request->sessionParameters = $env['_SESSION'];
283 |
284 | return $env['__request_object'] = $request;
285 | }
286 | }
287 |
--------------------------------------------------------------------------------
/src/Responder/SAPIResponder.php:
--------------------------------------------------------------------------------
1 | resource = $resource;
24 | }
25 |
26 | public function respondWithString($response)
27 | {
28 | http_response_code(200);
29 | fwrite($this->resource, $response);
30 | }
31 |
32 | public function respond($response)
33 | {
34 | // treat string format response as 200 OK
35 | if (is_string($response)) {
36 | // http_response_code is only available after 5.4
37 | http_response_code(200);
38 | fwrite($this->resource, $response);
39 | } else if (is_array($response)) {
40 | list($code, $headers, $body) = $response;
41 | http_response_code($code);
42 | foreach ($headers as $k => $header) {
43 | if (is_numeric($k)) {
44 | if (is_string($header)) {
45 | @header($header);
46 | } else if (is_array($header)) {
47 | // support for [ 'Content-Type' => 'text/html' ]
48 | foreach ($header as $field => $fieldValue) {
49 | // TODO: escape field value correctly
50 | @header($field . ':' . $fieldValue);
51 | }
52 | } else {
53 | throw new RuntimeException('Unexpected header value type.');
54 | }
55 | } else {
56 | @header($k . ':' . $header);
57 | }
58 | }
59 |
60 | if (is_array($body)) {
61 | fwrite($this->resource, join("",$body));
62 | } else {
63 | fwrite($this->resource, $body);
64 | }
65 | } else {
66 | // FIXME
67 | // throw new LogicException("Unsupported response value type.");
68 | }
69 | }
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/src/Responder/SAPIResponderTest.php:
--------------------------------------------------------------------------------
1 | respond('Hello World');
15 |
16 | rewind($fd);
17 | $s = fgets($fd);
18 | $this->assertEquals('Hello World', $s);
19 | fclose($fd);
20 | }
21 |
22 | public function testArrayResponse()
23 | {
24 | $fd = fopen('php://memory', 'r+');
25 | $responder = new SAPIResponder($fd);
26 | $responder->respond([ 200, [ 'Content-Type: text/plain' ], 'Hello World' ]);
27 |
28 | rewind($fd);
29 | $s = fgets($fd);
30 | $this->assertEquals('Hello World', $s);
31 |
32 | fclose($fd);
33 | }
34 |
35 | public function testHeaderListResponse()
36 | {
37 | $fd = fopen('php://memory', 'r+');
38 | $responder = new SAPIResponder($fd);
39 | $responder->respond([ 200, [ 'Content-Type: text/plain', 'X-Foo: Bar', ['X-Bar' => 'Zoo'] ], 'Hello World' ]);
40 |
41 | rewind($fd);
42 | $s = fgets($fd);
43 | $this->assertEquals('Hello World', $s);
44 |
45 | fclose($fd);
46 | }
47 |
48 | public function testHeaderAssocArrayResponse()
49 | {
50 | $fd = fopen('php://memory', 'r+');
51 | $responder = new SAPIResponder($fd);
52 | $responder->respond([ 200, [ 'Content-Type' => 'text/plain', 'X-Foo' => 'Bar', ['X-Bar' => 'Zoo'] ], 'Hello World' ]);
53 |
54 | rewind($fd);
55 | $s = fgets($fd);
56 | $this->assertEquals('Hello World', $s);
57 |
58 | fclose($fd);
59 | }
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/src/Server/BaseServer.php:
--------------------------------------------------------------------------------
1 | app = $app;
16 | $this->address = $address;
17 | $this->port = intval($port);
18 | }
19 |
20 |
21 | abstract public function listen();
22 |
23 |
24 | }
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/Server/EventHttpServer.php:
--------------------------------------------------------------------------------
1 | getUri(), PHP_EOL;
24 |
25 | $headers = $request->getInputHeaders();
26 | var_dump($headers);
27 |
28 | $conn = $request->getConnection();
29 | // $conn->setCloseCallback([$this, 'requestCloseHandler'], NULL);
30 |
31 | /*
32 | By enabling Event::READ we protect the server against unclosed conections.
33 | This is a peculiarity of Libevent. The library disables Event::READ events
34 | on this connection, and the server is not notified about terminated
35 | connections.
36 |
37 | So each time client terminates connection abruptly, we get an orphaned
38 | connection. For instance, the following is a part of `lsof -p $PID | grep TCP`
39 | command after client has terminated connection:
40 |
41 | 57-php 15057 ruslan 6u unix 0xffff8802fb59c780 0t0 125187 socket
42 | 58:php 15057 ruslan 7u IPv4 125189 0t0 TCP *:4242 (LISTEN)
43 | 59:php 15057 ruslan 8u IPv4 124342 0t0 TCP localhost:4242->localhost:37375 (CLOSE_WAIT)
44 |
45 | where $PID is our process ID.
46 |
47 | The following block of code fixes such kind of orphaned connections.
48 | */
49 | $bev = $request->getBufferEvent();
50 | $bev->enable(Event::READ);
51 |
52 | // $request->addHeader('Content-Type', 'text/plain', EventHttpRequest::OUTPUT_HEADER);
53 |
54 | /*
55 | $request->addHeader('Content-Type',
56 | 'multipart/x-mixed-replace;boundary=boundarydonotcross',
57 | EventHttpRequest::OUTPUT_HEADER);
58 | */
59 |
60 | $buf = new EventBuffer();
61 | $buf->add("Hello World\r\n");
62 |
63 | $request->sendReply(200, "OK");
64 | $request->sendReplyChunk($buf);
65 | $request->sendReplyEnd();
66 |
67 | // $request->closeConnection();
68 | }
69 |
70 |
71 | /**
72 | * Start listening
73 | */
74 | public function listen()
75 | {
76 | echo "Starting server at http://{$this->address}:{$this->port}...\n";
77 | $base = new EventBase();
78 | $http = new EventHttp($base);
79 | $http->setDefaultCallback([$this, 'handleRequest'], NULL);
80 | $ret = $http->bind($this->address, $this->port);
81 | if ($ret === false) {
82 | throw new Exception("EventHttp::bind failed on {$this->address}:{$this->port}");
83 | }
84 | $base->loop();
85 | }
86 |
87 |
88 |
89 | }
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/Server/StreamSocketServer.php:
--------------------------------------------------------------------------------
1 | address}:{$this->port}...\n";
15 |
16 | $this->socket = stream_socket_server($this->address . ':' . $this->port, $errNo, $errStr);
17 | if (!$this->socket) {
18 | throw new Exception("Can't connect socket: [$errNo] $errStr");
19 | }
20 | $this->connections[] = $this->socket;
21 |
22 |
23 | while (1) {
24 | echo "connections:";
25 | var_dump( $this->connections );
26 |
27 | $reads = $this->connections;
28 | $writes = NULL;
29 | $excepts = NULL;
30 | $modified = stream_select($reads, $writes, $excepts, 5);
31 | if ($modified === false) {
32 | break;
33 | }
34 |
35 | echo "modified fd:";
36 | var_dump( $modified );
37 |
38 |
39 | echo "reads:";
40 | var_dump( $reads );
41 |
42 | foreach ($reads as $modifiedRead) {
43 |
44 | if ($modifiedRead === $this->socket) {
45 | $conn = stream_socket_accept($this->socket);
46 | fwrite($conn, "Hello! The time is ".date("n/j/Y g:i a")."\n");
47 | $this->connections[] = $conn;
48 | } else {
49 | $data = fread($modifiedRead, 1024);
50 | var_dump($data);
51 |
52 | if (strlen($data) === 0) { // connection closed
53 | $idx = array_search($modifiedRead, $this->connections, TRUE);
54 | fclose($modifiedRead);
55 | if ($idx != -1) {
56 | unset($this->connections[$idx]);
57 | }
58 | } else if ($data === FALSE) {
59 | echo "Something bad happened";
60 | $idx = array_search($modifiedRead, $this->connections, TRUE);
61 | if ($idx != -1) {
62 | unset($this->connections[$idx]);
63 | }
64 | } else {
65 | echo "The client has sent :"; var_dump($data);
66 | fwrite($modifiedRead, "You have sent :[".$data."]\n");
67 | fclose($modifiedRead);
68 |
69 | $idx = array_search($modifiedRead, $this->connections, TRUE);
70 | if ($idx != -1) {
71 | unset($this->connections[$idx]);
72 | }
73 | }
74 | }
75 |
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Testing/TestUtils.php:
--------------------------------------------------------------------------------
1 | $method,
11 | 'PATH_INFO' => $pathInfo,
12 | ];
13 | $env['_POST'] = array();
14 | $env['_REQUEST'] = array();
15 | $env['_GET'] = array();
16 | $env['_COOKIE'] = array();
17 | $env['_SESSION'] = array();
18 | // fallback (backware compatible for $GLOBALS)
19 | $env['_SERVER'] = array();
20 | return $env;
21 | }
22 |
23 |
24 | static public function createEnvFromGlobals(array $globals)
25 | {
26 | $env = $globals['_SERVER'];
27 | $env['_REQUEST'] = $env['pux.parameters'] = $globals['_REQUEST'];
28 | $env['_POST'] = $env['pux.body_parameters'] = $globals['_POST'];
29 | $env['_GET'] = $env['pux.query_parameters'] = $globals['_GET'];
30 | $env['_COOKIE'] = $env['pux.cookies'] = $globals['_COOKIE'];
31 | $env['_SESSION'] = $env['pux.session'] = $globals['_SESSION'];
32 | return $env;
33 | }
34 |
35 | /**
36 | * createGlobals helps you define a global object for testing
37 | */
38 | static public function createGlobals($method, $pathInfo)
39 | {
40 | return [
41 | '_SERVER' => [
42 | 'REQUEST_METHOD' => $method,
43 | 'PATH_INFO' => $pathInfo,
44 | ],
45 | '_REQUEST' => [ ],
46 | '_POST' => [ ],
47 | '_GET' => [ ],
48 | '_ENV' => [ ],
49 | '_COOKIE' => [],
50 | '_SESSION' => [],
51 | ];
52 | }
53 | }
54 |
55 |
56 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | > ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
4 | fi
5 | # if [[ $(phpenv version-name) =~ 5.5 ]] ; then
6 | # # echo yes | pecl install -f APCu-beta
7 | # # echo "extension=apcu.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
8 | # fi
9 |
--------------------------------------------------------------------------------