├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── phpunit.xml
├── public
├── .htaccess
└── index.php
└── src
├── Application.php
├── Application
├── AbstractApplication.php
├── ApplicationEvent.php
├── ApplicationInterface.php
├── HttpApplicationEvent.php
├── Init
│ ├── InitConfigurationTrait.php
│ └── InitHaltHookTrait.php
├── MiddlewareAwareInterface.php
├── MiddlewareRunner.php
├── Providers
│ ├── MonologServiceProvider.php
│ └── WhoopsServiceProvider.php
├── Services
│ ├── Whoops
│ │ ├── ApplicationSystemFacade.php
│ │ └── HandlerService.php
│ └── WhoopsService.php
└── TerminableInterface.php
├── Configuration.php
├── Console.php
├── Console
├── Command.php
├── ConsoleEvent.php
└── Dispatcher.php
├── Stratigility
├── ApplicationMiddleware.php
└── MiddlewarePipeAdapter.php
└── Symfony
└── HttpKernelAdapter.php
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Hawkbit Changelog
2 |
3 | ## 2.3.2
4 |
5 | ### Fixed
6 |
7 | - Fix issue #34. Bind closure always to application instance.
8 |
9 | ### Altered
10 |
11 | - Upgrade league/container to 2.4
12 | - Upgrade zendframework/zend-diactoros to 1.4
13 | - Update container implementation to psr 11
14 |
15 | ## 2.3.1
16 |
17 | ### Fixed
18 |
19 | - Reopen and fix #29, Special Thanks to @BlackScorp for fixing and @designcise for identification
20 |
21 | ## 2.3.0
22 |
23 | ### Added
24 |
25 | - Add basic usage example
26 |
27 | ### Altered
28 |
29 | - Add official league/route 3.0 support
30 |
31 | ### Fixed
32 |
33 | - Fix #29, #30
34 |
35 | ## 2.2.0
36 |
37 | ### Added
38 |
39 | - Add console application
40 | - Add constructor injection for console commands
41 |
42 | ### Altered
43 |
44 | - Move application interface to correct position
45 | - Refactor configuration initiation into trait and reuse in application and console
46 |
47 | ## 2.1.3
48 |
49 | ### Fixed
50 |
51 | - Fix #25 Ajax request is always forcing JSON request and response
52 | - Fix #26 use league/route at dev master until official middleware release
53 |
54 | ### Removed
55 |
56 | - Remove HHVM integration due to inconsitencies
57 |
58 | ## 2.1.2
59 |
60 | ### Fixed
61 |
62 | - Fix #24 incorrect and buggy error handling
63 |
64 | ## 2.1.1
65 |
66 | ### Added
67 |
68 | - Add tests for controller constructor injection
69 |
70 | ## 2.1.0
71 |
72 | ### Fixed
73 |
74 | - Validate response contract after executing
75 |
76 | ### Altered
77 |
78 | - Whoops handlers has been refactored to `\Hawkbit\Application\Services\Whoops\HandlerService`
79 | - Error und shutdown handling has been refactored to `Hawkbit\Application\Init\InitHaltHooksTrait`
80 |
81 | ### Added
82 |
83 | - Add exception stack `Hawkbit\Application\Application\AbtractApplication::getExceptionStack` of all occured exceptions
84 | - Provide backwards / onwards compatibility for PHP 7 `\Throwables`
85 |
86 |
87 | ### Altered
88 |
89 | - Update [League Router](https://github.com/thephpleague/route/tree/507606b53d3935e7830aa7c48c43337bc2b1b2ba) and use router middleware implementation instead of application middleware.
90 | - Update Zend Stratigility to 1.3.1
91 | - Advanced error capturing and error stack for debugging
92 |
93 | ### Removed
94 |
95 | - Remove dotted notation from from configuration
96 | - Remove vagrant machine
97 |
98 | ## 2.0.1
99 |
100 | ### Altered
101 |
102 | - Fix documentation
103 | - Fix typo
104 |
105 | ## 2.0
106 |
107 | ### Notice
108 |
109 | __Migrate Turbine from PhpThinkTank to hawkbit.__
110 |
111 | ### Added
112 |
113 | - Add `\Hawkbit\Application\Configuration` (extending `\Zend\Config\Config`) as default configuration storage
114 | - Add PSR7 middleware implementation `\Hawkbit\Application\Application\MiddlewareRunner` for advanced control of application lifecycle
115 |
116 | ### Altered
117 |
118 | - Change Hawkbit\Application test namespace to Hawkbit\Application\Tests
119 | - Rewrite event behavior for advanced interception of requests, responses and errors
120 | - Implement dot chaining for nested configuration
121 |
122 | ## 1.1.7
123 |
124 | ### Altered
125 |
126 | - Fix wrong response determined by content type delegation
127 |
128 | ## 1.1.6
129 |
130 | ### Added
131 |
132 | - Add vagrant development environment
133 | - Add shutdown event
134 | - Add logic to force response emitting if headers already send
135 |
136 | ### Altered
137 |
138 | - Delegate request content type to response
139 | - Rename `Application::cleanUp` to `Application::collectGarbage`
140 | - Rename `Application::finishRequest` to `Application::shutdown`
141 | - Rename `Application::subscribe` to `Application::addListener`
142 | - Enhance error handling for different content types
143 | - Log application errors correctly, logging is silenced by default.
144 |
145 | ### Deprecated
146 |
147 | - `Application::cleanUp`
148 | - `Application::finishRequest`
149 | - `Application::subscribe`
150 |
151 |
152 | ## 1.1.5
153 |
154 | ### Added
155 |
156 | - Add `\Hawkbit\Application\Application\ConfiguratorInterface`
157 |
158 | ### Altered
159 |
160 | - `Application::getConfigurator` is now bound to `\Hawkbit\Application\Application\ConfiguratorInterface` contract
161 |
162 | ## 1.1.4
163 |
164 | ### Fixes
165 |
166 | - [\#9](../../issues/9) If class exists and is not part of container, `League\Container\Container::has` returns now false.
167 |
168 | ## 1.1.3
169 |
170 | ### Altered
171 |
172 | - Accept and process `\ArrayAccess` and `\Traversable` as configuration
173 |
174 | ## 1.1.2
175 |
176 | ### Altered
177 |
178 | - Replace applications [route collection methods](https://github.com/thephpleague/route/blob/master/src/RouteCollectionInterface.php) with `\League\Route\RouteCollectionMapTrait`
179 | - Application implements `\League\Route\RouteCollectionInterface`
180 | - add `\League\Route\RouteCollectionInterface::map()`
181 | - add `\Hawkbit\Application\Application::group()` for creating route groups, see [documentation](http://route.thephpleague.com/route-groups/)
182 |
183 | ### Deprecated
184 |
185 | - `\Hawkbit\Application\Application::subscribe()`
186 |
187 | ## 1.1.1
188 |
189 | ### Altered
190 |
191 | - Upgrade `league/route` from dev-develop to stable 2.x (`~2.0`) release
192 |
193 | ## 1.1.0
194 |
195 | ### Added
196 |
197 | - Add `filp/whoops` as default error handler
198 | - Add `zendframework/zend-stratigility` integration
199 |
200 | ### Altered
201 |
202 | - add request and response accessors
203 | - refactor error handling and replace exception decorator with whoops
204 | - pass and receive all config
205 | - remove possibilty to configure events, routes and services by callables
206 | - rename `Hawkbit\Application\Psr7\TerminableInterface` to `Hawkbit\Application\TerminableInterface`
207 | - rename debug config option to error
208 | - change configuration engine from `array` to instance of `\ArrayAccess`
209 | - Signature changes of `Hawkbit\Application\Application::handle`, `Hawkbit\Application\Application::run`, `Hawkbit\Application\Application::__construct`, `Hawkbit\Application\Application::handleErrors`
210 |
211 | ### Removed
212 |
213 | - `Hawkbit\Application\Psr7\HttpKernelInterface` replaced by `Hawkbit\Application\ApplicationInterface`
214 |
215 | ## 1.0.0 (2016-03-04)
216 |
217 | ### Added
218 |
219 | - `Hawkbit\Application\Psr7\HttpKernelInterface` and `Hawkbit\Application\Psr7\TerminableInterface` port of symfony HttpKernelInterface for PSR-7 compatibility
220 | - Add `zend/diactoros` for PSR-7 http support
221 | - provide compatibility with adapter `Hawkbit\Application\Symfony\HttpKernelAdapter` for StackPHP and other Symfony HttpKernelInterface implementations
222 | - Add `filp/whoops` as default error handler
223 | - Add `zendframework/zend-stratigility` integration
224 |
225 | ### Altered
226 |
227 | - upgrade `league/container` to latest version 2 and add interopt compatibility
228 | - upgrade `league/route` to latest version 2 (currently under development)
229 | - replace symfony request and response with diactoros request and response
230 | - enable auto wiring of container configurable and enable by default
231 | - events, routes and services configurable by callables
232 | - add request and response accessors
233 | - refactor error handling and replace exception decorator with whoops
234 | - enhance configuration handling
235 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at marco_bunge@web.de. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | We accept contributions via Pull Requests on [Github](https://github.com/Hawkbit).
6 |
7 | ## Pull Requests
8 |
9 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
10 |
11 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
12 |
13 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
14 |
15 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
16 |
17 | - **Create feature branches** - Don't ask us to pull from your master branch.
18 |
19 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
20 |
21 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.
22 |
23 | ## Install
24 |
25 | ``` bash
26 | $ composer install --dev
27 | ```
28 |
29 | ## Running Tests
30 |
31 | ``` bash
32 | $ composer test
33 | ```
34 |
35 | **Happy coding**!
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Alex Bilbie
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hawkbit\Application
2 |
3 | [![Latest Version on Packagist][ico-version]][link-packagist]
4 | [![Software License][ico-license]](LICENSE.md)
5 | [![Build Status][ico-travis]][link-travis]
6 | [![Total Downloads][ico-downloads]][link-downloads]
7 | [![Coverage Status][ico-coveralls]][link-coveralls]
8 |
9 | Hawkbit\Application micro framework is a high customizable, event driven and compatible with
10 | [PSR-7](https://github.com/php-fig/http-message),
11 | [StackPHP](http://stackphp.com/) and
12 | [Zend Stratigility](https://github.com/zendframework/zend-stratigility).
13 |
14 | Hawkbit\Application uses latest versions of [League\Route](https://github.com/thephpleague/route) for routing,
15 | [League\Container](https://github.com/thephpleague/container) for dependency injection,
16 | [League\Event](https://github.com/thephpleague/event) for event dispatching,
17 | [Zend Config](https://docs.zendframework.com/zend-config/) for configuration.
18 |
19 | Hawkbit\Application is an advanced derivate of [Proton](https://github.com/alexbilbie/Proton) and part of Hawkbit\Application Component collection by Marco Bunge. Hawkbit\Application 1.x is also known as Blast Hawkbit\Application.
20 |
21 | ### Quick start
22 |
23 | Please see [public/](public/) for example usage and read documentation.
24 |
25 | ### Integrations
26 |
27 | Hawkbit\Application delivers also optional packages:
28 |
29 | - Database: [`hawkbit/database`](https://github.com/HawkBitPhp/hawkbit-database)
30 | - Doctrine ORM Wrapper: [`hawkbit/doctrine`](https://github.com/HawkBitPhp/hawkbit-doctrine)
31 | - Plates View Engine: [`hawkbit/presentation`](https://github.com/HawkBitPhp/hawkbit-presentation)
32 |
33 | ## Motivation
34 |
35 | > My vision is to provide a micro framework which is able to handle HTTP and CLI in the same fashion. The developer should be able to reuse it's code, design it's business layer by his needs. Hawkbit should be a supporting tool instead of predefined framework. And yes it is under active development.
36 | >
37 | > I love PSR, phpleague und a minimal set of dependecies and want to create a micro framework which is used the best packages out there and bundeld in a nice application layer. I'm also love the style of component-based development.
38 | >
39 | > Hawkbit is built on top phpleague packages and keep PSR in mind. Hawkbit is designed to co-exist whith your code instead of replace code base. Hawkbit does has a small dependency footprint. Last but not least Hawkbit does not force the developer how to design the application bussiness logic, since we prefer to use POPO's for Controllers / Commands (the accessor to bussiness logic).
40 | >
41 | > At the moment I design and develop all Hawkbit packages and manage the whole codebase. I would be appreciate for support or even better: for contributors!
42 |
43 | Please refer to [Issue #33](https://github.com/HawkBitPhp/hawkbit/issues/33) for details.
44 |
45 | ## Special thanks
46 |
47 | Thank you for PR, identifing Bus, or any other Improvements!
48 |
49 | - [designcise](https://github.com/designcise)
50 | - [Vitalij Mik](https://github.com/BlackScorp)
51 |
52 | ## Install
53 |
54 | ### Using Composer
55 |
56 | Hawkbit\Application is available on [Packagist](https://packagist.org/packages/hawkbit/hawkbit) and can be installed using [Composer](https://getcomposer.org/). This can be done by running the following command or by updating your `composer.json` file.
57 |
58 | ```bash
59 | composer require hawkbit/hawkbit
60 | ```
61 |
62 | composer.json
63 |
64 | ```javascript
65 | {
66 | "require": {
67 | "hawkbit/hawkbit": "~2.0"
68 | }
69 | }
70 | ```
71 |
72 | Be sure to also include your Composer autoload file in your project:
73 |
74 | ```php
75 | 'value'
113 | ];
114 | $app = new \Hawkbit\Application($config);
115 | ```
116 |
117 | Add routes
118 |
119 | ```php
120 | get('/', function ($request, $response) {
124 | $response->getBody()->write('
It works!
');
125 | return $response;
126 | });
127 |
128 | $app->get('/hello/{name}', function ($request, $response, $args) {
129 | $response->getBody()->write(
130 | sprintf('Hello, %s!
', $args['name'])
131 | );
132 | return $response;
133 | });
134 | ```
135 |
136 | Run application
137 |
138 | ```php
139 | run();
142 | ```
143 |
144 | See also our example at `/public/index.php`.
145 |
146 | ## Configuration
147 |
148 | Add additional configuration to application
149 |
150 | Hawkbit\Application Configuration is managed by [zend-config](https://docs.zendframework.com/zend-config/).
151 |
152 | ```php
153 | setConfig([
157 | 'database' => [
158 | 'default' => 'mysql://root:root@localhost/acmedb',
159 | ],
160 | 'services' => [
161 | 'Acme\Services\ViewProvider',
162 | ]
163 | ]);
164 |
165 | //add a single value
166 | $app->setConfig('baseurl', 'localhost/');
167 |
168 | $app->getConfig()->baseurl = 'localhost/';
169 | $app->getConfig()['baseurl'] = 'localhost/';
170 | ```
171 |
172 | Access configuration
173 |
174 | ```php
175 | getConfig();
179 |
180 | //get configuration item
181 | $default = $app->getConfig('database')->default; // returns 'mysql://root:root@localhost/acmedb
182 | $default = $app->getConfig()->database->default; // returns 'mysql://root:root@localhost/acmedb
183 | $default = $app->getConfig('database')['default']; // returns 'mysql://root:root@localhost/acmedb
184 | $default = $app->getConfig()['database']['default']; // returns 'mysql://root:root@localhost/acmedb
185 | ```
186 |
187 | ## Middlewares
188 |
189 | Hawkbit\Application middlewares allows advanced control of lifecycle execution.
190 |
191 | ```php
192 | addMiddleware(new Acme\SomeMiddleware);
195 | ```
196 |
197 | Hawkbit\Application uses it's own runner `Hawkbit\Application\MiddelwareRunner`
198 |
199 | ## Routing
200 |
201 | Hawkbit\Application uses routing integration of `league/route` and allows access to route collection methods directly.
202 |
203 | Basic usage with anonymous functions:
204 |
205 | ```php
206 | get('/', function ($request, $response) {
210 | $response->getBody()->write('It works!
');
211 | return $response;
212 | });
213 |
214 | $app->get('/hello/{name}', function ($request, $response, $args) {
215 | $response->getBody()->write(
216 | sprintf('Hello, %s!
', $args['name'])
217 | );
218 | return $response;
219 | });
220 |
221 | $app->run();
222 | ```
223 |
224 | #### Access app from anonymous function
225 |
226 | Hawkbit\Application allows to access itself from anonymous function through closure binding.
227 |
228 | ```php
229 | get('/hello/{name}', function ($request, $response, $args) {
232 |
233 | // access Hawkbit\Application
234 | $app = $this;
235 |
236 | $response->getBody()->write(
237 | sprintf('Hello, %s!
', $args['name'])
238 | );
239 | return $response;
240 | });
241 |
242 | ```
243 |
244 | Basic usage with controllers:
245 |
246 | ```php
247 | get('/', 'HomeController::index'); // calls index method on HomeController class
254 |
255 | $app->run();
256 | ```
257 |
258 | ```php
259 | getBody()->write('It works!
');
271 | return $response;
272 | }
273 | }
274 | ```
275 |
276 | Automatic constructor injection of controllers:
277 |
278 | ```php
279 | getContainer()->add('CustomService', new CustomService);
288 | $app->get('/', 'HomeController::index'); // calls index method on HomeController class
289 |
290 | $app->run();
291 | ```
292 |
293 | *Please use boot method in Service Providers for correct injection of services into controller!*
294 |
295 | ```php
296 | service = $service;
316 | }
317 |
318 | /**
319 | * @return CustomService
320 | */
321 | public function getService()
322 | {
323 | return $this->service;
324 | }
325 |
326 | public function index(ServerRequestInterface $request, ResponseInterface $response, array $args)
327 | {
328 | //do somehing with service
329 | $service = $this->getService();
330 | return $response;
331 | }
332 | }
333 | ```
334 |
335 | For more information about routes [read this guide](http://route.thephpleague.com/)
336 |
337 | ### Route groups
338 |
339 | Hawkbit\Application add support for route groups.
340 |
341 | ```php
342 | group('/admin', function (\League\Route\RouteGroup $route) {
345 |
346 | //access app container (or any other method!)
347 | $app = $this;
348 |
349 | $route->map('GET', '/acme/route1', 'AcmeController::actionOne');
350 | $route->map('GET', '/acme/route2', 'AcmeController::actionTwo');
351 | $route->map('GET', '/acme/route3', 'AcmeController::actionThree');
352 | });
353 | ```
354 |
355 | #### Available vars
356 |
357 | - `$route` - `\League\Route\RouteGroup`
358 | - `$this` - `\Hawkbit\Application`
359 |
360 | ## Middleware integrations
361 |
362 | ### StackPHP
363 |
364 | Basic usage with StackPHP (using `Stack\Builder` and `Stack\Run`):
365 |
366 | ```php
367 | get('/', function ($request, $response) {
375 | $response->setContent('Hello World
');
376 | return $response;
377 | });
378 |
379 | $httpKernel = new Hawkbit\Application\Symfony\HttpKernelAdapter($app);
380 |
381 | $stack = (new \Stack\Builder())
382 | ->push('Some/MiddleWare') // This will execute first
383 | ->push('Some/MiddleWare') // This will execute second
384 | ->push('Some/MiddleWare'); // This will execute third
385 |
386 | $app = $stack->resolve($httpKernel);
387 | \Stack\run($httpKernel); // The app will run after all the middlewares have run
388 | ```
389 |
390 | ### Zend Stratigility
391 |
392 | Basic usage with Stratigility (using `Zend\Stratigility\MiddlewarePipe`):
393 |
394 | ```php
395 | get('/', function($request, ResponseInterface $response){
404 | $response->getBody()->write('Hello World');
405 | });
406 | $middleware = new MiddlewarePipeAdapter($application);
407 |
408 | //wrap html heading
409 | $middleware->pipe('/', function($request, ResponseInterface $response, $next){
410 | $response->getBody()->write('');
411 |
412 | /** @var ResponseInterface $response */
413 | $response = $next($request, $response);
414 |
415 | $response->getBody()->write('
');
416 | });
417 |
418 | /** @var ResponseInterface $response */
419 | $response = $middleware(ServerRequestFactory::fromGlobals(), $application->getResponse());
420 |
421 | echo $response->getBody(); //prints Hello World
422 |
423 | ```
424 |
425 | ## Error handling
426 |
427 | Hawkbit\Application uses Whoops error handling framework and determines the error handler by request content type.
428 |
429 | Set your own handler:
430 |
431 | ```php
432 | getErrorHandler()->push(new Acme\ErrorResponseHandler);
435 | ```
436 |
437 | By default Hawkbit\Application runs with error options disabled. To enable debugging add
438 |
439 | ```php
440 | setConfig('error', true);
443 | ```
444 |
445 | By default Hawkbit\Application is catching all errors. To disable error catching add
446 |
447 | ```php
448 | setConfig('error.catch', false);
451 | ```
452 |
453 | ## Console
454 |
455 | The console application inherit all methods from Http Application except routing and PSR-7 handling and capturing.
456 | In addition to http application, the console application does not support all events (Refer to events for more
457 | information!)
458 |
459 | ## Logging
460 |
461 | Hawkbit\Application has built in support for Monolog. To access a channel call:
462 |
463 | ```php
464 | getLogger('channel name');
467 | ```
468 |
469 | For more information about channels read this guide - [https://github.com/Seldaek/monolog/blob/master/doc/usage.md#leveraging-channels](https://github.com/Seldaek/monolog/blob/master/doc/usage.md#leveraging-channels).
470 |
471 | ## Events
472 |
473 | You can intercept requests and responses at seven points during the lifecycle. You can manipulate Request, Response and
474 | ErrorResponse via `Hawkbit\ApplicationEvent`.
475 |
476 | ### Application event
477 |
478 | ```php
479 | getParamCollection(); // returns a mutable \ArrayObject
485 |
486 | // access application
487 | $event->getApplication();
488 |
489 | ```
490 |
491 | ### request.received
492 |
493 | ```php
494 | addListener($app::EVENT_REQUEST_RECEIVED, function (\Hawkbit\Application\ApplicationEvent $event) {
497 | $request = $event->getRequest();
498 |
499 | // manipulate $request
500 |
501 | $event->setRequest($request);
502 | });
503 | ```
504 |
505 | This event is fired when a request is received but before it has been processed by the router.
506 |
507 | ### response.created
508 |
509 | *Not available for Console applications!*
510 |
511 | ```php
512 | addListener($app::EVENT_RESPONSE_CREATED, function (\Hawkbit\Application\ApplicationEvent $event) {
515 | $request = $event->getRequest();
516 | $response = $event->getResponse();
517 |
518 | // manipulate request or response
519 |
520 | $event->setRequest($request);
521 | $event->setResponse($response);
522 | });
523 | ```
524 |
525 | This event is fired when a response has been created but before it has been output.
526 |
527 | ### response.sent
528 |
529 | *Not available for Console applications! Please use `shutdown`*
530 |
531 | ```php
532 | addListener($app::EVENT_RESPONSE_SENT, function (\Hawkbit\Application\ApplicationEvent $event) {
535 | $request = $event->getRequest();
536 | $response = $event->getResponse();
537 |
538 | // manipulate request or response
539 |
540 | $event->setRequest($request);
541 | $event->setResponse($response);
542 | });
543 | ```
544 |
545 | This event is fired when a response has been output and before the application lifecycle is completed. Not available for Console Applications!
546 |
547 | ### runtime.error
548 |
549 | ```php
550 | addListener($app::EVENT_RUNTIME_ERROR, function (\Hawkbit\Application\ApplicationEvent $event, $exception) use ($app) {
553 | //process exception
554 | });
555 | ```
556 |
557 | This event is always fired when an error occurs.
558 |
559 | ### lifecycle.error
560 |
561 | *Not available for Console applications! Please use `runtime.error`*
562 |
563 | `$errorResponse` is used as default response
564 |
565 | ```php
566 | addListener($app::EVENT_LIFECYCLE_ERROR, function (\Hawkbit\Application\ApplicationEvent $event, \Exception $exception) {
569 | $errorResponse = $event->getErrorResponse();
570 |
571 | //manipulate error response and process exception
572 |
573 | $event->setErrorResponse($errorResponse);
574 | });
575 | ```
576 |
577 | This event is fired only when an error occurs while handling request/response lifecycle.
578 | This event is fired after runtime.error
579 |
580 | ### lifecycle.complete
581 |
582 | *Not available for Console applications! Please use `shutdown`*
583 |
584 | ```php
585 | addListener($app::EVENT_LIFECYCLE_COMPLETE, function (\Hawkbit\Application\ApplicationEvent $event) {
588 | // access the request using $event->getRequest()
589 | // access the response using $event->getResponse()
590 | });
591 | ```
592 |
593 | This event is fired when a response has been output and before the application lifecycle is completed.
594 |
595 | ### shutdown
596 |
597 | ```php
598 | addListener($app::EVENT_SHUTDOWN, function (\Hawkbit\Application\ApplicationEvent $event, $response, $terminatedOutputBuffers = []) {
601 | // access the response using $event->getResponse()
602 | // access terminated output buffer contents
603 | // or force application exit()
604 | });
605 | ```
606 |
607 | This event is always fired after each operation is completed or failed.
608 |
609 | ### Custom Events
610 |
611 | You can fire custom events using the event emitter directly:
612 |
613 | ```php
614 | addListener('custom.event', function ($event, $time) {
618 | return 'the time is '.$time;
619 | });
620 |
621 | // or with class addListener
622 | $app->addListener(Acme\Event::class, function (Acme\Event $event, $time) {
623 | return 'the time is '.$time;
624 | });
625 |
626 | // Publish
627 | $app->getEventEmitter()->emit('custom.event', time());
628 | ```
629 |
630 | ## Dependency Injection Container
631 |
632 | Hawkbit\Application uses `League/Container` as its dependency injection container.
633 |
634 | You can bind singleton objects into the container from the main application object using ArrayAccess:
635 |
636 | ```php
637 | getConfig('database');
641 | $manager = new Illuminate\Database\Capsule\Manager;
642 |
643 | $manager->addConnection([
644 | 'driver' => 'mysql',
645 | 'host' => $config['host'],
646 | 'database' => $config['name'],
647 | 'username' => $config['user'],
648 | 'password' => $config['pass'],
649 | 'charset' => 'utf8',
650 | 'collation' => 'utf8_unicode_ci'
651 | ], 'default');
652 |
653 | $manager->setAsGlobal();
654 |
655 | return $manager;
656 | };
657 | ```
658 |
659 | or by container access:
660 |
661 | ```php
662 | getContainer()->share('db', function () use($app) {
665 | $config = $app->getConfig('database');
666 | $manager = new Illuminate\Database\Capsule\Manager;
667 |
668 | $manager->addConnection([
669 | 'driver' => 'mysql',
670 | 'host' => $config['db_host'],
671 | 'database' => $config['db_name'],
672 | 'username' => $config['db_user'],
673 | 'password' => $config['db_pass'],
674 | 'charset' => 'utf8',
675 | 'collation' => 'utf8_unicode_ci'
676 | ], 'default');
677 |
678 | $manager->setAsGlobal();
679 |
680 | return $manager;
681 | });
682 | ```
683 |
684 | Multitons can be added using the `add` method on the container:
685 |
686 | ```php
687 | getContainer()->add('foo', function () {
691 | return new Foo();
692 | });
693 | ```
694 |
695 | Service providers can be registered using the `register` method on the Hawkbit\Application app or `addServiceProvider` on the container:
696 |
697 | ```php
698 | register('\My\Service\Provider');
701 | $app->getContainer()->addServiceProvider('\My\Service\Provider');
702 | ```
703 |
704 | For more information about service providers check out this page - [http://container.thephpleague.com/service-providers/](http://container.thephpleague.com/service-providers/).
705 |
706 | For easy testing down the road it is recommended you embrace constructor injection:
707 |
708 | ```php
709 | getContainer()->add('Bar', function () {
712 | return new Bar();
713 | });
714 |
715 | $app->getContainer()->add('Foo', function () use ($app) {
716 | return new Foo(
717 | $app->getContainer()->get('Bar')
718 | );
719 | });
720 | ```
721 |
722 | ### Container
723 |
724 | Set your own container needs an instance of `\League\Container\ContainerInterface`
725 |
726 | ```php
727 | setContainer($container);
730 | ```
731 |
732 | Get container
733 |
734 | ```php
735 | getContainer();
738 | ```
739 |
740 | ## Services
741 |
742 | Hawkbit\Application uses dependency injection container to access services. Following integrations can be exchanged.
743 |
744 | ### Configurator
745 |
746 | Uses in `Application::setConfig()`,`Application::getConfig()` and `Application::hasConfig()`
747 |
748 | ```php
749 | getConfigurator();
752 | ```
753 |
754 | ```php
755 | getContainer()->share(\Zend\Config\Config::class, new \Zend\Config\Config([], true));
758 | ```
759 |
760 | ### error handler
761 |
762 | ```php
763 | getContainer()->share(\Whoops\Run::class, new \Whoops\Run());
766 | ```
767 |
768 | ```php
769 | getErrorHandler();
772 | ```
773 |
774 | ### error response handler
775 |
776 | ```php
777 | getContainer()->share(\Whoops\Handler\HandlerInterface::class, Acme\ErrorResponseHandler::class);
780 | ```
781 |
782 | ```php
783 | getErrorResponseHandler();
786 | ```
787 |
788 | ### psr logger
789 |
790 | Get a new logger instance by channel name
791 |
792 | ```php
793 | getContainer()->add(\Psr\Log\LoggerInterface::class, \Monolog\Logger::class);
796 | ```
797 |
798 | ```php
799 | getLogger('channel name');
802 | ```
803 |
804 | Get a list of available logger channels
805 |
806 | ```php
807 | getLoggerChannels();
810 | ```
811 |
812 | ### psr server request
813 |
814 | ```php
815 | getContainer()->share(\Psr\Http\Message\ServerRequestInterface::class, \Zend\Diactoros\ServerRequestFactory::fromGlobals());
818 | ```
819 |
820 | ```php
821 | getRequest();
824 | ```
825 |
826 | ### psr response
827 |
828 | ```php
829 | getContainer()->add(\Psr\Http\Message\ResponseInterface::class, \Zend\Diactoros\Response::class);
832 | ```
833 |
834 | ```php
835 | getRequest();
838 | ```
839 |
840 | ### response emitter
841 |
842 | ```php
843 | getContainer()->share(\Zend\Diactoros\Response\EmitterInterface::class, \Zend\Diactoros\Response\SapiEmitter::class);
846 | ```
847 |
848 | ```php
849 | getResponseEmitter();
852 | ```
853 |
854 |
855 | ## Change log
856 |
857 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
858 |
859 | ## Testing
860 |
861 | ``` bash
862 | $ composer test
863 | ```
864 |
865 | ## Contributing
866 |
867 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
868 |
869 | ## Security
870 |
871 | If you discover any security related issues, please email instead of using the issue tracker.
872 |
873 | ## Credits
874 |
875 | - [Marco Bunge](https://github.com/mbunge)
876 | - [Alex Bilbie](https://github.com/alexbilbie) (Proton)
877 | - [All contributors](https://github.com/hawkbit/hawkbit/graphs/contributors)
878 |
879 | ## License
880 |
881 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
882 |
883 | [ico-version]: https://img.shields.io/packagist/v/hawkbit/hawkbit.svg?style=flat-square
884 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
885 | [ico-travis]: https://img.shields.io/travis/HawkBitPhp/hawkbit/master.svg?style=flat-square
886 | [ico-downloads]: https://img.shields.io/packagist/dt/hawkbit/hawkbit.svg?style=flat-square
887 | [ico-coveralls]: https://img.shields.io/coveralls/HawkBitPhp/hawkbit/master.svg?style=flat-square
888 |
889 | [link-packagist]: https://packagist.org/packages/hawkbit/hawkbit
890 | [link-travis]: https://travis-ci.org/HawkBitPhp/hawkbit
891 | [link-downloads]: https://packagist.org/packages/hawkbit/hawkbit
892 | [link-author]: https://github.com/mbunge
893 | [link-contributors]: ../../contributors
894 | [link-coveralls]: https://coveralls.io/github/HawkBitPhp/hawkbit
895 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hawkbit/hawkbit",
3 | "type": "library",
4 | "description": "PSR-7 Micro PHP framework",
5 | "keywords": [
6 | "framework",
7 | "StackPHP",
8 | "HttpKernelInterface",
9 | "PSR-7",
10 | "middleware",
11 | "micro",
12 | "Strategility",
13 | "Hawkbit"
14 | ],
15 | "homepage": "https://github.com/Hawkbit",
16 | "license": "MIT",
17 | "authors": [
18 | {
19 | "name": "Marco Bunge",
20 | "email": "marco_bunge@web.de"
21 | },
22 | {
23 | "name": "Alex Bilbie",
24 | "email": "hello@alexbilbie.com"
25 | }
26 | ],
27 | "require": {
28 | "php": ">=5.5.9",
29 | "filp/whoops": "^2.1",
30 | "league/route": "~3.0",
31 | "league/container": "^2.4",
32 | "league/event": "^2.1",
33 | "league/climate": "^3.2",
34 | "monolog/monolog": "^1.22",
35 | "zendframework/zend-config": "^2.6",
36 | "zendframework/zend-diactoros": "^1.4"
37 | },
38 | "require-dev": {
39 | "phpunit/phpunit": "~4.8",
40 | "symfony/http-kernel": "~2.7|~3.0",
41 | "symfony/psr-http-message-bridge": "*",
42 | "zendframework/zend-stratigility": "~1.1.2|~1.3.1"
43 | },
44 | "autoload": {
45 | "psr-4": {
46 | "Hawkbit\\": "src/"
47 | }
48 | },
49 | "autoload-dev": {
50 | "psr-4": {
51 | "Hawkbit\\Tests\\": "tests/"
52 | },
53 | "files": [
54 | "tests/TestAsset/Functions.php",
55 | "tests/TestAsset/SapiResponse.php"
56 | ]
57 | },
58 | "provide": {
59 | "psr/container-implementation": "~1.0",
60 | "psr/http-message-implementation": "~1.0",
61 | "psr/log-implementation": "~1.0"
62 | },
63 | "suggest": {
64 | "symfony/http-kernel": "Dependency of symfony http foundation adapter",
65 | "symfony/psr-http-message-bridge": "Dependency of symfony http foundation adapter",
66 | "zendframework/zend-stratigility": "Dependency of stratigility middleware adapter"
67 | },
68 | "scripts": {
69 | "test": [
70 | "cd vendor/phpunit/phpunit",
71 | "phpunit --configuration phpunit.xml"
72 | ]
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/
16 | ./tests/Stubs
17 |
18 |
19 |
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 | RewriteEngine On
2 |
3 | # Some hosts may require you to use the `RewriteBase` directive.
4 | # If you need to use the `RewriteBase` directive, it should be the
5 | # absolute physical path to the directory that contains this htaccess file.
6 | #
7 | RewriteBase /
8 |
9 | # Disallow set PHPSESSID
10 | RewriteCond %{QUERY_STRING} PHPSESSID=.*$
11 | RewriteRule .* %{REQUEST_URI}? [R=301,L]
12 |
13 | RewriteCond %{REQUEST_FILENAME} !-d
14 | RewriteCond %{REQUEST_FILENAME} !-f
15 | RewriteRule ^ index.php [QSA,L]
16 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | get('/', function (ServerRequestInterface $request, ResponseInterface $response) {
11 | $response->getBody()->write('Hello, World!
');
12 |
13 | return $response;
14 | });
15 |
16 | $app->run();
--------------------------------------------------------------------------------
/src/Application.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Alex Bilbie
7 | * @copyright Marco Bunge
8 | *
9 | * @license MIT
10 | */
11 |
12 | namespace Hawkbit;
13 |
14 | use Hawkbit\Application\Init\InitHaltHookTrait;
15 | use Hawkbit\Application\AbstractApplication;
16 | use Hawkbit\Application\Init\InitConfigurationTrait;
17 | use Hawkbit\Application\MiddlewareAwareInterface;
18 | use Hawkbit\Application\Providers\MonologServiceProvider;
19 | use Hawkbit\Application\Providers\WhoopsServiceProvider;
20 | use Hawkbit\Application\TerminableInterface;
21 | use League\Container\ReflectionContainer;
22 | use League\Container\ServiceProvider\ServiceProviderInterface;
23 | use League\Route\Http\Exception\NotFoundException;
24 | use League\Route\Middleware\ExecutionChain;
25 | use League\Route\RouteCollection;
26 | use League\Route\RouteCollectionInterface;
27 | use League\Route\RouteCollectionMapTrait;
28 | use Psr\Http\Message\ResponseInterface;
29 | use Psr\Http\Message\ServerRequestInterface;
30 | use Hawkbit\Application\HttpApplicationEvent;
31 | use Zend\Diactoros\Response;
32 | use Zend\Diactoros\ServerRequestFactory;
33 |
34 | /**
35 | * Hawkbit Application Class.
36 | */
37 | final class Application extends AbstractApplication
38 | implements RouteCollectionInterface, TerminableInterface, MiddlewareAwareInterface
39 | {
40 |
41 | use RouteCollectionMapTrait;
42 | use InitConfigurationTrait;
43 | use InitHaltHookTrait;
44 |
45 | /**
46 | * @var \League\Route\RouteCollection
47 | */
48 | protected $router;
49 |
50 | /**
51 | * @var \Zend\Diactoros\Response\EmitterInterface
52 | */
53 | protected $responseEmitter;
54 |
55 | /**
56 | * Flag which is forcing response termination after
57 | * handling request / response lifecycle
58 | *
59 | * Response always destroyed before throwing exceptions!
60 | *
61 | * @var bool
62 | */
63 | private $terminate = true;
64 |
65 | /**
66 | * Flag which forces response emitting and ignores already sended output
67 | *
68 | * @var bool
69 | */
70 | private $forceResponseEmitting = false;
71 |
72 | /**
73 | * Get content type of current request or response
74 | *
75 | * @var string
76 | */
77 | private $contentType = 'text/html';
78 |
79 | /**
80 | * @var string
81 | */
82 | protected $applicationEventClass = HttpApplicationEvent::class;
83 |
84 | /**
85 | * New Application.
86 | *
87 | * @param bool|array $configuration Enable debug mode
88 | * @param ServiceProviderInterface[] $defaultProviders
89 | */
90 | public function __construct($configuration = [], array $defaultProviders = [
91 | MonologServiceProvider::class,
92 | WhoopsServiceProvider::class
93 | ])
94 | {
95 | $this->init($configuration);
96 |
97 | foreach ($defaultProviders as $provider){
98 | $this->getContainer()->addServiceProvider($provider);
99 | }
100 | }
101 |
102 | /**
103 | * @param $configuration
104 | */
105 | public function init($configuration = [])
106 | {
107 | $this->initConfiguration($configuration);
108 | $this->initContentType();
109 | $this->initHaltHooks();
110 | }
111 |
112 | /*******************************************
113 | *
114 | * Middleware
115 | *
116 | */
117 |
118 | /**
119 | * Add a middleware
120 | *
121 | * @param callable $middleware
122 | */
123 | public function addMiddleware(callable $middleware)
124 | {
125 | $this->getRouter()->middleware($middleware);
126 | }
127 |
128 | /**
129 | * @return callable[]
130 | */
131 | public function getMiddlewares()
132 | {
133 | return $this->getRouter()->getMiddlewareStack();
134 | }
135 |
136 | /*******************************************
137 | *
138 | * GETTER / SETTER
139 | *
140 | */
141 |
142 | /**
143 | * Get the Emitter.
144 | *
145 | * @deprecated
146 | * @return \League\Event\EmitterInterface
147 | */
148 | public function getEmitter()
149 | {
150 | return $this->getEventEmitter();
151 | }
152 |
153 | /**
154 | * Return the router.
155 | *
156 | * @return \League\Route\RouteCollection
157 | */
158 | public function getRouter()
159 | {
160 | if (!isset($this->router)) {
161 | $container = clone $this->getContainer();
162 | $container->delegate(new ReflectionContainer);
163 | $this->router = (new RouteCollection($container));
164 | }
165 |
166 | /** @var RouteCollection $router */
167 | $router = $this->validateContract($this->router, RouteCollection::class);
168 | return $router;
169 | }
170 |
171 | /**
172 | * Get the request
173 | *
174 | * @return \Psr\Http\Message\ServerRequestInterface
175 | */
176 | public function getRequest()
177 | {
178 | if (!$this->getContainer()->has(ServerRequestInterface::class)) {
179 | $this->getContainer()->share(ServerRequestInterface::class,function(){
180 | $beforeIndexPosition = strpos($_SERVER['PHP_SELF'],'/index.php');
181 | /**
182 | * If there is some string before /index.php then remove this string from REQUEST_URI
183 | */
184 | if(false !== $beforeIndexPosition && $beforeIndexPosition > 0){
185 | $scriptUrl = substr($_SERVER['PHP_SELF'],0,$beforeIndexPosition);
186 | $_SERVER['REQUEST_URI'] = str_replace(['/index.php',$scriptUrl],'',$_SERVER['REQUEST_URI']);
187 | }
188 | return ServerRequestFactory::fromGlobals()->withHeader('content-type', $this->getContentType());
189 | });
190 | }
191 |
192 | /** @var ServerRequestInterface $request */
193 | $request = $this->validateContract($this->getContainer()->get(ServerRequestInterface::class),
194 | ServerRequestInterface::class);
195 | return $request;
196 | }
197 |
198 | /**
199 | * Get the response
200 | *
201 | * @param string $content
202 | * @return \Psr\Http\Message\ResponseInterface
203 | */
204 | public function getResponse($content = '')
205 | {
206 | //transform content by content type
207 | if ($this->isJsonRequest()) {
208 | if ($content instanceof Response\JsonResponse) {
209 | $content = json_decode($content->getBody());
210 | } elseif (!is_array($content)) {
211 | $content = [$content];
212 | }
213 | } else {
214 | if (is_array($content)) {
215 | $content = implode('', $content);
216 | } elseif ($content instanceof ResponseInterface) {
217 | $content = $content->getBody()->__toString();
218 | }
219 | }
220 |
221 | if (!$this->getContainer()->has(ResponseInterface::class)) {
222 | if ($this->isCli()) {
223 | $class = Response\TextResponse::class;
224 | } elseif ($this->isJsonRequest()) {
225 | $class = Response\JsonResponse::class;
226 | } else {
227 | $class = Response\HtmlResponse::class;
228 | }
229 | $this->getContainer()->add(ResponseInterface::class, $class);
230 | }
231 |
232 | /** @var ResponseInterface $response */
233 | $response = $this->validateContract($this->getContainer()->get(ResponseInterface::class, [$content]),
234 | ResponseInterface::class);
235 |
236 | //inject request content type
237 | $request = $this->getRequest();
238 |
239 | $contentTypeKey = 'content-type';
240 | foreach ($request->getHeader($contentTypeKey) as $contentType) {
241 | $response = $response->withHeader($contentTypeKey, $contentType);
242 | }
243 |
244 | return $response;
245 | }
246 |
247 |
248 | /**
249 | * Get response emitter
250 | *
251 | * @return \Zend\Diactoros\Response\EmitterInterface
252 | */
253 | public function getResponseEmitter()
254 | {
255 | if (!$this->getContainer()->has(Response\EmitterInterface::class)) {
256 | $this->getContainer()->share(Response\EmitterInterface::class, new Response\SapiEmitter());
257 | }
258 |
259 | /** @var Response\EmitterInterface $contract */
260 | $contract = $this->validateContract($this->getContainer()->get(Response\EmitterInterface::class),
261 | Response\EmitterInterface::class);
262 | return $contract;
263 | }
264 |
265 | /**
266 | * @return string
267 | */
268 | public function getContentType()
269 | {
270 | return $this->contentType;
271 | }
272 |
273 | /**
274 | * @param string $contentType
275 | * @return Application
276 | */
277 | public function setContentType($contentType)
278 | {
279 | $this->contentType = $contentType;
280 | return $this;
281 | }
282 |
283 | /**
284 | * Check if request is a ajax request
285 | *
286 | * @return bool
287 | */
288 | public function isAjaxRequest()
289 | {
290 | return
291 | false !== strpos(
292 | strtolower(ServerRequestFactory::getHeader('x-requested-with', $this->getRequest()->getHeaders(), '')),
293 | 'xmlhttprequest'
294 | ) && $this->isHttpRequest();
295 | }
296 |
297 | /**
298 | * Check if request is a ajax request
299 | *
300 | * @return bool
301 | */
302 | public function isJsonRequest()
303 | {
304 | return
305 | false !== strpos(
306 | $this->getContentType(),
307 | 'json'
308 | ) && $this->isHttpRequest();
309 | }
310 |
311 | /**
312 | * Check if request is a ajax request
313 | *
314 | * @return bool
315 | */
316 | public function isSoapRequest()
317 | {
318 | return
319 | false !== strpos(
320 | $this->getContentType(),
321 | 'soap'
322 | ) && $this->isHttpRequest();
323 | }
324 |
325 | /**
326 | * Check if request is a ajax request
327 | *
328 | * @return bool
329 | */
330 | public function isXmlRequest()
331 | {
332 | return
333 | false !== strpos(
334 | $this->getContentType(),
335 | 'xml'
336 | ) && $this->isHttpRequest();
337 | }
338 |
339 | /**
340 | * Check that terminate the response request
341 | * lifecycle is enabled
342 | *
343 | * @return boolean
344 | */
345 | public function canTerminate()
346 | {
347 | return $this->terminate;
348 | }
349 |
350 | /**
351 | * Set terminating flag
352 | *
353 | * @param boolean $terminate
354 | * @return $this
355 | */
356 | public function setCanTerminate($terminate)
357 | {
358 | $this->terminate = $terminate;
359 | return $this;
360 | }
361 |
362 | /**
363 | * @return boolean
364 | */
365 | public function canForceResponseEmitting()
366 | {
367 | return $this->forceResponseEmitting;
368 | }
369 |
370 | /**
371 | * @param boolean $forceResponseEmitting
372 | * @return $this
373 | */
374 | public function setForceResponseEmitting($forceResponseEmitting)
375 | {
376 | $this->forceResponseEmitting = $forceResponseEmitting;
377 | return $this;
378 | }
379 |
380 | /*******************************************
381 | *
382 | * ROUTER
383 | *
384 | */
385 |
386 | /**
387 | * Add a route to the map.
388 | *
389 | * @param $method
390 | * @param $route
391 | * @param $action
392 | *
393 | * @return \League\Route\Route
394 | */
395 | public function map($method, $route, $action)
396 | {
397 | return $this->getRouter()->map($method, $route, $this->bindClosureToInstance($action, $this));
398 | }
399 |
400 | /**
401 | * Add a group of routes to the collection. Binds $this to app instance
402 | *
403 | * @param $prefix
404 | * @param callable $group
405 | *
406 | * @return \League\Route\RouteGroup
407 | */
408 | public function group($prefix, callable $group)
409 | {
410 | return $this->getRouter()->group($prefix, $this->bindClosureToInstance($group, $this));
411 | }
412 |
413 | /*******************************************
414 | *
415 | * LIFECYCLE INVOCATION
416 | *
417 | */
418 |
419 | /**
420 | * Convert request into response. If an error occurs, Hawkbit tries
421 | * to handle error as response.
422 | *
423 | * @param ServerRequestInterface $request
424 | * @param ResponseInterface $response
425 | * @param bool $catch
426 | *
427 | * @return ResponseInterface
428 | *
429 | * @throws \Throwable
430 | */
431 | public function handle(
432 | ServerRequestInterface $request,
433 | ResponseInterface $response = null,
434 | $catch = self::DEFAULT_ERROR_CATCH
435 | )
436 | {
437 | /** @var ResponseInterface $response */
438 | // Passes the request to the container
439 | if (!$this->getContainer()->has(ServerRequestInterface::class)) {
440 | $this->getContainer()->share(ServerRequestInterface::class, $request);
441 | }
442 |
443 | //inject request content type
444 | $contentType = ServerRequestFactory::getHeader('content-type', $this->getRequest()->getHeaders(), '');
445 | $this->setContentType($contentType);
446 |
447 | if ($response === null) {
448 | $response = $this->getResponse();
449 | }
450 |
451 | $applicationEvent = $this->getApplicationEvent();
452 | $applicationEvent->setRequest($request);
453 | $applicationEvent->setResponse($response);
454 |
455 | try {
456 | $response = $this->handleResponse($this->handleRequest($request), $response);
457 | }catch (\Exception $exception){
458 | $notFoundException = $exception instanceof NotFoundException;
459 | $response = $this->handleError($exception, $request, $response, $catch)
460 | ->withStatus($notFoundException ? 404 : 500);
461 | }
462 |
463 | // validate response
464 | $response = $this->validateContract($response, ResponseInterface::class);
465 |
466 | // update content type
467 | $this->setContentType(ServerRequestFactory::getHeader('content-type', $response->getHeaders(), ''));
468 |
469 | return $response;
470 | }
471 |
472 | /**
473 | * Convert request into response.
474 | *
475 | * @param ServerRequestInterface $request
476 | * @return ServerRequestInterface
477 | */
478 | public function handleRequest(ServerRequestInterface $request)
479 | {
480 | $applicationEvent = $this->getApplicationEvent();
481 | $applicationEvent->setName(self::EVENT_REQUEST_RECEIVED);
482 | $this->emit($applicationEvent, $request);
483 |
484 | return $applicationEvent->getRequest();
485 | }
486 |
487 | /**
488 | * Handle Response
489 | *
490 | * @param ServerRequestInterface $request
491 | * @param ResponseInterface $response
492 | * @return ResponseInterface
493 | */
494 | public function handleResponse(ServerRequestInterface $request, ResponseInterface $response)
495 | {
496 | $applicationEvent = $this->getApplicationEvent();
497 | $applicationEvent->setName(self::EVENT_RESPONSE_CREATED);
498 | $applicationEvent->setResponse($this->getRouter()->dispatch(
499 | $request,
500 | $response
501 | ));
502 |
503 | $this->emit($applicationEvent, $request, $response);
504 |
505 | return $applicationEvent->getResponse();
506 | }
507 |
508 | /**
509 | * Handle error and return response of error message or throw
510 | * error if error.catch is disabled.
511 | *
512 | * @param \Throwable|\Exception $exception
513 | * @param \Psr\Http\Message\ServerRequestInterface $request
514 | * @param ResponseInterface $response
515 | * @param bool $catch
516 | *
517 | * @return \Psr\Http\Message\ResponseInterface
518 | *
519 | * @throws string
520 | */
521 | public function handleError(
522 | $exception,
523 | ServerRequestInterface $request,
524 | ResponseInterface $response,
525 | $catch = self::DEFAULT_ERROR_CATCH
526 | )
527 | {
528 | // notify app that an error occurs
529 | $this->error = true;
530 |
531 | $errorHandler = $this->getErrorHandler();
532 |
533 | // if delivered value of $catch, then configured value, then default value
534 | $catch = self::DEFAULT_ERROR_CATCH !== $catch ? $catch : $this->getConfig(self::KEY_ERROR_CATCH, $catch);
535 |
536 | $showError = $this->getConfig(self::KEY_ERROR, static::DEFAULT_ERROR);
537 |
538 | $this->pushException($errorHandler->decorateException($exception));
539 |
540 | //execute application middlewares
541 | try {
542 | $middlewares = $this->getRouter()->getMiddlewareStack();
543 | $middlewareRunner = new ExecutionChain();
544 | foreach ($middlewares as $middleware) {
545 | $middlewareRunner->middleware($middleware);
546 | }
547 |
548 | $response = $middlewareRunner->execute($request, $response);
549 | }catch (\Exception $e){
550 | $this->pushException($e);
551 | }
552 |
553 | // get last occured exception
554 | $exception = $this->getLastException();
555 |
556 | $message = $errorHandler->getErrorMessage($exception);
557 |
558 | $errorResponse = $this->determineErrorResponse($exception, $message, $response, $request);
559 |
560 | if (
561 | false === $catch
562 | && true === $showError
563 | ) {
564 | $this->throwException($exception);
565 | }
566 |
567 | return $errorResponse;
568 | }
569 |
570 | /**
571 | * Handle response / request lifecycle
572 | *
573 | * When $callable is a valid callable, callable will executed before emit response
574 | *
575 | * @param \Psr\Http\Message\ServerRequestInterface $request A Request instance
576 | * @param \Psr\Http\Message\ResponseInterface $response A response instance
577 | *
578 | * @return $this
579 | *
580 | */
581 | public function run(ServerRequestInterface $request = null, ResponseInterface $response = null)
582 | {
583 | if ($request === null) {
584 | $request = $this->getRequest();
585 | }
586 | $response = $this->handle($request, $response);
587 |
588 | $this->emitResponse($request, $response);
589 |
590 | if ($this->canTerminate()) {
591 | $this->terminate($request, $response);
592 | }
593 |
594 | $this->shutdown($response);
595 |
596 | return $this;
597 | }
598 |
599 | /**
600 | * Emit a response
601 | *
602 | * @param \Psr\Http\Message\ServerRequestInterface $request
603 | * @param \Psr\Http\Message\ResponseInterface $response
604 | * @throws \Exception
605 | */
606 | public function emitResponse(ServerRequestInterface $request, ResponseInterface $response)
607 | {
608 | try {
609 | $this->getResponseEmitter()->emit($response);
610 | } catch (\Exception $e) {
611 | if ($this->canForceResponseEmitting()) {
612 | // flush buffers
613 | $maxBufferLevel = ob_get_level();
614 |
615 | while (ob_get_level() > $maxBufferLevel) {
616 | ob_end_flush();
617 | }
618 |
619 | // print response
620 | echo $response->getBody();
621 | } else {
622 | throw $e;
623 | }
624 | }
625 | $applicationEvent = $this->getApplicationEvent();
626 | $applicationEvent->setName(self::EVENT_RESPONSE_SENT);
627 | $applicationEvent->setRequest($request);
628 | $applicationEvent->setResponse($response);
629 | $this->emit($applicationEvent);
630 | }
631 |
632 | /**
633 | * Terminates a request/response cycle.
634 | *
635 | * @param \Psr\Http\Message\ServerRequestInterface $request
636 | * @param \Psr\Http\Message\ResponseInterface $response
637 | *
638 | * @return void
639 | */
640 | public function terminate(ServerRequestInterface $request, ResponseInterface $response)
641 | {
642 | $applicationEvent = $this->getApplicationEvent();
643 | $applicationEvent->setRequest($request);
644 | $applicationEvent->setResponse($response);
645 | $applicationEvent->setName(self::EVENT_LIFECYCLE_COMPLETE);
646 | $this->emit($applicationEvent);
647 |
648 | }
649 |
650 | /**
651 | * Finish request. Collect garbage and terminate output buffering
652 | *
653 | * @param \Psr\Http\Message\ResponseInterface $response
654 | * @return void
655 | */
656 | public function shutdown($response = null)
657 | {
658 |
659 | $this->collectGarbage();
660 | $applicationEvent = $this->getApplicationEvent();
661 | $applicationEvent->setResponse($response);
662 | $applicationEvent->setName(self::EVENT_SYSTEM_SHUTDOWN);
663 | $this->emit($applicationEvent, $this->terminateOutputBuffering(1, $response));
664 | }
665 |
666 | /**
667 | * Close response stream and terminate output buffering
668 | *
669 | * @param int $level
670 | * @param null|\Psr\Http\Message\ResponseInterface $response
671 | * @return array
672 | */
673 | public function terminateOutputBuffering($level = 0, $response = null)
674 | {
675 |
676 | // close response stream before terminating output buffer
677 | // and only if response is an instance of
678 | // \Psr\Http\ResponseInterface
679 | if ($response instanceof ResponseInterface) {
680 | $body = $response->getBody();
681 | if ($body->isReadable()) {
682 | $body->close();
683 | }
684 | }
685 |
686 | // Command line output buffering is disabled in cli by default
687 | if ($this->isCli()) {
688 | return [];
689 | }
690 |
691 | // $level needs to be a numeric value
692 | if (!is_numeric($level)) {
693 | $level = 0;
694 | }
695 |
696 | // force type casting to an integer value
697 | if (!is_int($level)) {
698 | $level = (int)$level;
699 | }
700 |
701 | // avoid infinite loop on clearing
702 | // output buffer by set level to 0
703 | // if $level is smaller
704 | if (-1 > $level) {
705 | $level = 0;
706 | }
707 |
708 | // terminate all output buffers until $level is 0 or desired level
709 | // collect all contents and return
710 | $content = [];
711 | while (ob_get_level() > $level) {
712 | $content[] = ob_get_clean();
713 | }
714 | return $content;
715 | }
716 |
717 | /**
718 | * Perform garbage collection
719 | */
720 | public function collectGarbage()
721 | {
722 | // try to enable garbage collection
723 | if (!gc_enabled()) {
724 | @gc_enable();
725 | }
726 |
727 | // collect garbage only if garbage
728 | // collection is enabled
729 | if (gc_enabled()) {
730 | gc_collect_cycles();
731 | }
732 | }
733 |
734 | /**
735 | * Perform garbage collection
736 | * @deprecated
737 | */
738 | public function cleanUp()
739 | {
740 | return $this->collectGarbage();
741 | }
742 |
743 | /**
744 | * Determines response for error. Emits `lifecycle.error` event.
745 | *
746 | * @param \Throwable $exception
747 | * @param string $message
748 | * @param ResponseInterface $response
749 | * @param ServerRequestInterface $request
750 | * @return ResponseInterface
751 | */
752 | private function determineErrorResponse(
753 | $exception,
754 | $message,
755 | ResponseInterface $response,
756 | ServerRequestInterface $request
757 | )
758 | {
759 | $applicationEvent = $this->getApplicationEvent();
760 | $applicationEvent->setName(self::EVENT_LIFECYCLE_ERROR);
761 | $applicationEvent->setRequest($request);
762 | $applicationEvent->setResponse($response);
763 | $applicationEvent->setErrorResponse($this->getResponse());
764 |
765 | $this->emit($applicationEvent, $exception);
766 |
767 | $errorResponse = $applicationEvent->getErrorResponse();
768 |
769 | if (!$errorResponse->getBody()->isWritable()) {
770 | return $errorResponse;
771 | }
772 |
773 | $content = $errorResponse->getBody()->__toString();
774 | if (empty($content)) {
775 | $errorResponse->getBody()->write($message);
776 | }
777 |
778 | return $errorResponse;
779 | }
780 |
781 | /**
782 | * @return HttpApplicationEvent
783 | */
784 | public function getApplicationEvent()
785 | {
786 | /** @var HttpApplicationEvent $applicationEvent */
787 | $applicationEvent = parent::getApplicationEvent();
788 | return $applicationEvent;
789 | }
790 |
791 | /**
792 | *
793 | */
794 | protected function initContentType()
795 | {
796 | // configure request content type
797 | $this->setContentType(ServerRequestFactory::getHeader('content-type', ServerRequestFactory::fromGlobals()->getHeaders(), $this->getContentType()));
798 | }
799 |
800 | }
801 |
--------------------------------------------------------------------------------
/src/Application/AbstractApplication.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Daniyal Hamid (@Designcise)
7 | * @copyright Marco Bunge
8 | *
9 | * @license MIT
10 | */
11 |
12 | namespace Hawkbit\Application;
13 |
14 | use Hawkbit\Application\Services\WhoopsService;
15 | use Hawkbit\Configuration;
16 | use League\Container\Container;
17 | use League\Container\ContainerAwareInterface;
18 | use League\Container\ContainerInterface;
19 | use League\Event\Emitter;
20 | use League\Event\EmitterInterface;
21 | use League\Event\EmitterTrait;
22 | use League\Event\ListenerAcceptorInterface;
23 | use Monolog\Logger;
24 | use Psr\Log\LoggerInterface;
25 |
26 | abstract class AbstractApplication implements ApplicationInterface, ContainerAwareInterface, ListenerAcceptorInterface, \ArrayAccess
27 | {
28 | use EmitterTrait;
29 |
30 | /**
31 | * Set while handle exception.
32 | * @var bool
33 | */
34 | protected $error = false;
35 |
36 | /**
37 | * @var array
38 | */
39 | protected $loggers = [];
40 |
41 | /**
42 | * @var ContainerInterface
43 | */
44 | protected $container;
45 |
46 | /**
47 | * @var ApplicationEvent
48 | */
49 | protected $applicationEvent;
50 |
51 | /**
52 | * @var string
53 | */
54 | protected $applicationEventClass = ApplicationEvent::class;
55 |
56 | /**
57 | * @var \Exception[]|\Throwable[]
58 | */
59 | protected $exceptionStack = [];
60 |
61 | /** IOC */
62 |
63 | /**
64 | * Set a container.
65 | *
66 | * @param \League\Container\ContainerInterface $container
67 | * @return $this
68 | */
69 | public function setContainer(ContainerInterface $container)
70 | {
71 | $application = $this;
72 | $container->share(ApplicationInterface::class, $application);
73 | $container->share(\Interop\Container\ContainerInterface::class, $container);
74 |
75 | $this->container = $container;
76 |
77 | return $this;
78 | }
79 |
80 | /**
81 | * Get the container.
82 | *
83 | * @return \League\Container\Container|\League\Container\ContainerInterface
84 | */
85 | public function getContainer()
86 | {
87 | if (!isset($this->container)) {
88 | $this->setContainer(new Container);
89 | }
90 |
91 | return $this->container;
92 | }
93 |
94 |
95 | /**
96 | * Array Access get.
97 | *
98 | * @param string $key
99 | *
100 | * @return mixed
101 | */
102 | public function offsetGet($key)
103 | {
104 | return $this->getContainer()->get($key);
105 | }
106 |
107 | /**
108 | * Array Access set.
109 | *
110 | * @param string $key
111 | * @param mixed $value
112 | *
113 | * @return void
114 | */
115 | public function offsetSet($key, $value)
116 | {
117 | $this->getContainer()->share($key, $value);
118 | }
119 |
120 | /**
121 | * Removing services are not support by
122 | * `league/container` 2.0 and greater
123 | *
124 | * @param string $key
125 | *
126 | * @return void
127 | */
128 | public function offsetUnset($key)
129 | {
130 | }
131 |
132 | /**
133 | * Array Access isset.
134 | *
135 | * @param string $key
136 | *
137 | * @return bool
138 | */
139 | public function offsetExists($key)
140 | {
141 | return $this->getContainer()->has($key);
142 | }
143 |
144 | /**
145 | * Register a new service provider
146 | *
147 | * @param $serviceProvider
148 | */
149 | public function register($serviceProvider)
150 | {
151 | $this->getContainer()->addServiceProvider($serviceProvider);
152 | }
153 |
154 | /** Config */
155 |
156 | /**
157 | * Get configuration container
158 | *
159 | * @return \Hawkbit\Configuration
160 | *
161 | */
162 | public function getConfigurator()
163 | {
164 | if (!$this->getContainer()->has(Configuration::class)) {
165 | $this->getContainer()->share(Configuration::class, (new Configuration([], true)));
166 | }
167 |
168 | return $this->getContainer()->get(Configuration::class);
169 | }
170 |
171 |
172 | /**
173 | * Set a config item. Add recursive if key is traversable.
174 | *
175 | * @param string|array|\Traversable $key
176 | * @param mixed $value
177 | *
178 | * @return $this
179 | */
180 | public function setConfig($key, $value = null)
181 | {
182 | $configurator = $this->getConfigurator();
183 | if(!is_scalar($key)){
184 | $configuratorClass = get_class($configurator);
185 | $configurator->merge(new $configuratorClass($key, true));
186 | }else{
187 | $configurator[$key] = $value;
188 | }
189 |
190 | return $this;
191 | }
192 |
193 | /**
194 | * Get a config key's value
195 | *
196 | * @param string $key
197 | * @param mixed $default
198 | *
199 | * @return mixed
200 | */
201 | public function getConfig($key = null, $default = null)
202 | {
203 |
204 | $configurator = $this->getConfigurator();
205 | if (null === $key) {
206 | return $configurator;
207 | }
208 |
209 | return $configurator->get($key, $default);
210 | }
211 |
212 | /**
213 | * Check if key exists
214 | *
215 | * @param $key
216 | *
217 | * @return bool
218 | */
219 | public function hasConfig($key)
220 | {
221 | $configurator = $this->getConfigurator();
222 | return isset($configurator[$key]);
223 | }
224 |
225 | /**
226 | * @return \Hawkbit\Application\Services\WhoopsService
227 | */
228 | public function getErrorHandler()
229 | {
230 | /** @var \Hawkbit\Application\Services\WhoopsService $contract */
231 | $contract = $this->validateContract($this->getContainer()->get(WhoopsService::class), WhoopsService::class);
232 | return $contract;
233 | }
234 |
235 | /**
236 | * @return \Exception[]|\Throwable[]
237 | */
238 | public function getExceptionStack()
239 | {
240 | return $this->exceptionStack;
241 | }
242 |
243 | /**
244 | * @param \Exception|\Throwable $exception
245 | * @return $this
246 | */
247 | public function pushException($exception){
248 | array_push($this->exceptionStack, $exception);
249 | return $this;
250 | }
251 |
252 | /**
253 | * @return \Exception|\Throwable
254 | */
255 | public function getLastException(){
256 | $exceptionStack = $this->getExceptionStack();
257 | $exception = end($exceptionStack);
258 | reset($exception);
259 | return $exception;
260 | }
261 |
262 | /** Events */
263 |
264 | /**
265 | * Return the event emitter.
266 | *
267 | * @return \League\Event\Emitter|\League\Event\EmitterInterface
268 | */
269 | public function getEventEmitter()
270 | {
271 | if (!$this->getContainer()->has(EmitterInterface::class)) {
272 | $this->getContainer()->share(EmitterInterface::class, new Emitter());
273 | }
274 |
275 | /** @var EmitterInterface $validateContract */
276 | $validateContract = $this->validateContract($this->getContainer()->get(EmitterInterface::class), EmitterInterface::class);
277 | return $validateContract;
278 | }
279 |
280 | /** Logging */
281 |
282 | /**
283 | * Return a logger
284 | *
285 | * @param string $channel
286 | * @return \Psr\Log\LoggerInterface
287 | */
288 | public function getLogger($channel = 'default')
289 | {
290 | if (isset($this->loggers[$channel])) {
291 | return $this->loggers[$channel];
292 | }
293 |
294 | /** @var Logger $logger */
295 | $logger = $this->getContainer()->get(LoggerInterface::class, [$channel]);
296 |
297 | $this->loggers[$channel] = $logger;
298 |
299 | /** @var LoggerInterface $contract */
300 | $contract = $this->validateContract($this->loggers[$channel], LoggerInterface::class);
301 | return $contract;
302 | }
303 |
304 | /**
305 | * Get a list of logger names
306 | *
307 | * @return string[]
308 | */
309 | public function getLoggerChannels()
310 | {
311 | return array_keys($this->loggers);
312 | }
313 |
314 | /**
315 | * throw a exception
316 | *
317 | * @param \Throwable|\Exception $exception
318 | *
319 | * @throws \Throwable|\Exception
320 | */
321 | public function throwException($exception)
322 | {
323 | $this->shutdown();
324 | throw $exception;
325 | }
326 |
327 | /**
328 | * Validates that class is instance of contract
329 | *
330 | * @param $class
331 | * @param $contract
332 | *
333 | * @return string|object
334 | *
335 | * @throws \InvalidArgumentException|\LogicException
336 | */
337 | public function validateContract($class, $contract)
338 | {
339 | $validateObject = function ($object) {
340 | //does need trigger when calling *_exists with object
341 | $condition = is_string($object) ? class_exists($object) || interface_exists($object) : is_object($object);
342 | if (false === $condition) {
343 | $this->throwException(new \InvalidArgumentException('Class not exists ' . $object));
344 | }
345 | };
346 |
347 | $convertClassToString = function ($object) {
348 | if (is_object($object)) {
349 | $object = get_class($object);
350 | }
351 |
352 | return is_string($object) ? $object : false;
353 | };
354 |
355 | $validateObject($class);
356 | $validateObject($contract);
357 |
358 | if (!($class instanceof $contract)) {
359 |
360 | if (is_object($class)) {
361 | $class = get_class($class);
362 | }
363 | $this->throwException(new \LogicException($convertClassToString($class) . ' needs to be an instance of ' . $convertClassToString($contract)));
364 | }
365 |
366 | return $class;
367 | }
368 |
369 | /**
370 | * Bind any closure to application instance
371 | *
372 | * @param $closure
373 | * @param $instance
374 | *
375 | * @return mixed
376 | */
377 | protected function bindClosureToInstance($closure, $instance)
378 | {
379 | if ($closure instanceof \Closure) {
380 | $closure = $closure->bind($closure, $instance, get_class($instance));
381 | }
382 |
383 | return $closure;
384 | }
385 |
386 | /**
387 | * @return ApplicationEvent
388 | */
389 | public function getApplicationEvent()
390 | {
391 | if (null === $this->applicationEvent) {
392 | $class = $this->applicationEventClass;
393 | $this->applicationEvent = new $class($this);
394 | }
395 | return $this->applicationEvent;
396 | }
397 |
398 |
399 | /**
400 | * Check server environment for cli
401 | *
402 | * @return bool
403 | */
404 | public function isCli()
405 | {
406 | return php_sapi_name() === 'cli';
407 | }
408 |
409 | /**
410 | * Check server environment for http
411 | *
412 | * @return bool
413 | */
414 | public function isHttpRequest()
415 | {
416 | return !$this->isCli();
417 | }
418 |
419 | /**
420 | * Check if an error has been occurred
421 | *
422 | * @return boolean
423 | */
424 | public function isError()
425 | {
426 | return $this->error;
427 | }
428 |
429 | /**
430 | * Shutdown application lifecycle
431 | *
432 | * @return void
433 | */
434 | abstract public function shutdown();
435 |
436 | /**
437 | * Initialize Application
438 | *
439 | * @return void
440 | */
441 | abstract public function init();
442 | }
443 |
--------------------------------------------------------------------------------
/src/Application/ApplicationEvent.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Alex Bilbie
7 | * @copyright Marco Bunge
8 | *
9 | * @license MIT
10 | */
11 |
12 | namespace Hawkbit\Application;
13 |
14 |
15 | use League\Event\AbstractEvent;
16 | use Psr\Http\Message\ResponseInterface;
17 | use Psr\Http\Message\ServerRequestInterface;
18 | use Hawkbit\Application\ApplicationInterface;
19 | use Zend\Stdlib\ArrayObject;
20 |
21 | class ApplicationEvent extends AbstractEvent
22 | {
23 | /**
24 | * @var
25 | */
26 | private $name;
27 | /**
28 | * @var ApplicationInterface
29 | */
30 | private $application;
31 |
32 | /**
33 | * @var \ArrayObject
34 | */
35 | private $paramCollection = null;
36 |
37 | /**
38 | * ApplicationEvent constructor.
39 | * @param ApplicationInterface $application
40 | */
41 | public function __construct(ApplicationInterface $application)
42 | {
43 | $this->application = $application;
44 | $this->paramCollection = new ArrayObject();
45 | }
46 |
47 | /**
48 | * @return string
49 | */
50 | public function getName()
51 | {
52 | return $this->name;
53 | }
54 |
55 | /**
56 | * @param mixed $name
57 | * @return $this
58 | */
59 | public function setName($name)
60 | {
61 | $this->name = $name;
62 | return $this;
63 | }
64 |
65 | /**
66 | * @return ApplicationInterface
67 | */
68 | public function getApplication()
69 | {
70 | return $this->application;
71 | }
72 |
73 | /**
74 | * @return \ArrayObject
75 | */
76 | public function getParamCollection()
77 | {
78 | return $this->paramCollection;
79 | }
80 | }
--------------------------------------------------------------------------------
/src/Application/ApplicationInterface.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Alex Bilbie
7 | * @copyright Marco Bunge
8 | *
9 | * @license MIT
10 | */
11 |
12 | namespace Hawkbit\Application;
13 |
14 | use Psr\Http\Message\ResponseInterface;
15 | use Psr\Http\Message\ServerRequestInterface;
16 |
17 | interface ApplicationInterface
18 | {
19 |
20 | /**
21 | * This event is fired when a request is received but before it has been processed by the router.
22 | */
23 | const EVENT_REQUEST_RECEIVED = 'request.received';
24 |
25 | /**
26 | * This event is fired when a response has been created but before it has been output.
27 | */
28 | const EVENT_RESPONSE_CREATED = 'response.created';
29 |
30 | /**
31 | * This event is fired when a response has been output.
32 | */
33 | const EVENT_RESPONSE_SENT = 'response.sent';
34 |
35 | /**
36 | * This event is fired only when an error occurs while handling request/response lifecycle.
37 | * This event is fired after `runtime.error`
38 | */
39 | const EVENT_LIFECYCLE_ERROR = 'lifecycle.error';
40 |
41 | /**
42 | * This event is always fired when an error occurs.
43 | */
44 | const EVENT_HANDLE_ERROR = 'handle.error';
45 |
46 | /**
47 | * This event is always fired when an error occurs.
48 | */
49 | const EVENT_SYSTEM_ERROR = 'system.error';
50 |
51 | /**
52 | * This event is fired before completing application lifecycle.
53 | */
54 | const EVENT_LIFECYCLE_COMPLETE = 'lifecycle.complete';
55 |
56 | /**
57 | * This event is fired on each shutdown.
58 | */
59 | const EVENT_SYSTEM_SHUTDOWN = 'system.shutdown';
60 |
61 | /**
62 | * This event is fired when throw an exception.
63 | */
64 | const EVENT_SYSTEM_EXCEPTION = 'system.exception';
65 |
66 | const KEY_ERROR_CATCH = 'error.catch';
67 | const KEY_ERROR = 'error';
68 |
69 | /**
70 | * Show or hide errors
71 | */
72 | const DEFAULT_ERROR = false;
73 |
74 | /**
75 | * catch or throw errors
76 | */
77 | const DEFAULT_ERROR_CATCH = true;
78 | }
79 |
--------------------------------------------------------------------------------
/src/Application/HttpApplicationEvent.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Alex Bilbie
7 | * @copyright Marco Bunge
8 | *
9 | * @license MIT
10 | */
11 |
12 | namespace Hawkbit\Application;
13 |
14 |
15 | use League\Event\AbstractEvent;
16 | use Psr\Http\Message\ResponseInterface;
17 | use Psr\Http\Message\ServerRequestInterface;
18 | use Hawkbit\Application\ApplicationInterface;
19 | use Zend\Stdlib\ArrayObject;
20 |
21 | class HttpApplicationEvent extends ApplicationEvent
22 | {
23 |
24 | /**
25 | * @var
26 | */
27 | private $response;
28 |
29 | /**
30 | * @var
31 | */
32 | private $request;
33 |
34 | /**
35 | * @var
36 | */
37 | private $errorResponse;
38 |
39 | /**
40 | * @return ResponseInterface
41 | */
42 | public function getResponse()
43 | {
44 | return $this->response;
45 | }
46 |
47 | /**
48 | * @param ResponseInterface $response
49 | * @return HttpApplicationEvent
50 | */
51 | public function setResponse($response)
52 | {
53 | $this->response = $response;
54 | return $this;
55 | }
56 |
57 | /**
58 | * @return ServerRequestInterface
59 | */
60 | public function getRequest()
61 | {
62 | return $this->request;
63 | }
64 |
65 | /**
66 | * @param ServerRequestInterface $request
67 | * @return HttpApplicationEvent
68 | */
69 | public function setRequest($request)
70 | {
71 | $this->request = $request;
72 | return $this;
73 | }
74 |
75 | /**
76 | * @return ResponseInterface
77 | */
78 | public function getErrorResponse()
79 | {
80 | return $this->errorResponse;
81 | }
82 |
83 | /**
84 | * @param ResponseInterface $errorResponse
85 | * @return HttpApplicationEvent
86 | */
87 | public function setErrorResponse($errorResponse)
88 | {
89 | $this->errorResponse = $errorResponse;
90 | return $this;
91 | }
92 | }
--------------------------------------------------------------------------------
/src/Application/Init/InitConfigurationTrait.php:
--------------------------------------------------------------------------------
1 | setConfig($this::KEY_ERROR, $configuration);
25 | } elseif (
26 | is_array($configuration) ||
27 | ($configuration instanceof \ArrayAccess ||
28 | $configuration instanceof \Traversable)
29 | ) {
30 | $this->setConfig($configuration);
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/Application/Init/InitHaltHookTrait.php:
--------------------------------------------------------------------------------
1 | getApplicationEvent();
28 | $this->emit($event->setName($this::EVENT_HANDLE_ERROR), $level, $message, $file, $line);
29 | });
30 |
31 | // exception handler
32 | set_exception_handler(function ($exception) {
33 | /** @var $this Application|Console */
34 | /** @var \Exception|\Throwable $exception */
35 | // Convert throwable to exception for backwards compatibility
36 | if (!($exception instanceof \Exception)) {
37 | $throwable = $exception;
38 | $exception = new \ErrorException(
39 | $throwable->getMessage(),
40 | $throwable->getCode(),
41 | E_ERROR,
42 | $throwable->getFile(),
43 | $throwable->getLine()
44 | );
45 | }
46 |
47 | $event = $this->getApplicationEvent();
48 | $this->emit($event->setName($this::EVENT_SYSTEM_EXCEPTION), $exception);
49 | });
50 |
51 | if (method_exists($this, 'shutdown')) {
52 | // shutdown function
53 | register_shutdown_function([$this, 'shutdown']);
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/Application/MiddlewareAwareInterface.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Alex Bilbie
7 | * @copyright Marco Bunge
8 | *
9 | * @license MIT
10 | */
11 |
12 | /**
13 | * Created by PhpStorm.
14 | * User: marco.bunge
15 | * Date: 04.10.2016
16 | * Time: 00:02
17 | */
18 |
19 | namespace Hawkbit\Application;
20 |
21 | use Psr\Http\Message\ResponseInterface;
22 | use Psr\Http\Message\ServerRequestInterface;
23 |
24 | class MiddlewareRunner
25 | {
26 |
27 | /**
28 | * @var array
29 | */
30 | private $middlewares;
31 |
32 | /**
33 | * MiddlewareRunner constructor.
34 | * @param array $middlewares
35 | */
36 | public function __construct(array $middlewares)
37 | {
38 | $this->middlewares = $middlewares;
39 | }
40 |
41 | /**
42 | * @param $middleware
43 | * @return $this
44 | */
45 | public function addMiddleware($middleware)
46 | {
47 | $this->middlewares[] = $middleware;
48 | return $this;
49 | }
50 |
51 | /**
52 | * Execute middlewares
53 | *
54 | * @param callable $final
55 | * @param callable|null $fail
56 | * @param array $args Arguments for middleware
57 | * @return mixed
58 | */
59 | public function run(array $args = [], callable $final = null, callable $fail = null)
60 | {
61 | $result = null;
62 |
63 | // declare default final middleware
64 | if (!is_callable($final)) {
65 | $final = function ($command) {
66 | return $command;
67 | };
68 | }
69 |
70 | // declare default error middleware
71 | if (!is_callable($fail)) {
72 | $fail = function ($exception) {
73 | throw $exception;
74 | };
75 | }
76 |
77 | try {
78 | $result = $this->handle($args, $final);
79 | } catch (\Exception $exception) {
80 | $result = $this->handleError($args, $fail, $exception, $result);
81 | }
82 |
83 | return $result;
84 | }
85 |
86 | /**
87 | * Resolve middleware queue
88 | *
89 | * @param $middlewares
90 | * @return \Closure
91 | */
92 | public function resolve($middlewares)
93 | {
94 | $last = function ($request, $response) {
95 | // no op
96 | };
97 | while ($middleware = array_pop($middlewares)) {
98 | if (is_object($middleware)) {
99 | if (method_exists($middleware, '__invoke')) {
100 | $middleware = [$middleware, '__invoke'];
101 | }
102 | }
103 |
104 | if (!is_callable($middleware)) {
105 | throw new \InvalidArgumentException('Middle needs to be callable');
106 | }
107 |
108 | $last = function () use ($middleware, $last) {
109 | $args = func_get_args();
110 | $args[] = $last;
111 | return call_user_func_array($middleware, $args);
112 | };
113 | }
114 | return $last;
115 | }
116 |
117 | /**
118 | * @param array $args
119 | * @return mixed
120 | */
121 | public function handle(array $args, callable $final)
122 | {
123 | $middlewares = $this->middlewares;
124 | array_push($middlewares, $final);
125 | return call_user_func_array($this->resolve($middlewares), $args);
126 | }
127 |
128 | /**
129 | * @param array $args
130 | * @param callable $fail
131 | * @param \Exception|\Throwable $exception
132 | * @param $result
133 | * @return mixed
134 | */
135 | public function handleError(array $args, callable $fail, $exception, $result)
136 | {
137 | // modify middleware args
138 | // push exception to top
139 | array_unshift($args, $exception);
140 |
141 | // push result to end
142 | $args[] = $result;
143 |
144 | // execute error middleware
145 | $result = call_user_func_array($fail, $args);
146 | return $result;
147 | }
148 |
149 | }
--------------------------------------------------------------------------------
/src/Application/Providers/MonologServiceProvider.php:
--------------------------------------------------------------------------------
1 | container property or the `getContainer` method
26 | * from the ContainerAwareTrait.
27 | *
28 | * @return void
29 | */
30 | public function register()
31 | {
32 | $this->getContainer()->add(LoggerInterface::class, Logger::class);
33 | }
34 | }
--------------------------------------------------------------------------------
/src/Application/Providers/WhoopsServiceProvider.php:
--------------------------------------------------------------------------------
1 | container property or the `getContainer` method
34 | * from the ContainerAwareTrait.
35 | *
36 | * @return void
37 | */
38 | public function register()
39 | {
40 |
41 | }
42 |
43 | /**
44 | * Method will be invoked on registration of a service provider implementing
45 | * this interface. Provides ability for eager loading of Service Providers.
46 | *
47 | * @return void
48 | */
49 | public function boot()
50 | {
51 | /** @var Application\AbstractApplication $app */
52 | $app = $this->getContainer()->get(ApplicationInterface::class);
53 |
54 | $errorHandler = new Run(new ApplicationSystemFacade($app));
55 | $handlerService = new HandlerService($app);
56 |
57 | $errorHandler->pushHandler([$handlerService, 'recognizeErrorResponseHandler']);
58 | $errorHandler->pushHandler([$handlerService, 'notifySystemWithError']);
59 | $errorHandler->register();
60 |
61 | $service = new Application\Services\WhoopsService($errorHandler, $app);
62 | $this->getContainer()->share(Application\Services\WhoopsService::class, $service);
63 | }
64 | }
--------------------------------------------------------------------------------
/src/Application/Services/Whoops/ApplicationSystemFacade.php:
--------------------------------------------------------------------------------
1 | application = $application;
33 | }
34 |
35 |
36 | /**
37 | * @param callable $handler
38 | * @param int|string $types
39 | * @return EmitterTrait
40 | */
41 | public function setErrorHandler(callable $handler, $types = 'use-php-defaults')
42 | {
43 |
44 | // $capturedHandler = $handler;
45 | // $event = ApplicationInterface::EVENT_HANDLE_ERROR;
46 | // $handler = function () use($capturedHandler, $event) {
47 | // $args = func_get_args();
48 | // $this->application->emit($event, $args);
49 | // call_user_func_array($capturedHandler, $args);
50 | // };
51 | //
52 | // return parent::setErrorHandler($handler, $types);
53 |
54 | $this->errorHandler = $handler;
55 | return $this->application->addListener(
56 | ApplicationInterface::EVENT_HANDLE_ERROR,
57 | function ($event) use ($handler) {
58 | $args = func_get_args();
59 | array_shift($args);
60 | call_user_func_array($handler, $args);
61 | }
62 | );
63 | }
64 |
65 | /**
66 | * @param callable $handler
67 | * @return EmitterTrait
68 | */
69 | public function setExceptionHandler(callable $handler)
70 | {
71 | return $this->application->addListener(
72 | ApplicationInterface::EVENT_SYSTEM_EXCEPTION,
73 | function ($event) use ($handler) {
74 | $args = func_get_args();
75 | array_shift($args);
76 | call_user_func_array($handler, $args);
77 | }
78 | );
79 | }
80 |
81 | /**
82 | *
83 | */
84 | public function restoreExceptionHandler()
85 | {
86 | $this->application->removeListener(ApplicationInterface::EVENT_HANDLE_ERROR, $this->exceptionHandler);
87 | $this->exceptionHandler = null;
88 | }
89 |
90 | /**
91 | *
92 | */
93 | public function restoreErrorHandler()
94 | {
95 | $this->application->removeListener(ApplicationInterface::EVENT_SYSTEM_EXCEPTION, $this->exceptionHandler);
96 | $this->exceptionHandler = null;
97 | }
98 |
99 | /**
100 | * @param callable $function
101 | * @return EmitterTrait
102 | */
103 | public function registerShutdownFunction(callable $function)
104 | {
105 | return $this->application->addListener(
106 | ApplicationInterface::EVENT_SYSTEM_SHUTDOWN,
107 | function ($event) use ($function) {
108 | call_user_func_array($function, []);
109 | }
110 | );
111 | }
112 |
113 | }
--------------------------------------------------------------------------------
/src/Application/Services/Whoops/HandlerService.php:
--------------------------------------------------------------------------------
1 | application = $application;
37 | }
38 |
39 | /**
40 | * Recognize response handler for error by content type and environment.
41 | *
42 | * Cli is always handled as plaintext!
43 | *
44 | * @param $exception
45 | * @param $inspector
46 | * @param Run $run
47 | * @return int|null
48 | */
49 | public function recognizeErrorResponseHandler($exception, $inspector, Run $run)
50 | {
51 | $app = $this->application;
52 |
53 | $handlerClass = PrettyPageHandler::class;
54 | if ($app->isCli() || false === $app->getConfig(ApplicationInterface::KEY_ERROR, ApplicationInterface::DEFAULT_ERROR)) {
55 | $handlerClass = PlainTextHandler::class;
56 | }
57 |
58 | if ($app instanceof Application) {
59 | if ($app->isSoapRequest() || $app->isXmlRequest()) {
60 | $handlerClass = XmlResponseHandler::class;
61 | } elseif ($app->isAjaxRequest() || $app->isJsonRequest()) {
62 | $handlerClass = JsonResponseHandler::class;
63 | }
64 | }
65 |
66 | /** @var HandlerInterface $handler */
67 | $handler = new $handlerClass;
68 | $handler->setException($exception);
69 | $handler->setInspector($inspector);
70 | $handler->setRun($run);
71 | return $handler->handle();
72 | }
73 |
74 | /**
75 | * Notify system after error occurs
76 | *
77 | * @param \Exception|\Throwable $exception
78 | * @return int
79 | */
80 | public function notifySystemWithError($exception)
81 | {
82 |
83 | $app = $this->application;
84 | // log all errors
85 | $app->getLogger()->error($exception->getMessage());
86 |
87 | $applicationEvent = $app->getApplicationEvent();
88 | $applicationEvent->setName(ApplicationInterface::EVENT_SYSTEM_ERROR);
89 | $app->emit($applicationEvent, $exception);
90 |
91 | return Handler::DONE;
92 | }
93 |
94 | }
--------------------------------------------------------------------------------
/src/Application/Services/WhoopsService.php:
--------------------------------------------------------------------------------
1 | runner = $runner;
36 | $this->application = $application;
37 | }
38 |
39 | /**
40 | * Determine error message by error configuration
41 | *
42 | * @param \Throwable|\Exception $exception
43 | *
44 | * @return string
45 | *
46 | */
47 | public function getErrorMessage($exception)
48 | {
49 | $application = $this->application;
50 | $errorHandler = $this->runner;
51 |
52 | // quit if error occured
53 | $shouldQuit = $application->isError();
54 |
55 | //
56 | if($application instanceof Application){
57 | // if request type ist not strict e.g. xml or json consider error config
58 | if (!$application->isXmlRequest() && !$application->isJsonRequest()) {
59 | $shouldQuit = $shouldQuit && $application->getConfig(ApplicationInterface::KEY_ERROR, ApplicationInterface::DEFAULT_ERROR);
60 | }
61 | }
62 |
63 | $errorHandler->allowQuit($shouldQuit);
64 |
65 | $method = $errorHandler::EXCEPTION_HANDLER;
66 | ob_start();
67 | $errorHandler->$method($exception);
68 | $message = ob_get_clean();
69 |
70 | return $message;
71 | }
72 |
73 | /**
74 | * Convert any type into an exception
75 | *
76 | * @param $error
77 | * @return \Exception|string
78 | */
79 | public function decorateException($error)
80 | {
81 | $application = $this->application;
82 |
83 | if (is_callable($error)) {
84 | $error = $application->getContainer()->call($error, [$application]);
85 | }
86 |
87 | if (is_object($error) && !($error instanceof \Exception)) {
88 | $error = method_exists($error,
89 | '__toString') ? $error->__toString() : 'Error with object ' . get_class($error);
90 | }
91 |
92 | if (is_resource($error)) {
93 | $error = 'Error with resource type ' . get_resource_type($error);
94 | }
95 |
96 | if (is_array($error)) {
97 | $error = implode("\n", $error);
98 | }
99 |
100 | if (!($error instanceof \Exception)) {
101 | $error = new \Exception(is_scalar($error) ? $error : 'Error with ' . gettype($error));
102 | }
103 |
104 | return $error;
105 | }
106 | }
--------------------------------------------------------------------------------
/src/Application/TerminableInterface.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Alex Bilbie
7 | * @copyright Marco Bunge
8 | *
9 | * @license MIT
10 | */
11 |
12 | namespace Hawkbit\Application;
13 |
14 | use Psr\Http\Message\ResponseInterface;
15 | use Psr\Http\Message\ServerRequestInterface;
16 |
17 |
18 | /**
19 | * PSR-7 port of \Symfony\Component\HttpKernel\TerminableInterface
20 | *
21 | * Terminable extends the Kernel request/response cycle with dispatching a post
22 | * response event after sending the response and before shutting down the kernel.
23 | *
24 | * @author Jordi Boggiano
25 | * @author Pierre Minnieur
26 | * @author Marco Bunge
27 | */
28 | interface TerminableInterface
29 | {
30 | /**
31 | * Terminates a request/response cycle.
32 | *
33 | * Should be called after sending the response and before shutting down the kernel.
34 | *
35 | * @param ServerRequestInterface $request A Request instance
36 | * @param ResponseInterface $response A Response instance
37 | */
38 | public function terminate(ServerRequestInterface $request, ResponseInterface $response);
39 | }
40 |
--------------------------------------------------------------------------------
/src/Configuration.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Alex Bilbie
7 | * @copyright Marco Bunge
8 | *
9 | * @license MIT
10 | */
11 |
12 | /**
13 | * Dot chaining support for nested configuratio
14 | *
15 | * @see \Hawkbit\Configuration::internalGet()
16 | * @see \Hawkbit\Configuration::internalSet()
17 | * @see \Hawkbit\Configuration::internalUnset()
18 | *
19 | * (c) m1
20 | *
21 | * For the full copyright and license information, please view the LICENSE
22 | * file that was distributed with m1/vars source code.
23 | *
24 | * @package m1/vars
25 | * @version 1.1.0
26 | * @author Miles Croxford
27 | * @copyright Copyright (c) Miles Croxford
28 | * @license http://github.com/m1/vars/blob/master/LICENSE
29 | * @link http://github.com/m1/vars/blob/master/README.MD Documentation
30 | */
31 |
32 | namespace Hawkbit;
33 |
34 |
35 | use Zend\Config\Config;
36 |
37 | /**
38 | * Supports dot chaining for nested configuration inspired by M1/vars
39 | *
40 | * Class Configuration
41 | * @package Hawkbit
42 | */
43 | final class Configuration extends Config
44 | {
45 |
46 | }
--------------------------------------------------------------------------------
/src/Console.php:
--------------------------------------------------------------------------------
1 | init($configuration);
66 |
67 | foreach ($defaultProviders as $provider) {
68 | $this->getContainer()->addServiceProvider($provider);
69 | }
70 | }
71 |
72 | /**
73 | * Shutdown application lifecycle
74 | *
75 | * @return void
76 | */
77 | public function shutdown()
78 | {
79 | // dispatch shutdown event
80 | $applicationEvent = $this->getApplicationEvent();
81 | $applicationEvent->setName(self::EVENT_SYSTEM_SHUTDOWN);
82 | $this->emit($applicationEvent);
83 |
84 | exit((int)$this->isError());
85 | }
86 |
87 | /**
88 | * Initialize Application
89 | *
90 | * @param array $configuration
91 | * @return void
92 | */
93 | public function init($configuration = [])
94 | {
95 | $this->initConfiguration($configuration);
96 | $this->initHaltHooks();
97 | }
98 |
99 | /**
100 | * Map first argument to callback
101 | *
102 | * - callable
103 | * - [class, method], including __invoke
104 | *
105 | * Argument matching full integrated from climate http://climate.thephpleague.com/arguments/
106 | *
107 | * @param $name
108 | * @param $callback
109 | * @param array $arguments
110 | * @return Command
111 | */
112 | public function map($name, $callback, array $arguments = [])
113 | {
114 | $command = new Command($name, $callback, $arguments);
115 | $this->commands[$name] = $command;
116 | return $command;
117 | }
118 |
119 | /**
120 | * Dispatch command from given args
121 | *
122 | * @param array $args
123 | */
124 | public function handle(array $args = [])
125 | {
126 | // remove source file name from argv
127 | $source = array_shift($args);
128 |
129 | /** @var ConsoleEvent $applicationEvent */
130 | $applicationEvent = $this->getApplicationEvent();
131 | $applicationEvent->setName(self::EVENT_REQUEST_RECEIVED);
132 | $applicationEvent->setSourceFile($source);
133 | $applicationEvent->setArguments($args);
134 | $this->emit($applicationEvent);
135 |
136 | // init dispatcher
137 | $dispatcher = new Dispatcher($this->commands, $this->container);
138 |
139 | $middlewareRunner = new MiddlewareRunner($this->getMiddlewares());
140 |
141 | $middlewareRunner->run([$applicationEvent->getArguments()], function ($args) use ($applicationEvent, $dispatcher) {
142 | // dispatch command with args from cli
143 | $dispatcher->dispatch($args);
144 | });
145 | }
146 |
147 | /**
148 | * execute console lifecycle
149 | *
150 | * @param array|null $argv
151 | */
152 | public function run(array $argv = null)
153 | {
154 | // If no $argv is provided then use the global PHP defined $argv.
155 | if (is_null($argv)) {
156 | global $argv;
157 | }
158 |
159 | // handle call
160 | $this->handle($argv);
161 |
162 | // exit cli with code 0 or even 1 for error
163 | $this->shutdown();
164 | }
165 |
166 | /**
167 | * @param $command
168 | * @return bool
169 | */
170 | public function hasCommand($command)
171 | {
172 | return isset($this->commands[$command]);
173 | }
174 |
175 | /**
176 | * Add a middleware
177 | *
178 | * @param callable $middleware
179 | */
180 | public function addMiddleware(callable $middleware)
181 | {
182 | $this->middlewares[] = $this->bindClosureToInstance($middleware, $this);
183 | }
184 |
185 | /**
186 | * @return callable[]
187 | */
188 | public function getMiddlewares()
189 | {
190 | return $this->middlewares;
191 | }
192 | }
--------------------------------------------------------------------------------
/src/Console/Command.php:
--------------------------------------------------------------------------------
1 | handler = $handler;
35 |
36 | $manager = new Manager();
37 | if(!is_null($arguments)){
38 | $manager->add($arguments);
39 | }
40 | $this->arguments = $manager;
41 | $this->name = $name;
42 | }
43 |
44 | /**
45 | * @return callable
46 | */
47 | public function getHandler()
48 | {
49 | return $this->handler;
50 | }
51 |
52 | /**
53 | * @return Manager
54 | */
55 | public function getArguments()
56 | {
57 | return $this->arguments;
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/src/Console/ConsoleEvent.php:
--------------------------------------------------------------------------------
1 | arguments;
33 | }
34 |
35 | /**
36 | * @param array $arguments
37 | * @return ConsoleEvent
38 | */
39 | public function setArguments($arguments)
40 | {
41 | $this->arguments = $arguments;
42 | return $this;
43 | }
44 |
45 | /**
46 | * @return string
47 | */
48 | public function getSourceFile()
49 | {
50 | return $this->sourceFile;
51 | }
52 |
53 | /**
54 | * @param string $sourceFile
55 | * @return ConsoleEvent
56 | */
57 | public function setSourceFile($sourceFile)
58 | {
59 | $this->sourceFile = $sourceFile;
60 | return $this;
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/src/Console/Dispatcher.php:
--------------------------------------------------------------------------------
1 | commands = $commands;
34 | $dispatcherContainer = clone $container;
35 | $dispatcherContainer->delegate(new ReflectionContainer());
36 | $this->container = $dispatcherContainer;
37 | }
38 |
39 | /**
40 | * @param $command
41 | * @return bool
42 | */
43 | private function hasCommand($command)
44 | {
45 | return isset($this->commands[$command]);
46 | }
47 |
48 | /**
49 | * Dispatch handler for given command
50 | *
51 | * @param $argv
52 | */
53 | public function dispatch($argv){
54 | $name = reset($argv);
55 |
56 | if(!$this->hasCommand($name)){
57 | throw new \InvalidArgumentException('Command not found');
58 | }
59 |
60 | // load command
61 | $command = $this->commands[$name];
62 |
63 | // parse arguments
64 | $arguments = $command->getArguments();
65 | $arguments->parse($argv);
66 |
67 | // execute command
68 | $handler = $command->getHandler();
69 | if(is_array($handler)){
70 | $class = $handler[0];
71 | // if($this->container->has($class)){
72 | // $this->container->add($class);
73 | // }
74 | $obj = $this->container->get($class);
75 | $handler[0] = $obj;
76 | }
77 | call_user_func_array($handler, [$arguments]);
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/src/Stratigility/ApplicationMiddleware.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Alex Bilbie
7 | * @copyright Marco Bunge
8 | *
9 | * @license MIT
10 | */
11 |
12 | namespace Hawkbit\Stratigility;
13 |
14 |
15 | use Psr\Http\Message\ResponseInterface as Response;
16 | use Psr\Http\Message\ServerRequestInterface as Request;
17 | use Hawkbit\Application\ApplicationInterface;
18 | use Zend\Stratigility\MiddlewareInterface;
19 |
20 | class ApplicationMiddleware implements MiddlewareInterface
21 | {
22 | /**
23 | * @var ApplicationInterface
24 | */
25 | private $application;
26 |
27 | /**
28 | * ApplicationMiddleware constructor.
29 | * @param ApplicationInterface $application
30 | */
31 | public function __construct(ApplicationInterface $application)
32 | {
33 | $this->application = $application;
34 | }
35 |
36 | /**
37 | * @return ApplicationInterface
38 | */
39 | public function getApplication()
40 | {
41 | return $this->application;
42 | }
43 |
44 | /**
45 | * Process an incoming request and/or response.
46 | *
47 | * Accepts a server-side request and a response instance, and does
48 | * something with them.
49 | *
50 | * If the response is not complete and/or further processing would not
51 | * interfere with the work done in the middleware, or if the middleware
52 | * wants to delegate to another process, it can use the `$out` callable
53 | * if present.
54 | *
55 | * If the middleware does not return a value, execution of the current
56 | * request is considered complete, and the response instance provided will
57 | * be considered the response to return.
58 | *
59 | * Alternately, the middleware may return a response instance.
60 | *
61 | * Often, middleware will `return $out();`, with the assumption that a
62 | * later middleware will return a response.
63 | *
64 | * @param Request $request
65 | * @param Response $response
66 | * @param null|callable $out
67 | * @return null|Response
68 | */
69 | public function __invoke(Request $request, Response $response, callable $out = null)
70 | {
71 | $response = $this->getApplication()->handle($request, $response);
72 | if(is_callable($out)){
73 | $out($request,$response);
74 | }
75 | return $response;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Stratigility/MiddlewarePipeAdapter.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Alex Bilbie
7 | * @copyright Marco Bunge
8 | *
9 | * @license MIT
10 | */
11 |
12 | namespace Hawkbit\Stratigility;
13 |
14 |
15 | use League\Route\Http\Exception;
16 | use Psr\Http\Message\ResponseInterface;
17 | use Psr\Http\Message\ServerRequestInterface;
18 | use Hawkbit\Application;
19 | use Zend\Diactoros\Response\HtmlResponse;
20 | use Zend\Stratigility\MiddlewarePipe;
21 |
22 | class MiddlewarePipeAdapter extends MiddlewarePipe
23 | {
24 | /**
25 | * @var Application
26 | */
27 | private $application;
28 |
29 | /**
30 | * MiddlewareAdapter constructor.
31 | * @param Application $application
32 | */
33 | public function __construct(Application $application)
34 | {
35 | parent::__construct();
36 | $this->application = $application;
37 | }
38 |
39 | /**
40 | * @return Application
41 | */
42 | public function getApplication()
43 | {
44 | return $this->application;
45 | }
46 |
47 | /**
48 | * Handle the request
49 | *
50 | * @param ServerRequestInterface $request
51 | * @param ResponseInterface $response
52 | * @param callable|null $out
53 | * @return ResponseInterface|void
54 | */
55 | public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $out = null){
56 | $application = $this->getApplication();
57 |
58 | // handling errors by application with
59 | // custom $finalHandler if $out is null
60 | $finalHandler = $out ? $out : function (ServerRequestInterface $request, ResponseInterface $response, $err = null) use($application, $out){
61 | if($err){
62 | return $application->handleError($err, $request, $response);
63 | }
64 | return $response;
65 | };
66 | $this->pipe(function(ServerRequestInterface $request, ResponseInterface $response, $next = null) use ($application){
67 | return $application->handle($request, $response);
68 | });
69 | return parent::__invoke($request, $response, $finalHandler);
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/Symfony/HttpKernelAdapter.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Alex Bilbie
7 | * @copyright Marco Bunge
8 | *
9 | * @license MIT
10 | */
11 |
12 | namespace Hawkbit\Symfony;
13 |
14 |
15 |
16 | use Hawkbit\Application;
17 | use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
18 | use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
19 | use Symfony\Component\HttpFoundation\Request;
20 | use Symfony\Component\HttpFoundation\Response;
21 | use Symfony\Component\HttpKernel\HttpKernelInterface;
22 | use Symfony\Component\HttpKernel\TerminableInterface;
23 |
24 | class HttpKernelAdapter implements HttpKernelInterface, TerminableInterface
25 | {
26 |
27 | /**
28 | * @var Application
29 | */
30 | private $application;
31 |
32 | /**
33 | * @var DiactorosFactory
34 | */
35 | private $diactorosFactory;
36 |
37 | /**
38 | * @var HttpFoundationFactory
39 | */
40 | private $httpFoundationFactory;
41 |
42 | public function __construct(Application $application){
43 |
44 | $this->application = $application;
45 | }
46 |
47 | /**
48 | * @return Application
49 | */
50 | public function getApplication()
51 | {
52 | return $this->application;
53 | }
54 |
55 | /**
56 | * Handles a Request to convert it to a Response.
57 | *
58 | * Bridging between PSR-7 and Symfony HTTP
59 | *
60 | * When $catch is true, the implementation must catch all exceptions
61 | * and do its best to convert them to a Response instance.
62 | *
63 | * @param Request $request A Request instance
64 | * @param int $type The type of the request
65 | * (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST)
66 | * @param bool $catch Whether to catch exceptions or not
67 | *
68 | * @return Response A Response instance
69 | *
70 | * @throws \Exception When an Exception occurs during processing
71 | */
72 | public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
73 | {
74 | $response = $this->getApplication()->handle($this->getDiactorosFactory()->createRequest($request), null, $catch);
75 | return $this->getHttpFoundationFactory()->createResponse($response);
76 | }
77 |
78 | /**
79 | * Terminates a request/response cycle.
80 | *
81 | * Should be called after sending the response and before shutting down the kernel.
82 | *
83 | * @param Request $request A Request instance
84 | * @param Response $response A Response instance
85 | */
86 | public function terminate(Request $request, Response $response)
87 | {
88 | $diactorosFactory = $this->getDiactorosFactory();
89 | $this->getApplication()->terminate($diactorosFactory->createRequest($this->recomposeSymfonyRequest($request)), $diactorosFactory->createResponse($response));
90 | }
91 |
92 | /**
93 | * Convert from symfony http foundation to PSR-7
94 | *
95 | * @return DiactorosFactory
96 | */
97 | public function getDiactorosFactory()
98 | {
99 | if($this->diactorosFactory === null){
100 | $this->diactorosFactory = new DiactorosFactory();
101 | }
102 | return $this->diactorosFactory;
103 | }
104 |
105 | /**
106 | * Convert from PSR-7 to symfony http foundation
107 | *
108 | * @return HttpFoundationFactory
109 | */
110 | public function getHttpFoundationFactory()
111 | {
112 | if($this->httpFoundationFactory === null){
113 | $this->httpFoundationFactory = new HttpFoundationFactory();
114 | }
115 | return $this->httpFoundationFactory;
116 | }
117 |
118 | /**
119 | * Avoid exception throw when request content is null
120 | *
121 | * @param Request $request
122 | * @return Request
123 | */
124 | private function recomposeSymfonyRequest(Request $request)
125 | {
126 | try{
127 | try {
128 | $content = $request->getContent(true);
129 | } catch (\LogicException $e) {
130 | $content = $request->getContent();
131 | }
132 | }catch(\LogicException $e){
133 | $content = file_get_contents('php://input');
134 | }
135 | return $request::create(
136 | $request->getUri(),
137 | $request->getMethod(),
138 | $request->getMethod() === 'PATCH' ? $request->request->all() : $request->query->all(),
139 | $request->cookies->all(),
140 | $request->files->all(),
141 | $request->server->all(),
142 | $content
143 | );
144 | }
145 | }
146 |
--------------------------------------------------------------------------------